From 83cb70db17e10ee9c42ba5c20e5547efff6a9118 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Mon, 19 Aug 2019 22:54:07 +0200 Subject: [PATCH 0001/5199] Added initial AimAssist mod --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 97 +++++++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 1 + 2 files changed, 98 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs new file mode 100644 index 0000000000..325c5d0c13 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -0,0 +1,97 @@ +// 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.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Osu.Mods +{ + internal class OsuModAimAssist : Mod, IApplicableToDrawableHitObjects, IUpdatableByPlayfield + { + public override string Name => "AimAssist"; + public override string Acronym => "AA"; + public override IconUsage Icon => FontAwesome.Solid.MousePointer; + public override ModType Type => ModType.Fun; + public override string Description => "No need to chase the circle, the circle chases you"; + public override double ScoreMultiplier => 1; + public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModAutoplay), typeof(OsuModWiggle), typeof(OsuModTransform) }; + + private HashSet movingObjects = new HashSet(); + private int updateCounter = 0; + + public void Update(Playfield playfield) + { + // Avoid crowded judgment displays + playfield.DisplayJudgements.Value = false; + + // Object destination updated when cursor updates + playfield.Cursor.ActiveCursor.OnUpdate += drawableCursor => + { + // ... every 500th cursor update iteration + // (lower -> potential lags ; higher -> easier to miss if cursor too fast) + if (updateCounter++ < 500) return; + updateCounter = 0; + + // First move objects to new destination, then remove them from movingObjects set if they're too old + movingObjects.RemoveWhere(d => + { + var currentTime = playfield.Clock.CurrentTime; + var h = d.HitObject; + d.ClearTransforms(); + switch (d) + { + case DrawableHitCircle circle: + d.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime)); + return currentTime > h.StartTime; + case DrawableSlider slider: + + // Move slider to cursor + if (currentTime < h.StartTime) + d.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime)); + + // Move slider so that sliderball stays on the cursor + else + d.MoveTo(drawableCursor.DrawPosition - slider.Ball.DrawPosition, Math.Max(0, h.StartTime - currentTime)); + return currentTime > (h as IHasEndTime).EndTime - 50; + case DrawableSpinner spinner: + // TODO + return true; + } + return true; // never happens(?) + }); + }; + } + + public void ApplyToDrawableHitObjects(IEnumerable drawables) + { + foreach (var drawable in drawables) + drawable.ApplyCustomUpdateState += drawableOnApplyCustomUpdateState; + } + + private void drawableOnApplyCustomUpdateState(DrawableHitObject drawable, ArmedState state) + { + if (!(drawable is DrawableOsuHitObject d)) + return; + var h = d.HitObject; + using (d.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) + movingObjects.Add(d); + } + } + + /* + * TODOs + * - remove object timing glitches / artifacts + * - remove FollowPoints + * - automate sliders + * - combine with OsuModRelax (?) + * - must be some way to make this more effictient + * + */ +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index d50d4f401c..9dbc144501 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -136,6 +136,7 @@ namespace osu.Game.Rulesets.Osu new OsuModSpinIn(), new MultiMod(new OsuModGrow(), new OsuModDeflate()), new MultiMod(new ModWindUp(), new ModWindDown()), + new OsuModAimAssist(), }; case ModType.System: From 3434458e0a2119e1ee3171b96186f1b2cc4a0a53 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Mon, 19 Aug 2019 23:51:47 +0200 Subject: [PATCH 0002/5199] Minor formatting changes --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 325c5d0c13..26f0d64a5d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -23,8 +23,8 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModAutoplay), typeof(OsuModWiggle), typeof(OsuModTransform) }; - private HashSet movingObjects = new HashSet(); - private int updateCounter = 0; + private readonly HashSet movingObjects = new HashSet(); + private int updateCounter; public void Update(Playfield playfield) { @@ -36,7 +36,9 @@ namespace osu.Game.Rulesets.Osu.Mods { // ... every 500th cursor update iteration // (lower -> potential lags ; higher -> easier to miss if cursor too fast) - if (updateCounter++ < 500) return; + if (updateCounter++ < 500) + return; + updateCounter = 0; // First move objects to new destination, then remove them from movingObjects set if they're too old @@ -45,11 +47,13 @@ namespace osu.Game.Rulesets.Osu.Mods var currentTime = playfield.Clock.CurrentTime; var h = d.HitObject; d.ClearTransforms(); + switch (d) { case DrawableHitCircle circle: - d.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime)); + circle.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime)); return currentTime > h.StartTime; + case DrawableSlider slider: // Move slider to cursor @@ -59,11 +63,13 @@ namespace osu.Game.Rulesets.Osu.Mods // Move slider so that sliderball stays on the cursor else d.MoveTo(drawableCursor.DrawPosition - slider.Ball.DrawPosition, Math.Max(0, h.StartTime - currentTime)); - return currentTime > (h as IHasEndTime).EndTime - 50; - case DrawableSpinner spinner: + return currentTime > (h as IHasEndTime)?.EndTime - 50; + + case DrawableSpinner _: // TODO return true; } + return true; // never happens(?) }); }; @@ -79,6 +85,7 @@ namespace osu.Game.Rulesets.Osu.Mods { if (!(drawable is DrawableOsuHitObject d)) return; + var h = d.HitObject; using (d.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) movingObjects.Add(d); From 28f78f67b237a952bb403fb802640b7c4d8c47b3 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Tue, 20 Aug 2019 16:33:56 +0200 Subject: [PATCH 0003/5199] No longer subscribing to OnUpdate --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 70 ++++++++----------- 1 file changed, 28 insertions(+), 42 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 26f0d64a5d..8d0c1fdcfe 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Mods { internal class OsuModAimAssist : Mod, IApplicableToDrawableHitObjects, IUpdatableByPlayfield { - public override string Name => "AimAssist"; + public override string Name => "Aim Assist"; public override string Acronym => "AA"; public override IconUsage Icon => FontAwesome.Solid.MousePointer; public override ModType Type => ModType.Fun; @@ -24,55 +24,45 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModAutoplay), typeof(OsuModWiggle), typeof(OsuModTransform) }; private readonly HashSet movingObjects = new HashSet(); - private int updateCounter; public void Update(Playfield playfield) { + var drawableCursor = playfield.Cursor.ActiveCursor; + // Avoid crowded judgment displays playfield.DisplayJudgements.Value = false; - // Object destination updated when cursor updates - playfield.Cursor.ActiveCursor.OnUpdate += drawableCursor => + // First move objects to new destination, then remove them from movingObjects set if they're too old + movingObjects.RemoveWhere(d => { - // ... every 500th cursor update iteration - // (lower -> potential lags ; higher -> easier to miss if cursor too fast) - if (updateCounter++ < 500) - return; + var currentTime = playfield.Clock.CurrentTime; + var h = d.HitObject; - updateCounter = 0; - - // First move objects to new destination, then remove them from movingObjects set if they're too old - movingObjects.RemoveWhere(d => + switch (d) { - var currentTime = playfield.Clock.CurrentTime; - var h = d.HitObject; - d.ClearTransforms(); + case DrawableHitCircle circle: + circle.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime - 10)); + return currentTime > h.StartTime; - switch (d) - { - case DrawableHitCircle circle: - circle.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime)); - return currentTime > h.StartTime; + case DrawableSlider slider: - case DrawableSlider slider: + // Move slider to cursor + if (currentTime < h.StartTime) + d.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime - 10)); - // Move slider to cursor - if (currentTime < h.StartTime) - d.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime)); + // Move slider so that sliderball stays on the cursor + else + d.MoveTo(drawableCursor.DrawPosition - slider.Ball.DrawPosition); + return currentTime > (h as IHasEndTime)?.EndTime; - // Move slider so that sliderball stays on the cursor - else - d.MoveTo(drawableCursor.DrawPosition - slider.Ball.DrawPosition, Math.Max(0, h.StartTime - currentTime)); - return currentTime > (h as IHasEndTime)?.EndTime - 50; + case DrawableSpinner _: + // TODO + return true; - case DrawableSpinner _: - // TODO - return true; - } - - return true; // never happens(?) - }); - }; + default: + return true; + } + }); } public void ApplyToDrawableHitObjects(IEnumerable drawables) @@ -83,11 +73,7 @@ namespace osu.Game.Rulesets.Osu.Mods private void drawableOnApplyCustomUpdateState(DrawableHitObject drawable, ArmedState state) { - if (!(drawable is DrawableOsuHitObject d)) - return; - - var h = d.HitObject; - using (d.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) + if (drawable is DrawableOsuHitObject d) movingObjects.Add(d); } } @@ -96,7 +82,7 @@ namespace osu.Game.Rulesets.Osu.Mods * TODOs * - remove object timing glitches / artifacts * - remove FollowPoints - * - automate sliders + * - automate spinners * - combine with OsuModRelax (?) * - must be some way to make this more effictient * From 30f923edde9987bfcfbe866f9bf5258dfdb5b8bf Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Tue, 20 Aug 2019 17:23:50 +0200 Subject: [PATCH 0004/5199] Hiding follow points --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 6 +++--- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 8d0c1fdcfe..54c80525bf 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -10,6 +10,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.Osu.UI; namespace osu.Game.Rulesets.Osu.Mods { @@ -29,8 +30,9 @@ namespace osu.Game.Rulesets.Osu.Mods { var drawableCursor = playfield.Cursor.ActiveCursor; - // Avoid crowded judgment displays + // Avoid crowded judgment displays and hide follow points playfield.DisplayJudgements.Value = false; + (playfield as OsuPlayfield)?.ConnectionLayer.Hide(); // First move objects to new destination, then remove them from movingObjects set if they're too old movingObjects.RemoveWhere(d => @@ -81,10 +83,8 @@ namespace osu.Game.Rulesets.Osu.Mods /* * TODOs * - remove object timing glitches / artifacts - * - remove FollowPoints * - automate spinners * - combine with OsuModRelax (?) - * - must be some way to make this more effictient * */ } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 9037faf606..64620d3629 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.UI { private readonly ApproachCircleProxyContainer approachCircles; private readonly JudgementContainer judgementLayer; - private readonly ConnectionRenderer connectionLayer; + public readonly ConnectionRenderer ConnectionLayer; public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.UI { InternalChildren = new Drawable[] { - connectionLayer = new FollowPointRenderer + ConnectionLayer = new FollowPointRenderer { RelativeSizeAxes = Axes.Both, Depth = 2, @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Osu.UI public override void PostProcess() { - connectionLayer.HitObjects = HitObjectContainer.Objects.Select(d => d.HitObject).OfType(); + ConnectionLayer.HitObjects = HitObjectContainer.Objects.Select(d => d.HitObject).OfType(); } private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) From d4d348390a6ce2dfac7638d61b8800e5439dceaf Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Tue, 20 Aug 2019 23:18:57 +0200 Subject: [PATCH 0005/5199] Change set to list + minor changes --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 54c80525bf..1101b93ed1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -24,38 +24,46 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModAutoplay), typeof(OsuModWiggle), typeof(OsuModTransform) }; - private readonly HashSet movingObjects = new HashSet(); + private readonly List movingObjects = new List(); public void Update(Playfield playfield) { - var drawableCursor = playfield.Cursor.ActiveCursor; + var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; - // Avoid crowded judgment displays and hide follow points + // Avoid relocating judgment displays and hide follow points playfield.DisplayJudgements.Value = false; (playfield as OsuPlayfield)?.ConnectionLayer.Hide(); - // First move objects to new destination, then remove them from movingObjects set if they're too old - movingObjects.RemoveWhere(d => + // First move objects to new destination, then remove them from movingObjects list if they're too old + movingObjects.RemoveAll(d => { - var currentTime = playfield.Clock.CurrentTime; var h = d.HitObject; + var currentTime = playfield.Clock.CurrentTime; + var endTime = (h as IHasEndTime)?.EndTime ?? h.StartTime; + d.ClearTransforms(); switch (d) { case DrawableHitCircle circle: - circle.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime - 10)); - return currentTime > h.StartTime; + + // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast + circle.MoveTo(cursorPos, Math.Max(0, endTime - currentTime - 10)); + return currentTime > endTime; case DrawableSlider slider: // Move slider to cursor if (currentTime < h.StartTime) - d.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime - 10)); - + { + slider.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + return false; + } // Move slider so that sliderball stays on the cursor else - d.MoveTo(drawableCursor.DrawPosition - slider.Ball.DrawPosition); - return currentTime > (h as IHasEndTime)?.EndTime; + { + slider.MoveTo(cursorPos - slider.Ball.DrawPosition); + return currentTime > endTime; + } case DrawableSpinner _: // TODO @@ -75,14 +83,15 @@ namespace osu.Game.Rulesets.Osu.Mods private void drawableOnApplyCustomUpdateState(DrawableHitObject drawable, ArmedState state) { - if (drawable is DrawableOsuHitObject d) - movingObjects.Add(d); + if (drawable is DrawableOsuHitObject hitobject) + movingObjects.Add(hitobject); } } /* * TODOs - * - remove object timing glitches / artifacts + * - fix sliders reappearing at original position after their EndTime (see https://puu.sh/E7zT4/111cf9cdc8.gif) + * - relocate / hide slider headcircle's explosion, flash, ... * - automate spinners * - combine with OsuModRelax (?) * From 4cd5eb783a017f53682291aaa61eb32e91b72bd9 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Wed, 21 Aug 2019 21:37:56 +0200 Subject: [PATCH 0006/5199] Add spinner automation --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 64 +++++++++++++++---- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 2 +- 4 files changed, 54 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 1101b93ed1..f70ad2ac7c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Osu.UI; +using osuTK; namespace osu.Game.Rulesets.Osu.Mods { @@ -22,25 +23,30 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override string Description => "No need to chase the circle, the circle chases you"; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModAutoplay), typeof(OsuModWiggle), typeof(OsuModTransform) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; private readonly List movingObjects = new List(); + private DrawableSpinner activeSpinner; + private double spinnerAngle; // in radians public void Update(Playfield playfield) { var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; + var currentTime = playfield.Clock.CurrentTime; // Avoid relocating judgment displays and hide follow points playfield.DisplayJudgements.Value = false; (playfield as OsuPlayfield)?.ConnectionLayer.Hide(); - // First move objects to new destination, then remove them from movingObjects list if they're too old + // If object too old, remove from movingObjects list, otherwise move to new destination movingObjects.RemoveAll(d => { var h = d.HitObject; - var currentTime = playfield.Clock.CurrentTime; var endTime = (h as IHasEndTime)?.EndTime ?? h.StartTime; - d.ClearTransforms(); + + // Object no longer required to be moved -> remove from list + if (currentTime > endTime) + return true; switch (d) { @@ -48,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.Mods // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast circle.MoveTo(cursorPos, Math.Max(0, endTime - currentTime - 10)); - return currentTime > endTime; + return false; case DrawableSlider slider: @@ -56,23 +62,56 @@ namespace osu.Game.Rulesets.Osu.Mods if (currentTime < h.StartTime) { slider.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); - return false; } // Move slider so that sliderball stays on the cursor else { + slider.HeadCircle.Hide(); // temporary solution to supress HeadCircle's explosion, flash, ... at wrong location slider.MoveTo(cursorPos - slider.Ball.DrawPosition); - return currentTime > endTime; } - case DrawableSpinner _: - // TODO - return true; + return false; + + case DrawableSpinner spinner: + + // Move spinner to cursor + if (currentTime < h.StartTime) + { + spinner.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + return false; + } + else + { + spinnerAngle = 0; + activeSpinner = spinner; + return true; + } default: return true; } }); + + if (activeSpinner != null) + { + if (currentTime > (activeSpinner.HitObject as IHasEndTime)?.EndTime) + { + activeSpinner = null; + spinnerAngle = 0; + } + else + { + const float additional_degrees = 4; + const int dist_from_cursor = 30; + spinnerAngle += additional_degrees * Math.PI / 180; + + // Visual progress + activeSpinner.MoveTo(new Vector2((float)(dist_from_cursor * Math.Cos(spinnerAngle) + cursorPos.X), (float)(dist_from_cursor * Math.Sin(spinnerAngle) + cursorPos.Y))); + + // Logical progress + activeSpinner.Disc.RotationAbsolute += additional_degrees; + } + } } public void ApplyToDrawableHitObjects(IEnumerable drawables) @@ -91,9 +130,8 @@ namespace osu.Game.Rulesets.Osu.Mods /* * TODOs * - fix sliders reappearing at original position after their EndTime (see https://puu.sh/E7zT4/111cf9cdc8.gif) - * - relocate / hide slider headcircle's explosion, flash, ... - * - automate spinners - * - combine with OsuModRelax (?) + * - find nicer way to handle slider headcircle explosion, flash, ... + * - add Aim Assist as incompatible mod for Autoplay (?) * */ } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index ca72f18e9c..0757547d1c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Automation; public override string Description => @"Automatic cursor movement - just follow the rhythm."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModAimAssist) }; public bool AllowFail => false; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs index 9b079895fa..7799cdac32 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override string Description => "Everything rotates. EVERYTHING."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle), typeof(OsuModAimAssist) }; private float theta; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 17fcd03dd5..a14fd88ff5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override string Description => "They just won't stay still..."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModAimAssist) }; private const int wiggle_duration = 90; // (ms) Higher = fewer wiggles private const int wiggle_strength = 10; // Higher = stronger wiggles From aab3277024a58e58cc8a31ed2a42c7ce73575413 Mon Sep 17 00:00:00 2001 From: emu1337 Date: Mon, 11 Oct 2021 03:12:57 +0200 Subject: [PATCH 0007/5199] changed speed acc scaling to be closer to worst case scenario --- .../Difficulty/OsuDifficultyAttributes.cs | 1 + .../Difficulty/OsuDifficultyCalculator.cs | 2 ++ .../Difficulty/OsuPerformanceCalculator.cs | 9 ++++++++- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 16 +++++++++++++++- 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index bd4c0f2ad5..1f8b66edb7 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty { public double AimStrain { get; set; } public double SpeedStrain { get; set; } + public double SpeedNoteCount { get; set; } public double FlashlightRating { get; set; } public double ApproachRate { get; set; } public double OverallDifficulty { get; set; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 790aa0eb7d..4160da9d5e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -36,6 +36,7 @@ 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 flashlightRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; + double speedNotes = (skills[1] as Speed).RelevantNoteCount(); if (mods.Any(h => h is OsuModRelax)) speedRating = 0.0; @@ -72,6 +73,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty Mods = mods, AimStrain = aimRating, SpeedStrain = speedRating, + SpeedNoteCount = speedNotes, FlashlightRating = flashlightRating, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, OverallDifficulty = (80 - hitWindowGreat) / 6, diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 4e4dbc02a1..140c49393b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -168,8 +168,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty speedValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); } + // Calculate accuracy assuming the worst case scenario + double relevantTotalDiff = totalHits - Attributes.SpeedNoteCount; + double relevantCountGreat = Math.Max(0, countGreat - relevantTotalDiff); + double relevantCountOk = Math.Max(0, countOk - Math.Max(0, relevantTotalDiff - countGreat)); + double relevantCountMeh = Math.Max(0, countMeh - Math.Max(0, relevantTotalDiff - countGreat - countOk)); + double relevantAccuracy = (relevantCountGreat * 6.0 + relevantCountOk * 2.0 + relevantCountMeh) / (Attributes.SpeedNoteCount * 6.0); + // Scale the speed value with accuracy and OD. - speedValue *= (0.95 + Math.Pow(Attributes.OverallDifficulty, 2) / 750) * Math.Pow(accuracy, (14.5 - Math.Max(Attributes.OverallDifficulty, 8)) / 2); + speedValue *= (0.95 + Math.Pow(Attributes.OverallDifficulty, 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - Math.Max(Attributes.OverallDifficulty, 8)) / 2); // Scale the speed value with # of 50s to punish doubletapping. speedValue *= Math.Pow(0.98, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index cae6b8e01c..f8c1042a10 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -7,6 +7,8 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; using osu.Framework.Utils; +using System.Collections.Generic; +using System.Linq; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { @@ -33,6 +35,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private readonly double greatWindow; + private List objectStrains = new List(); + public Speed(Mod[] mods, double hitWindowGreat) : base(mods) { @@ -170,7 +174,17 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills currentRhythm = calculateRhythmBonus(current); - return currentStrain * currentRhythm; + double totalStrain = currentStrain * currentRhythm; + + objectStrains.Add(totalStrain); + + return totalStrain; + } + + public double RelevantNoteCount() + { + double maxStrain = objectStrains.Max(); + return objectStrains.Aggregate((total, next) => total + (1.0 / (1.0 + Math.Pow(Math.E, -(next/maxStrain * 12.0 - 6.0))))); } } } From d7483a6c5da7ace483c9e6cac31edf4f3ca0a67c Mon Sep 17 00:00:00 2001 From: emu1337 Date: Mon, 11 Oct 2021 19:58:03 +0200 Subject: [PATCH 0008/5199] changed pow to exp function --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index f8c1042a10..f477f8e17d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -184,7 +184,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public double RelevantNoteCount() { double maxStrain = objectStrains.Max(); - return objectStrains.Aggregate((total, next) => total + (1.0 / (1.0 + Math.Pow(Math.E, -(next/maxStrain * 12.0 - 6.0))))); + return objectStrains.Aggregate((total, next) => total + (1.0 / (1.0 + Math.Exp(-(next/maxStrain * 12.0 - 6.0))))); } } } From 131e64e56c14760ffc408993ca5416d44f863528 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Fri, 12 Nov 2021 21:29:51 +1100 Subject: [PATCH 0009/5199] Add bonus based on opacity of hit objects --- .../Difficulty/OsuDifficultyCalculator.cs | 17 ++++---- .../Difficulty/OsuPerformanceCalculator.cs | 14 ------- .../Difficulty/Skills/Flashlight.cs | 39 +++++++++++++++++-- 3 files changed, 43 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 558ddc16ef..19e92d4365 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty { private const double difficulty_multiplier = 0.0675; private double hitWindowGreat; + private double preempt; public OsuDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) @@ -34,11 +35,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty return new OsuDifficultyAttributes { Mods = mods, Skills = skills }; double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; - double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; - double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; - double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier; - - double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; + double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; + double flashlightRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; if (mods.Any(h => h is OsuModRelax)) speedRating = 0.0; @@ -77,7 +75,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty AimStrain = aimRating, SpeedStrain = speedRating, FlashlightRating = flashlightRating, - SliderFactor = sliderFactor, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, OverallDifficulty = (80 - hitWindowGreat) / 6, DrainRate = drainRate, @@ -110,12 +107,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty hitWindowGreat = hitWindows.WindowFor(HitResult.Great) / clockRate; + preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; + return new Skill[] { - new Aim(mods, true), - new Aim(mods, false), + new Aim(mods), new Speed(mods, hitWindowGreat), - new Flashlight(mods) + new Flashlight(mods, preempt) }; } @@ -126,6 +124,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty new OsuModEasy(), new OsuModHardRock(), new OsuModFlashlight(), + new OsuModHidden(), }; } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 8d45c7a8cc..817b0b63a8 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -125,16 +125,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); } - // We assume 15% of sliders in a map are difficult since there's no way to tell from the performance calculator. - double estimateDifficultSliders = Attributes.SliderCount * 0.15; - - if (Attributes.SliderCount > 0) - { - double estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, Attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); - double sliderNerfFactor = (1 - Attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + Attributes.SliderFactor; - aimValue *= sliderNerfFactor; - } - aimValue *= accuracy; // It is important to also consider accuracy difficulty when doing that. aimValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500; @@ -234,10 +224,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty double flashlightValue = Math.Pow(rawFlashlight, 2.0) * 25.0; - // Add an additional bonus for HDFL. - if (mods.Any(h => h is OsuModHidden)) - flashlightValue *= 1.3; - // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 466f0556ab..55ef8db129 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; @@ -14,16 +16,24 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public class Flashlight : OsuStrainSkill { - public Flashlight(Mod[] mods) + public Flashlight(Mod[] mods, double preemptTime) : base(mods) { + this.mods = mods; + this.preemptTime = preemptTime; } - private double skillMultiplier => 0.15; + private double skillMultiplier => 0.12; private double strainDecayBase => 0.15; protected override double DecayWeight => 1.0; protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations. + private Mod[] mods; + private bool hidden; + private double preemptTime; + + private const double max_opacity_bonus = 0.4; + private double currentStrain; private double strainValueOf(DifficultyHitObject current) @@ -31,6 +41,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (current.BaseObject is Spinner) return 0; + hidden = mods.Any(m => m is OsuModHidden); + var osuCurrent = (OsuDifficultyHitObject)current; var osuHitObject = (OsuHitObject)(osuCurrent.BaseObject); @@ -58,11 +70,30 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills // We also want to nerf stacks so that only the first object of the stack is accounted for. double stackNerf = Math.Min(1.0, (osuPrevious.JumpDistance / scalingFactor) / 25.0); - result += Math.Pow(0.8, i) * stackNerf * scalingFactor * jumpDistance / cumulativeStrainTime; + // Bonus based on how visible the object is. + double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - opacity(cumulativeStrainTime, preemptTime, hidden)); + + result += Math.Pow(0.8, i) * stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime; } } - return Math.Pow(smallDistNerf * result, 2.0); + result = Math.Pow(smallDistNerf * result, 2.0); + + if (hidden) { + result *= 1.0 + max_opacity_bonus; + } + + return result; + } + + private double opacity(double ms, double preemptTime, bool hidden) { + if (hidden) { + return Math.Clamp(Math.Min((1 - ms / preemptTime) * 2.5, (ms / preemptTime) * (1.0 / 0.3)), 0.0, 1.0); + } + else + { + return Math.Clamp((1.0 - ms / preemptTime) * 1.5, 0.0, 1.0); + } } private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); From 5a3be778a172405402bdcaadfdfdb7bb04ce190d Mon Sep 17 00:00:00 2001 From: MBmasher Date: Fri, 12 Nov 2021 21:41:01 +1100 Subject: [PATCH 0010/5199] Resolve conflicts with recent slider hotfix --- .../Difficulty/OsuDifficultyCalculator.cs | 15 ++++++++++----- .../Difficulty/OsuPerformanceCalculator.cs | 10 ++++++++++ .../Difficulty/Skills/Flashlight.cs | 2 +- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 19e92d4365..4e916af813 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -35,8 +35,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty return new OsuDifficultyAttributes { Mods = mods, Skills = skills }; double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; - double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; - double flashlightRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; + double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; + double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; + double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier; + + double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; if (mods.Any(h => h is OsuModRelax)) speedRating = 0.0; @@ -75,6 +78,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty AimStrain = aimRating, SpeedStrain = speedRating, FlashlightRating = flashlightRating, + SliderFactor = sliderFactor, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, OverallDifficulty = (80 - hitWindowGreat) / 6, DrainRate = drainRate, @@ -111,7 +115,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty return new Skill[] { - new Aim(mods), + new Aim(mods, true), + new Aim(mods, false), new Speed(mods, hitWindowGreat), new Flashlight(mods, preempt) }; @@ -124,7 +129,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty new OsuModEasy(), new OsuModHardRock(), new OsuModFlashlight(), - new OsuModHidden(), + new OsuModHidden() }; } -} +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 817b0b63a8..f4c451f80b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -125,6 +125,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); } + // We assume 15% of sliders in a map are difficult since there's no way to tell from the performance calculator. + double estimateDifficultSliders = Attributes.SliderCount * 0.15; + + if (Attributes.SliderCount > 0) + { + double estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, Attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); + double sliderNerfFactor = (1 - Attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + Attributes.SliderFactor; + aimValue *= sliderNerfFactor; + } + aimValue *= accuracy; // It is important to also consider accuracy difficulty when doing that. aimValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 55ef8db129..e0d2a084bd 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; From efac11e886f887673b36d5d42d840bc85ead9688 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Fri, 12 Nov 2021 21:42:27 +1100 Subject: [PATCH 0011/5199] Add extra bonus for hidden+flashlight --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index e0d2a084bd..caae53516d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills this.preemptTime = preemptTime; } - private double skillMultiplier => 0.12; + private double skillMultiplier => 0.11; private double strainDecayBase => 0.15; protected override double DecayWeight => 1.0; protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations. @@ -33,6 +33,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private double preemptTime; private const double max_opacity_bonus = 0.4; + private const double hidden_bonus = 0.3; private double currentStrain; @@ -81,6 +82,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (hidden) { result *= 1.0 + max_opacity_bonus; + // Additional bonus for Hidden due to there being no approach circles. + result *= 1.0 + hidden_bonus; } return result; From f2d05ea899ba2aaa93deb9680042c1ce6a5cba8f Mon Sep 17 00:00:00 2001 From: MBmasher Date: Wed, 17 Nov 2021 11:27:48 +1100 Subject: [PATCH 0012/5199] Remove strain being multiplied by max opacity bonus --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index caae53516d..6d6f2b0f6c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -80,11 +80,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills result = Math.Pow(smallDistNerf * result, 2.0); - if (hidden) { - result *= 1.0 + max_opacity_bonus; - // Additional bonus for Hidden due to there being no approach circles. + // Additional bonus for Hidden due to there being no approach circles. + if (hidden) result *= 1.0 + hidden_bonus; - } return result; } From 63c5f7d9d7b16309f045fa22faf39e4809a0a631 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Wed, 17 Nov 2021 11:39:12 +1100 Subject: [PATCH 0013/5199] Balancing opacity and hidden bonus --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 6d6f2b0f6c..12580cb450 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private double preemptTime; private const double max_opacity_bonus = 0.4; - private const double hidden_bonus = 0.3; + private const double hidden_bonus = 0.4; private double currentStrain; From 6a444b9edbc2c8b780e965b08dcef1ade102e990 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Thu, 18 Nov 2021 09:47:41 +1100 Subject: [PATCH 0014/5199] Further balancing opacity/hidden bonus --- .../Difficulty/Flashlight.cs | 112 ++++++++++++++++++ .../Difficulty/Skills/Flashlight.cs | 8 +- 2 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Difficulty/Flashlight.cs diff --git a/osu.Game.Rulesets.Osu/Difficulty/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Flashlight.cs new file mode 100644 index 0000000000..da7ab3d1fc --- /dev/null +++ b/osu.Game.Rulesets.Osu/Difficulty/Flashlight.cs @@ -0,0 +1,112 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Difficulty.Skills +{ + /// + /// Represents the skill required to memorise and hit every object in a map with the Flashlight mod enabled. + /// + public class Flashlight : OsuStrainSkill + { + public Flashlight(Mod[] mods, double preemptTime) + : base(mods) + { + this.mods = mods; + this.preemptTime = preemptTime; + } + + private double skillMultiplier => 0.07; + private double strainDecayBase => 0.15; + protected override double DecayWeight => 1.0; + protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations. + + private Mod[] mods; + private bool hidden; + private double preemptTime; + + private const double max_opacity_bonus = 1.0; + private const double hidden_bonus = 0.8; + + private double currentStrain; + + private double strainValueOf(DifficultyHitObject current) + { + if (current.BaseObject is Spinner) + return 0; + + hidden = mods.Any(m => m is OsuModHidden); + + var osuCurrent = (OsuDifficultyHitObject)current; + var osuHitObject = (OsuHitObject)(osuCurrent.BaseObject); + + double scalingFactor = 52.0 / osuHitObject.Radius; + double smallDistNerf = 1.0; + double cumulativeStrainTime = 0.0; + + double result = 0.0; + + for (int i = 0; i < Previous.Count; i++) + { + var osuPrevious = (OsuDifficultyHitObject)Previous[i]; + var osuPreviousHitObject = (OsuHitObject)(osuPrevious.BaseObject); + + if (!(osuPrevious.BaseObject is Spinner)) + { + double jumpDistance = (osuHitObject.StackedPosition - osuPreviousHitObject.EndPosition).Length; + + cumulativeStrainTime += osuPrevious.StrainTime; + + // We want to nerf objects that can be easily seen within the Flashlight circle radius. + if (i == 0) + smallDistNerf = Math.Min(1.0, jumpDistance / 75.0); + + // We also want to nerf stacks so that only the first object of the stack is accounted for. + double stackNerf = Math.Min(1.0, (osuPrevious.JumpDistance / scalingFactor) / 25.0); + + // Bonus based on how visible the object is. + double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - opacity(cumulativeStrainTime, preemptTime, hidden)); + + result += Math.Pow(0.8, i) * stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime; + } + } + + result = Math.Pow(smallDistNerf * result, 2.0); + + // Additional bonus for Hidden due to there being no approach circles. + if (hidden) + result *= 1.0 + hidden_bonus; + + return result; + } + + private double opacity(double ms, double preemptTime, bool hidden) { + if (hidden) { + return Math.Clamp(Math.Min((1 - ms / preemptTime) * 2.5, (ms / preemptTime) * (1.0 / 0.3)), 0.0, 1.0); + } + else + { + return Math.Clamp((1.0 - ms / preemptTime) * 1.5, 0.0, 1.0); + } + } + + private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); + + protected override double CalculateInitialStrain(double time) => currentStrain * strainDecay(time - Previous[0].StartTime); + + protected override double StrainValueAt(DifficultyHitObject current) + { + currentStrain *= strainDecay(current.DeltaTime); + currentStrain += strainValueOf(current) * skillMultiplier; + + return currentStrain; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 12580cb450..209374714d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills this.preemptTime = preemptTime; } - private double skillMultiplier => 0.11; + private double skillMultiplier => 0.09; private double strainDecayBase => 0.15; protected override double DecayWeight => 1.0; protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations. @@ -32,8 +32,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private bool hidden; private double preemptTime; - private const double max_opacity_bonus = 0.4; - private const double hidden_bonus = 0.4; + private const double max_opacity_bonus = 0.7; + private const double hidden_bonus = 0.5; private double currentStrain; From 8e8571543d526ee218cceb1c56b0ae784bc870bc Mon Sep 17 00:00:00 2001 From: MBmasher Date: Thu, 18 Nov 2021 09:48:18 +1100 Subject: [PATCH 0015/5199] Removing unnecessary file --- .../Difficulty/Flashlight.cs | 112 ------------------ 1 file changed, 112 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Difficulty/Flashlight.cs diff --git a/osu.Game.Rulesets.Osu/Difficulty/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Flashlight.cs deleted file mode 100644 index da7ab3d1fc..0000000000 --- a/osu.Game.Rulesets.Osu/Difficulty/Flashlight.cs +++ /dev/null @@ -1,112 +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.Linq; -using osu.Game.Rulesets.Difficulty.Preprocessing; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; -using osu.Game.Rulesets.Osu.Objects; - -namespace osu.Game.Rulesets.Osu.Difficulty.Skills -{ - /// - /// Represents the skill required to memorise and hit every object in a map with the Flashlight mod enabled. - /// - public class Flashlight : OsuStrainSkill - { - public Flashlight(Mod[] mods, double preemptTime) - : base(mods) - { - this.mods = mods; - this.preemptTime = preemptTime; - } - - private double skillMultiplier => 0.07; - private double strainDecayBase => 0.15; - protected override double DecayWeight => 1.0; - protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations. - - private Mod[] mods; - private bool hidden; - private double preemptTime; - - private const double max_opacity_bonus = 1.0; - private const double hidden_bonus = 0.8; - - private double currentStrain; - - private double strainValueOf(DifficultyHitObject current) - { - if (current.BaseObject is Spinner) - return 0; - - hidden = mods.Any(m => m is OsuModHidden); - - var osuCurrent = (OsuDifficultyHitObject)current; - var osuHitObject = (OsuHitObject)(osuCurrent.BaseObject); - - double scalingFactor = 52.0 / osuHitObject.Radius; - double smallDistNerf = 1.0; - double cumulativeStrainTime = 0.0; - - double result = 0.0; - - for (int i = 0; i < Previous.Count; i++) - { - var osuPrevious = (OsuDifficultyHitObject)Previous[i]; - var osuPreviousHitObject = (OsuHitObject)(osuPrevious.BaseObject); - - if (!(osuPrevious.BaseObject is Spinner)) - { - double jumpDistance = (osuHitObject.StackedPosition - osuPreviousHitObject.EndPosition).Length; - - cumulativeStrainTime += osuPrevious.StrainTime; - - // We want to nerf objects that can be easily seen within the Flashlight circle radius. - if (i == 0) - smallDistNerf = Math.Min(1.0, jumpDistance / 75.0); - - // We also want to nerf stacks so that only the first object of the stack is accounted for. - double stackNerf = Math.Min(1.0, (osuPrevious.JumpDistance / scalingFactor) / 25.0); - - // Bonus based on how visible the object is. - double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - opacity(cumulativeStrainTime, preemptTime, hidden)); - - result += Math.Pow(0.8, i) * stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime; - } - } - - result = Math.Pow(smallDistNerf * result, 2.0); - - // Additional bonus for Hidden due to there being no approach circles. - if (hidden) - result *= 1.0 + hidden_bonus; - - return result; - } - - private double opacity(double ms, double preemptTime, bool hidden) { - if (hidden) { - return Math.Clamp(Math.Min((1 - ms / preemptTime) * 2.5, (ms / preemptTime) * (1.0 / 0.3)), 0.0, 1.0); - } - else - { - return Math.Clamp((1.0 - ms / preemptTime) * 1.5, 0.0, 1.0); - } - } - - private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); - - protected override double CalculateInitialStrain(double time) => currentStrain * strainDecay(time - Previous[0].StartTime); - - protected override double StrainValueAt(DifficultyHitObject current) - { - currentStrain *= strainDecay(current.DeltaTime); - currentStrain += strainValueOf(current) * skillMultiplier; - - return currentStrain; - } - } -} From 05b44f5df46697ef6989ad1cb5e934e85b734478 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Thu, 18 Nov 2021 09:55:04 +1100 Subject: [PATCH 0016/5199] Add slider bonus to Flashlight skill --- .../Difficulty/Skills/Flashlight.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 466f0556ab..53634e9125 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -24,6 +24,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected override double DecayWeight => 1.0; protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations. + private const double min_velocity = 0.5; + private const double slider_multiplier = 2.5; + private double currentStrain; private double strainValueOf(DifficultyHitObject current) @@ -62,7 +65,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills } } - return Math.Pow(smallDistNerf * result, 2.0); + result = Math.Pow(smallDistNerf * result, 2.0); + + double sliderBonus = 0.0; + if (osuCurrent.TravelTime != 0) + { + // Reward sliders based on velocity. + double normalisedTravelDistance = osuCurrent.TravelDistance / scalingFactor; + sliderBonus = Math.Max(0.0, (normalisedTravelDistance) / osuCurrent.TravelTime - min_velocity); + // Longer sliders require more memorisation. + sliderBonus *= normalisedTravelDistance; + } + + result += sliderBonus * slider_multiplier; + + return result; } private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); From a77a9a2309ae0068c4a50689981959a85a8f8cf8 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Thu, 18 Nov 2021 10:13:25 +1100 Subject: [PATCH 0017/5199] Balancing slider bonus --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 53634e9125..29ad7b2911 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations. private const double min_velocity = 0.5; - private const double slider_multiplier = 2.5; + private const double slider_multiplier = 1.3; private double currentStrain; @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills { // Reward sliders based on velocity. double normalisedTravelDistance = osuCurrent.TravelDistance / scalingFactor; - sliderBonus = Math.Max(0.0, (normalisedTravelDistance) / osuCurrent.TravelTime - min_velocity); + sliderBonus = Math.Pow(Math.Max(0.0, (normalisedTravelDistance) / osuCurrent.TravelTime - min_velocity), 0.5); // Longer sliders require more memorisation. sliderBonus *= normalisedTravelDistance; } From e42f28990b6db55357ca95fab01b192e9a91de73 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Thu, 18 Nov 2021 10:30:17 +1100 Subject: [PATCH 0018/5199] Add blank line --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 29ad7b2911..c926222875 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -68,6 +68,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills result = Math.Pow(smallDistNerf * result, 2.0); double sliderBonus = 0.0; + if (osuCurrent.TravelTime != 0) { // Reward sliders based on velocity. From 92cf447180144a3b2a00fff624aacd531eda8406 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Thu, 18 Nov 2021 10:32:41 +1100 Subject: [PATCH 0019/5199] Remove unnecessary braces --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 209374714d..d8e73ffad4 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -88,13 +88,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills } private double opacity(double ms, double preemptTime, bool hidden) { - if (hidden) { + if (hidden) return Math.Clamp(Math.Min((1 - ms / preemptTime) * 2.5, (ms / preemptTime) * (1.0 / 0.3)), 0.0, 1.0); - } else - { return Math.Clamp((1.0 - ms / preemptTime) * 1.5, 0.0, 1.0); - } } private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); From 30e18f16d9b6c42eec1b5cace8836cbc1151f7bf Mon Sep 17 00:00:00 2001 From: MBmasher Date: Thu, 18 Nov 2021 10:33:44 +1100 Subject: [PATCH 0020/5199] Change mods and preemptTime to readonly --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index d8e73ffad4..3ae9c0eca9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -28,9 +28,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected override double DecayWeight => 1.0; protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations. - private Mod[] mods; + private readonly Mod[] mods; private bool hidden; - private double preemptTime; + private readonly double preemptTime; private const double max_opacity_bonus = 0.7; private const double hidden_bonus = 0.5; From f4b23f09607ca514c62e2da8f1e8b07a707f2036 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Thu, 18 Nov 2021 10:37:07 +1100 Subject: [PATCH 0021/5199] Remove setting preempt in CreateDifficultyAttributes --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 4e916af813..fe20ce112e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -60,7 +60,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty double starRating = basePerformance > 0.00001 ? Math.Cbrt(1.12) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0; - double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; double drainRate = beatmap.Difficulty.DrainRate; int maxCombo = beatmap.HitObjects.Count; From fe83b8fc77c8e247bcf48fe597f35a4c381b49c3 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Thu, 18 Nov 2021 10:50:32 +1100 Subject: [PATCH 0022/5199] Add line break --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 3ae9c0eca9..b45a54f9e7 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -87,7 +87,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills return result; } - private double opacity(double ms, double preemptTime, bool hidden) { + private double opacity(double ms, double preemptTime, bool hidden) + { if (hidden) return Math.Clamp(Math.Min((1 - ms / preemptTime) * 2.5, (ms / preemptTime) * (1.0 / 0.3)), 0.0, 1.0); else From abbc13c60feeaf42ddb57d21d160020fcd5c6cf7 Mon Sep 17 00:00:00 2001 From: David Paiva Date: Sat, 20 Nov 2021 12:41:01 +0000 Subject: [PATCH 0023/5199] Added Beatmap Link button to Discord Rich Presence --- osu.Desktop/DiscordRichPresence.cs | 31 +++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index e1e7e6ad18..f58633ae9e 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -47,6 +47,8 @@ namespace osu.Desktop SkipIdenticalPresence = false // handles better on discord IPC loss, see updateStatus call in onReady. }; + client.RegisterUriScheme(); + client.OnReady += onReady; // safety measure for now, until we performance test / improve backoff for failed connections. @@ -90,10 +92,22 @@ namespace osu.Desktop return; } - if (status.Value is UserStatusOnline && activity.Value != null) + if (/*status.Value is UserStatusOnline &&*/ activity.Value != null) { presence.State = truncate(activity.Value.Status); presence.Details = truncate(getDetails(activity.Value)); + + if (getOnlineID(activity.Value) != string.Empty) + { + presence.Buttons = new Button[] + { + new Button() { Label = "Beatmap Link", Url = $"https://osu.ppy.sh/b/{getOnlineID(activity.Value)}" } + }; + } + else + { + presence.Buttons = null; + } } else { @@ -109,6 +123,7 @@ namespace osu.Desktop // update ruleset presence.Assets.SmallImageKey = ruleset.Value.ID <= 3 ? $"mode_{ruleset.Value.ID}" : "mode_custom"; + presence.Assets.SmallImageText = ruleset.Value.Name; client.SetPresence(presence); @@ -152,6 +167,20 @@ namespace osu.Desktop return string.Empty; } + private string getOnlineID(UserActivity activity) + { + switch (activity) + { + case UserActivity.InGame game: + if (!(game.BeatmapInfo.OnlineID < 1)) + return game.BeatmapInfo.OnlineID.ToString(); + + return string.Empty; + } + + return string.Empty; + } + protected override void Dispose(bool isDisposing) { client.Dispose(); From e65826979e8162c92613e19df322fb3546e3c0bb Mon Sep 17 00:00:00 2001 From: David Paiva Date: Sat, 20 Nov 2021 12:41:31 +0000 Subject: [PATCH 0024/5199] Whoops, forgot that comment --- osu.Desktop/DiscordRichPresence.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index f58633ae9e..dd3fca095e 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -92,7 +92,7 @@ namespace osu.Desktop return; } - if (/*status.Value is UserStatusOnline &&*/ activity.Value != null) + if (status.Value is UserStatusOnline && activity.Value != null) { presence.State = truncate(activity.Value.Status); presence.Details = truncate(getDetails(activity.Value)); From 5276300c081ce7d3cfd2042d89c7189fa9866e33 Mon Sep 17 00:00:00 2001 From: David Paiva Date: Sat, 20 Nov 2021 14:11:02 +0000 Subject: [PATCH 0025/5199] Added required changes. --- osu.Desktop/DiscordRichPresence.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index dd3fca095e..d72774e96f 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -97,7 +97,7 @@ namespace osu.Desktop presence.State = truncate(activity.Value.Status); presence.Details = truncate(getDetails(activity.Value)); - if (getOnlineID(activity.Value) != string.Empty) + if (getOnlineID(activity.Value) != null) { presence.Buttons = new Button[] { @@ -167,18 +167,14 @@ namespace osu.Desktop return string.Empty; } - private string getOnlineID(UserActivity activity) + private int? getOnlineID(UserActivity activity) { - switch (activity) + if (activity is UserActivity.InGame game && game.BeatmapInfo.OnlineID > 0) { - case UserActivity.InGame game: - if (!(game.BeatmapInfo.OnlineID < 1)) - return game.BeatmapInfo.OnlineID.ToString(); - - return string.Empty; + return game.BeatmapInfo.OnlineID; } - return string.Empty; + return null; } protected override void Dispose(bool isDisposing) From 58d3e66d8b194a07a83d52e6953a99d9c52261c4 Mon Sep 17 00:00:00 2001 From: David Paiva Date: Sun, 21 Nov 2021 09:36:05 +0000 Subject: [PATCH 0026/5199] Update osu.Desktop/DiscordRichPresence.cs Co-authored-by: Salman Ahmed --- osu.Desktop/DiscordRichPresence.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index d72774e96f..46aa080dfe 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -101,7 +101,7 @@ namespace osu.Desktop { presence.Buttons = new Button[] { - new Button() { Label = "Beatmap Link", Url = $"https://osu.ppy.sh/b/{getOnlineID(activity.Value)}" } + new Button() { Label = "Open Beatmap", Url = $"https://osu.ppy.sh/b/{getOnlineID(activity.Value)}" } }; } else From afbec941249b1d309ed31be7fcd0e84e180805f6 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Sun, 21 Nov 2021 23:40:15 +1100 Subject: [PATCH 0027/5199] Move opacity function to OsuDifficultyHitObject --- .../Preprocessing/OsuDifficultyHitObject.cs | 11 +++++++++++ osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 10 +--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index d073d751d0..dc8188929a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -56,12 +56,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing private readonly OsuHitObject lastLastObject; private readonly OsuHitObject lastObject; + private readonly double clockRate; public OsuDifficultyHitObject(HitObject hitObject, HitObject lastLastObject, HitObject lastObject, double clockRate) : base(hitObject, lastObject, clockRate) { this.lastLastObject = (OsuHitObject)lastLastObject; this.lastObject = (OsuHitObject)lastObject; + this.clockRate = clockRate; // Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects. StrainTime = Math.Max(DeltaTime, min_delta_time); @@ -69,6 +71,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing setDistances(clockRate); } + public double opacity(double ms, bool hidden) + { + double preemptTime = BaseObject.TimePreempt / clockRate; + if (hidden) + return Math.Clamp(Math.Min((1 - ms / preemptTime) * 2.5, (ms / preemptTime) * (1.0 / 0.3)), 0.0, 1.0); + else + return Math.Clamp((1.0 - ms / preemptTime) * 1.5, 0.0, 1.0); + } + private void setDistances(double clockRate) { // We don't need to calculate either angle or distance when one of the last->curr objects is a spinner diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index b45a54f9e7..701670974b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double stackNerf = Math.Min(1.0, (osuPrevious.JumpDistance / scalingFactor) / 25.0); // Bonus based on how visible the object is. - double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - opacity(cumulativeStrainTime, preemptTime, hidden)); + double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.opacity(cumulativeStrainTime, hidden)); result += Math.Pow(0.8, i) * stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime; } @@ -87,14 +87,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills return result; } - private double opacity(double ms, double preemptTime, bool hidden) - { - if (hidden) - return Math.Clamp(Math.Min((1 - ms / preemptTime) * 2.5, (ms / preemptTime) * (1.0 / 0.3)), 0.0, 1.0); - else - return Math.Clamp((1.0 - ms / preemptTime) * 1.5, 0.0, 1.0); - } - private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); protected override double CalculateInitialStrain(double time) => currentStrain * strainDecay(time - Previous[0].StartTime); From a57c277a585573b3081f6834b1cab6f09be60cf8 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Sun, 21 Nov 2021 23:43:09 +1100 Subject: [PATCH 0028/5199] Move preempt back to CreateDifficultyAttributes --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 6 ++---- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 4 +--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index fe20ce112e..14101f8302 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -22,7 +22,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty { private const double difficulty_multiplier = 0.0675; private double hitWindowGreat; - private double preempt; public OsuDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) @@ -60,6 +59,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double starRating = basePerformance > 0.00001 ? Math.Cbrt(1.12) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0; + double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; double drainRate = beatmap.Difficulty.DrainRate; int maxCombo = beatmap.HitObjects.Count; @@ -110,14 +110,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty hitWindowGreat = hitWindows.WindowFor(HitResult.Great) / clockRate; - preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; - return new Skill[] { new Aim(mods, true), new Aim(mods, false), new Speed(mods, hitWindowGreat), - new Flashlight(mods, preempt) + new Flashlight(mods) }; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 701670974b..8969e95aba 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -16,11 +16,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public class Flashlight : OsuStrainSkill { - public Flashlight(Mod[] mods, double preemptTime) + public Flashlight(Mod[] mods) : base(mods) { this.mods = mods; - this.preemptTime = preemptTime; } private double skillMultiplier => 0.09; @@ -30,7 +29,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private readonly Mod[] mods; private bool hidden; - private readonly double preemptTime; private const double max_opacity_bonus = 0.7; private const double hidden_bonus = 0.5; From e9a4ee68004b998264f6577fe79050b276b7c1a0 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Sun, 21 Nov 2021 23:53:40 +1100 Subject: [PATCH 0029/5199] Cleaning up code --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 3 ++- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index dc8188929a..cc699aa3f9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -71,9 +71,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing setDistances(clockRate); } - public double opacity(double ms, bool hidden) + public double Opacity(double ms, bool hidden) { double preemptTime = BaseObject.TimePreempt / clockRate; + if (hidden) return Math.Clamp(Math.Min((1 - ms / preemptTime) * 2.5, (ms / preemptTime) * (1.0 / 0.3)), 0.0, 1.0); else diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 8969e95aba..2523f66bf6 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double stackNerf = Math.Min(1.0, (osuPrevious.JumpDistance / scalingFactor) / 25.0); // Bonus based on how visible the object is. - double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.opacity(cumulativeStrainTime, hidden)); + double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.Opacity(cumulativeStrainTime, hidden)); result += Math.Pow(0.8, i) * stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime; } From e9745a3ac41c93ec5860505cf833ec5618c5a177 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Mon, 22 Nov 2021 08:32:35 +1100 Subject: [PATCH 0030/5199] Fix wrong opacity formula --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index cc699aa3f9..4109c068ea 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -74,11 +74,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing public double Opacity(double ms, bool hidden) { double preemptTime = BaseObject.TimePreempt / clockRate; + double fadeInTime = BaseObject.TimeFadeIn / clockRate; if (hidden) - return Math.Clamp(Math.Min((1 - ms / preemptTime) * 2.5, (ms / preemptTime) * (1.0 / 0.3)), 0.0, 1.0); + return Math.Clamp(Math.Min((1.0 - ms / preemptTime) * 2.5, (ms / preemptTime - 0.3) * (1.0 / 0.3)), 0.0, 1.0); else - return Math.Clamp((1.0 - ms / preemptTime) * 1.5, 0.0, 1.0); + return Math.Clamp((preemptTime - ms) / fadeInTime, 0.0, 1.0); } private void setDistances(double clockRate) From 7833fab02d0f1e3add202aa2f24118b67f4f404d Mon Sep 17 00:00:00 2001 From: MBmasher Date: Mon, 22 Nov 2021 08:41:56 +1100 Subject: [PATCH 0031/5199] Balancing bonuses to adjust for corrected opacity formula --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 2523f66bf6..7518364dd1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills this.mods = mods; } - private double skillMultiplier => 0.09; + private double skillMultiplier => 0.11; private double strainDecayBase => 0.15; protected override double DecayWeight => 1.0; protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations. @@ -30,8 +30,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private readonly Mod[] mods; private bool hidden; - private const double max_opacity_bonus = 0.7; - private const double hidden_bonus = 0.5; + private const double max_opacity_bonus = 0.5; + private const double hidden_bonus = 0.1; private double currentStrain; From 65ef03034187ed9e65e288b1eee96d030eef145f Mon Sep 17 00:00:00 2001 From: MBmasher Date: Mon, 22 Nov 2021 08:59:41 +1100 Subject: [PATCH 0032/5199] Further balancing --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 7518364dd1..7b4119d354 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills this.mods = mods; } - private double skillMultiplier => 0.11; + private double skillMultiplier => 0.1; private double strainDecayBase => 0.15; protected override double DecayWeight => 1.0; protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations. @@ -30,8 +30,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private readonly Mod[] mods; private bool hidden; - private const double max_opacity_bonus = 0.5; - private const double hidden_bonus = 0.1; + private const double max_opacity_bonus = 0.4; + private const double hidden_bonus = 0.2; private double currentStrain; From 383bf7cdfc7f71d3528ad054fcfd7c16aef751ff Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 30 Nov 2021 10:39:48 +0900 Subject: [PATCH 0033/5199] Only allow HD combination alongside FL --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 1b6eb2915b..79314344ea 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty new OsuModEasy(), new OsuModHardRock(), new OsuModFlashlight(), - new OsuModHidden() + new MultiMod(new OsuModFlashlight(), new OsuModHidden()) }; } -} \ No newline at end of file +} From b0dc8bf0616cbf5ac9cb07edc8f26a7b33845223 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Tue, 30 Nov 2021 12:58:49 +1100 Subject: [PATCH 0034/5199] Change Opacity function to take in absolute map time rather than relative time --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 5 ++++- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 4109c068ea..c94fd10db5 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -71,8 +71,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing setDistances(clockRate); } - public double Opacity(double ms, bool hidden) + public double Opacity(double T, bool hidden) { + double ms = (BaseObject.StartTime - T) / clockRate; + if (ms < 0) + return 0.0; double preemptTime = BaseObject.TimePreempt / clockRate; double fadeInTime = BaseObject.TimeFadeIn / clockRate; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 7b4119d354..d7f515602e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double stackNerf = Math.Min(1.0, (osuPrevious.JumpDistance / scalingFactor) / 25.0); // Bonus based on how visible the object is. - double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.Opacity(cumulativeStrainTime, hidden)); + double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.Opacity(osuPreviousHitObject.StartTime, hidden)); result += Math.Pow(0.8, i) * stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime; } From 3339afd6485270b4b3ac9db1d1dbffe599dc31da Mon Sep 17 00:00:00 2001 From: MBmasher Date: Tue, 30 Nov 2021 13:52:58 +1100 Subject: [PATCH 0035/5199] Change input variable name in Opacity function --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index c94fd10db5..7ab12f109f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -71,9 +71,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing setDistances(clockRate); } - public double Opacity(double T, bool hidden) + public double Opacity(double mapTime, bool hidden) { - double ms = (BaseObject.StartTime - T) / clockRate; + double ms = (BaseObject.StartTime - mapTime) / clockRate; if (ms < 0) return 0.0; double preemptTime = BaseObject.TimePreempt / clockRate; From 5884b058b9f15ab2d518316d5e62c690431367b0 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Tue, 30 Nov 2021 13:54:41 +1100 Subject: [PATCH 0036/5199] Add blank line --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 7ab12f109f..a497c95990 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -76,6 +76,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing double ms = (BaseObject.StartTime - mapTime) / clockRate; if (ms < 0) return 0.0; + double preemptTime = BaseObject.TimePreempt / clockRate; double fadeInTime = BaseObject.TimeFadeIn / clockRate; From 9824d805eac344ab727d87d2b269ac0fca34d6b2 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Tue, 30 Nov 2021 14:36:38 +1100 Subject: [PATCH 0037/5199] Remove unnecessary clockRate in Opacity function --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index a497c95990..dacbb4c51f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -56,14 +56,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing private readonly OsuHitObject lastLastObject; private readonly OsuHitObject lastObject; - private readonly double clockRate; public OsuDifficultyHitObject(HitObject hitObject, HitObject lastLastObject, HitObject lastObject, double clockRate) : base(hitObject, lastObject, clockRate) { this.lastLastObject = (OsuHitObject)lastLastObject; this.lastObject = (OsuHitObject)lastObject; - this.clockRate = clockRate; // Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects. StrainTime = Math.Max(DeltaTime, min_delta_time); @@ -73,12 +71,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing public double Opacity(double mapTime, bool hidden) { - double ms = (BaseObject.StartTime - mapTime) / clockRate; + double ms = BaseObject.StartTime - mapTime; if (ms < 0) return 0.0; - double preemptTime = BaseObject.TimePreempt / clockRate; - double fadeInTime = BaseObject.TimeFadeIn / clockRate; + double preemptTime = BaseObject.TimePreempt; + double fadeInTime = BaseObject.TimeFadeIn; if (hidden) return Math.Clamp(Math.Min((1.0 - ms / preemptTime) * 2.5, (ms / preemptTime - 0.3) * (1.0 / 0.3)), 0.0, 1.0); From a7aea49cb38be1108d91bcc884a25b9526019ee4 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Tue, 21 Dec 2021 20:06:07 +1100 Subject: [PATCH 0038/5199] Rename `osuPreviousHitObject` to `currentHitObject` --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 76ce6dc057..1a707b26b5 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double stackNerf = Math.Min(1.0, (currentObj.LazyJumpDistance / scalingFactor) / 25.0); // Bonus based on how visible the object is. - double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.Opacity(osuPreviousHitObject.StartTime, hidden)); + double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.Opacity(currentHitObject.StartTime, hidden)); result += stackNerf * scalingFactor * jumpDistance / cumulativeStrainTime; } From 3d3de00581648712338aa5237926a143f8275710 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Tue, 21 Dec 2021 20:06:53 +1100 Subject: [PATCH 0039/5199] Move `hidden` initialisation to Flashlight constructor --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 1a707b26b5..3b23ec505b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills : base(mods) { this.mods = mods; + this.hidden = mods.Any(m => m is OsuModHidden); } private double skillMultiplier => 0.1; @@ -40,8 +41,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (current.BaseObject is Spinner) return 0; - hidden = mods.Any(m => m is OsuModHidden); - var osuCurrent = (OsuDifficultyHitObject)current; var osuHitObject = (OsuHitObject)(osuCurrent.BaseObject); From 5d8968498cacbadfa704f4aa730652d6a6709228 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Tue, 21 Dec 2021 20:08:57 +1100 Subject: [PATCH 0040/5199] Adjust `skillMultiplier` after merging #15728, #15867 --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 3b23ec505b..266f4e8960 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills this.hidden = mods.Any(m => m is OsuModHidden); } - private double skillMultiplier => 0.1; + private double skillMultiplier => 0.05; private double strainDecayBase => 0.15; protected override double DecayWeight => 1.0; protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations. From c5de203aa5258b66a2b7067c6306c12b75e158f3 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Tue, 21 Dec 2021 20:10:19 +1100 Subject: [PATCH 0041/5199] Multiply `opacityBonus` to base strain --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 266f4e8960..8b2e66e0a7 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills // Bonus based on how visible the object is. double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.Opacity(currentHitObject.StartTime, hidden)); - result += stackNerf * scalingFactor * jumpDistance / cumulativeStrainTime; + result += stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime; } lastObj = currentObj; From 2aafcd3628463ccd2c5c0c8c88b32495ebc8694c Mon Sep 17 00:00:00 2001 From: MBmasher Date: Tue, 21 Dec 2021 20:58:05 +1100 Subject: [PATCH 0042/5199] Refactor code regarding `hidden` boolean --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 8b2e66e0a7..5d2052046b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -19,8 +19,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public Flashlight(Mod[] mods) : base(mods) { - this.mods = mods; - this.hidden = mods.Any(m => m is OsuModHidden); + hidden = mods.Any(m => m is OsuModHidden); } private double skillMultiplier => 0.05; @@ -28,8 +27,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected override double DecayWeight => 1.0; protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations. - private readonly Mod[] mods; - private bool hidden; + private readonly bool hidden; private const double max_opacity_bonus = 0.4; private const double hidden_bonus = 0.2; From 327822de5bb5e15098c53fce05ae0eddad495e58 Mon Sep 17 00:00:00 2001 From: pikokr Date: Mon, 27 Dec 2021 19:41:36 +0900 Subject: [PATCH 0043/5199] Add touchscreen support for osu!mania ruleset --- osu.Game.Rulesets.Mania/UI/Column.cs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 9d060944cd..df39b5397b 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -136,6 +136,33 @@ namespace osu.Game.Rulesets.Mania.UI { } + // https://github.com/ppy/osu-framework/blob/49c954321c3686628b2c223670363438f88a0341/osu.Framework/Graphics/Drawable.cs#L1513-L1524 + private T findClosestParent() where T : class, IDrawable + { + Drawable cursor = this; + + while ((cursor = cursor.Parent) != null) + { + if (cursor is T match) + return match; + } + + return default; + } + + private ManiaInputManager.RulesetKeyBindingContainer keyBindingManager => findClosestParent(); + + protected override bool OnTouchDown(TouchDownEvent e) + { + keyBindingManager.TriggerPressed(Action.Value); + return base.OnTouchDown(e); + } + + protected override void OnTouchUp(TouchUpEvent e) + { + keyBindingManager.TriggerReleased(Action.Value); + } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) // This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border => DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos)); From 58994b790c8e8a3c85fa5d82edc2835b648b8843 Mon Sep 17 00:00:00 2001 From: pikokr Date: Mon, 27 Dec 2021 21:20:52 +0900 Subject: [PATCH 0044/5199] Get key binding container once instead of getting on every touch --- osu.Game.Rulesets.Mania/ManiaInputManager.cs | 5 +++++ osu.Game.Rulesets.Mania/UI/Column.cs | 21 ++++++-------------- osu.Game/Rulesets/UI/DrawableRuleset.cs | 1 + 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaInputManager.cs b/osu.Game.Rulesets.Mania/ManiaInputManager.cs index 186fc4b15d..bf94735077 100644 --- a/osu.Game.Rulesets.Mania/ManiaInputManager.cs +++ b/osu.Game.Rulesets.Mania/ManiaInputManager.cs @@ -13,6 +13,11 @@ namespace osu.Game.Rulesets.Mania : base(ruleset, variant, SimultaneousBindingMode.Unique) { } + + public RulesetKeyBindingContainer GetKeyBindingContainer() + { + return (RulesetKeyBindingContainer)KeyBindingContainer; + } } public enum ManiaAction diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index df39b5397b..6be36619c7 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -136,31 +136,22 @@ namespace osu.Game.Rulesets.Mania.UI { } - // https://github.com/ppy/osu-framework/blob/49c954321c3686628b2c223670363438f88a0341/osu.Framework/Graphics/Drawable.cs#L1513-L1524 - private T findClosestParent() where T : class, IDrawable + private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } + + private ManiaInputManager.RulesetKeyBindingContainer getKeyBindingManager() { - Drawable cursor = this; - - while ((cursor = cursor.Parent) != null) - { - if (cursor is T match) - return match; - } - - return default; + return keyBindingContainer ??= ((ManiaInputManager)GetContainingInputManager()).GetKeyBindingContainer(); } - private ManiaInputManager.RulesetKeyBindingContainer keyBindingManager => findClosestParent(); - protected override bool OnTouchDown(TouchDownEvent e) { - keyBindingManager.TriggerPressed(Action.Value); + getKeyBindingManager().TriggerPressed(Action.Value); return base.OnTouchDown(e); } protected override void OnTouchUp(TouchUpEvent e) { - keyBindingManager.TriggerReleased(Action.Value); + getKeyBindingManager().TriggerReleased(Action.Value); } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 29559f5036..30012784a3 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -50,6 +50,7 @@ namespace osu.Game.Rulesets.UI /// /// The key conversion input manager for this DrawableRuleset. /// + [Cached] public PassThroughInputManager KeyBindingInputManager; public override double GameplayStartTime => Objects.FirstOrDefault()?.StartTime - 2000 ?? 0; From d62930500298d23419b2f37b0376881f0e6bb573 Mon Sep 17 00:00:00 2001 From: pikokr Date: Tue, 28 Dec 2021 21:47:58 +0900 Subject: [PATCH 0045/5199] Remove `Cached` attribute from `DrawableRuleset.KeyBindingInputManager` --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 30012784a3..29559f5036 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -50,7 +50,6 @@ namespace osu.Game.Rulesets.UI /// /// The key conversion input manager for this DrawableRuleset. /// - [Cached] public PassThroughInputManager KeyBindingInputManager; public override double GameplayStartTime => Objects.FirstOrDefault()?.StartTime - 2000 ?? 0; From 59b4aea5f9dc22a3cf543bddae589e462a8be383 Mon Sep 17 00:00:00 2001 From: pikokr Date: Tue, 28 Dec 2021 21:52:46 +0900 Subject: [PATCH 0046/5199] Make method and property name to match class name --- osu.Game.Rulesets.Mania/UI/Column.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 6be36619c7..36473aa900 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -136,22 +136,22 @@ namespace osu.Game.Rulesets.Mania.UI { } - private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } + private ManiaInputManager.RulesetKeyBindingContainer rulesetKeyBindingContainer { get; set; } - private ManiaInputManager.RulesetKeyBindingContainer getKeyBindingManager() + private ManiaInputManager.RulesetKeyBindingContainer getRulesetKeyBindingContainer() { - return keyBindingContainer ??= ((ManiaInputManager)GetContainingInputManager()).GetKeyBindingContainer(); + return rulesetKeyBindingContainer ??= ((ManiaInputManager)GetContainingInputManager()).GetKeyBindingContainer(); } protected override bool OnTouchDown(TouchDownEvent e) { - getKeyBindingManager().TriggerPressed(Action.Value); + getRulesetKeyBindingContainer().TriggerPressed(Action.Value); return base.OnTouchDown(e); } protected override void OnTouchUp(TouchUpEvent e) { - getKeyBindingManager().TriggerReleased(Action.Value); + getRulesetKeyBindingContainer().TriggerReleased(Action.Value); } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) From 62d6bb8c2e9c8a6e10a318599a7ad04974d55e76 Mon Sep 17 00:00:00 2001 From: pikokr Date: Tue, 28 Dec 2021 22:35:45 +0900 Subject: [PATCH 0047/5199] Trigger touch on click key area --- osu.Game.Rulesets.Mania/UI/Column.cs | 18 ------------- .../UI/Components/DefaultKeyArea.cs | 25 +++++++++++++++++-- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 36473aa900..9d060944cd 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -136,24 +136,6 @@ namespace osu.Game.Rulesets.Mania.UI { } - private ManiaInputManager.RulesetKeyBindingContainer rulesetKeyBindingContainer { get; set; } - - private ManiaInputManager.RulesetKeyBindingContainer getRulesetKeyBindingContainer() - { - return rulesetKeyBindingContainer ??= ((ManiaInputManager)GetContainingInputManager()).GetKeyBindingContainer(); - } - - protected override bool OnTouchDown(TouchDownEvent e) - { - getRulesetKeyBindingContainer().TriggerPressed(Action.Value); - return base.OnTouchDown(e); - } - - protected override void OnTouchUp(TouchUpEvent e) - { - getRulesetKeyBindingContainer().TriggerReleased(Action.Value); - } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) // This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border => DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos)); diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs index 267ed1f5f4..4e0bb71514 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs @@ -36,10 +36,31 @@ namespace osu.Game.Rulesets.Mania.UI.Components RelativeSizeAxes = Axes.Both; } + public class ChildContainer : Container + { + private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } + + private ManiaInputManager.RulesetKeyBindingContainer getKeyBindingContainer() + { + return keyBindingContainer ??= ((ManiaInputManager)GetContainingInputManager()).GetKeyBindingContainer(); + } + + protected override bool OnTouchDown(TouchDownEvent e) + { + getKeyBindingContainer().TriggerPressed(((DefaultKeyArea)Parent).column.Action.Value); + return base.OnTouchDown(e); + } + + protected override void OnTouchUp(TouchUpEvent e) + { + getKeyBindingContainer().TriggerReleased(((DefaultKeyArea)Parent).column.Action.Value); + } + } + [BackgroundDependencyLoader] private void load(IScrollingInfo scrollingInfo) { - InternalChild = directionContainer = new Container + InternalChild = directionContainer = new ChildContainer { RelativeSizeAxes = Axes.X, Height = Stage.HIT_TARGET_POSITION, @@ -69,7 +90,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components AlwaysPresent = true } } - } + }, } }; From dec1f317494c73bbb4313d7be2f1cadb5fde488c Mon Sep 17 00:00:00 2001 From: pikokr Date: Tue, 28 Dec 2021 22:43:07 +0900 Subject: [PATCH 0048/5199] Make `KeyBindingContainer` public --- osu.Game.Rulesets.Mania/ManiaInputManager.cs | 5 ----- osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs | 2 +- osu.Game/Rulesets/UI/RulesetInputManager.cs | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaInputManager.cs b/osu.Game.Rulesets.Mania/ManiaInputManager.cs index bf94735077..186fc4b15d 100644 --- a/osu.Game.Rulesets.Mania/ManiaInputManager.cs +++ b/osu.Game.Rulesets.Mania/ManiaInputManager.cs @@ -13,11 +13,6 @@ namespace osu.Game.Rulesets.Mania : base(ruleset, variant, SimultaneousBindingMode.Unique) { } - - public RulesetKeyBindingContainer GetKeyBindingContainer() - { - return (RulesetKeyBindingContainer)KeyBindingContainer; - } } public enum ManiaAction diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs index 4e0bb71514..b3a2a97bf7 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components private ManiaInputManager.RulesetKeyBindingContainer getKeyBindingContainer() { - return keyBindingContainer ??= ((ManiaInputManager)GetContainingInputManager()).GetKeyBindingContainer(); + return keyBindingContainer ??= (ManiaInputManager.RulesetKeyBindingContainer)((ManiaInputManager)GetContainingInputManager()).KeyBindingContainer; } protected override bool OnTouchDown(TouchDownEvent e) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 6564ff9e23..6ef99f8aae 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.UI protected override InputState CreateInitialState() => new RulesetInputManagerInputState(base.CreateInitialState()); - protected readonly KeyBindingContainer KeyBindingContainer; + public readonly KeyBindingContainer KeyBindingContainer; protected override Container Content => content; From 4cb8272d14369fb7a129d5ec3997b92a14e14867 Mon Sep 17 00:00:00 2001 From: pikokr Date: Thu, 30 Dec 2021 17:37:14 +0900 Subject: [PATCH 0049/5199] Column Touch area & highlighting on start --- osu.Game.Rulesets.Mania/UI/Column.cs | 60 ++++++++++++++++++- .../UI/Components/DefaultKeyArea.cs | 23 +------ 2 files changed, 60 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 9d060944cd..1e07c84d9f 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -44,6 +44,63 @@ namespace osu.Game.Rulesets.Mania.UI private readonly GameplaySampleTriggerSource sampleTriggerSource; + public class ColumnTouchInputArea : Container + { + private Column column => (Column)Parent; + + private Container hintContainer; + + public ColumnTouchInputArea() + { + RelativeSizeAxes = Axes.X; + Anchor = Anchor.BottomCentre; + Origin = Anchor.BottomCentre; + Height = 100; + InternalChild = hintContainer = new Container + { + RelativeSizeAxes = Axes.Both, + BorderColour = Color4.Red, + BorderThickness = 5, + Masking = true, + }; + } + + protected override void LoadComplete() + { + hintContainer.Delay(1000).FadeOutFromOne(500, Easing.OutSine); + } + + private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } + + private ManiaInputManager.RulesetKeyBindingContainer getKeyBindingContainer() + { + return keyBindingContainer ??= (ManiaInputManager.RulesetKeyBindingContainer)((ManiaInputManager)GetContainingInputManager()).KeyBindingContainer; + } + + protected override bool OnTouchDown(TouchDownEvent e) + { + getKeyBindingContainer().TriggerPressed(column.Action.Value); + return base.OnTouchDown(e); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + getKeyBindingContainer().TriggerPressed(column.Action.Value); + + return base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseUpEvent e) + { + getKeyBindingContainer().TriggerReleased(column.Action.Value); + } + + protected override void OnTouchUp(TouchUpEvent e) + { + getKeyBindingContainer().TriggerReleased(column.Action.Value); + } + } + public Column(int index) { Index = index; @@ -68,7 +125,8 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Both }, background, - TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both } + TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }, + new ColumnTouchInputArea() }; hitPolicy = new OrderedHitPolicy(HitObjectContainer); diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs index b3a2a97bf7..15018b464f 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs @@ -36,31 +36,10 @@ namespace osu.Game.Rulesets.Mania.UI.Components RelativeSizeAxes = Axes.Both; } - public class ChildContainer : Container - { - private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } - - private ManiaInputManager.RulesetKeyBindingContainer getKeyBindingContainer() - { - return keyBindingContainer ??= (ManiaInputManager.RulesetKeyBindingContainer)((ManiaInputManager)GetContainingInputManager()).KeyBindingContainer; - } - - protected override bool OnTouchDown(TouchDownEvent e) - { - getKeyBindingContainer().TriggerPressed(((DefaultKeyArea)Parent).column.Action.Value); - return base.OnTouchDown(e); - } - - protected override void OnTouchUp(TouchUpEvent e) - { - getKeyBindingContainer().TriggerReleased(((DefaultKeyArea)Parent).column.Action.Value); - } - } - [BackgroundDependencyLoader] private void load(IScrollingInfo scrollingInfo) { - InternalChild = directionContainer = new ChildContainer + InternalChild = directionContainer = new Container { RelativeSizeAxes = Axes.X, Height = Stage.HIT_TARGET_POSITION, From 612f69782b14e6d0397c9fd248b9f77ffd48e01e Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Tue, 4 Jan 2022 14:29:44 +0100 Subject: [PATCH 0050/5199] use Playfield.HitObjectContainer.AliveObjects --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 93 ++++++++++--------- 1 file changed, 49 insertions(+), 44 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index f70ad2ac7c..1849b48073 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -7,54 +7,55 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Osu.UI; using osuTK; +using System.Linq; namespace osu.Game.Rulesets.Osu.Mods { - internal class OsuModAimAssist : Mod, IApplicableToDrawableHitObjects, IUpdatableByPlayfield + internal class OsuModAimAssist : Mod, IUpdatableByPlayfield { public override string Name => "Aim Assist"; public override string Acronym => "AA"; - public override IconUsage Icon => FontAwesome.Solid.MousePointer; + public override IconUsage? Icon => FontAwesome.Solid.MousePointer; public override ModType Type => ModType.Fun; public override string Description => "No need to chase the circle, the circle chases you"; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; - private readonly List movingObjects = new List(); + public const float SPIN_RADIUS = 50; // same as OsuAutoGeneratorBase.SPIN_RADIUS + private DrawableSpinner activeSpinner; - private double spinnerAngle; // in radians + private float spinnerAngle; // in radians public void Update(Playfield playfield) { var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; - var currentTime = playfield.Clock.CurrentTime; + double currentTime = playfield.Clock.CurrentTime; - // Avoid relocating judgment displays and hide follow points + // Judgment displays would all be cramped onto the cursor playfield.DisplayJudgements.Value = false; - (playfield as OsuPlayfield)?.ConnectionLayer.Hide(); + + // FIXME: Hide follow points + //(playfield as OsuPlayfield)?.ConnectionLayer.Hide(); // If object too old, remove from movingObjects list, otherwise move to new destination - movingObjects.RemoveAll(d => + foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { - var h = d.HitObject; - var endTime = (h as IHasEndTime)?.EndTime ?? h.StartTime; + var h = drawable.HitObject; + double endTime = h.GetEndTime(); - // Object no longer required to be moved -> remove from list - if (currentTime > endTime) - return true; - - switch (d) + switch (drawable) { case DrawableHitCircle circle: // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast circle.MoveTo(cursorPos, Math.Max(0, endTime - currentTime - 10)); - return false; + + break; case DrawableSlider slider: @@ -66,11 +67,12 @@ namespace osu.Game.Rulesets.Osu.Mods // Move slider so that sliderball stays on the cursor else { - slider.HeadCircle.Hide(); // temporary solution to supress HeadCircle's explosion, flash, ... at wrong location + // FIXME: Hide flashes + //slider.HeadCircle.Hide(); slider.MoveTo(cursorPos - slider.Ball.DrawPosition); } - return false; + break; case DrawableSpinner spinner: @@ -78,23 +80,32 @@ namespace osu.Game.Rulesets.Osu.Mods if (currentTime < h.StartTime) { spinner.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); - return false; } else { - spinnerAngle = 0; - activeSpinner = spinner; - return true; + // TODO: + // - get current angle to cursor + // - move clockwise(?) + // - call spinner.RotationTracker.AddRotation + + // TODO: Remove + //spinnerAngle = 0; + //activeSpinner = spinner; } - default: - return true; - } - }); + break; + default: + continue; + } + } + + // Move active spinner around the cursor if (activeSpinner != null) { - if (currentTime > (activeSpinner.HitObject as IHasEndTime)?.EndTime) + double spinnerEndTime = activeSpinner.HitObject.GetEndTime(); + + if (currentTime > spinnerEndTime) { activeSpinner = null; spinnerAngle = 0; @@ -102,29 +113,23 @@ namespace osu.Game.Rulesets.Osu.Mods else { const float additional_degrees = 4; - const int dist_from_cursor = 30; - spinnerAngle += additional_degrees * Math.PI / 180; + float added_degrees = additional_degrees * (float)Math.PI / 180; + spinnerAngle += added_degrees; + + //int spinsRequired = activeSpinner.HitObject.SpinsRequired; + //float spunDegrees = activeSpinner.Result.RateAdjustedRotation; + //double timeLeft = spinnerEndTime - currentTime; // Visual progress - activeSpinner.MoveTo(new Vector2((float)(dist_from_cursor * Math.Cos(spinnerAngle) + cursorPos.X), (float)(dist_from_cursor * Math.Sin(spinnerAngle) + cursorPos.Y))); + activeSpinner.MoveTo(new Vector2((float)(SPIN_RADIUS * Math.Cos(spinnerAngle) + cursorPos.X), (float)(SPIN_RADIUS * Math.Sin(spinnerAngle) + cursorPos.Y))); // Logical progress - activeSpinner.Disc.RotationAbsolute += additional_degrees; + activeSpinner.RotationTracker.AddRotation(added_degrees); + Console.WriteLine($"added_degrees={added_degrees}"); + //activeSpinner.Disc.RotationAbsolute += additional_degrees; } } } - - public void ApplyToDrawableHitObjects(IEnumerable drawables) - { - foreach (var drawable in drawables) - drawable.ApplyCustomUpdateState += drawableOnApplyCustomUpdateState; - } - - private void drawableOnApplyCustomUpdateState(DrawableHitObject drawable, ArmedState state) - { - if (drawable is DrawableOsuHitObject hitobject) - movingObjects.Add(hitobject); - } } /* From 27a8bfa4968672c44c64e7beb1e842894755ecf5 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Tue, 4 Jan 2022 22:17:50 +0100 Subject: [PATCH 0051/5199] handle spinners and follow points --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 80 +++++-------------- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 9 ++- 2 files changed, 26 insertions(+), 63 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 1849b48073..306e7a8b24 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -2,11 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.UI; @@ -26,23 +24,19 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; - public const float SPIN_RADIUS = 50; // same as OsuAutoGeneratorBase.SPIN_RADIUS - - private DrawableSpinner activeSpinner; - private float spinnerAngle; // in radians + private const float spin_radius = 30; + private Vector2? prevCursorPos; public void Update(Playfield playfield) { var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; double currentTime = playfield.Clock.CurrentTime; - // Judgment displays would all be cramped onto the cursor + // Hide judgment displays and follow points playfield.DisplayJudgements.Value = false; + (playfield as OsuPlayfield)?.FollowPoints.Clear(); - // FIXME: Hide follow points - //(playfield as OsuPlayfield)?.ConnectionLayer.Hide(); - - // If object too old, remove from movingObjects list, otherwise move to new destination + // Move all currently alive object to new destination foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { var h = drawable.HitObject; @@ -79,64 +73,32 @@ namespace osu.Game.Rulesets.Osu.Mods // Move spinner to cursor if (currentTime < h.StartTime) { - spinner.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + spinner.MoveTo(cursorPos + new Vector2(0, -spin_radius), Math.Max(0, h.StartTime - currentTime - 10)); } else { - // TODO: - // - get current angle to cursor - // - move clockwise(?) - // - call spinner.RotationTracker.AddRotation + // Move spinner visually + Vector2 delta = spin_radius * (spinner.Position - prevCursorPos ?? cursorPos).Normalized(); + const float angle = 3 * MathF.PI / 180; // radians per update, arbitrary value - // TODO: Remove - //spinnerAngle = 0; - //activeSpinner = spinner; + // Rotation matrix + var targetPos = new Vector2( + delta.X * MathF.Cos(angle) - delta.Y * MathF.Sin(angle) + cursorPos.X, + delta.X * MathF.Sin(angle) + delta.Y * MathF.Cos(angle) + cursorPos.Y + ); + + spinner.MoveTo(targetPos); + + // Logically finish spinner immediatly, no need for the user to click. + // Temporary workaround until spinner rotations are easier to handle, similar as Autopilot mod. + spinner.Result.RateAdjustedRotation = spinner.HitObject.SpinsRequired * 360; } break; - - default: - continue; } } - // Move active spinner around the cursor - if (activeSpinner != null) - { - double spinnerEndTime = activeSpinner.HitObject.GetEndTime(); - - if (currentTime > spinnerEndTime) - { - activeSpinner = null; - spinnerAngle = 0; - } - else - { - const float additional_degrees = 4; - float added_degrees = additional_degrees * (float)Math.PI / 180; - spinnerAngle += added_degrees; - - //int spinsRequired = activeSpinner.HitObject.SpinsRequired; - //float spunDegrees = activeSpinner.Result.RateAdjustedRotation; - //double timeLeft = spinnerEndTime - currentTime; - - // Visual progress - activeSpinner.MoveTo(new Vector2((float)(SPIN_RADIUS * Math.Cos(spinnerAngle) + cursorPos.X), (float)(SPIN_RADIUS * Math.Sin(spinnerAngle) + cursorPos.Y))); - - // Logical progress - activeSpinner.RotationTracker.AddRotation(added_degrees); - Console.WriteLine($"added_degrees={added_degrees}"); - //activeSpinner.Disc.RotationAbsolute += additional_degrees; - } - } + prevCursorPos = cursorPos; } } - - /* - * TODOs - * - fix sliders reappearing at original position after their EndTime (see https://puu.sh/E7zT4/111cf9cdc8.gif) - * - find nicer way to handle slider headcircle explosion, flash, ... - * - add Aim Assist as incompatible mod for Autoplay (?) - * - */ } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 2233a547b9..bc1e80cd12 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -31,7 +31,8 @@ namespace osu.Game.Rulesets.Osu.UI private readonly ProxyContainer approachCircles; private readonly ProxyContainer spinnerProxies; private readonly JudgementContainer judgementLayer; - private readonly FollowPointRenderer followPoints; + + public FollowPointRenderer FollowPoints { get; } public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); @@ -50,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.UI { playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both }, spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both }, - followPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both }, + FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both }, judgementLayer = new JudgementContainer { RelativeSizeAxes = Axes.Both }, HitObjectContainer, judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both }, @@ -131,13 +132,13 @@ namespace osu.Game.Rulesets.Osu.UI protected override void OnHitObjectAdded(HitObject hitObject) { base.OnHitObjectAdded(hitObject); - followPoints.AddFollowPoints((OsuHitObject)hitObject); + FollowPoints.AddFollowPoints((OsuHitObject)hitObject); } protected override void OnHitObjectRemoved(HitObject hitObject) { base.OnHitObjectRemoved(hitObject); - followPoints.RemoveFollowPoints((OsuHitObject)hitObject); + FollowPoints.RemoveFollowPoints((OsuHitObject)hitObject); } private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) From 5a62760fe4f301d7f6a736f35feee14c847f5c96 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Wed, 5 Jan 2022 13:05:22 +0100 Subject: [PATCH 0052/5199] hold spinners & minor adjustments --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 306e7a8b24..cde86c8868 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -5,16 +5,16 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Osu.UI; using osuTK; using System.Linq; +using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Mods { - internal class OsuModAimAssist : Mod, IUpdatableByPlayfield + internal class OsuModAimAssist : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset { public override string Name => "Aim Assist"; public override string Acronym => "AA"; @@ -25,7 +25,15 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; private const float spin_radius = 30; + private Vector2? prevCursorPos; + private OsuInputManager inputManager; + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + // Grab the input manager for future use + inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; + } public void Update(Playfield playfield) { @@ -34,20 +42,20 @@ namespace osu.Game.Rulesets.Osu.Mods // Hide judgment displays and follow points playfield.DisplayJudgements.Value = false; - (playfield as OsuPlayfield)?.FollowPoints.Clear(); + (playfield as OsuPlayfield)?.FollowPoints.Hide(); // Move all currently alive object to new destination foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { var h = drawable.HitObject; - double endTime = h.GetEndTime(); switch (drawable) { case DrawableHitCircle circle: // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast - circle.MoveTo(cursorPos, Math.Max(0, endTime - currentTime - 10)); + circle.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + // FIXME: some circles cause flash at original(?) position when clicked too early break; @@ -61,16 +69,16 @@ namespace osu.Game.Rulesets.Osu.Mods // Move slider so that sliderball stays on the cursor else { - // FIXME: Hide flashes - //slider.HeadCircle.Hide(); + slider.HeadCircle.Hide(); // hide flash, triangles, ... so they don't move with slider slider.MoveTo(cursorPos - slider.Ball.DrawPosition); + // FIXME: some sliders re-appearing at their original position for a single frame when they're done } break; case DrawableSpinner spinner: - // Move spinner to cursor + // Move spinner _next_ to cursor if (currentTime < h.StartTime) { spinner.MoveTo(cursorPos + new Vector2(0, -spin_radius), Math.Max(0, h.StartTime - currentTime - 10)); @@ -89,9 +97,12 @@ namespace osu.Game.Rulesets.Osu.Mods spinner.MoveTo(targetPos); - // Logically finish spinner immediatly, no need for the user to click. - // Temporary workaround until spinner rotations are easier to handle, similar as Autopilot mod. - spinner.Result.RateAdjustedRotation = spinner.HitObject.SpinsRequired * 360; + // Move spinner logically + if (inputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false) + { + // Arbitrary value, might lead to some inconsistencies depending on clock rate, replay, ... + spinner.RotationTracker.AddRotation(2 * MathF.PI); + } } break; From 04d060aba3f95cb75899d642a964c168679524a4 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Thu, 6 Jan 2022 10:38:30 +0100 Subject: [PATCH 0053/5199] update general playfield only once --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index cde86c8868..f983ece71b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -33,6 +33,10 @@ namespace osu.Game.Rulesets.Osu.Mods { // Grab the input manager for future use inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; + + // Hide judgment displays and follow points + drawableRuleset.Playfield.DisplayJudgements.Value = false; + (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); } public void Update(Playfield playfield) @@ -40,10 +44,6 @@ namespace osu.Game.Rulesets.Osu.Mods var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; double currentTime = playfield.Clock.CurrentTime; - // Hide judgment displays and follow points - playfield.DisplayJudgements.Value = false; - (playfield as OsuPlayfield)?.FollowPoints.Hide(); - // Move all currently alive object to new destination foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { From b9d2a10530695421bf5eee2ddad792718624493d Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Thu, 6 Jan 2022 14:47:58 +0100 Subject: [PATCH 0054/5199] adjustable assist strength + dont update spinner & running slider --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 82 ++++--------------- 1 file changed, 16 insertions(+), 66 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index f983ece71b..c7cabd7ab4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -11,6 +11,8 @@ using osu.Game.Rulesets.Osu.UI; using osuTK; using System.Linq; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Configuration; +using osu.Framework.Bindables; namespace osu.Game.Rulesets.Osu.Mods { @@ -24,16 +26,16 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; - private const float spin_radius = 30; - - private Vector2? prevCursorPos; - private OsuInputManager inputManager; + [SettingSource("Assist strength", "Change the distance notes should travel towards you.", 0)] + public BindableFloat AssistStrength { get; } = new BindableFloat(0.3f) + { + Precision = 0.05f, + MinValue = 0.0f, + MaxValue = 1.0f, + }; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - // Grab the input manager for future use - inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; - // Hide judgment displays and follow points drawableRuleset.Playfield.DisplayJudgements.Value = false; (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); @@ -41,75 +43,23 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { - var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; + Vector2 cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; double currentTime = playfield.Clock.CurrentTime; - // Move all currently alive object to new destination foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { var h = drawable.HitObject; - switch (drawable) + if (currentTime < h.StartTime && (drawable is DrawableHitCircle || drawable is DrawableSlider)) { - case DrawableHitCircle circle: + double timeMoving = currentTime - (h.StartTime - h.TimePreempt); + float percentDoneMoving = (float)(timeMoving / h.TimePreempt); + float percentDistLeft = Math.Clamp(AssistStrength.Value - percentDoneMoving + 0.1f, 0, 1); - // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast - circle.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); - // FIXME: some circles cause flash at original(?) position when clicked too early - - break; - - case DrawableSlider slider: - - // Move slider to cursor - if (currentTime < h.StartTime) - { - slider.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); - } - // Move slider so that sliderball stays on the cursor - else - { - slider.HeadCircle.Hide(); // hide flash, triangles, ... so they don't move with slider - slider.MoveTo(cursorPos - slider.Ball.DrawPosition); - // FIXME: some sliders re-appearing at their original position for a single frame when they're done - } - - break; - - case DrawableSpinner spinner: - - // Move spinner _next_ to cursor - if (currentTime < h.StartTime) - { - spinner.MoveTo(cursorPos + new Vector2(0, -spin_radius), Math.Max(0, h.StartTime - currentTime - 10)); - } - else - { - // Move spinner visually - Vector2 delta = spin_radius * (spinner.Position - prevCursorPos ?? cursorPos).Normalized(); - const float angle = 3 * MathF.PI / 180; // radians per update, arbitrary value - - // Rotation matrix - var targetPos = new Vector2( - delta.X * MathF.Cos(angle) - delta.Y * MathF.Sin(angle) + cursorPos.X, - delta.X * MathF.Sin(angle) + delta.Y * MathF.Cos(angle) + cursorPos.Y - ); - - spinner.MoveTo(targetPos); - - // Move spinner logically - if (inputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false) - { - // Arbitrary value, might lead to some inconsistencies depending on clock rate, replay, ... - spinner.RotationTracker.AddRotation(2 * MathF.PI); - } - } - - break; + Vector2 targetPos = drawable.Position + percentDistLeft * (cursorPos - drawable.Position); + drawable.MoveTo(targetPos, h.StartTime - currentTime); } } - - prevCursorPos = cursorPos; } } } From 197ada1a8cf43c7fcb9db2fffa072e310be22b9e Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Thu, 6 Jan 2022 16:04:38 +0100 Subject: [PATCH 0055/5199] naive 10hz update --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index c7cabd7ab4..89a385dbdc 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -34,6 +34,8 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 1.0f, }; + private DateTime? lastUpdate; + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { // Hide judgment displays and follow points @@ -43,6 +45,9 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { + if (DateTime.Now - (lastUpdate ?? DateTime.MinValue) < TimeSpan.FromMilliseconds(100)) + return; + Vector2 cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; double currentTime = playfield.Clock.CurrentTime; @@ -60,6 +65,8 @@ namespace osu.Game.Rulesets.Osu.Mods drawable.MoveTo(targetPos, h.StartTime - currentTime); } } + + lastUpdate = DateTime.Now; } } } From b3230868cc88ca7ada83ea6c4c8f0542c333c569 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Thu, 6 Jan 2022 16:31:30 +0100 Subject: [PATCH 0056/5199] use playfield clock --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 89a385dbdc..a383b533fd 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 1.0f, }; - private DateTime? lastUpdate; + private double? lastUpdate; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { @@ -45,11 +45,12 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { - if (DateTime.Now - (lastUpdate ?? DateTime.MinValue) < TimeSpan.FromMilliseconds(100)) + double currentTime = playfield.Clock.CurrentTime; + + if (currentTime - (lastUpdate ?? double.MinValue) < 100) return; Vector2 cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; - double currentTime = playfield.Clock.CurrentTime; foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { @@ -66,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Mods } } - lastUpdate = DateTime.Now; + lastUpdate = currentTime; } } } From b0d61a18b05424bdc5b39b248a36c5d139ad241c Mon Sep 17 00:00:00 2001 From: pikokr Date: Fri, 7 Jan 2022 15:57:30 +0900 Subject: [PATCH 0057/5199] Load keyBindingContainer once on LoadComplete() & make touch area height to const --- osu.Game.Rulesets.Mania/UI/Column.cs | 43 ++++++---------------------- 1 file changed, 9 insertions(+), 34 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 1e07c84d9f..cd5f3d2170 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -28,6 +28,7 @@ namespace osu.Game.Rulesets.Mania.UI { public const float COLUMN_WIDTH = 80; public const float SPECIAL_COLUMN_WIDTH = 70; + public const float TOUCH_AREA_HEIGHT = 100; /// /// The index of this column as part of the whole playfield. @@ -44,60 +45,34 @@ namespace osu.Game.Rulesets.Mania.UI private readonly GameplaySampleTriggerSource sampleTriggerSource; - public class ColumnTouchInputArea : Container + public class ColumnTouchInputArea : Drawable { - private Column column => (Column)Parent; - - private Container hintContainer; - public ColumnTouchInputArea() { RelativeSizeAxes = Axes.X; Anchor = Anchor.BottomCentre; Origin = Anchor.BottomCentre; - Height = 100; - InternalChild = hintContainer = new Container - { - RelativeSizeAxes = Axes.Both, - BorderColour = Color4.Red, - BorderThickness = 5, - Masking = true, - }; + Height = TOUCH_AREA_HEIGHT; } + private Column column => (Column)Parent; + protected override void LoadComplete() { - hintContainer.Delay(1000).FadeOutFromOne(500, Easing.OutSine); + keyBindingContainer = (ManiaInputManager.RulesetKeyBindingContainer)((ManiaInputManager)GetContainingInputManager()).KeyBindingContainer; } private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } - private ManiaInputManager.RulesetKeyBindingContainer getKeyBindingContainer() - { - return keyBindingContainer ??= (ManiaInputManager.RulesetKeyBindingContainer)((ManiaInputManager)GetContainingInputManager()).KeyBindingContainer; - } - protected override bool OnTouchDown(TouchDownEvent e) { - getKeyBindingContainer().TriggerPressed(column.Action.Value); - return base.OnTouchDown(e); - } - - protected override bool OnMouseDown(MouseDownEvent e) - { - getKeyBindingContainer().TriggerPressed(column.Action.Value); - - return base.OnMouseDown(e); - } - - protected override void OnMouseUp(MouseUpEvent e) - { - getKeyBindingContainer().TriggerReleased(column.Action.Value); + keyBindingContainer.TriggerPressed(column.Action.Value); + return false; } protected override void OnTouchUp(TouchUpEvent e) { - getKeyBindingContainer().TriggerReleased(column.Action.Value); + keyBindingContainer.TriggerReleased(column.Action.Value); } } From f3eaa950416971ccfd10f8e492c4b8b61b53e92d Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Fri, 14 Jan 2022 23:16:07 +0100 Subject: [PATCH 0058/5199] Move input handler settings creation to `OsuGameBase` --- osu.Desktop/OsuGameDesktop.cs | 25 +++++++++++ osu.Game/OsuGameBase.cs | 21 ++++++++++ .../Settings/Sections/InputSection.cs | 41 ++----------------- 3 files changed, 49 insertions(+), 38 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index b234207848..a0c2a80bb8 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -20,8 +20,15 @@ using osu.Framework.Screens; using osu.Game.Screens.Menu; using osu.Game.Updater; using osu.Desktop.Windows; +using osu.Framework.Input.Handlers; +using osu.Framework.Input.Handlers.Joystick; +using osu.Framework.Input.Handlers.Mouse; +using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Threading; using osu.Game.IO; +using osu.Game.Overlays.Settings; +using osu.Game.Overlays.Settings.Sections; +using osu.Game.Overlays.Settings.Sections.Input; namespace osu.Desktop { @@ -156,6 +163,24 @@ namespace osu.Desktop desktopWindow.DragDrop += f => fileDrop(new[] { f }); } + public override SettingsSubsection CreateSettingsSubsectionFor(InputHandler handler) + { + switch (handler) + { + case ITabletHandler th: + return new TabletSettings(th); + + case MouseHandler mh: + return new MouseSettings(mh); + + case JoystickHandler _: + return new InputSection.HandlerSection(handler); + + default: + return base.CreateSettingsSubsectionFor(handler); + } + } + private readonly List importableFiles = new List(); private ScheduledDelegate importSchedule; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 9256514a0a..0059fc2204 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -17,6 +17,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Textures; using osu.Framework.Input; +using osu.Framework.Input.Handlers; +using osu.Framework.Input.Handlers.Midi; using osu.Framework.IO.Stores; using osu.Framework.Logging; using osu.Framework.Platform; @@ -35,6 +37,8 @@ using osu.Game.Online.Chat; using osu.Game.Online.Multiplayer; using osu.Game.Online.Spectator; using osu.Game.Overlays; +using osu.Game.Overlays.Settings; +using osu.Game.Overlays.Settings.Sections; using osu.Game.Resources; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -448,6 +452,23 @@ namespace osu.Game protected override Storage CreateStorage(GameHost host, Storage defaultStorage) => new OsuStorage(host, defaultStorage); + /// + /// Creates an input settings subsection for an . + /// + /// Should be overriden per-platform to provide settings for platform-specific handlers. + public virtual SettingsSubsection CreateSettingsSubsectionFor(InputHandler handler) + { + switch (handler) + { + case MidiHandler _: + return new InputSection.HandlerSection(handler); + + // return null for handlers that shouldn't have settings. + default: + return null; + } + } + private void onRulesetChanged(ValueChangedEvent r) { if (r.NewValue?.Available != true) diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs index d282ba5318..77076ae920 100644 --- a/osu.Game/Overlays/Settings/Sections/InputSection.cs +++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs @@ -5,10 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Handlers; -using osu.Framework.Input.Handlers.Joystick; -using osu.Framework.Input.Handlers.Midi; -using osu.Framework.Input.Handlers.Mouse; -using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Game.Localisation; @@ -22,9 +18,6 @@ namespace osu.Game.Overlays.Settings.Sections public override LocalisableString Header => InputSettingsStrings.InputSectionHeader; - [Resolved] - private GameHost host { get; set; } - public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.Keyboard @@ -36,7 +29,7 @@ namespace osu.Game.Overlays.Settings.Sections } [BackgroundDependencyLoader] - private void load() + private void load(GameHost host, OsuGameBase game) { Children = new Drawable[] { @@ -45,42 +38,14 @@ namespace osu.Game.Overlays.Settings.Sections foreach (var handler in host.AvailableInputHandlers) { - var handlerSection = createSectionFor(handler); + var handlerSection = game.CreateSettingsSubsectionFor(handler); if (handlerSection != null) Add(handlerSection); } } - private SettingsSubsection createSectionFor(InputHandler handler) - { - SettingsSubsection section; - - switch (handler) - { - // ReSharper disable once SuspiciousTypeConversion.Global (net standard fuckery) - case ITabletHandler th: - section = new TabletSettings(th); - break; - - case MouseHandler mh: - section = new MouseSettings(mh); - break; - - // whitelist the handlers which should be displayed to avoid any weird cases of users touching settings they shouldn't. - case JoystickHandler _: - case MidiHandler _: - section = new HandlerSection(handler); - break; - - default: - return null; - } - - return section; - } - - private class HandlerSection : SettingsSubsection + public class HandlerSection : SettingsSubsection { private readonly InputHandler handler; From 037e56f13eca292e9b8b4a43a0eb4da262ac0fa8 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Sat, 15 Jan 2022 14:38:38 +0100 Subject: [PATCH 0059/5199] Add Android mouse settings --- osu.Android/AndroidMouseSettings.cs | 97 +++++++++++++++++++ osu.Android/OsuGameAndroid.cs | 22 +++++ osu.Android/osu.Android.csproj | 1 + .../Settings/Sections/Input/MouseSettings.cs | 4 +- 4 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 osu.Android/AndroidMouseSettings.cs diff --git a/osu.Android/AndroidMouseSettings.cs b/osu.Android/AndroidMouseSettings.cs new file mode 100644 index 0000000000..7dff929cd4 --- /dev/null +++ b/osu.Android/AndroidMouseSettings.cs @@ -0,0 +1,97 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Android.OS; +using osu.Framework.Allocation; +using osu.Framework.Android.Input; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Localisation; +using osu.Game.Configuration; +using osu.Game.Localisation; +using osu.Game.Overlays.Settings; +using osu.Game.Overlays.Settings.Sections.Input; + +namespace osu.Android +{ + public class AndroidMouseSettings : SettingsSubsection + { + private readonly AndroidMouseHandler mouseHandler; + + protected override LocalisableString Header => MouseSettingsStrings.Mouse; + + private Bindable handlerSensitivity; + + private Bindable localSensitivity; + + private Bindable relativeMode; + + public AndroidMouseSettings(AndroidMouseHandler mouseHandler) + { + this.mouseHandler = mouseHandler; + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager osuConfig) + { + // use local bindable to avoid changing enabled state of game host's bindable. + handlerSensitivity = mouseHandler.Sensitivity.GetBoundCopy(); + localSensitivity = handlerSensitivity.GetUnboundCopy(); + + relativeMode = mouseHandler.UseRelativeMode.GetBoundCopy(); + + // High precision/pointer capture is only available on Android 8.0 and up + if (Build.VERSION.SdkInt >= BuildVersionCodes.O) + { + AddRange(new Drawable[] + { + new SettingsCheckbox + { + LabelText = MouseSettingsStrings.HighPrecisionMouse, + TooltipText = MouseSettingsStrings.HighPrecisionMouseTooltip, + Current = relativeMode, + Keywords = new[] { @"raw", @"input", @"relative", @"cursor", @"captured", @"pointer" }, + }, + new MouseSettings.SensitivitySetting + { + LabelText = MouseSettingsStrings.CursorSensitivity, + Current = localSensitivity, + }, + }); + } + + AddRange(new Drawable[] + { + new SettingsCheckbox + { + LabelText = MouseSettingsStrings.DisableMouseWheelVolumeAdjust, + TooltipText = MouseSettingsStrings.DisableMouseWheelVolumeAdjustTooltip, + Current = osuConfig.GetBindable(OsuSetting.MouseDisableWheel), + }, + new SettingsCheckbox + { + LabelText = MouseSettingsStrings.DisableMouseButtons, + Current = osuConfig.GetBindable(OsuSetting.MouseDisableButtons), + }, + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + relativeMode.BindValueChanged(relative => localSensitivity.Disabled = !relative.NewValue, true); + + handlerSensitivity.BindValueChanged(val => + { + bool disabled = localSensitivity.Disabled; + + localSensitivity.Disabled = false; + localSensitivity.Value = val.NewValue; + localSensitivity.Disabled = disabled; + }, true); + + localSensitivity.BindValueChanged(val => handlerSensitivity.Value = val.NewValue); + } + } +} diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index 050bf2b787..ba2527d71e 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -5,7 +5,11 @@ using System; using Android.App; using Android.OS; using osu.Framework.Allocation; +using osu.Framework.Android.Input; +using osu.Framework.Input.Handlers; +using osu.Framework.Platform; using osu.Game; +using osu.Game.Overlays.Settings; using osu.Game.Updater; using osu.Game.Utils; using Xamarin.Essentials; @@ -73,10 +77,28 @@ namespace osu.Android LoadComponentAsync(new GameplayScreenRotationLocker(), Add); } + public override void SetHost(GameHost host) + { + base.SetHost(host); + host.Window.CursorState |= CursorState.Hidden; + } + protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager(); protected override BatteryInfo CreateBatteryInfo() => new AndroidBatteryInfo(); + public override SettingsSubsection CreateSettingsSubsectionFor(InputHandler handler) + { + switch (handler) + { + case AndroidMouseHandler mh: + return new AndroidMouseSettings(mh); + + default: + return base.CreateSettingsSubsectionFor(handler); + } + } + private class AndroidBatteryInfo : BatteryInfo { public override double ChargeLevel => Battery.ChargeLevel; diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj index fc50ca9fa1..90b02c527b 100644 --- a/osu.Android/osu.Android.csproj +++ b/osu.Android/osu.Android.csproj @@ -26,6 +26,7 @@ true + diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index 4235dc0a05..6bb97c1137 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -124,7 +124,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, true); } - private class SensitivitySetting : SettingsSlider + public class SensitivitySetting : SettingsSlider { public SensitivitySetting() { @@ -133,7 +133,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input } } - private class SensitivitySlider : OsuSliderBar + public class SensitivitySlider : OsuSliderBar { public override LocalisableString TooltipText => Current.Disabled ? MouseSettingsStrings.EnableHighPrecisionForSensitivityAdjust : $"{base.TooltipText}x"; } From d4af8498af6795213d648844d1c11da99f65f4e6 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Sat, 15 Jan 2022 14:48:41 +0100 Subject: [PATCH 0060/5199] Add iOS mouse settings Functionality is currently limited to some OsuSettings, but will expand in the future when high precision mouse is added. --- osu.iOS/IOSMouseSettings.cs | 36 ++++++++++++++++++++++++++++++++++++ osu.iOS/OsuGameIOS.cs | 15 +++++++++++++++ osu.iOS/osu.iOS.csproj | 1 + 3 files changed, 52 insertions(+) create mode 100644 osu.iOS/IOSMouseSettings.cs diff --git a/osu.iOS/IOSMouseSettings.cs b/osu.iOS/IOSMouseSettings.cs new file mode 100644 index 0000000000..1979a881f7 --- /dev/null +++ b/osu.iOS/IOSMouseSettings.cs @@ -0,0 +1,36 @@ +// 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.Localisation; +using osu.Game.Configuration; +using osu.Game.Localisation; +using osu.Game.Overlays.Settings; + +namespace osu.iOS +{ + public class IOSMouseSettings : SettingsSubsection + { + protected override LocalisableString Header => MouseSettingsStrings.Mouse; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager osuConfig) + { + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = MouseSettingsStrings.DisableMouseWheelVolumeAdjust, + TooltipText = MouseSettingsStrings.DisableMouseWheelVolumeAdjustTooltip, + Current = osuConfig.GetBindable(OsuSetting.MouseDisableWheel), + }, + new SettingsCheckbox + { + LabelText = MouseSettingsStrings.DisableMouseButtons, + Current = osuConfig.GetBindable(OsuSetting.MouseDisableButtons), + }, + }; + } + } +} diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index 702aef45f5..c3f7033752 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -3,7 +3,10 @@ using System; using Foundation; +using osu.Framework.Input.Handlers; +using osu.Framework.iOS.Input; using osu.Game; +using osu.Game.Overlays.Settings; using osu.Game.Updater; using osu.Game.Utils; using Xamarin.Essentials; @@ -18,6 +21,18 @@ namespace osu.iOS protected override BatteryInfo CreateBatteryInfo() => new IOSBatteryInfo(); + public override SettingsSubsection CreateSettingsSubsectionFor(InputHandler handler) + { + switch (handler) + { + case IOSMouseHandler _: + return new IOSMouseSettings(); + + default: + return base.CreateSettingsSubsectionFor(handler); + } + } + private class IOSBatteryInfo : BatteryInfo { public override double ChargeLevel => Battery.ChargeLevel; diff --git a/osu.iOS/osu.iOS.csproj b/osu.iOS/osu.iOS.csproj index 1203c3659b..b9da874f30 100644 --- a/osu.iOS/osu.iOS.csproj +++ b/osu.iOS/osu.iOS.csproj @@ -23,6 +23,7 @@ + From 2a59735525abb6d481fadc044808f497ace7f2a4 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Sat, 15 Jan 2022 21:43:28 +0100 Subject: [PATCH 0061/5199] Initial commit --- .../Mods/CatchModFlashlight.cs | 21 +++------- .../Mods/ManiaModFlashlight.cs | 13 ++++-- .../Mods/OsuModFlashlight.cs | 42 ++++++++++--------- .../Mods/TaikoModFlashlight.cs | 20 ++++----- osu.Game/Rulesets/Mods/ModFlashlight.cs | 42 +++++++++++++++++++ 5 files changed, 89 insertions(+), 49 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index f399f48ebd..e5da168dc6 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -15,9 +15,10 @@ namespace osu.Game.Rulesets.Catch.Mods { public override double ScoreMultiplier => 1.12; - private const float default_flashlight_size = 350; + public override bool DefaultComboDependency => true; + public override float DefaultRadius => 350; - public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield); + public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value); private CatchPlayfield playfield; @@ -31,10 +32,10 @@ namespace osu.Game.Rulesets.Catch.Mods { private readonly CatchPlayfield playfield; - public CatchFlashlight(CatchPlayfield playfield) + public CatchFlashlight(CatchPlayfield playfield, bool isRadiusBasedOnCombo, float initialRadius) : base(isRadiusBasedOnCombo, initialRadius) { this.playfield = playfield; - FlashlightSize = new Vector2(0, getSizeFor(0)); + FlashlightSize = new Vector2(0, GetRadiusFor(0)); } protected override void Update() @@ -44,19 +45,9 @@ namespace osu.Game.Rulesets.Catch.Mods FlashlightPosition = playfield.CatcherArea.ToSpaceOfOtherDrawable(playfield.Catcher.DrawPosition, this); } - private float getSizeFor(int combo) - { - if (combo > 200) - return default_flashlight_size * 0.8f; - else if (combo > 100) - return default_flashlight_size * 0.9f; - else - return default_flashlight_size; - } - protected override void OnComboChange(ValueChangedEvent e) { - this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); + this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetRadiusFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "CircularFlashlight"; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 86a00271e9..676b5f3842 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -16,17 +16,19 @@ namespace osu.Game.Rulesets.Mania.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModHidden) }; - private const float default_flashlight_size = 180; + public override bool DefaultComboDependency => false; + public override float DefaultRadius => 180; - public override Flashlight CreateFlashlight() => new ManiaFlashlight(); + public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value); private class ManiaFlashlight : Flashlight { private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); - public ManiaFlashlight() + public ManiaFlashlight(bool isRadiusBasedOnCombo, float initialRadius) + : base(isRadiusBasedOnCombo, initialRadius) { - FlashlightSize = new Vector2(0, default_flashlight_size); + FlashlightSize = new Vector2(DrawWidth, GetRadiusFor(0)); AddLayout(flashlightProperties); } @@ -46,9 +48,12 @@ namespace osu.Game.Rulesets.Mania.Mods protected override void OnComboChange(ValueChangedEvent e) { + this.TransformTo(nameof(FlashlightSize), new Vector2(DrawWidth, GetRadiusFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "RectangularFlashlight"; } } } + + diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 300a9d48aa..f15527460c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -12,7 +12,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Osu.Mods @@ -21,13 +20,16 @@ namespace osu.Game.Rulesets.Osu.Mods { public override double ScoreMultiplier => 1.12; - private const float default_flashlight_size = 180; + public override bool DefaultComboDependency => true; + + //private const float default_flashlight_size = 180; + public override float DefaultRadius => 180; private const double default_follow_delay = 120; private OsuFlashlight flashlight; - public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(); + public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value); public void ApplyToDrawableHitObject(DrawableHitObject drawable) { @@ -35,13 +37,6 @@ namespace osu.Game.Rulesets.Osu.Mods s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange; } - public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) - { - base.ApplyToDrawableRuleset(drawableRuleset); - - flashlight.FollowDelay = FollowDelay.Value; - } - [SettingSource("Follow delay", "Milliseconds until the flashlight reaches the cursor")] public BindableNumber FollowDelay { get; } = new BindableDouble(default_follow_delay) { @@ -54,9 +49,15 @@ namespace osu.Game.Rulesets.Osu.Mods { public double FollowDelay { private get; set; } - public OsuFlashlight() + //public float InitialRadius { private get; set; } + public bool ChangeRadius { private get; set; } + + public OsuFlashlight(bool isRadiusBasedOnCombo, float initialRadius, double followDelay) + : base(isRadiusBasedOnCombo, initialRadius) { - FlashlightSize = new Vector2(0, getSizeFor(0)); + FollowDelay = followDelay; + + FlashlightSize = new Vector2(0, GetRadiusFor(0)); } public void OnSliderTrackingChange(ValueChangedEvent e) @@ -78,17 +79,20 @@ namespace osu.Game.Rulesets.Osu.Mods private float getSizeFor(int combo) { - if (combo > 200) - return default_flashlight_size * 0.8f; - else if (combo > 100) - return default_flashlight_size * 0.9f; - else - return default_flashlight_size; + if (ChangeRadius) + { + if (combo > 200) + return InitialRadius * 0.8f; + else if (combo > 100) + return InitialRadius * 0.9f; + } + + return InitialRadius; } protected override void OnComboChange(ValueChangedEvent e) { - this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); + this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetRadiusFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "CircularFlashlight"; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 0a325f174e..29f29863c0 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -16,9 +16,12 @@ namespace osu.Game.Rulesets.Taiko.Mods { public override double ScoreMultiplier => 1.12; - private const float default_flashlight_size = 250; + public override bool DefaultComboDependency => true; - public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield); + //private const float default_flashlight_size = 250; + public override float DefaultRadius => 250; + + public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value); private TaikoPlayfield playfield; @@ -33,7 +36,8 @@ namespace osu.Game.Rulesets.Taiko.Mods private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); private readonly TaikoPlayfield taikoPlayfield; - public TaikoFlashlight(TaikoPlayfield taikoPlayfield) + public TaikoFlashlight(TaikoPlayfield taikoPlayfield, bool isRadiusBasedOnCombo, float initialRadius) + : base(isRadiusBasedOnCombo, initialRadius) { this.taikoPlayfield = taikoPlayfield; FlashlightSize = getSizeFor(0); @@ -43,15 +47,9 @@ namespace osu.Game.Rulesets.Taiko.Mods private Vector2 getSizeFor(int combo) { - float size = default_flashlight_size; - - if (combo > 200) - size *= 0.8f; - else if (combo > 100) - size *= 0.9f; - // Preserve flashlight size through the playfield's aspect adjustment. - return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); + // return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); + return new Vector2(0, GetRadiusFor(combo) * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); } protected override void OnComboChange(ValueChangedEvent e) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index a77a83b36c..bff0e2f12d 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps.Timing; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.OpenGL.Vertices; using osu.Game.Rulesets.Objects; @@ -32,8 +33,26 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Restricted view area."; + [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] + public Bindable ChangeRadius { get; private set; } + + [SettingSource("Initial radius", "Initial radius of the flashlight area.")] + public BindableNumber InitialRadius { get; private set; } + + public abstract float DefaultRadius { get; } + + public abstract bool DefaultComboDependency { get; } + internal ModFlashlight() { + InitialRadius = new BindableFloat + { + MinValue = 90f, + MaxValue = 250f, + Precision = 5f, + }; + + ChangeRadius = new BindableBool(DefaultComboDependency); } } @@ -93,6 +112,16 @@ namespace osu.Game.Rulesets.Mods public List Breaks; + public readonly bool IsRadiusBasedOnCombo; + + public readonly float InitialRadius; + + protected Flashlight(bool isRadiusBasedOnCombo, float initialRadius) + { + IsRadiusBasedOnCombo = isRadiusBasedOnCombo; + InitialRadius = initialRadius; + } + [BackgroundDependencyLoader] private void load(ShaderManager shaderManager) { @@ -124,6 +153,19 @@ namespace osu.Game.Rulesets.Mods protected abstract string FragmentShader { get; } + protected float GetRadiusFor(int combo) + { + if (IsRadiusBasedOnCombo) + { + if (combo > 200) + return InitialRadius * 0.8f; + else if (combo > 100) + return InitialRadius * 0.9f; + } + + return InitialRadius; + } + private Vector2 flashlightPosition; protected Vector2 FlashlightPosition From 57cc2f78933fb89c3226c7ab28bc1a3fe6c0e8da Mon Sep 17 00:00:00 2001 From: mk-56 Date: Sun, 16 Jan 2022 14:26:26 +0100 Subject: [PATCH 0062/5199] Adjustment to size values of FL per mode --- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 3 ++- osu.Game/Rulesets/Mods/ModFlashlight.cs | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index e5da168dc6..9d9fa5aed4 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -32,7 +32,8 @@ namespace osu.Game.Rulesets.Catch.Mods { private readonly CatchPlayfield playfield; - public CatchFlashlight(CatchPlayfield playfield, bool isRadiusBasedOnCombo, float initialRadius) : base(isRadiusBasedOnCombo, initialRadius) + public CatchFlashlight(CatchPlayfield playfield, bool isRadiusBasedOnCombo, float initialRadius) + : base(isRadiusBasedOnCombo, initialRadius) { this.playfield = playfield; FlashlightSize = new Vector2(0, GetRadiusFor(0)); diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index bff0e2f12d..c218ab45fe 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -45,10 +45,10 @@ namespace osu.Game.Rulesets.Mods internal ModFlashlight() { - InitialRadius = new BindableFloat + InitialRadius = new BindableFloat(DefaultRadius) { - MinValue = 90f, - MaxValue = 250f, + MinValue = DefaultRadius * .5f, + MaxValue = DefaultRadius * 1.5f, Precision = 5f, }; From 511a607599215e8981321e16df99c4a32918f67c Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 17 Jan 2022 18:28:17 +0800 Subject: [PATCH 0063/5199] Display performance breakdown in a tooltip --- .../Difficulty/ManiaPerformanceAttributes.cs | 11 +++ .../Difficulty/OsuPerformanceAttributes.cs | 13 +++ .../Difficulty/TaikoPerformanceAttributes.cs | 10 +++ .../Difficulty/PerformanceAttributes.cs | 10 +++ .../Difficulty/PerformanceDisplayAttribute.cs | 26 ++++++ osu.Game/Scoring/ScorePerformanceCache.cs | 9 ++- .../Statistics/PerformanceStatistic.cs | 30 ++++--- .../Statistics/PerformanceStatisticTooltip.cs | 80 +++++++++++++++++++ 8 files changed, 172 insertions(+), 17 deletions(-) create mode 100644 osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs create mode 100644 osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs index da9634ba47..0d3a53f3f3 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.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.Collections.Generic; using Newtonsoft.Json; using osu.Game.Rulesets.Difficulty; @@ -16,5 +17,15 @@ namespace osu.Game.Rulesets.Mania.Difficulty [JsonProperty("scaled_score")] public double ScaledScore { get; set; } + + public override IEnumerable GetAttributesForDisplay() + { + foreach (var attribute in base.GetAttributesForDisplay()) + yield return attribute; + + yield return new PerformanceDisplayAttribute("Difficulty", Difficulty); + yield return new PerformanceDisplayAttribute("Accuracy", Accuracy); + yield return new PerformanceDisplayAttribute("Scaled Score", ScaledScore); + } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs index 6c7760d144..db7ca6af88 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.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.Collections.Generic; using Newtonsoft.Json; using osu.Game.Rulesets.Difficulty; @@ -22,5 +23,17 @@ namespace osu.Game.Rulesets.Osu.Difficulty [JsonProperty("effective_miss_count")] public double EffectiveMissCount { get; set; } + + public override IEnumerable GetAttributesForDisplay() + { + foreach (var attribute in base.GetAttributesForDisplay()) + yield return attribute; + + yield return new PerformanceDisplayAttribute("Aim", Aim); + yield return new PerformanceDisplayAttribute("Speed", Speed); + yield return new PerformanceDisplayAttribute("Accuracy", Accuracy); + yield return new PerformanceDisplayAttribute("Flashlight Bonus", Flashlight); + yield return new PerformanceDisplayAttribute("Effective Miss Count", EffectiveMissCount); + } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs index 80552880ea..efbcdd3703 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.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.Collections.Generic; using Newtonsoft.Json; using osu.Game.Rulesets.Difficulty; @@ -13,5 +14,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty [JsonProperty("accuracy")] public double Accuracy { get; set; } + + public override IEnumerable GetAttributesForDisplay() + { + foreach (var attribute in base.GetAttributesForDisplay()) + yield return attribute; + + yield return new PerformanceDisplayAttribute("Difficulty", Difficulty); + yield return new PerformanceDisplayAttribute("Accuracy", Accuracy); + } } } diff --git a/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs b/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs index 025b38257c..98c6c75f6c 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceAttributes.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.Collections.Generic; using Newtonsoft.Json; namespace osu.Game.Rulesets.Difficulty @@ -12,5 +13,14 @@ namespace osu.Game.Rulesets.Difficulty /// [JsonProperty("pp")] public double Total { get; set; } + + /// + /// Return a for each attribute so that a performance breakdown can be displayed. + /// + /// + public virtual IEnumerable GetAttributesForDisplay() + { + yield return new PerformanceDisplayAttribute("Total", Total); + } } } diff --git a/osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs b/osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs new file mode 100644 index 0000000000..5bb505847e --- /dev/null +++ b/osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +namespace osu.Game.Rulesets.Difficulty +{ + /// + /// Data for displaying a performance attribute to user. Includes a display name for clarity. + /// + public class PerformanceDisplayAttribute + { + /// + /// A custom display name for the attribute. + /// + public string DisplayName { get; } + + /// + /// The associated attribute value. + /// + public double Value { get; } + + public PerformanceDisplayAttribute(string displayName, double value) + { + DisplayName = displayName; + Value = value; + } + } +} diff --git a/osu.Game/Scoring/ScorePerformanceCache.cs b/osu.Game/Scoring/ScorePerformanceCache.cs index b855343505..a428a66aae 100644 --- a/osu.Game/Scoring/ScorePerformanceCache.cs +++ b/osu.Game/Scoring/ScorePerformanceCache.cs @@ -8,6 +8,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Rulesets.Difficulty; namespace osu.Game.Scoring { @@ -15,7 +16,7 @@ namespace osu.Game.Scoring /// A component which performs and acts as a central cache for performance calculations of locally databased scores. /// Currently not persisted between game sessions. /// - public class ScorePerformanceCache : MemoryCachingComponent + public class ScorePerformanceCache : MemoryCachingComponent { [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } @@ -27,10 +28,10 @@ namespace osu.Game.Scoring /// /// The score to do the calculation on. /// An optional to cancel the operation. - public Task CalculatePerformanceAsync([NotNull] ScoreInfo score, CancellationToken token = default) => + public Task CalculatePerformanceAsync([NotNull] ScoreInfo score, CancellationToken token = default) => GetAsync(new PerformanceCacheLookup(score), token); - protected override async Task ComputeValueAsync(PerformanceCacheLookup lookup, CancellationToken token = default) + protected override async Task ComputeValueAsync(PerformanceCacheLookup lookup, CancellationToken token = default) { var score = lookup.ScoreInfo; @@ -44,7 +45,7 @@ namespace osu.Game.Scoring var calculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator(attributes.Value.Attributes, score); - return calculator?.Calculate().Total; + return calculator?.Calculate(); } public readonly struct PerformanceCacheLookup diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index d6e4cfbe51..888552e568 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -7,12 +7,14 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Difficulty; using osu.Game.Scoring; namespace osu.Game.Screens.Ranking.Expanded.Statistics { - public class PerformanceStatistic : StatisticDisplay + public class PerformanceStatistic : StatisticDisplay, IHasCustomTooltip { private readonly ScoreInfo score; @@ -22,6 +24,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics private RollingCounter counter; + private PerformanceAttributes attributes; + public PerformanceStatistic(ScoreInfo score) : base("PP") { @@ -31,21 +35,17 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics [BackgroundDependencyLoader] private void load(ScorePerformanceCache performanceCache) { - if (score.PP.HasValue) - { - setPerformanceValue(score.PP.Value); - } - else - { - performanceCache.CalculatePerformanceAsync(score, cancellationTokenSource.Token) - .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely())), cancellationTokenSource.Token); - } + performanceCache.CalculatePerformanceAsync(score, cancellationTokenSource.Token) + .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely())), cancellationTokenSource.Token); } - private void setPerformanceValue(double? pp) + private void setPerformanceValue(PerformanceAttributes pp) { - if (pp.HasValue) - performance.Value = (int)Math.Round(pp.Value, MidpointRounding.AwayFromZero); + if (pp != null) + { + attributes = pp; + performance.Value = (int)Math.Round(pp.Total, MidpointRounding.AwayFromZero); + } } public override void Appear() @@ -65,5 +65,9 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre }; + + public ITooltip GetCustomTooltip() => new PerformanceStatisticTooltip(); + + public PerformanceAttributes TooltipContent => attributes; } } diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs new file mode 100644 index 0000000000..32ba231e42 --- /dev/null +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs @@ -0,0 +1,80 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Difficulty; +using osuTK; + +namespace osu.Game.Screens.Ranking.Expanded.Statistics +{ + public class PerformanceStatisticTooltip : VisibilityContainer, ITooltip + { + private readonly Box background; + + protected override Container Content { get; } + + public PerformanceStatisticTooltip() + { + AutoSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 5; + + InternalChildren = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + Content = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Left = 10, Right = 10, Top = 5, Bottom = 5 } + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Gray3; + } + + protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); + protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); + + private PerformanceAttributes lastAttributes; + + public void SetContent(PerformanceAttributes attributes) + { + if (attributes == lastAttributes) + return; + + lastAttributes = attributes; + + UpdateDisplay(attributes); + } + + protected virtual void UpdateDisplay(PerformanceAttributes attributes) + { + Content.Clear(); + + foreach (PerformanceDisplayAttribute attr in attributes.GetAttributesForDisplay()) + { + Content.Add(new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.Regular), + Text = $"{attr.DisplayName}: {(int)Math.Round(attr.Value, MidpointRounding.AwayFromZero)}" + }); + } + } + + public void Move(Vector2 pos) => Position = pos; + } +} From 85c60bfc2d50b1c5b27348c7a8e821fbeca3b340 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 17 Jan 2022 20:36:18 +0800 Subject: [PATCH 0064/5199] Improve tooltip design --- .../Statistics/PerformanceStatisticTooltip.cs | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs index 32ba231e42..618a94a309 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.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.Globalization; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,6 +17,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics public class PerformanceStatisticTooltip : VisibilityContainer, ITooltip { private readonly Box background; + private Colour4 textColor; protected override Container Content { get; } @@ -44,6 +46,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics private void load(OsuColour colours) { background.Colour = colours.Gray3; + textColor = colours.BlueLighter; } protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); @@ -67,10 +70,35 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics foreach (PerformanceDisplayAttribute attr in attributes.GetAttributesForDisplay()) { - Content.Add(new OsuSpriteText + Content.Add(new GridContainer { - Font = OsuFont.GetFont(weight: FontWeight.Regular), - Text = $"{attr.DisplayName}: {(int)Math.Round(attr.Value, MidpointRounding.AwayFromZero)}" + AutoSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 140), + new Dimension(GridSizeMode.AutoSize) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.Regular), + Text = attr.DisplayName, + Colour = textColor + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.SemiBold), + Text = ((int)Math.Round(attr.Value, MidpointRounding.AwayFromZero)).ToString(CultureInfo.CurrentCulture) + } + } + } }); } } From d014fef179a9248afc4f4b09237fa84fb16beeaf Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 17 Jan 2022 20:36:36 +0800 Subject: [PATCH 0065/5199] Hide confusing attributes --- osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs | 1 - osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs | 1 - osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs | 1 + 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs index 0d3a53f3f3..48895cd389 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs @@ -25,7 +25,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty yield return new PerformanceDisplayAttribute("Difficulty", Difficulty); yield return new PerformanceDisplayAttribute("Accuracy", Accuracy); - yield return new PerformanceDisplayAttribute("Scaled Score", ScaledScore); } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs index db7ca6af88..0a685b7cd6 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs @@ -33,7 +33,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty yield return new PerformanceDisplayAttribute("Speed", Speed); yield return new PerformanceDisplayAttribute("Accuracy", Accuracy); yield return new PerformanceDisplayAttribute("Flashlight Bonus", Flashlight); - yield return new PerformanceDisplayAttribute("Effective Miss Count", EffectiveMissCount); } } } diff --git a/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs b/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs index 98c6c75f6c..ee1868ecff 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs @@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Difficulty /// /// Return a for each attribute so that a performance breakdown can be displayed. + /// Some attributes may be omitted if they are not meant for display. /// /// public virtual IEnumerable GetAttributesForDisplay() From b81fc675e89b6df6161e18a45f6ae392fdbd183a Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 17 Jan 2022 20:45:25 +0800 Subject: [PATCH 0066/5199] Include PropertyName in PerformanceDisplayAttribute --- .../Difficulty/ManiaPerformanceAttributes.cs | 4 ++-- .../Difficulty/OsuPerformanceAttributes.cs | 8 ++++---- .../Difficulty/TaikoPerformanceAttributes.cs | 4 ++-- osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs | 2 +- .../Rulesets/Difficulty/PerformanceDisplayAttribute.cs | 8 +++++++- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs index 48895cd389..17c864a268 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs @@ -23,8 +23,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty foreach (var attribute in base.GetAttributesForDisplay()) yield return attribute; - yield return new PerformanceDisplayAttribute("Difficulty", Difficulty); - yield return new PerformanceDisplayAttribute("Accuracy", Accuracy); + yield return new PerformanceDisplayAttribute(nameof(Difficulty), "Difficulty", Difficulty); + yield return new PerformanceDisplayAttribute(nameof(Accuracy), "Accuracy", Accuracy); } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs index 0a685b7cd6..0aeaf7669f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs @@ -29,10 +29,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty foreach (var attribute in base.GetAttributesForDisplay()) yield return attribute; - yield return new PerformanceDisplayAttribute("Aim", Aim); - yield return new PerformanceDisplayAttribute("Speed", Speed); - yield return new PerformanceDisplayAttribute("Accuracy", Accuracy); - yield return new PerformanceDisplayAttribute("Flashlight Bonus", Flashlight); + yield return new PerformanceDisplayAttribute(nameof(Aim), "Aim", Aim); + yield return new PerformanceDisplayAttribute(nameof(Speed), "Speed", Speed); + yield return new PerformanceDisplayAttribute(nameof(Accuracy), "Accuracy", Accuracy); + yield return new PerformanceDisplayAttribute(nameof(Flashlight), "Flashlight Bonus", Flashlight); } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs index efbcdd3703..fa5c0202dd 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs @@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty foreach (var attribute in base.GetAttributesForDisplay()) yield return attribute; - yield return new PerformanceDisplayAttribute("Difficulty", Difficulty); - yield return new PerformanceDisplayAttribute("Accuracy", Accuracy); + yield return new PerformanceDisplayAttribute(nameof(Difficulty), "Difficulty", Difficulty); + yield return new PerformanceDisplayAttribute(nameof(Accuracy), "Accuracy", Accuracy); } } } diff --git a/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs b/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs index ee1868ecff..9bdb5f8f6f 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Difficulty /// public virtual IEnumerable GetAttributesForDisplay() { - yield return new PerformanceDisplayAttribute("Total", Total); + yield return new PerformanceDisplayAttribute(nameof(Total), "Total", Total); } } } diff --git a/osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs b/osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs index 5bb505847e..e95cb03053 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs @@ -7,6 +7,11 @@ namespace osu.Game.Rulesets.Difficulty /// public class PerformanceDisplayAttribute { + /// + /// Name of the attribute property in . + /// + public string PropertyName { get; } + /// /// A custom display name for the attribute. /// @@ -17,8 +22,9 @@ namespace osu.Game.Rulesets.Difficulty /// public double Value { get; } - public PerformanceDisplayAttribute(string displayName, double value) + public PerformanceDisplayAttribute(string propertyName, string displayName, double value) { + PropertyName = propertyName; DisplayName = displayName; Value = value; } From c49cd60487584fd8c2d6e208bc9158942e6ce519 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 17 Jan 2022 21:26:55 +0800 Subject: [PATCH 0067/5199] Add bar chart to tooltip --- .../Statistics/PerformanceStatisticTooltip.cs | 108 ++++++++++++------ 1 file changed, 74 insertions(+), 34 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs index 618a94a309..ab7156e0c5 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs @@ -2,22 +2,27 @@ // See the LICENCE file in the repository root for full licence text. using System; using System.Globalization; +using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Difficulty; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Ranking.Expanded.Statistics { public class PerformanceStatisticTooltip : VisibilityContainer, ITooltip { private readonly Box background; - private Colour4 textColor; + private Colour4 totalColour; + private Colour4 textColour; protected override Container Content { get; } @@ -46,10 +51,16 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics private void load(OsuColour colours) { background.Colour = colours.Gray3; - textColor = colours.BlueLighter; + totalColour = colours.Blue; + textColour = colours.BlueLighter; + } + + protected override void PopIn() + { + if (lastAttributes.GetAttributesForDisplay().Count() > 1) + this.FadeIn(200, Easing.OutQuint); } - protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); private PerformanceAttributes lastAttributes; @@ -64,42 +75,71 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics UpdateDisplay(attributes); } + private Drawable createAttributeItem(PerformanceDisplayAttribute attribute, double attributeSum) + { + bool isTotal = attribute.PropertyName == nameof(PerformanceAttributes.Total); + return new GridContainer + { + AutoSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 110), + new Dimension(GridSizeMode.Absolute, 140), + new Dimension(GridSizeMode.AutoSize) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] + { + new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Font = OsuFont.GetFont(weight: FontWeight.Regular), + Text = attribute.DisplayName, + Colour = isTotal ? totalColour : textColour + }, + new Bar + { + Alpha = isTotal ? 0 : 1, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Width = 130, + Height = 5, + BackgroundColour = Color4.White.Opacity(0.5f), + Length = (float)(attribute.Value / attributeSum), + Margin = new MarginPadding { Left = 5, Right = 5 } + }, + new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Font = OsuFont.GetFont(weight: FontWeight.SemiBold), + Text = ((int)Math.Round(attribute.Value, MidpointRounding.AwayFromZero)).ToString(CultureInfo.CurrentCulture), + Colour = isTotal ? totalColour : textColour + } + } + } + }; + } + protected virtual void UpdateDisplay(PerformanceAttributes attributes) { Content.Clear(); - foreach (PerformanceDisplayAttribute attr in attributes.GetAttributesForDisplay()) + var displayAttributes = attributes.GetAttributesForDisplay(); + + double attributeSum = displayAttributes + .Where(attr => attr.PropertyName != nameof(PerformanceAttributes.Total)) + .Sum(attr => attr.Value); + + foreach (PerformanceDisplayAttribute attr in displayAttributes) { - Content.Add(new GridContainer - { - AutoSizeAxes = Axes.Both, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 140), - new Dimension(GridSizeMode.AutoSize) - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] - { - new OsuSpriteText - { - Font = OsuFont.GetFont(weight: FontWeight.Regular), - Text = attr.DisplayName, - Colour = textColor - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(weight: FontWeight.SemiBold), - Text = ((int)Math.Round(attr.Value, MidpointRounding.AwayFromZero)).ToString(CultureInfo.CurrentCulture) - } - } - } - }); + Content.Add(createAttributeItem(attr, attributeSum)); } } From eddf45329445f8b43cb37b65cbeaa3d3bce0f332 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 17 Jan 2022 21:32:13 +0800 Subject: [PATCH 0068/5199] Fix code quality issues --- osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs | 1 + .../Ranking/Expanded/Statistics/PerformanceStatistic.cs | 6 ++---- .../Expanded/Statistics/PerformanceStatisticTooltip.cs | 1 + 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs b/osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs index e95cb03053..7958bc174e 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceDisplayAttribute.cs @@ -1,5 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. + namespace osu.Game.Rulesets.Difficulty { /// diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index 888552e568..4fd6964a68 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -24,8 +24,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics private RollingCounter counter; - private PerformanceAttributes attributes; - public PerformanceStatistic(ScoreInfo score) : base("PP") { @@ -43,7 +41,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics { if (pp != null) { - attributes = pp; + TooltipContent = pp; performance.Value = (int)Math.Round(pp.Total, MidpointRounding.AwayFromZero); } } @@ -68,6 +66,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics public ITooltip GetCustomTooltip() => new PerformanceStatisticTooltip(); - public PerformanceAttributes TooltipContent => attributes; + public PerformanceAttributes TooltipContent { get; private set; } } } diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs index ab7156e0c5..e0bbf91381 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs @@ -1,5 +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.Globalization; using System.Linq; From 83387cb00bcb89b9974b8bcc223b56f78cb72f3e Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 17 Jan 2022 21:41:17 +0800 Subject: [PATCH 0069/5199] Add a comment --- .../Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs index e0bbf91381..deef30124c 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs @@ -58,6 +58,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics protected override void PopIn() { + // Don't display the tooltip if "Total" is the only item if (lastAttributes.GetAttributesForDisplay().Count() > 1) this.FadeIn(200, Easing.OutQuint); } From bd308ca38c5afbddbc18cebaa4e8effa1d4cca90 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Mon, 17 Jan 2022 15:15:25 +0100 Subject: [PATCH 0070/5199] Cleanup --- .../Mods/ManiaModFlashlight.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 15 ++------------- .../Mods/TaikoModFlashlight.cs | 1 - 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 676b5f3842..4fff736c57 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -55,5 +55,3 @@ namespace osu.Game.Rulesets.Mania.Mods } } } - - diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index f15527460c..f381d14ffe 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -27,6 +27,8 @@ namespace osu.Game.Rulesets.Osu.Mods private const double default_follow_delay = 120; + + private OsuFlashlight flashlight; public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value); @@ -77,19 +79,6 @@ namespace osu.Game.Rulesets.Osu.Mods return base.OnMouseMove(e); } - private float getSizeFor(int combo) - { - if (ChangeRadius) - { - if (combo > 200) - return InitialRadius * 0.8f; - else if (combo > 100) - return InitialRadius * 0.9f; - } - - return InitialRadius; - } - protected override void OnComboChange(ValueChangedEvent e) { this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetRadiusFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 29f29863c0..76f7c45b75 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -48,7 +48,6 @@ namespace osu.Game.Rulesets.Taiko.Mods private Vector2 getSizeFor(int combo) { // Preserve flashlight size through the playfield's aspect adjustment. - // return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); return new Vector2(0, GetRadiusFor(combo) * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); } From 43e5bd731cbfa0fff4d2a0825e10ceaefb56d599 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 18 Jan 2022 21:57:12 +0800 Subject: [PATCH 0071/5199] Compare performance to a perfect play --- .../Statistics/PerformanceStatistic.cs | 97 +++++++++++++++++-- .../Statistics/PerformanceStatisticTooltip.cs | 38 ++++---- 2 files changed, 107 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index 4fd6964a68..493f6a2dc9 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -2,19 +2,24 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; +using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; namespace osu.Game.Screens.Ranking.Expanded.Statistics { - public class PerformanceStatistic : StatisticDisplay, IHasCustomTooltip + public class PerformanceStatistic : StatisticDisplay, IHasCustomTooltip { private readonly ScoreInfo score; @@ -24,6 +29,15 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics private RollingCounter counter; + [Resolved] + private ScorePerformanceCache performanceCache { get; set; } + + [Resolved] + private BeatmapDifficultyCache difficultyCache { get; set; } + + [Resolved] + private BeatmapManager beatmapManager { get; set; } + public PerformanceStatistic(ScoreInfo score) : base("PP") { @@ -31,18 +45,74 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics } [BackgroundDependencyLoader] - private void load(ScorePerformanceCache performanceCache) + private void load() { - performanceCache.CalculatePerformanceAsync(score, cancellationTokenSource.Token) - .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely())), cancellationTokenSource.Token); + Task.WhenAll( + // actual performance + performanceCache.CalculatePerformanceAsync(score, cancellationTokenSource.Token), + // performance for a perfect play + getPerfectPerformance(score) + ).ContinueWith(attr => + { + PerformanceAttributes[] result = attr.GetResultSafely(); + setPerformanceValue(new PerformanceBreakdown { Performance = result[0], PerfectPerformance = result[1] }); + }); } - private void setPerformanceValue(PerformanceAttributes pp) + private async Task getPerfectPerformance(ScoreInfo originalScore) { - if (pp != null) + ScoreInfo perfectScore = await getPerfectScore(originalScore).ConfigureAwait(false); + return await performanceCache.CalculatePerformanceAsync(perfectScore, cancellationTokenSource.Token).ConfigureAwait(false); + } + + private Task getPerfectScore(ScoreInfo originalScore) + { + return Task.Factory.StartNew(() => { - TooltipContent = pp; - performance.Value = (int)Math.Round(pp.Total, MidpointRounding.AwayFromZero); + var beatmap = beatmapManager.GetWorkingBeatmap(originalScore.BeatmapInfo).GetPlayableBeatmap(originalScore.Ruleset, originalScore.Mods); + ScoreInfo perfectPlay = originalScore.DeepClone(); + perfectPlay.Accuracy = 1; + perfectPlay.Passed = true; + + // create statistics assuming all hit objects have perfect hit result + var statistics = beatmap.HitObjects + .Select(ho => ho.CreateJudgement().MaxResult) + .GroupBy(hr => hr, (hr, list) => (hitResult: hr, count: list.Count())) + .ToDictionary(pair => pair.hitResult, pair => pair.count); + perfectPlay.Statistics = statistics; + + // calculate max combo + var difficulty = difficultyCache.GetDifficultyAsync( + beatmap.BeatmapInfo, + originalScore.Ruleset, + originalScore.Mods, + cancellationTokenSource.Token + ).GetResultSafely(); + perfectPlay.MaxCombo = difficulty?.MaxCombo ?? originalScore.MaxCombo; + + // calculate total score + ScoreProcessor scoreProcessor = originalScore.Ruleset.CreateInstance().CreateScoreProcessor(); + perfectPlay.TotalScore = (long)scoreProcessor.GetImmediateScore(ScoringMode.Standardised, perfectPlay.MaxCombo, statistics); + + // compute rank achieved + // default to SS, then adjust the rank with mods + perfectPlay.Rank = ScoreRank.X; + + foreach (IApplicableToScoreProcessor mod in perfectPlay.Mods.OfType()) + { + perfectPlay.Rank = mod.AdjustRank(perfectPlay.Rank, 1); + } + + return perfectPlay; + }, cancellationTokenSource.Token); + } + + private void setPerformanceValue(PerformanceBreakdown breakdown) + { + if (breakdown != null) + { + TooltipContent = breakdown; + performance.Value = (int)Math.Round(breakdown.Performance.Total, MidpointRounding.AwayFromZero); } } @@ -64,8 +134,15 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Origin = Anchor.TopCentre }; - public ITooltip GetCustomTooltip() => new PerformanceStatisticTooltip(); + public ITooltip GetCustomTooltip() => new PerformanceStatisticTooltip(); - public PerformanceAttributes TooltipContent { get; private set; } + public PerformanceBreakdown TooltipContent { get; private set; } + + public class PerformanceBreakdown + { + public PerformanceAttributes Performance { get; set; } + + public PerformanceAttributes PerfectPerformance { get; set; } + } } } diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs index deef30124c..564c195a3d 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs @@ -1,11 +1,10 @@ // 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.Globalization; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -19,7 +18,9 @@ using osuTK.Graphics; namespace osu.Game.Screens.Ranking.Expanded.Statistics { - public class PerformanceStatisticTooltip : VisibilityContainer, ITooltip + using static PerformanceStatistic; + + public class PerformanceStatisticTooltip : VisibilityContainer, ITooltip { private readonly Box background; private Colour4 totalColour; @@ -59,27 +60,30 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics protected override void PopIn() { // Don't display the tooltip if "Total" is the only item - if (lastAttributes.GetAttributesForDisplay().Count() > 1) + if (currentPerformance.Performance.GetAttributesForDisplay().Count() > 1) this.FadeIn(200, Easing.OutQuint); } protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); - private PerformanceAttributes lastAttributes; + private PerformanceBreakdown currentPerformance; - public void SetContent(PerformanceAttributes attributes) + public void SetContent(PerformanceBreakdown performance) { - if (attributes == lastAttributes) + if (performance == currentPerformance) return; - lastAttributes = attributes; + currentPerformance = performance; - UpdateDisplay(attributes); + UpdateDisplay(performance); } - private Drawable createAttributeItem(PerformanceDisplayAttribute attribute, double attributeSum) + private Drawable createAttributeItem(PerformanceDisplayAttribute attribute, PerformanceDisplayAttribute perfectAttribute) { bool isTotal = attribute.PropertyName == nameof(PerformanceAttributes.Total); + float fraction = (float)(attribute.Value / perfectAttribute.Value); + if (float.IsNaN(fraction)) + fraction = 0; return new GridContainer { AutoSizeAxes = Axes.Both, @@ -113,7 +117,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Width = 130, Height = 5, BackgroundColour = Color4.White.Opacity(0.5f), - Length = (float)(attribute.Value / attributeSum), + Length = fraction, Margin = new MarginPadding { Left = 5, Right = 5 } }, new OsuSpriteText @@ -121,7 +125,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, Font = OsuFont.GetFont(weight: FontWeight.SemiBold), - Text = ((int)Math.Round(attribute.Value, MidpointRounding.AwayFromZero)).ToString(CultureInfo.CurrentCulture), + Text = fraction.ToLocalisableString("0%"), Colour = isTotal ? totalColour : textColour } } @@ -129,19 +133,17 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics }; } - protected virtual void UpdateDisplay(PerformanceAttributes attributes) + protected virtual void UpdateDisplay(PerformanceBreakdown performance) { Content.Clear(); - var displayAttributes = attributes.GetAttributesForDisplay(); + var displayAttributes = performance.Performance.GetAttributesForDisplay(); - double attributeSum = displayAttributes - .Where(attr => attr.PropertyName != nameof(PerformanceAttributes.Total)) - .Sum(attr => attr.Value); + var perfectDisplayAttributes = performance.PerfectPerformance.GetAttributesForDisplay(); foreach (PerformanceDisplayAttribute attr in displayAttributes) { - Content.Add(createAttributeItem(attr, attributeSum)); + Content.Add(createAttributeItem(attr, perfectDisplayAttributes.First(a => a.PropertyName == attr.PropertyName))); } } From a5b53c01c813a94fadadd5f3102990f0aece2137 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 18 Jan 2022 21:59:51 +0800 Subject: [PATCH 0072/5199] Add comments and tidy up --- .../Ranking/Expanded/Statistics/PerformanceStatistic.cs | 6 ++++++ .../Expanded/Statistics/PerformanceStatisticTooltip.cs | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index 493f6a2dc9..f00eb9d71f 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -140,8 +140,14 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics public class PerformanceBreakdown { + /// + /// Actual gameplay performance. + /// public PerformanceAttributes Performance { get; set; } + /// + /// Performance of a perfect play for comparison. + /// public PerformanceAttributes PerfectPerformance { get; set; } } } diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs index 564c195a3d..7209db53f6 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics public class PerformanceStatisticTooltip : VisibilityContainer, ITooltip { private readonly Box background; - private Colour4 totalColour; + private Colour4 titleColor; private Colour4 textColour; protected override Container Content { get; } @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics private void load(OsuColour colours) { background.Colour = colours.Gray3; - totalColour = colours.Blue; + titleColor = colours.Blue; textColour = colours.BlueLighter; } @@ -107,7 +107,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Anchor = Anchor.CentreLeft, Font = OsuFont.GetFont(weight: FontWeight.Regular), Text = attribute.DisplayName, - Colour = isTotal ? totalColour : textColour + Colour = isTotal ? titleColor : textColour }, new Bar { @@ -126,7 +126,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Anchor = Anchor.CentreLeft, Font = OsuFont.GetFont(weight: FontWeight.SemiBold), Text = fraction.ToLocalisableString("0%"), - Colour = isTotal ? totalColour : textColour + Colour = isTotal ? titleColor : textColour } } } From 31e03e99cd08f1bfd3c648461a6c17e959b0495a Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 18 Jan 2022 22:11:43 +0800 Subject: [PATCH 0073/5199] Improve display of "total PP" --- osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs | 2 +- .../Expanded/Statistics/PerformanceStatisticTooltip.cs | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs b/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs index 9bdb5f8f6f..f210c669a6 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Difficulty /// public virtual IEnumerable GetAttributesForDisplay() { - yield return new PerformanceDisplayAttribute(nameof(Total), "Total", Total); + yield return new PerformanceDisplayAttribute(nameof(Total), "Final PP", Total); } } } diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs index 7209db53f6..528ccafd41 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.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.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -84,6 +85,11 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics float fraction = (float)(attribute.Value / perfectAttribute.Value); if (float.IsNaN(fraction)) fraction = 0; + string text = fraction.ToLocalisableString("0%").ToString(); + + if (isTotal) + text = (int)Math.Round(attribute.Value, MidpointRounding.AwayFromZero) + "/" + (int)Math.Round(perfectAttribute.Value, MidpointRounding.AwayFromZero); + return new GridContainer { AutoSizeAxes = Axes.Both, @@ -111,12 +117,12 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics }, new Bar { - Alpha = isTotal ? 0 : 1, Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, Width = 130, Height = 5, BackgroundColour = Color4.White.Opacity(0.5f), + Colour = isTotal ? titleColor : textColour, Length = fraction, Margin = new MarginPadding { Left = 5, Right = 5 } }, @@ -125,7 +131,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, Font = OsuFont.GetFont(weight: FontWeight.SemiBold), - Text = fraction.ToLocalisableString("0%"), + Text = text, Colour = isTotal ? titleColor : textColour } } From 664b4fdaf09e86e4b55d6c4e0ddf9c3929aac172 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 14:17:56 +0900 Subject: [PATCH 0074/5199] Fix legacy score imports not correctly getting classic mod assigned --- osu.Game/Scoring/ScoreInfo.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index b268e2cd91..fe99d5d9a8 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -191,9 +191,8 @@ namespace osu.Game.Scoring } set { - apiMods = null; + clearAllMods(); mods = value; - updateModsJson(); } } @@ -220,14 +219,19 @@ namespace osu.Game.Scoring } set { + clearAllMods(); apiMods = value; - mods = null; - - // We potentially can't update this yet due to Ruleset being late-bound, so instead update on read as necessary. updateModsJson(); } } + private void clearAllMods() + { + ModsJson = string.Empty; + mods = null; + apiMods = null; + } + private void updateModsJson() { ModsJson = JsonConvert.SerializeObject(APIMods); From bb6f40d16e53816a86443a397c6d1747ed3e7180 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 14:25:40 +0900 Subject: [PATCH 0075/5199] Add test coverage of all mod storages containing classic mod post-import --- osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index 3d4b05b52b..2ba8c51a10 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -12,6 +12,7 @@ using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.UI; @@ -51,6 +52,11 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(829_931, score.ScoreInfo.TotalScore); Assert.AreEqual(3, score.ScoreInfo.MaxCombo); + + Assert.IsTrue(score.ScoreInfo.Mods.Any(m => m is ManiaModClassic)); + Assert.IsTrue(score.ScoreInfo.APIMods.Any(m => m.Acronym == "CL")); + Assert.IsTrue(score.ScoreInfo.ModsJson.Contains("CL")); + Assert.IsTrue(Precision.AlmostEquals(0.8889, score.ScoreInfo.Accuracy, 0.0001)); Assert.AreEqual(ScoreRank.B, score.ScoreInfo.Rank); From 18886f5d2ebdd2061e390854920df1d1c718aba0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 14:28:16 +0900 Subject: [PATCH 0076/5199] Use empty string instead of empty array for `ModsJson` when no mods are present Not really correct, but this is how most scores have been stored until now so let's do this for consistency. --- osu.Game/Scoring/ScoreInfo.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index fe99d5d9a8..cfc37e956d 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -234,7 +234,9 @@ namespace osu.Game.Scoring private void updateModsJson() { - ModsJson = JsonConvert.SerializeObject(APIMods); + ModsJson = APIMods.Length > 0 + ? JsonConvert.SerializeObject(APIMods) + : string.Empty; } public IEnumerable GetStatisticsForDisplay() From a002dacdce3bdb66d5f44cdee6a2423e954a1bc4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 14:32:56 +0900 Subject: [PATCH 0077/5199] Add test coverage of various `ScoreInfo` mod set operations --- osu.Game.Tests/NonVisual/ScoreInfoTest.cs | 41 +++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/osu.Game.Tests/NonVisual/ScoreInfoTest.cs b/osu.Game.Tests/NonVisual/ScoreInfoTest.cs index 6e5718cd4c..ea2f50f3bb 100644 --- a/osu.Game.Tests/NonVisual/ScoreInfoTest.cs +++ b/osu.Game.Tests/NonVisual/ScoreInfoTest.cs @@ -2,6 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Game.Online.API; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -29,5 +34,41 @@ namespace osu.Game.Tests.NonVisual Assert.That(scoreCopy.Rank, Is.EqualTo(ScoreRank.B)); Assert.That(score.Rank, Is.EqualTo(ScoreRank.X)); } + + [Test] + public void TestModsInitiallyEmpty() + { + var score = new ScoreInfo(); + + Assert.That(score.Mods, Is.Empty); + Assert.That(score.APIMods, Is.Empty); + Assert.That(score.ModsJson, Is.Empty); + } + + [Test] + public void TestModsUpdatedCorrectly() + { + var score = new ScoreInfo + { + Mods = new Mod[] { new ManiaModClassic() }, + Ruleset = new ManiaRuleset().RulesetInfo, + }; + + Assert.That(score.Mods, Contains.Item(new ManiaModClassic())); + Assert.That(score.APIMods, Contains.Item(new APIMod(new ManiaModClassic()))); + Assert.That(score.ModsJson, Contains.Substring("CL")); + + score.APIMods = new[] { new APIMod(new ManiaModDoubleTime()) }; + + Assert.That(score.Mods, Contains.Item(new ManiaModDoubleTime())); + Assert.That(score.APIMods, Contains.Item(new APIMod(new ManiaModDoubleTime()))); + Assert.That(score.ModsJson, Contains.Substring("DT")); + + score.Mods = new Mod[] { new ManiaModClassic() }; + + Assert.That(score.Mods, Contains.Item(new ManiaModClassic())); + Assert.That(score.APIMods, Contains.Item(new APIMod(new ManiaModClassic()))); + Assert.That(score.ModsJson, Contains.Substring("CL")); + } } } From faec62be5128f5e54832d7ea7256f3d0301de91f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 15:11:36 +0900 Subject: [PATCH 0078/5199] Force a realm refresh after migration This really shouldn't have much effect as it will be run in the first `Update` method and is probably a noop (we are already pointing to the newest version due to just performing writes), but seems like a safe addition. In general `Realm.Refresh()` only really does anything when there's multithreaded usage and notifications to be sent. --- osu.Game/Database/EFToRealmMigrator.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 1de6c25070..0a7d42e808 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -42,6 +42,9 @@ namespace osu.Game.Database migrateScores(ef); } + Logger.Log("Refreshing realm...", LoggingTarget.Database); + realmContextFactory.Refresh(); + // Delete the database permanently. // Will cause future startups to not attempt migration. Logger.Log("Migration successful, deleting EF database", LoggingTarget.Database); From 973836484c5b168684677f2d3aebf2cd4af659b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 15:52:59 +0900 Subject: [PATCH 0079/5199] Avoid using a write context for EF migration This reduces a stall delay as EF tries to process changes over tracked objects during disposal of the context. --- osu.Game/Database/EFToRealmMigrator.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 0a7d42e808..908f1b0294 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -34,7 +34,7 @@ namespace osu.Game.Database public void Run() { - using (var ef = efContextFactory.GetForWrite()) + using (var ef = efContextFactory.Get()) { migrateSettings(ef); migrateSkins(ef); @@ -51,10 +51,10 @@ namespace osu.Game.Database efContextFactory.ResetDatabase(); } - private void migrateBeatmaps(DatabaseWriteUsage ef) + private void migrateBeatmaps(OsuDbContext ef) { // can be removed 20220730. - var existingBeatmapSets = ef.Context.EFBeatmapSetInfo + var existingBeatmapSets = ef.EFBeatmapSetInfo .Include(s => s.Beatmaps).ThenInclude(b => b.RulesetInfo) .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) @@ -180,10 +180,10 @@ namespace osu.Game.Database }; } - private void migrateScores(DatabaseWriteUsage db) + private void migrateScores(OsuDbContext db) { // can be removed 20220730. - var existingScores = db.Context.ScoreInfo + var existingScores = db.ScoreInfo .Include(s => s.Ruleset) .Include(s => s.BeatmapInfo) .Include(s => s.Files) @@ -263,10 +263,10 @@ namespace osu.Game.Database } } - private void migrateSkins(DatabaseWriteUsage db) + private void migrateSkins(OsuDbContext db) { // can be removed 20220530. - var existingSkins = db.Context.SkinInfo + var existingSkins = db.SkinInfo .Include(s => s.Files) .ThenInclude(f => f.FileInfo) .ToList(); @@ -335,10 +335,10 @@ namespace osu.Game.Database } } - private void migrateSettings(DatabaseWriteUsage db) + private void migrateSettings(OsuDbContext db) { // migrate ruleset settings. can be removed 20220315. - var existingSettings = db.Context.DatabasedSetting.ToList(); + var existingSettings = db.DatabasedSetting.ToList(); // previous entries in EF are removed post migration. if (!existingSettings.Any()) From 42736c99951341a28224b207d185a9e8803bb74f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 16:01:17 +0900 Subject: [PATCH 0080/5199] Add transactional committing of scores/beatmaps This helps slightly with performance, allows better monitoring via realm studio, but most importantly greatly reduces filesize. fully compacted: 109M transaction size 100: 115M transaction size 1000: 123M transaction size 10000: 164M single transaction: 183M With a transaction size of 100 there is a performance reduction, so 1000 seems to be the best middle-ground. --- osu.Game/Database/EFToRealmMigrator.cs | 42 +++++++++++++++++++++----- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 908f1b0294..a7cfd71ff9 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -94,10 +94,20 @@ namespace osu.Game.Database } else { - using (var transaction = realm.BeginWrite()) + var transaction = realm.BeginWrite(); + int written = 0; + + try { foreach (var beatmapSet in existingBeatmapSets) { + if (++written % 1000 == 0) + { + transaction.Commit(); + transaction = realm.BeginWrite(); + Logger.Log($"Migrated {written}/{count} beatmaps...", LoggingTarget.Database); + } + var realmBeatmapSet = new BeatmapSetInfo { OnlineID = beatmapSet.OnlineID ?? -1, @@ -149,10 +159,13 @@ namespace osu.Game.Database realm.Add(realmBeatmapSet); } - - transaction.Commit(); - Logger.Log($"Successfully migrated {count} beatmaps to realm", LoggingTarget.Database); } + finally + { + transaction.Commit(); + } + + Logger.Log($"Successfully migrated {count} beatmaps to realm", LoggingTarget.Database); } } } @@ -221,10 +234,20 @@ namespace osu.Game.Database } else { - using (var transaction = realm.BeginWrite()) + var transaction = realm.BeginWrite(); + int written = 0; + + try { foreach (var score in existingScores) { + if (++written % 1000 == 0) + { + transaction.Commit(); + transaction = realm.BeginWrite(); + Logger.Log($"Migrated {written}/{count} scores...", LoggingTarget.Database); + } + var realmScore = new ScoreInfo { Hash = score.Hash, @@ -255,10 +278,13 @@ namespace osu.Game.Database realm.Add(realmScore); } - - transaction.Commit(); - Logger.Log($"Successfully migrated {count} scores to realm", LoggingTarget.Database); } + finally + { + transaction.Commit(); + } + + Logger.Log($"Successfully migrated {count} scores to realm", LoggingTarget.Database); } } } From fd5198d667f256ab59781d686721fd9507256ff7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 16:22:17 +0900 Subject: [PATCH 0081/5199] Avoid using parameterless constructors in migration code Minor performance improvement (less garbage). --- osu.Game/Database/EFToRealmMigrator.cs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index a7cfd71ff9..5e49a44733 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -122,7 +122,10 @@ namespace osu.Game.Database foreach (var beatmap in beatmapSet.Beatmaps) { - var realmBeatmap = new BeatmapInfo + var ruleset = realm.Find(beatmap.RulesetInfo.ShortName); + var metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata); + + var realmBeatmap = new BeatmapInfo(ruleset, new BeatmapDifficulty(beatmap.BaseDifficulty), metadata) { DifficultyName = beatmap.DifficultyName, Status = beatmap.Status, @@ -148,9 +151,6 @@ namespace osu.Game.Database CountdownOffset = beatmap.CountdownOffset, MaxCombo = beatmap.MaxCombo, Bookmarks = beatmap.Bookmarks, - Ruleset = realm.Find(beatmap.RulesetInfo.ShortName), - Difficulty = new BeatmapDifficulty(beatmap.BaseDifficulty), - Metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata), BeatmapSet = realmBeatmapSet, }; @@ -180,7 +180,7 @@ namespace osu.Game.Database TitleUnicode = metadata.TitleUnicode, Artist = metadata.Artist, ArtistUnicode = metadata.ArtistUnicode, - Author = new RealmUser + Author = { OnlineID = metadata.Author.Id, Username = metadata.Author.Username, @@ -248,7 +248,15 @@ namespace osu.Game.Database Logger.Log($"Migrated {written}/{count} scores...", LoggingTarget.Database); } - var realmScore = new ScoreInfo + var beatmap = realm.All().First(b => b.Hash == score.BeatmapInfo.Hash); + var ruleset = realm.Find(score.Ruleset.ShortName); + var user = new RealmUser + { + OnlineID = score.User.OnlineID, + Username = score.User.Username + }; + + var realmScore = new ScoreInfo(beatmap, ruleset, user) { Hash = score.Hash, DeletePending = score.DeletePending, @@ -262,8 +270,8 @@ namespace osu.Game.Database HasReplay = ((IScoreInfo)score).HasReplay, Date = score.Date, PP = score.PP, - BeatmapInfo = realm.All().First(b => b.Hash == score.BeatmapInfo.Hash), - Ruleset = realm.Find(score.Ruleset.ShortName), + BeatmapInfo = beatmap, + Ruleset = ruleset, Rank = score.Rank, HitEvents = score.HitEvents, Passed = score.Passed, From 0d708efb734571e8c2617b830b07c8406cdf5e31 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 19 Jan 2022 15:33:33 +0800 Subject: [PATCH 0082/5199] Split off `PerformanceBreakdown` and its own calculation logic --- .../Difficulty/PerformanceBreakdown.cs | 21 +++++ .../PerformanceBreakdownCalculator.cs | 90 +++++++++++++++++++ .../Statistics/PerformanceStatistic.cs | 80 +---------------- .../Statistics/PerformanceStatisticTooltip.cs | 2 - 4 files changed, 115 insertions(+), 78 deletions(-) create mode 100644 osu.Game/Rulesets/Difficulty/PerformanceBreakdown.cs create mode 100644 osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdown.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdown.cs new file mode 100644 index 0000000000..273d8613c5 --- /dev/null +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdown.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Difficulty +{ + /// + /// Data for generating a performance breakdown by comparing performance to a perfect play. + /// + public class PerformanceBreakdown + { + /// + /// Actual gameplay performance. + /// + public PerformanceAttributes Performance { get; set; } + + /// + /// Performance of a perfect play for comparison. + /// + public PerformanceAttributes PerfectPerformance { get; set; } + } +} diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs new file mode 100644 index 0000000000..fad7937a99 --- /dev/null +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -0,0 +1,90 @@ +// 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 System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using osu.Framework.Extensions; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; + +namespace osu.Game.Rulesets.Difficulty +{ + public class PerformanceBreakdownCalculator + { + private readonly BeatmapManager beatmapManager; + private readonly BeatmapDifficultyCache difficultyCache; + private readonly ScorePerformanceCache performanceCache; + + public PerformanceBreakdownCalculator(BeatmapManager beatmapManager, BeatmapDifficultyCache difficultyCache, ScorePerformanceCache performanceCache) + { + this.beatmapManager = beatmapManager; + this.difficultyCache = difficultyCache; + this.performanceCache = performanceCache; + } + + [ItemCanBeNull] + public async Task CalculateAsync(ScoreInfo score, CancellationToken cancellationToken = default) + { + PerformanceAttributes performance = await performanceCache.CalculatePerformanceAsync(score, cancellationToken).ConfigureAwait(false); + + ScoreInfo perfectScore = await getPerfectScore(score, cancellationToken).ConfigureAwait(false); + if (perfectScore == null) + return null; + + PerformanceAttributes perfectPerformance = await performanceCache.CalculatePerformanceAsync(perfectScore, cancellationToken).ConfigureAwait(false); + + return new PerformanceBreakdown { Performance = performance, PerfectPerformance = perfectPerformance }; + } + + [ItemCanBeNull] + private Task getPerfectScore(ScoreInfo score, CancellationToken cancellationToken = default) + { + return Task.Factory.StartNew(() => + { + IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(score.BeatmapInfo).GetPlayableBeatmap(score.Ruleset, score.Mods); + ScoreInfo perfectPlay = score.DeepClone(); + perfectPlay.Accuracy = 1; + perfectPlay.Passed = true; + + // calculate max combo + var difficulty = difficultyCache.GetDifficultyAsync( + beatmap.BeatmapInfo, + score.Ruleset, + score.Mods, + cancellationToken + ).GetResultSafely(); + + if (difficulty == null) + return null; + + perfectPlay.MaxCombo = difficulty.Value.MaxCombo; + + // create statistics assuming all hit objects have perfect hit result + var statistics = beatmap.HitObjects + .Select(ho => ho.CreateJudgement().MaxResult) + .GroupBy(hr => hr, (hr, list) => (hitResult: hr, count: list.Count())) + .ToDictionary(pair => pair.hitResult, pair => pair.count); + perfectPlay.Statistics = statistics; + + // calculate total score + ScoreProcessor scoreProcessor = score.Ruleset.CreateInstance().CreateScoreProcessor(); + perfectPlay.TotalScore = (long)scoreProcessor.GetImmediateScore(ScoringMode.Standardised, perfectPlay.MaxCombo, statistics); + + // compute rank achieved + // default to SS, then adjust the rank with mods + perfectPlay.Rank = ScoreRank.X; + + foreach (IApplicableToScoreProcessor mod in perfectPlay.Mods.OfType()) + { + perfectPlay.Rank = mod.AdjustRank(perfectPlay.Rank, 1); + } + + return perfectPlay; + }, cancellationToken); + } + } +} diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index f00eb9d71f..f934dd1836 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -2,9 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using System.Threading; -using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -13,13 +11,11 @@ using osu.Framework.Graphics.Cursor; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; namespace osu.Game.Screens.Ranking.Expanded.Statistics { - public class PerformanceStatistic : StatisticDisplay, IHasCustomTooltip + public class PerformanceStatistic : StatisticDisplay, IHasCustomTooltip { private readonly ScoreInfo score; @@ -47,64 +43,9 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics [BackgroundDependencyLoader] private void load() { - Task.WhenAll( - // actual performance - performanceCache.CalculatePerformanceAsync(score, cancellationTokenSource.Token), - // performance for a perfect play - getPerfectPerformance(score) - ).ContinueWith(attr => - { - PerformanceAttributes[] result = attr.GetResultSafely(); - setPerformanceValue(new PerformanceBreakdown { Performance = result[0], PerfectPerformance = result[1] }); - }); - } - - private async Task getPerfectPerformance(ScoreInfo originalScore) - { - ScoreInfo perfectScore = await getPerfectScore(originalScore).ConfigureAwait(false); - return await performanceCache.CalculatePerformanceAsync(perfectScore, cancellationTokenSource.Token).ConfigureAwait(false); - } - - private Task getPerfectScore(ScoreInfo originalScore) - { - return Task.Factory.StartNew(() => - { - var beatmap = beatmapManager.GetWorkingBeatmap(originalScore.BeatmapInfo).GetPlayableBeatmap(originalScore.Ruleset, originalScore.Mods); - ScoreInfo perfectPlay = originalScore.DeepClone(); - perfectPlay.Accuracy = 1; - perfectPlay.Passed = true; - - // create statistics assuming all hit objects have perfect hit result - var statistics = beatmap.HitObjects - .Select(ho => ho.CreateJudgement().MaxResult) - .GroupBy(hr => hr, (hr, list) => (hitResult: hr, count: list.Count())) - .ToDictionary(pair => pair.hitResult, pair => pair.count); - perfectPlay.Statistics = statistics; - - // calculate max combo - var difficulty = difficultyCache.GetDifficultyAsync( - beatmap.BeatmapInfo, - originalScore.Ruleset, - originalScore.Mods, - cancellationTokenSource.Token - ).GetResultSafely(); - perfectPlay.MaxCombo = difficulty?.MaxCombo ?? originalScore.MaxCombo; - - // calculate total score - ScoreProcessor scoreProcessor = originalScore.Ruleset.CreateInstance().CreateScoreProcessor(); - perfectPlay.TotalScore = (long)scoreProcessor.GetImmediateScore(ScoringMode.Standardised, perfectPlay.MaxCombo, statistics); - - // compute rank achieved - // default to SS, then adjust the rank with mods - perfectPlay.Rank = ScoreRank.X; - - foreach (IApplicableToScoreProcessor mod in perfectPlay.Mods.OfType()) - { - perfectPlay.Rank = mod.AdjustRank(perfectPlay.Rank, 1); - } - - return perfectPlay; - }, cancellationTokenSource.Token); + new PerformanceBreakdownCalculator(beatmapManager, difficultyCache, performanceCache) + .CalculateAsync(score, cancellationTokenSource.Token) + .ContinueWith(t => setPerformanceValue(t.GetResultSafely())); } private void setPerformanceValue(PerformanceBreakdown breakdown) @@ -137,18 +78,5 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics public ITooltip GetCustomTooltip() => new PerformanceStatisticTooltip(); public PerformanceBreakdown TooltipContent { get; private set; } - - public class PerformanceBreakdown - { - /// - /// Actual gameplay performance. - /// - public PerformanceAttributes Performance { get; set; } - - /// - /// Performance of a perfect play for comparison. - /// - public PerformanceAttributes PerfectPerformance { get; set; } - } } } diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs index 528ccafd41..930bfba96a 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs @@ -19,8 +19,6 @@ using osuTK.Graphics; namespace osu.Game.Screens.Ranking.Expanded.Statistics { - using static PerformanceStatistic; - public class PerformanceStatisticTooltip : VisibilityContainer, ITooltip { private readonly Box background; From dd42c89260c26d1cd719242c78388217f23a2f6d Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 19 Jan 2022 16:08:45 +0800 Subject: [PATCH 0083/5199] Feed more info to the temporary score processor for more accurate total score --- osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs index fad7937a99..17cb1de303 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -72,6 +72,8 @@ namespace osu.Game.Rulesets.Difficulty // calculate total score ScoreProcessor scoreProcessor = score.Ruleset.CreateInstance().CreateScoreProcessor(); + scoreProcessor.HighestCombo.Value = perfectPlay.MaxCombo; + scoreProcessor.Mods.Value = perfectPlay.Mods; perfectPlay.TotalScore = (long)scoreProcessor.GetImmediateScore(ScoringMode.Standardised, perfectPlay.MaxCombo, statistics); // compute rank achieved From 2789986699b4f4fc77da1fe946af96e617cde4f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 17:47:46 +0900 Subject: [PATCH 0084/5199] Use asynchronous loading for beatmap carousel again --- osu.Game/Screens/Select/BeatmapCarousel.cs | 34 ++++++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index ea10e47cff..7f9b19443a 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -181,6 +181,11 @@ namespace osu.Game.Screens.Select RightClickScrollingEnabled.ValueChanged += enabled => Scroll.RightMouseScrollbar = enabled.NewValue; RightClickScrollingEnabled.TriggerChange(); + + using (var realm = realmFactory.CreateContext()) + { + loadBeatmapSets(getBeatmapSets(realm)); + } } [Resolved] @@ -190,7 +195,7 @@ namespace osu.Game.Screens.Select { base.LoadComplete(); - subscriptionSets = realmFactory.Context.All().Where(s => !s.DeletePending && !s.Protected).QueryAsyncWithNotifications(beatmapSetsChanged); + subscriptionSets = getBeatmapSets(realmFactory.Context).QueryAsyncWithNotifications(beatmapSetsChanged); subscriptionBeatmaps = realmFactory.Context.All().Where(b => !b.Hidden).QueryAsyncWithNotifications(beatmapsChanged); // Can't use main subscriptions because we can't lookup deleted indices. @@ -220,8 +225,29 @@ namespace osu.Game.Screens.Select if (changes == null) { - // initial load - loadBeatmapSets(sender); + // During initial population, we must manually account for the fact that our original query was done on an async thread. + // Since then, there may have been imports or deletions. + // Here we manually catch up on any changes. + var populatedSets = new HashSet(); + foreach (var s in beatmapSets) + populatedSets.Add(s.BeatmapSet.ID); + + var realmSets = new HashSet(); + foreach (var s in sender) + realmSets.Add(s.ID); + + foreach (var s in realmSets) + { + if (!populatedSets.Contains(s)) + UpdateBeatmapSet(realmFactory.Context.Find(s)); + } + + foreach (var s in populatedSets) + { + if (!realmSets.Contains(s)) + RemoveBeatmapSet(realmFactory.Context.Find(s)); + } + return; } @@ -242,6 +268,8 @@ namespace osu.Game.Screens.Select UpdateBeatmapSet(sender[i].BeatmapSet); } + private IRealmCollection getBeatmapSets(Realm realm) => realm.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); + public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => { var existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.Equals(beatmapSet)); From 6c46fd6931a73f3329964c2e97ba5b650832edc5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 00:14:00 +0900 Subject: [PATCH 0085/5199] Fix some failing tests due to realm beatmaps overwriting test beatmaps --- osu.Game/Screens/Select/BeatmapCarousel.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 7f9b19443a..2dc9bfba0f 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -182,9 +182,10 @@ namespace osu.Game.Screens.Select RightClickScrollingEnabled.ValueChanged += enabled => Scroll.RightMouseScrollbar = enabled.NewValue; RightClickScrollingEnabled.TriggerChange(); - using (var realm = realmFactory.CreateContext()) + if (!loadedTestBeatmaps) { - loadBeatmapSets(getBeatmapSets(realm)); + using (var realm = realmFactory.CreateContext()) + loadBeatmapSets(getBeatmapSets(realm)); } } From fea4400c030f6eabad7bf476a0c536e473fa07db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jan 2022 18:49:37 +0100 Subject: [PATCH 0086/5199] Remove unused using directive --- osu.Game.Tests/NonVisual/ScoreInfoTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/ScoreInfoTest.cs b/osu.Game.Tests/NonVisual/ScoreInfoTest.cs index ea2f50f3bb..e0acc6d8db 100644 --- a/osu.Game.Tests/NonVisual/ScoreInfoTest.cs +++ b/osu.Game.Tests/NonVisual/ScoreInfoTest.cs @@ -6,7 +6,6 @@ using osu.Game.Online.API; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; From a27a3b308cd204cbaddd52f7fbaf20020925d635 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 04:34:00 +0900 Subject: [PATCH 0087/5199] Remove duplicate setters --- osu.Game/Database/EFToRealmMigrator.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 5e49a44733..717d18b9e5 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -263,15 +263,12 @@ namespace osu.Game.Database OnlineID = score.OnlineID ?? -1, ModsJson = score.ModsJson, StatisticsJson = score.StatisticsJson, - User = score.User, TotalScore = score.TotalScore, MaxCombo = score.MaxCombo, Accuracy = score.Accuracy, HasReplay = ((IScoreInfo)score).HasReplay, Date = score.Date, PP = score.PP, - BeatmapInfo = beatmap, - Ruleset = ruleset, Rank = score.Rank, HitEvents = score.HitEvents, Passed = score.Passed, From d925e44b40c087b1e3ca0f0c1e6030f7b372513b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jan 2022 22:02:01 +0100 Subject: [PATCH 0088/5199] Add test coverage for beatmap card expanded content clipping --- .../Online/TestSceneBeatmapListingOverlay.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index d0e3340f2a..a056e0cd2c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -292,6 +293,33 @@ namespace osu.Game.Tests.Visual.Online noPlaceholderShown(); } + [Test] + public void TestExpandedCardContentNotClipped() + { + AddAssert("is visible", () => overlay.State.Value == Visibility.Visible); + + AddStep("show result with many difficulties", () => + { + var beatmapSet = CreateAPIBeatmapSet(Ruleset.Value); + beatmapSet.Beatmaps = Enumerable.Repeat(beatmapSet.Beatmaps.First(), 100).ToArray(); + fetchFor(beatmapSet); + }); + assertAllCardsOfType(1); + + AddStep("hover extra info row", () => + { + var difficultyArea = this.ChildrenOfType().Single(); + InputManager.MoveMouseTo(difficultyArea); + }); + AddUntilStep("wait for expanded", () => this.ChildrenOfType().Single().Expanded.Value); + AddAssert("expanded content not clipped", () => + { + var cardContainer = this.ChildrenOfType>().Single().Parent; + var expandedContent = this.ChildrenOfType().Single(); + return expandedContent.ScreenSpaceDrawQuad.GetVertices().ToArray().All(v => cardContainer.ScreenSpaceDrawQuad.Contains(v)); + }); + } + private static int searchCount; private void fetchFor(params APIBeatmapSet[] beatmaps) From 33ab356dc53439c5a69f3d322c3e011e5069f11c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jan 2022 22:10:05 +0100 Subject: [PATCH 0089/5199] Fix expanded card content being clipped on beatmap listing overlay --- osu.Game/Overlays/BeatmapListingOverlay.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index a8e5201aa3..fbed234cc7 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -187,8 +187,10 @@ namespace osu.Game.Overlays Alpha = 0, Margin = new MarginPadding { - Vertical = 15, - Bottom = ExpandedContentScrollContainer.HEIGHT + Top = 15, + // the + 20 adjustment is roughly eyeballed in order to fit all of the expanded content height after it's scaled + // as well as provide visual balance to the top margin. + Bottom = ExpandedContentScrollContainer.HEIGHT + 20 }, ChildrenEnumerable = newCards }; From 77748a5f9308e8434d6e607381eed758c4428000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jan 2022 22:21:05 +0100 Subject: [PATCH 0090/5199] Show scrollbar on expanded card content where applicable --- .../Drawables/Cards/ExpandedContentScrollContainer.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs b/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs index edf4c5328c..2e1ad6b516 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs @@ -12,16 +12,12 @@ namespace osu.Game.Beatmaps.Drawables.Cards { public const float HEIGHT = 200; - public ExpandedContentScrollContainer() - { - ScrollbarVisible = false; - } - protected override void Update() { base.Update(); Height = Math.Min(Content.DrawHeight, HEIGHT); + ScrollbarVisible = allowScroll; } private bool allowScroll => !Precision.AlmostEquals(DrawSize, Content.DrawSize); From 247c557eaf3a2abe25d693a0a7797d5d4ab0ce3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jan 2022 22:30:49 +0100 Subject: [PATCH 0091/5199] Fix expanded content scrollbar inadvertently hiding expanded content --- .../Cards/ExpandedContentScrollContainer.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs b/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs index 2e1ad6b516..adde72d1e8 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/ExpandedContentScrollContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics; using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Graphics.Containers; @@ -12,6 +13,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards { public const float HEIGHT = 200; + protected override ScrollbarContainer CreateScrollbar(Direction direction) => new ExpandedContentScrollbar(direction); + protected override void Update() { base.Update(); @@ -53,5 +56,22 @@ namespace osu.Game.Beatmaps.Drawables.Cards return base.OnScroll(e); } + + private class ExpandedContentScrollbar : OsuScrollbar + { + public ExpandedContentScrollbar(Direction scrollDir) + : base(scrollDir) + { + } + + protected override bool OnHover(HoverEvent e) + { + base.OnHover(e); + // do not handle hover, as handling hover would make the beatmap card's expanded content not-hovered + // and therefore cause it to hide when trying to drag the scroll bar. + // see: `BeatmapCardContent.dropdownContent` and its `Unhovered` handler. + return false; + } + } } } From 4cad5890c608d5b5a8d3497e99a4b029444de668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jan 2022 23:12:35 +0100 Subject: [PATCH 0092/5199] Add test coverage for news sidebar showing wrong headers --- .../Visual/Online/TestSceneNewsSidebar.cs | 76 +++++++++++++++++++ .../Overlays/News/Sidebar/MonthSection.cs | 9 ++- 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs index b000553a7b..382d76676a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs @@ -38,6 +38,14 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("No month sections were created", () => !sidebar.ChildrenOfType().Any()); } + [Test] + public void TestMetadataWithMultipleYears() + { + AddStep("Add data spanning multiple years", () => sidebar.Metadata.Value = metadata_with_multiple_years); + AddUntilStep("2022 month sections exist", () => sidebar.ChildrenOfType().Any(s => s.Year == 2022)); + AddUntilStep("2021 month sections exist", () => sidebar.ChildrenOfType().Any(s => s.Year == 2021)); + } + [Test] public void TestYearsPanelVisibility() { @@ -133,6 +141,74 @@ namespace osu.Game.Tests.Visual.Online NewsPosts = Array.Empty() }; + // see https://osu.ppy.sh/docs/index.html#get-news-listing: + // "NewsPost collections queried by year will also include posts published in November and December of the previous year if the current date is the same year and before April." + private static readonly APINewsSidebar metadata_with_multiple_years = new APINewsSidebar + { + CurrentYear = 2022, + Years = new[] + { + 2022, + 2021, + 2020, + 2019, + 2018, + 2017, + 2016, + 2015, + 2014, + 2013 + }, + NewsPosts = new List + { + new APINewsPost + { + Title = "(Mar 2022) Short title", + PublishedAt = new DateTime(2022, 3, 1) + }, + new APINewsPost + { + Title = "(Mar 2022) Oh boy that's a long post title I wonder if it will break anything", + PublishedAt = new DateTime(2022, 3, 1) + }, + new APINewsPost + { + Title = "(Feb 2022) Medium title, nothing to see here", + PublishedAt = new DateTime(2022, 2, 1) + }, + new APINewsPost + { + Title = "(Feb 2022) Short title", + PublishedAt = new DateTime(2022, 2, 1) + }, + new APINewsPost + { + Title = "(Jan 2022) Oh boy that's a long post title I wonder if it will break anything", + PublishedAt = new DateTime(2022, 1, 1) + }, + new APINewsPost + { + Title = "(Jan 2022) Medium title, nothing to see here", + PublishedAt = new DateTime(2022, 1, 1) + }, + new APINewsPost + { + Title = "(Jan 2022) Short title", + PublishedAt = new DateTime(2022, 1, 1) + }, + new APINewsPost + { + Title = "(Dec 2021) Surprise, the last year's not gone yet", + PublishedAt = new DateTime(2021, 12, 1) + }, + new APINewsPost + { + Title = "(Nov 2021) Same goes for November", + PublishedAt = new DateTime(2021, 11, 1) + } + } + }; + private class TestNewsSidebar : NewsSidebar { public Action YearChanged; diff --git a/osu.Game/Overlays/News/Sidebar/MonthSection.cs b/osu.Game/Overlays/News/Sidebar/MonthSection.cs index 948f312f15..aa83f89689 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthSection.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthSection.cs @@ -24,16 +24,21 @@ namespace osu.Game.Overlays.News.Sidebar { public class MonthSection : CompositeDrawable { + public int Year { get; private set; } + public int Month { get; private set; } + public readonly BindableBool Expanded = new BindableBool(); + private const int animation_duration = 250; private Sample sampleOpen; private Sample sampleClose; - public readonly BindableBool Expanded = new BindableBool(); - public MonthSection(int month, int year, IEnumerable posts) { Debug.Assert(posts.All(p => p.PublishedAt.Month == month && p.PublishedAt.Year == year)); + Year = year; + Month = month; + RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Masking = true; From b8184a3005ab8f412ecc1790893b533e1beb429f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jan 2022 23:13:30 +0100 Subject: [PATCH 0093/5199] Fix news sidebar assuming returned posts are always from given year --- osu.Game/Overlays/News/Sidebar/NewsSidebar.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs b/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs index fe965385d8..829fc5b3eb 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs @@ -56,19 +56,17 @@ namespace osu.Game.Overlays.News.Sidebar if (allPosts?.Any() != true) return; - var lookup = metadata.NewValue.NewsPosts.ToLookup(post => post.PublishedAt.Month); + var lookup = metadata.NewValue.NewsPosts.ToLookup(post => (post.PublishedAt.Month, post.PublishedAt.Year)); var keys = lookup.Select(kvp => kvp.Key); - var sortedKeys = keys.OrderByDescending(k => k).ToList(); - - int year = metadata.NewValue.CurrentYear; + var sortedKeys = keys.OrderByDescending(k => k.Year).ThenByDescending(k => k.Month).ToList(); for (int i = 0; i < sortedKeys.Count; i++) { - int month = sortedKeys[i]; - var posts = lookup[month]; + var key = sortedKeys[i]; + var posts = lookup[key]; - monthsFlow.Add(new MonthSection(month, year, posts) + monthsFlow.Add(new MonthSection(key.Month, key.Year, posts) { Expanded = { Value = i == 0 } }); From 261fae68735ef6df0b43c9d1599869972ba18a1c Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 20 Jan 2022 08:39:33 +0800 Subject: [PATCH 0094/5199] Move checks out of PopIn() --- .../Ranking/Expanded/Statistics/PerformanceStatistic.cs | 4 +++- .../Expanded/Statistics/PerformanceStatisticTooltip.cs | 7 +------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index f934dd1836..bda147cf22 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -50,7 +51,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics private void setPerformanceValue(PerformanceBreakdown breakdown) { - if (breakdown != null) + // Don't display the tooltip if "Total" is the only item + if (breakdown != null && breakdown.Performance.GetAttributesForDisplay().Count() > 1) { TooltipContent = breakdown; performance.Value = (int)Math.Round(breakdown.Performance.Total, MidpointRounding.AwayFromZero); diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs index 930bfba96a..44e5c366bb 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs @@ -56,12 +56,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics textColour = colours.BlueLighter; } - protected override void PopIn() - { - // Don't display the tooltip if "Total" is the only item - if (currentPerformance.Performance.GetAttributesForDisplay().Count() > 1) - this.FadeIn(200, Easing.OutQuint); - } + protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); From 42d904acee23490e51086cbae21fd9bf3aff9045 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 20 Jan 2022 12:50:28 +0800 Subject: [PATCH 0095/5199] Remove blocking calls and add back Task.WhenAll --- .../PerformanceBreakdownCalculator.cs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs index 17cb1de303..3c9c00ff3b 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; -using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; @@ -29,21 +28,30 @@ namespace osu.Game.Rulesets.Difficulty [ItemCanBeNull] public async Task CalculateAsync(ScoreInfo score, CancellationToken cancellationToken = default) { - PerformanceAttributes performance = await performanceCache.CalculatePerformanceAsync(score, cancellationToken).ConfigureAwait(false); + PerformanceAttributes[] performanceArray = await Task.WhenAll( + // compute actual performance + performanceCache.CalculatePerformanceAsync(score, cancellationToken), + // compute performance for perfect play + getPerfectPerformance(score, cancellationToken) + ).ConfigureAwait(false); + return new PerformanceBreakdown { Performance = performanceArray[0], PerfectPerformance = performanceArray[1] }; + } + + [ItemCanBeNull] + private async Task getPerfectPerformance(ScoreInfo score, CancellationToken cancellationToken = default) + { ScoreInfo perfectScore = await getPerfectScore(score, cancellationToken).ConfigureAwait(false); if (perfectScore == null) return null; - PerformanceAttributes perfectPerformance = await performanceCache.CalculatePerformanceAsync(perfectScore, cancellationToken).ConfigureAwait(false); - - return new PerformanceBreakdown { Performance = performance, PerfectPerformance = perfectPerformance }; + return await performanceCache.CalculatePerformanceAsync(perfectScore, cancellationToken).ConfigureAwait(false); } [ItemCanBeNull] private Task getPerfectScore(ScoreInfo score, CancellationToken cancellationToken = default) { - return Task.Factory.StartNew(() => + return Task.Run(async () => { IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(score.BeatmapInfo).GetPlayableBeatmap(score.Ruleset, score.Mods); ScoreInfo perfectPlay = score.DeepClone(); @@ -51,12 +59,12 @@ namespace osu.Game.Rulesets.Difficulty perfectPlay.Passed = true; // calculate max combo - var difficulty = difficultyCache.GetDifficultyAsync( + var difficulty = await difficultyCache.GetDifficultyAsync( beatmap.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken - ).GetResultSafely(); + ).ConfigureAwait(false); if (difficulty == null) return null; From 6c97fbd3f28f8374822e41950ca293a3d5a1e62f Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 20 Jan 2022 13:06:00 +0800 Subject: [PATCH 0096/5199] Calculate perfect performance without caching --- .../PerformanceBreakdownCalculator.cs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs index 3c9c00ff3b..5cf63d0102 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -39,20 +39,11 @@ namespace osu.Game.Rulesets.Difficulty } [ItemCanBeNull] - private async Task getPerfectPerformance(ScoreInfo score, CancellationToken cancellationToken = default) - { - ScoreInfo perfectScore = await getPerfectScore(score, cancellationToken).ConfigureAwait(false); - if (perfectScore == null) - return null; - - return await performanceCache.CalculatePerformanceAsync(perfectScore, cancellationToken).ConfigureAwait(false); - } - - [ItemCanBeNull] - private Task getPerfectScore(ScoreInfo score, CancellationToken cancellationToken = default) + private Task getPerfectPerformance(ScoreInfo score, CancellationToken cancellationToken = default) { return Task.Run(async () => { + Ruleset ruleset = score.Ruleset.CreateInstance(); IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(score.BeatmapInfo).GetPlayableBeatmap(score.Ruleset, score.Mods); ScoreInfo perfectPlay = score.DeepClone(); perfectPlay.Accuracy = 1; @@ -79,7 +70,7 @@ namespace osu.Game.Rulesets.Difficulty perfectPlay.Statistics = statistics; // calculate total score - ScoreProcessor scoreProcessor = score.Ruleset.CreateInstance().CreateScoreProcessor(); + ScoreProcessor scoreProcessor = ruleset.CreateScoreProcessor(); scoreProcessor.HighestCombo.Value = perfectPlay.MaxCombo; scoreProcessor.Mods.Value = perfectPlay.Mods; perfectPlay.TotalScore = (long)scoreProcessor.GetImmediateScore(ScoringMode.Standardised, perfectPlay.MaxCombo, statistics); @@ -93,7 +84,9 @@ namespace osu.Game.Rulesets.Difficulty perfectPlay.Rank = mod.AdjustRank(perfectPlay.Rank, 1); } - return perfectPlay; + // calculate performance for this perfect score + // ScorePerformanceCache is not used to avoid caching multiple copies of essentially identical perfect performance attributes + return ruleset.CreatePerformanceCalculator(difficulty.Value.Attributes, perfectPlay)?.Calculate(); }, cancellationToken); } } From 1dabf6c8a52cf176a2acae19e4b571b599b93347 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 16:39:42 +0900 Subject: [PATCH 0097/5199] Fix `BeatmapCarousel` signalling it is finished loading before catching up on realm changes --- osu.Game/Screens/Select/BeatmapCarousel.cs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 2dc9bfba0f..437567d4b2 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -126,14 +126,8 @@ namespace osu.Game.Screens.Select applyActiveCriteria(false); - // Run on late scheduler want to ensure this runs after all pending UpdateBeatmapSet / RemoveBeatmapSet operations are run. - SchedulerAfterChildren.Add(() => - { - BeatmapSetsChanged?.Invoke(); - BeatmapSetsLoaded = true; - - itemsCache.Invalidate(); - }); + if (loadedTestBeatmaps) + signalBeatmapsLoaded(); } private readonly List visibleItems = new List(); @@ -249,6 +243,7 @@ namespace osu.Game.Screens.Select RemoveBeatmapSet(realmFactory.Context.Find(s)); } + signalBeatmapsLoaded(); return; } @@ -547,6 +542,16 @@ namespace osu.Game.Screens.Select } } + private void signalBeatmapsLoaded() + { + Debug.Assert(BeatmapSetsLoaded == false); + + BeatmapSetsChanged?.Invoke(); + BeatmapSetsLoaded = true; + + itemsCache.Invalidate(); + } + private float? scrollTarget; /// From 3faf980fed86b494f4c6a8337fa8c6480a58bc13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 22:51:45 +0900 Subject: [PATCH 0098/5199] Avoid constructor overhead for realm `BeatmapInfo` parameterless constructor --- osu.Game/Beatmaps/BeatmapInfo.cs | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index cddd7e9b30..1ea1c8ab4c 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -26,37 +26,35 @@ namespace osu.Game.Beatmaps public class BeatmapInfo : RealmObject, IHasGuidPrimaryKey, IBeatmapInfo, IEquatable { [PrimaryKey] - public Guid ID { get; set; } = Guid.NewGuid(); + public Guid ID { get; set; } public string DifficultyName { get; set; } = string.Empty; - public RulesetInfo Ruleset { get; set; } + public RulesetInfo Ruleset { get; set; } = null!; - public BeatmapDifficulty Difficulty { get; set; } + public BeatmapDifficulty Difficulty { get; set; } = null!; - public BeatmapMetadata Metadata { get; set; } + public BeatmapMetadata Metadata { get; set; } = null!; [Backlink(nameof(ScoreInfo.BeatmapInfo))] public IQueryable Scores { get; } = null!; - public BeatmapInfo(RulesetInfo ruleset, BeatmapDifficulty difficulty, BeatmapMetadata metadata) + public BeatmapInfo(RulesetInfo? ruleset = null, BeatmapDifficulty? difficulty = null, BeatmapMetadata? metadata = null) { - Ruleset = ruleset; - Difficulty = difficulty; - Metadata = metadata; - } - - [UsedImplicitly] - public BeatmapInfo() // TODO: consider removing this and migrating all usages to ctor with parameters. - { - Ruleset = new RulesetInfo + ID = Guid.NewGuid(); + Ruleset = ruleset ?? new RulesetInfo { OnlineID = 0, ShortName = @"osu", Name = @"null placeholder ruleset" }; - Difficulty = new BeatmapDifficulty(); - Metadata = new BeatmapMetadata(); + Difficulty = difficulty ?? new BeatmapDifficulty(); + Metadata = metadata ?? new BeatmapMetadata(); + } + + [UsedImplicitly] + private BeatmapInfo() + { } public BeatmapSetInfo? BeatmapSet { get; set; } From 3c852e6d025d9fc3503c28f266ee66c9d99e5d77 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 16:43:51 +0900 Subject: [PATCH 0099/5199] Avoid constructor overhead for realm `ScoreInfo` parameterless constructor --- osu.Game/Scoring/ScoreInfo.cs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index cfc37e956d..a28e16450f 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -29,11 +29,11 @@ namespace osu.Game.Scoring public class ScoreInfo : RealmObject, IHasGuidPrimaryKey, IHasRealmFiles, ISoftDelete, IEquatable, IScoreInfo { [PrimaryKey] - public Guid ID { get; set; } = Guid.NewGuid(); + public Guid ID { get; set; } - public BeatmapInfo BeatmapInfo { get; set; } + public BeatmapInfo BeatmapInfo { get; set; } = null!; - public RulesetInfo Ruleset { get; set; } + public RulesetInfo Ruleset { get; set; } = null!; public IList Files { get; } = null!; @@ -57,7 +57,7 @@ namespace osu.Game.Scoring public long OnlineID { get; set; } = -1; [MapTo("User")] - public RealmUser RealmUser { get; set; } + public RealmUser RealmUser { get; set; } = null!; [MapTo("Mods")] public string ModsJson { get; set; } = string.Empty; @@ -65,19 +65,17 @@ namespace osu.Game.Scoring [MapTo("Statistics")] public string StatisticsJson { get; set; } = string.Empty; - public ScoreInfo(BeatmapInfo beatmap, RulesetInfo ruleset, RealmUser realmUser) + public ScoreInfo(BeatmapInfo? beatmap = null, RulesetInfo? ruleset = null, RealmUser? realmUser = null) { - Ruleset = ruleset; - BeatmapInfo = beatmap; - RealmUser = realmUser; + Ruleset = ruleset ?? new RulesetInfo(); + BeatmapInfo = beatmap ?? new BeatmapInfo(); + RealmUser = realmUser ?? new RealmUser(); + ID = Guid.NewGuid(); } - [UsedImplicitly] - public ScoreInfo() // TODO: consider removing this and migrating all usages to ctor with parameters. + [UsedImplicitly] // Realm + private ScoreInfo() { - Ruleset = new RulesetInfo(); - RealmUser = new RealmUser(); - BeatmapInfo = new BeatmapInfo(); } // TODO: this is a bit temporary to account for the fact that this class is used to ferry API user data to certain UI components. From ccddf9b47d94d9f7a3b00aa0e05c577f886e13ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 16:49:39 +0900 Subject: [PATCH 0100/5199] Avoid constructor overhead for realm `BeatmapSetInfo` parameterless constructor --- osu.Game/Beatmaps/BeatmapSetInfo.cs | 16 +++++++++++++++- osu.Game/Database/RealmObjectExtensions.cs | 3 +++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index a934d1a2e3..3cda111e97 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Extensions; @@ -19,7 +20,7 @@ namespace osu.Game.Beatmaps public class BeatmapSetInfo : RealmObject, IHasGuidPrimaryKey, IHasRealmFiles, ISoftDelete, IEquatable, IBeatmapSetInfo { [PrimaryKey] - public Guid ID { get; set; } = Guid.NewGuid(); + public Guid ID { get; set; } [Indexed] public int OnlineID { get; set; } = -1; @@ -57,6 +58,19 @@ namespace osu.Game.Beatmaps public double MaxBPM => Beatmaps.Count == 0 ? 0 : Beatmaps.Max(b => b.BPM); + public BeatmapSetInfo(IEnumerable? beatmaps = null) + : this() + { + ID = Guid.NewGuid(); + if (beatmaps != null) + Beatmaps.AddRange(beatmaps); + } + + [UsedImplicitly] // Realm + private BeatmapSetInfo() + { + } + /// /// Returns the storage path for the file in this beatmapset with the given filename, if any exists, otherwise null. /// The path returned is relative to the user file storage. diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index 746a43fd37..c25aeab336 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -48,6 +48,7 @@ namespace osu.Game.Database copyChangesToRealm(s.Metadata, d.Metadata); }); c.CreateMap() + .ConstructUsing(_ => new BeatmapSetInfo(null)) .ForMember(s => s.Beatmaps, cc => cc.Ignore()) .AfterMap((s, d) => { @@ -77,6 +78,7 @@ namespace osu.Game.Database applyCommonConfiguration(c); c.CreateMap() + .ConstructUsing(_ => new BeatmapSetInfo(null)) .MaxDepth(2) .AfterMap((s, d) => { @@ -109,6 +111,7 @@ namespace osu.Game.Database applyCommonConfiguration(c); c.CreateMap() + .ConstructUsing(_ => new BeatmapSetInfo(null)) .MaxDepth(2) .ForMember(b => b.Files, cc => cc.Ignore()) .AfterMap((s, d) => From deb108816cd17e92f5700391e65bd03e34aa7e70 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 17:09:31 +0900 Subject: [PATCH 0101/5199] Fix some regressions in json output (we need to make all these explicit instead) --- osu.Game/Beatmaps/BeatmapInfo.cs | 1 + osu.Game/Beatmaps/BeatmapSetInfo.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 1ea1c8ab4c..96254295a6 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -36,6 +36,7 @@ namespace osu.Game.Beatmaps public BeatmapMetadata Metadata { get; set; } = null!; + [JsonIgnore] [Backlink(nameof(ScoreInfo.BeatmapInfo))] public IQueryable Scores { get; } = null!; diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 3cda111e97..9a4207d5cf 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; +using Newtonsoft.Json; using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Extensions; @@ -27,6 +28,7 @@ namespace osu.Game.Beatmaps public DateTimeOffset DateAdded { get; set; } + [JsonIgnore] public IBeatmapMetadataInfo Metadata => Beatmaps.FirstOrDefault()?.Metadata ?? new BeatmapMetadata(); public IList Beatmaps { get; } = null!; From 6c10531df2b5ddd70c1c9ecf095b5d4a8617f7ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 17:14:51 +0900 Subject: [PATCH 0102/5199] Avoid constructor overhead for realm `BeatmapMetadata` parameterless constructor --- osu.Game/Beatmaps/BeatmapMetadata.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index a3385e3abe..cb38373bd3 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.Testing; using osu.Game.Models; @@ -27,7 +28,7 @@ namespace osu.Game.Beatmaps [JsonProperty("artist_unicode")] public string ArtistUnicode { get; set; } = string.Empty; - public RealmUser Author { get; set; } = new RealmUser(); // TODO: not sure we want to initialise this only to have it overwritten by retrieval. + public RealmUser Author { get; set; } = null!; public string Source { get; set; } = string.Empty; @@ -43,6 +44,16 @@ namespace osu.Game.Beatmaps public string AudioFile { get; set; } = string.Empty; public string BackgroundFile { get; set; } = string.Empty; + public BeatmapMetadata(RealmUser? user = null) + { + Author = new RealmUser(); + } + + [UsedImplicitly] // Realm + private BeatmapMetadata() + { + } + IUser IBeatmapMetadataInfo.Author => Author; public override string ToString() => this.GetDisplayTitle(); From 70cc03fe4304b1c982178bf34601718503dd3547 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 17:18:53 +0900 Subject: [PATCH 0103/5199] Avoid constructor overhead for realm `RealmKeyBinding` parameterless constructor --- .../Database/TestRealmKeyBindingStore.cs | 20 +++---------------- osu.Game/Input/Bindings/RealmKeyBinding.cs | 20 +++++++++++++++++-- osu.Game/Input/RealmKeyBindingStore.cs | 8 +------- 3 files changed, 22 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index f05d9ab3dc..e3c1d42667 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -63,23 +63,9 @@ namespace osu.Game.Tests.Database using (var realm = realmContextFactory.CreateContext()) using (var transaction = realm.BeginWrite()) { - realm.Add(new RealmKeyBinding - { - Action = GlobalAction.Back, - KeyCombination = new KeyCombination(InputKey.A) - }); - - realm.Add(new RealmKeyBinding - { - Action = GlobalAction.Back, - KeyCombination = new KeyCombination(InputKey.S) - }); - - realm.Add(new RealmKeyBinding - { - Action = GlobalAction.Back, - KeyCombination = new KeyCombination(InputKey.D) - }); + realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.A))); + realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.S))); + realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.D))); transaction.Commit(); } diff --git a/osu.Game/Input/Bindings/RealmKeyBinding.cs b/osu.Game/Input/Bindings/RealmKeyBinding.cs index 32813ada16..c941319ddb 100644 --- a/osu.Game/Input/Bindings/RealmKeyBinding.cs +++ b/osu.Game/Input/Bindings/RealmKeyBinding.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using JetBrains.Annotations; using osu.Framework.Input.Bindings; using osu.Game.Database; using Realms; @@ -14,7 +15,7 @@ namespace osu.Game.Input.Bindings public class RealmKeyBinding : RealmObject, IHasGuidPrimaryKey, IKeyBinding { [PrimaryKey] - public Guid ID { get; set; } = Guid.NewGuid(); + public Guid ID { get; set; } public string? RulesetName { get; set; } @@ -38,6 +39,21 @@ namespace osu.Game.Input.Bindings public int ActionInt { get; set; } [MapTo(nameof(KeyCombination))] - public string KeyCombinationString { get; set; } = string.Empty; + public string KeyCombinationString { get; set; } = null!; + + public RealmKeyBinding(object action, KeyCombination keyCombination, string? rulesetName = null, int? variant = null) + { + Action = action; + KeyCombination = keyCombination; + + RulesetName = rulesetName; + Variant = variant; + ID = Guid.NewGuid(); + } + + [UsedImplicitly] // Realm + private RealmKeyBinding() + { + } } } diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index cb51797685..99f5752cfb 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -92,13 +92,7 @@ namespace osu.Game.Input if (defaultsCount > existingCount) { // insert any defaults which are missing. - realm.Add(defaultsForAction.Skip(existingCount).Select(k => new RealmKeyBinding - { - KeyCombinationString = k.KeyCombination.ToString(), - ActionInt = (int)k.Action, - RulesetName = rulesetName, - Variant = variant - })); + realm.Add(defaultsForAction.Skip(existingCount).Select(k => new RealmKeyBinding(k.Action, k.KeyCombination, rulesetName, variant))); } else if (defaultsCount < existingCount) { From 0bd7486a832e0ec701e54d8ed5b271e1190d4a1c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 17:29:03 +0900 Subject: [PATCH 0104/5199] Avoid constructor overhead for realm `SkinInfo` parameterless constructor --- osu.Game/Skinning/SkinInfo.cs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index fee8c3edb2..a89725e466 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Testing; @@ -26,16 +27,16 @@ namespace osu.Game.Skinning [PrimaryKey] [JsonProperty] - public Guid ID { get; set; } = Guid.NewGuid(); + public Guid ID { get; set; } [JsonProperty] - public string Name { get; set; } = string.Empty; + public string Name { get; set; } = null!; [JsonProperty] - public string Creator { get; set; } = string.Empty; + public string Creator { get; set; } = null!; [JsonProperty] - public string InstantiationInfo { get; set; } = string.Empty; + public string InstantiationInfo { get; set; } = null!; public string Hash { get; set; } = string.Empty; @@ -55,6 +56,19 @@ namespace osu.Game.Skinning public bool DeletePending { get; set; } + public SkinInfo(string? name = null, string? creator = null, string? instantiationInfo = null) + { + Name = name ?? string.Empty; + Creator = creator ?? string.Empty; + InstantiationInfo = instantiationInfo ?? string.Empty; + ID = Guid.NewGuid(); + } + + [UsedImplicitly] // Realm + private SkinInfo() + { + } + public bool Equals(SkinInfo? other) { if (ReferenceEquals(this, other)) return true; From 4235fb317d617909ee8b3b80a4462a1cbe5fa939 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 15:00:05 +0900 Subject: [PATCH 0105/5199] Remove unnecessary detach operation --- osu.Game/Collections/CollectionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index d230e649f7..c4f991094c 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -209,7 +209,7 @@ namespace osu.Game.Collections string checksum = sr.ReadString(); - var beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == checksum)?.Detach(); + var beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == checksum); if (beatmap != null) collection.Beatmaps.Add(beatmap); } From 3ba712703b7a464c85ffe08927b370c8b8f6514a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 17:50:17 +0900 Subject: [PATCH 0106/5199] Add a note about hidden beatmap check --- osu.Game/Screens/Select/BeatmapCarousel.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 437567d4b2..d23febf429 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -713,6 +713,11 @@ namespace osu.Game.Screens.Select { beatmapSet = beatmapSet.Detach(); + // This can be moved to the realm query if required using: + // .Filter("DeletePending == false && Protected == false && ALL Beatmaps.Hidden == false") + // + // As long as we are detaching though, it makes more sense to do it here as adding to the realm query has an overhead + // as seen at https://github.com/realm/realm-dotnet/discussions/2773#discussioncomment-2004275. if (beatmapSet.Beatmaps.All(b => b.Hidden)) return null; From b1cf3befa6700c49403d5e628f14da0daa37d7df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 18:36:20 +0900 Subject: [PATCH 0107/5199] Fix incorrect query in comment --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index d23febf429..75ad0511e6 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -714,7 +714,7 @@ namespace osu.Game.Screens.Select beatmapSet = beatmapSet.Detach(); // This can be moved to the realm query if required using: - // .Filter("DeletePending == false && Protected == false && ALL Beatmaps.Hidden == false") + // .Filter("DeletePending == false && Protected == false && ANY Beatmaps.Hidden == false") // // As long as we are detaching though, it makes more sense to do it here as adding to the realm query has an overhead // as seen at https://github.com/realm/realm-dotnet/discussions/2773#discussioncomment-2004275. From 5df46d0a54785719585b47646dfc305674615e9e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 20:23:17 +0900 Subject: [PATCH 0108/5199] Remove all calls to `Realm.Refresh` to fix blocking overhead from subscriptions Turns out this is not required if realm is aware of a `SynchronizationContext`. See https://github.com/realm/realm-dotnet/discussions/2775#discussioncomment-2005412 for further reading. --- osu.Game/Database/EFToRealmMigrator.cs | 3 --- osu.Game/Database/RealmContextFactory.cs | 14 +------------- osu.Game/OsuGameBase.cs | 7 ------- 3 files changed, 1 insertion(+), 23 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index cff83938bf..0f726f8ee5 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -46,9 +46,6 @@ namespace osu.Game.Database migrateScores(ef); } - Logger.Log("Refreshing realm...", LoggingTarget.Database); - realmContextFactory.Refresh(); - // Delete the database permanently. // Will cause future startups to not attempt migration. Logger.Log("Migration successful, deleting EF database", LoggingTarget.Database); diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 99b357710e..31dbb0c6c4 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -61,10 +61,10 @@ namespace osu.Game.Database private readonly ThreadLocal currentThreadCanCreateContexts = new ThreadLocal(); - private static readonly GlobalStatistic refreshes = GlobalStatistics.Get(@"Realm", @"Dirty Refreshes"); private static readonly GlobalStatistic contexts_created = GlobalStatistics.Get(@"Realm", @"Contexts (Created)"); private readonly object contextLock = new object(); + private Realm? context; public Realm Context @@ -169,18 +169,6 @@ namespace osu.Game.Database /// public bool Compact() => Realm.Compact(getConfiguration()); - /// - /// Perform a blocking refresh on the main realm context. - /// - public void Refresh() - { - lock (contextLock) - { - if (context?.Refresh() == true) - refreshes.Value++; - } - } - public Realm CreateContext() { if (isDisposed) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index fb09dac1b1..5af992f800 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -351,13 +351,6 @@ namespace osu.Game FrameStatistics.ValueChanged += e => fpsDisplayVisible.Value = e.NewValue != FrameStatisticsMode.None; } - protected override void Update() - { - base.Update(); - - realmFactory.Refresh(); - } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); From a8ce2c5edf15f5af96cb8e14183e89757c650ae9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 21:14:10 +0900 Subject: [PATCH 0109/5199] Detach before sending `BeatmapSetInfo` to any handling method --- osu.Game/Screens/Select/BeatmapCarousel.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 75ad0511e6..0ee59f7f04 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -114,7 +114,7 @@ namespace osu.Game.Screens.Select { CarouselRoot newRoot = new CarouselRoot(this); - newRoot.AddChildren(beatmapSets.Select(createCarouselSet).Where(g => g != null)); + newRoot.AddChildren(beatmapSets.Select(s => createCarouselSet(s.Detach())).Where(g => g != null)); root = newRoot; if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet)) @@ -209,7 +209,7 @@ namespace osu.Game.Screens.Select return; foreach (int i in changes.InsertedIndices) - RemoveBeatmapSet(sender[i]); + RemoveBeatmapSet(sender[i].Detach()); } private void beatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) @@ -248,10 +248,10 @@ namespace osu.Game.Screens.Select } foreach (int i in changes.NewModifiedIndices) - UpdateBeatmapSet(sender[i]); + UpdateBeatmapSet(sender[i].Detach()); foreach (int i in changes.InsertedIndices) - UpdateBeatmapSet(sender[i]); + UpdateBeatmapSet(sender[i].Detach()); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) @@ -261,7 +261,7 @@ namespace osu.Game.Screens.Select return; foreach (int i in changes.InsertedIndices) - UpdateBeatmapSet(sender[i].BeatmapSet); + UpdateBeatmapSet(sender[i].BeatmapSet?.Detach()); } private IRealmCollection getBeatmapSets(Realm realm) => realm.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); @@ -711,8 +711,6 @@ namespace osu.Game.Screens.Select private CarouselBeatmapSet createCarouselSet(BeatmapSetInfo beatmapSet) { - beatmapSet = beatmapSet.Detach(); - // This can be moved to the realm query if required using: // .Filter("DeletePending == false && Protected == false && ANY Beatmaps.Hidden == false") // From 9a864267d2054cb48fe06db37ccbdd9e4b8f26fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 21:57:16 +0900 Subject: [PATCH 0110/5199] Fix `CarouselGroupEagerSelect` not invoking subclassed `AddChild` from `AddChildren` calls --- .../Select/Carousel/CarouselGroupEagerSelect.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index 9e8aad4b6f..aac0e4ed82 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -55,10 +55,16 @@ namespace osu.Game.Screens.Select.Carousel updateSelectedIndex(); } + private bool addingChildren; + public void AddChildren(IEnumerable items) { + addingChildren = true; + foreach (var i in items) - base.AddChild(i); + AddChild(i); + + addingChildren = false; attemptSelection(); } @@ -66,7 +72,8 @@ namespace osu.Game.Screens.Select.Carousel public override void AddChild(CarouselItem i) { base.AddChild(i); - attemptSelection(); + if (!addingChildren) + attemptSelection(); } protected override void ChildItemStateChanged(CarouselItem item, CarouselItemState value) From 0b93f3c88f2f1d2a6ae7b3e2300d5b77f8606cfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 21:58:16 +0900 Subject: [PATCH 0111/5199] Add `` dictionary to speed up update operations in carousel --- osu.Game/Screens/Select/BeatmapCarousel.cs | 99 ++++++++++++++-------- 1 file changed, 64 insertions(+), 35 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 0ee59f7f04..458a987130 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -95,7 +95,7 @@ namespace osu.Game.Screens.Select protected readonly CarouselScrollContainer Scroll; - private IEnumerable beatmapSets => root.Children.OfType(); + private IEnumerable beatmapSets => root.BeatmapSetsByID.Values; // todo: only used for testing, maybe remove. private bool loadedTestBeatmaps; @@ -117,6 +117,7 @@ namespace osu.Game.Screens.Select newRoot.AddChildren(beatmapSets.Select(s => createCarouselSet(s.Detach())).Where(g => g != null)); root = newRoot; + if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet)) selectedBeatmapSet = null; @@ -209,7 +210,7 @@ namespace osu.Game.Screens.Select return; foreach (int i in changes.InsertedIndices) - RemoveBeatmapSet(sender[i].Detach()); + removeBeatmapSet(sender[i].ID); } private void beatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) @@ -223,24 +224,20 @@ namespace osu.Game.Screens.Select // During initial population, we must manually account for the fact that our original query was done on an async thread. // Since then, there may have been imports or deletions. // Here we manually catch up on any changes. - var populatedSets = new HashSet(); - foreach (var s in beatmapSets) - populatedSets.Add(s.BeatmapSet.ID); - var realmSets = new HashSet(); foreach (var s in sender) realmSets.Add(s.ID); - foreach (var s in realmSets) + foreach (var id in realmSets) { - if (!populatedSets.Contains(s)) - UpdateBeatmapSet(realmFactory.Context.Find(s)); + if (!root.BeatmapSetsByID.ContainsKey(id)) + UpdateBeatmapSet(realmFactory.Context.Find(id).Detach()); } - foreach (var s in populatedSets) + foreach (var id in root.BeatmapSetsByID.Keys) { - if (!realmSets.Contains(s)) - RemoveBeatmapSet(realmFactory.Context.Find(s)); + if (!realmSets.Contains(id)) + removeBeatmapSet(id); } signalBeatmapsLoaded(); @@ -261,16 +258,30 @@ namespace osu.Game.Screens.Select return; foreach (int i in changes.InsertedIndices) - UpdateBeatmapSet(sender[i].BeatmapSet?.Detach()); + { + var beatmapInfo = sender[i]; + var beatmapSet = beatmapInfo.BeatmapSet; + + Debug.Assert(beatmapSet != null); + + // Only require to action here if the beatmap is missing. + // This avoids processing these events unnecessarily when new beatmaps are imported, for example. + if (root.BeatmapSetsByID.TryGetValue(beatmapSet.ID, out var existingSet) + && existingSet.BeatmapSet.Beatmaps.All(b => b.ID != beatmapInfo.ID)) + { + UpdateBeatmapSet(beatmapSet.Detach()); + } + } } private IRealmCollection getBeatmapSets(Realm realm) => realm.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); - public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => - { - var existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.Equals(beatmapSet)); + public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => + removeBeatmapSet(beatmapSet.ID); - if (existingSet == null) + private void removeBeatmapSet(Guid beatmapSetID) => Schedule(() => + { + if (!root.BeatmapSetsByID.TryGetValue(beatmapSetID, out var existingSet)) return; root.RemoveChild(existingSet); @@ -281,33 +292,27 @@ namespace osu.Game.Screens.Select { Guid? previouslySelectedID = null; - CarouselBeatmapSet existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.Equals(beatmapSet)); - // If the selected beatmap is about to be removed, store its ID so it can be re-selected if required - if (existingSet?.State?.Value == CarouselItemState.Selected) + if (selectedBeatmapSet?.BeatmapSet.ID == beatmapSet.ID) previouslySelectedID = selectedBeatmap?.BeatmapInfo.ID; var newSet = createCarouselSet(beatmapSet); - if (existingSet != null) - root.RemoveChild(existingSet); + root.RemoveChild(beatmapSet.ID); - if (newSet == null) + if (newSet != null) { - itemsCache.Invalidate(); - return; + root.AddChild(newSet); + + // only reset scroll position if already near the scroll target. + // without this, during a large beatmap import it is impossible to navigate the carousel. + applyActiveCriteria(false, alwaysResetScrollPosition: false); + + // check if we can/need to maintain our current selection. + if (previouslySelectedID != null) + select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet); } - root.AddChild(newSet); - - // only reset scroll position if already near the scroll target. - // without this, during a large beatmap import it is impossible to navigate the carousel. - applyActiveCriteria(false, alwaysResetScrollPosition: false); - - // check if we can/need to maintain our current selection. - if (previouslySelectedID != null) - select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet); - itemsCache.Invalidate(); Schedule(() => BeatmapSetsChanged?.Invoke()); }); @@ -911,6 +916,8 @@ namespace osu.Game.Screens.Select { private readonly BeatmapCarousel carousel; + public readonly Dictionary BeatmapSetsByID = new Dictionary(); + public CarouselRoot(BeatmapCarousel carousel) { // root should always remain selected. if not, PerformSelection will not be called. @@ -920,6 +927,28 @@ namespace osu.Game.Screens.Select this.carousel = carousel; } + public override void AddChild(CarouselItem i) + { + CarouselBeatmapSet set = (CarouselBeatmapSet)i; + BeatmapSetsByID[set.BeatmapSet.ID] = set; + + base.AddChild(i); + } + + public void RemoveChild(Guid beatmapSetID) + { + if (BeatmapSetsByID.TryGetValue(beatmapSetID, out var carouselBeatmapSet)) + RemoveChild(carouselBeatmapSet); + } + + public override void RemoveChild(CarouselItem i) + { + CarouselBeatmapSet set = (CarouselBeatmapSet)i; + BeatmapSetsByID.Remove(set.BeatmapSet.ID); + + base.RemoveChild(i); + } + protected override void PerformSelection() { if (LastSelected == null || LastSelected.Filtered.Value) From 80f3a67876adcad87ba910e41e7bc82fd46cf820 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 22:21:00 +0900 Subject: [PATCH 0112/5199] Use `for` instead of `foreach` to avoid enumerator overhead --- osu.Game/Screens/Select/BeatmapCarousel.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 458a987130..a5704b3b2e 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -225,8 +225,9 @@ namespace osu.Game.Screens.Select // Since then, there may have been imports or deletions. // Here we manually catch up on any changes. var realmSets = new HashSet(); - foreach (var s in sender) - realmSets.Add(s.ID); + + for (int i = 0; i < sender.Count; i++) + realmSets.Add(sender[i].ID); foreach (var id in realmSets) { From ba31ddee017a950be2c8b50c4c9750ebeb575af6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 00:33:46 +0900 Subject: [PATCH 0113/5199] Revert `beatmapSets` reference to fix tests New version changed order. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index a5704b3b2e..961dac9856 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -95,7 +95,7 @@ namespace osu.Game.Screens.Select protected readonly CarouselScrollContainer Scroll; - private IEnumerable beatmapSets => root.BeatmapSetsByID.Values; + private IEnumerable beatmapSets => root.Children.OfType(); // todo: only used for testing, maybe remove. private bool loadedTestBeatmaps; From 079b2dfc42ac22168dbc4f436653a648b51f9ad8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 00:01:34 +0900 Subject: [PATCH 0114/5199] Create backup of databases before opening contexts Attempt to avoid file IO issues. Closes #16531. --- osu.Game/Database/EFToRealmMigrator.cs | 30 ++++++++------------------ 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 0f726f8ee5..727815cc4d 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -26,8 +26,6 @@ namespace osu.Game.Database private readonly OsuConfigManager config; private readonly Storage storage; - private bool hasTakenBackup; - public EFToRealmMigrator(DatabaseContextFactory efContextFactory, RealmContextFactory realmContextFactory, OsuConfigManager config, Storage storage) { this.efContextFactory = efContextFactory; @@ -38,6 +36,8 @@ namespace osu.Game.Database public void Run() { + createBackup(); + using (var ef = efContextFactory.Get()) { migrateSettings(ef); @@ -77,8 +77,6 @@ namespace osu.Game.Database { Logger.Log($"Found {count} beatmaps in EF", LoggingTarget.Database); - ensureBackup(); - // only migrate data if the realm database is empty. // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. if (realm.All().Any(s => !s.Protected)) @@ -210,8 +208,6 @@ namespace osu.Game.Database { Logger.Log($"Found {count} scores in EF", LoggingTarget.Database); - ensureBackup(); - // only migrate data if the realm database is empty. if (realm.All().Any()) { @@ -291,8 +287,6 @@ namespace osu.Game.Database if (!existingSkins.Any()) return; - ensureBackup(); - var userSkinChoice = config.GetBindable(OsuSetting.Skin); int.TryParse(userSkinChoice.Value, out int userSkinInt); @@ -363,7 +357,6 @@ namespace osu.Game.Database return; Logger.Log("Beginning settings migration to realm", LoggingTarget.Database); - ensureBackup(); using (var realm = realmContextFactory.CreateContext()) using (var transaction = realm.BeginWrite()) @@ -400,21 +393,16 @@ namespace osu.Game.Database private string? getRulesetShortNameFromLegacyID(long rulesetId) => efContextFactory.Get().RulesetInfo.FirstOrDefault(r => r.ID == rulesetId)?.ShortName; - private void ensureBackup() + private void createBackup() { - if (!hasTakenBackup) - { - string migration = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; + string migration = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; - efContextFactory.CreateBackup($"client.{migration}.db"); - realmContextFactory.CreateBackup($"client.{migration}.realm"); + efContextFactory.CreateBackup($"client.{migration}.db"); + realmContextFactory.CreateBackup($"client.{migration}.realm"); - using (var source = storage.GetStream("collection.db")) - using (var destination = storage.GetStream($"collection.{migration}.db", FileAccess.Write, FileMode.CreateNew)) - source.CopyTo(destination); - - hasTakenBackup = true; - } + using (var source = storage.GetStream("collection.db")) + using (var destination = storage.GetStream($"collection.{migration}.db", FileAccess.Write, FileMode.CreateNew)) + source.CopyTo(destination); } } } From 7aad2780b1fae24d9fd6213f0e44c519fc87cc89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 00:46:47 +0900 Subject: [PATCH 0115/5199] Add retry logic for realm backup creation --- osu.Game/Database/RealmContextFactory.cs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 31dbb0c6c4..ffadf8258d 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -367,9 +367,24 @@ namespace osu.Game.Database using (BlockAllOperations()) { Logger.Log($"Creating full realm database backup at {backupFilename}", LoggingTarget.Database); - using (var source = storage.GetStream(Filename)) - using (var destination = storage.GetStream(backupFilename, FileAccess.Write, FileMode.CreateNew)) - source.CopyTo(destination); + + int attempts = 10; + + while (attempts-- > 0) + { + try + { + using (var source = storage.GetStream(Filename)) + using (var destination = storage.GetStream(backupFilename, FileAccess.Write, FileMode.CreateNew)) + source.CopyTo(destination); + return; + } + catch (IOException) + { + // file may be locked during use. + Thread.Sleep(500); + } + } } } From 0c9eb3ad61a412f6ebed4d0dc08f593743cee8d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 01:33:45 +0900 Subject: [PATCH 0116/5199] Add realm factory helper methods to run work on the correct context Avoids constructing a new `Realm` instance when called from the update thread without worrying about disposal. --- osu.Game/Database/RealmContextFactory.cs | 35 ++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 31dbb0c6c4..50e456a0c8 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -169,6 +169,41 @@ namespace osu.Game.Database /// public bool Compact() => Realm.Compact(getConfiguration()); + /// + /// Run work on realm with a return value. + /// + /// + /// Handles correct context management automatically. + /// + /// The work to run. + /// The return type. + public T Run(Func action) + { + if (ThreadSafety.IsUpdateThread) + return action(Context); + + using (var realm = CreateContext()) + return action(realm); + } + + /// + /// Run work on realm. + /// + /// + /// Handles correct context management automatically. + /// + /// The work to run. + public void Run(Action action) + { + if (ThreadSafety.IsUpdateThread) + action(Context); + else + { + using (var realm = CreateContext()) + action(realm); + } + } + public Realm CreateContext() { if (isDisposed) From a5d2047f055bbbda917d8acec11f09a363b202ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 01:34:20 +0900 Subject: [PATCH 0117/5199] Fix various cases of creating realm contexts from update thread when not necessary --- osu.Game/Beatmaps/BeatmapManager.cs | 39 ++++++++++--------- osu.Game/Beatmaps/BeatmapModelManager.cs | 7 ++-- .../Settings/Sections/Input/KeyBindingRow.cs | 4 +- osu.Game/Scoring/ScoreModelManager.cs | 3 +- osu.Game/Skinning/SkinManager.cs | 9 ++--- osu.Game/Stores/BeatmapImporter.cs | 3 +- osu.Game/Stores/RealmArchiveModelManager.cs | 8 ++-- 7 files changed, 36 insertions(+), 37 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index ee649ad960..cc765657cc 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -119,15 +119,17 @@ namespace osu.Game.Beatmaps /// The beatmap difficulty to hide. public void Hide(BeatmapInfo beatmapInfo) { - using (var realm = contextFactory.CreateContext()) - using (var transaction = realm.BeginWrite()) + contextFactory.Run(realm => { - if (!beatmapInfo.IsManaged) - beatmapInfo = realm.Find(beatmapInfo.ID); + using (var transaction = realm.BeginWrite()) + { + if (!beatmapInfo.IsManaged) + beatmapInfo = realm.Find(beatmapInfo.ID); - beatmapInfo.Hidden = true; - transaction.Commit(); - } + beatmapInfo.Hidden = true; + transaction.Commit(); + } + }); } /// @@ -136,15 +138,17 @@ namespace osu.Game.Beatmaps /// The beatmap difficulty to restore. public void Restore(BeatmapInfo beatmapInfo) { - using (var realm = contextFactory.CreateContext()) - using (var transaction = realm.BeginWrite()) + contextFactory.Run(realm => { - if (!beatmapInfo.IsManaged) - beatmapInfo = realm.Find(beatmapInfo.ID); + using (var transaction = realm.BeginWrite()) + { + if (!beatmapInfo.IsManaged) + beatmapInfo = realm.Find(beatmapInfo.ID); - beatmapInfo.Hidden = false; - transaction.Commit(); - } + beatmapInfo.Hidden = false; + transaction.Commit(); + } + }); } public void RestoreAll() @@ -176,8 +180,7 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public ILive? QueryBeatmapSet(Expression> query) { - using (var context = contextFactory.CreateContext()) - return context.All().FirstOrDefault(query)?.ToLive(contextFactory); + return contextFactory.Run(realm => realm.All().FirstOrDefault(query)?.ToLive(contextFactory)); } #region Delegation to BeatmapModelManager (methods which previously existed locally). @@ -305,13 +308,13 @@ namespace osu.Game.Beatmaps // If we seem to be missing files, now is a good time to re-fetch. if (importedBeatmap?.BeatmapSet?.Files.Count == 0) { - using (var realm = contextFactory.CreateContext()) + contextFactory.Run(realm => { var refetch = realm.Find(importedBeatmap.ID)?.Detach(); if (refetch != null) importedBeatmap = refetch; - } + }); } return workingBeatmapCache.GetWorkingBeatmap(importedBeatmap); diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 3822c6e121..a4ba13a88d 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -98,17 +98,16 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapInfo? QueryBeatmap(Expression> query) { - using (var context = ContextFactory.CreateContext()) - return context.All().FirstOrDefault(query)?.Detach(); + return ContextFactory.Run(realm => realm.All().FirstOrDefault(query)?.Detach()); } public void Update(BeatmapSetInfo item) { - using (var realm = ContextFactory.CreateContext()) + ContextFactory.Run(realm => { var existing = realm.Find(item.ID); realm.Write(r => item.CopyChangesToRealm(existing)); - } + }); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index e0a1a82326..60aff91301 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -386,11 +386,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input private void updateStoreFromButton(KeyButton button) { - using (var realm = realmFactory.CreateContext()) + realmFactory.Run(realm => { var binding = realm.Find(((IHasGuidPrimaryKey)button.KeyBinding).ID); realm.Write(() => binding.KeyCombinationString = button.KeyBinding.KeyCombinationString); - } + }); } private void updateIsDefaultValue() diff --git a/osu.Game/Scoring/ScoreModelManager.cs b/osu.Game/Scoring/ScoreModelManager.cs index 5ba152fad3..5e560effa1 100644 --- a/osu.Game/Scoring/ScoreModelManager.cs +++ b/osu.Game/Scoring/ScoreModelManager.cs @@ -74,8 +74,7 @@ namespace osu.Game.Scoring public override bool IsAvailableLocally(ScoreInfo model) { - using (var context = ContextFactory.CreateContext()) - return context.All().Any(b => b.OnlineID == model.OnlineID); + return ContextFactory.Run(realm => realm.All().Any(s => s.OnlineID == model.OnlineID)); } } } diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index cde21b78c1..3f6e5754fb 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -113,10 +113,10 @@ namespace osu.Game.Skinning public void SelectRandomSkin() { - using (var context = contextFactory.CreateContext()) + contextFactory.Run(realm => { // choose from only user skins, removing the current selection to ensure a new one is chosen. - var randomChoices = context.All().Where(s => !s.DeletePending && s.ID != CurrentSkinInfo.Value.ID).ToArray(); + var randomChoices = realm.All().Where(s => !s.DeletePending && s.ID != CurrentSkinInfo.Value.ID).ToArray(); if (randomChoices.Length == 0) { @@ -127,7 +127,7 @@ namespace osu.Game.Skinning var chosen = randomChoices.ElementAt(RNG.Next(0, randomChoices.Length)); CurrentSkinInfo.Value = chosen.ToLive(contextFactory); - } + }); } /// @@ -182,8 +182,7 @@ namespace osu.Game.Skinning /// The first result for the provided query, or null if no results were found. public ILive Query(Expression> query) { - using (var context = contextFactory.CreateContext()) - return context.All().FirstOrDefault(query)?.ToLive(contextFactory); + return contextFactory.Run(realm => realm.All().FirstOrDefault(query)?.ToLive(contextFactory)); } public event Action SourceChanged; diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index d285a6b61c..61178014ef 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -165,8 +165,7 @@ namespace osu.Game.Stores public override bool IsAvailableLocally(BeatmapSetInfo model) { - using (var context = ContextFactory.CreateContext()) - return context.All().Any(b => b.OnlineID == model.OnlineID); + return ContextFactory.Run(realm => realm.All().Any(b => b.OnlineID == model.OnlineID)); } public override string HumanisedModelName => "beatmap"; diff --git a/osu.Game/Stores/RealmArchiveModelManager.cs b/osu.Game/Stores/RealmArchiveModelManager.cs index b456dae343..115fbf721d 100644 --- a/osu.Game/Stores/RealmArchiveModelManager.cs +++ b/osu.Game/Stores/RealmArchiveModelManager.cs @@ -165,7 +165,7 @@ namespace osu.Game.Stores public bool Delete(TModel item) { - using (var realm = ContextFactory.CreateContext()) + return ContextFactory.Run(realm => { if (!item.IsManaged) item = realm.Find(item.ID); @@ -175,12 +175,12 @@ namespace osu.Game.Stores realm.Write(r => item.DeletePending = true); return true; - } + }); } public void Undelete(TModel item) { - using (var realm = ContextFactory.CreateContext()) + ContextFactory.Run(realm => { if (!item.IsManaged) item = realm.Find(item.ID); @@ -189,7 +189,7 @@ namespace osu.Game.Stores return; realm.Write(r => item.DeletePending = false); - } + }); } public abstract bool IsAvailableLocally(TModel model); From e0fe8af365535a20c077cc397a69b2bf801c9dba Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 21 Jan 2022 08:54:08 +0800 Subject: [PATCH 0118/5199] Schedule setPerformanceValue --- .../Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index bda147cf22..158fd82b29 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics { new PerformanceBreakdownCalculator(beatmapManager, difficultyCache, performanceCache) .CalculateAsync(score, cancellationTokenSource.Token) - .ContinueWith(t => setPerformanceValue(t.GetResultSafely())); + .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely()))); } private void setPerformanceValue(PerformanceBreakdown breakdown) From 45bf35c42532d4051166df6a8df955d41a8b7e49 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 12:26:24 +0900 Subject: [PATCH 0119/5199] Avoid performing keyword filtering at song select unless keywords are specified --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index d54a3bb54e..6b198ab505 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Select.Carousel match &= !criteria.UserStarDifficulty.HasFilter || criteria.UserStarDifficulty.IsInRange(BeatmapInfo.StarRating); - if (match) + if (match && criteria.SearchTerms.Length > 0) { string[] terms = BeatmapInfo.GetSearchableTerms(); From 5b24800b0e9c88af8747f9e33a3c14b97ca1f4d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 12:37:17 +0900 Subject: [PATCH 0120/5199] Avoid applying filter in `UpdateBeatmapSet` flow --- osu.Game/Screens/Select/BeatmapCarousel.cs | 12 +++++---- .../Screens/Select/Carousel/CarouselGroup.cs | 27 +++++++++++++++++-- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 75ad0511e6..6d3d7aa185 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -300,16 +300,18 @@ namespace osu.Game.Screens.Select root.AddChild(newSet); - // only reset scroll position if already near the scroll target. - // without this, during a large beatmap import it is impossible to navigate the carousel. - applyActiveCriteria(false, alwaysResetScrollPosition: false); - // check if we can/need to maintain our current selection. if (previouslySelectedID != null) select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet); itemsCache.Invalidate(); - Schedule(() => BeatmapSetsChanged?.Invoke()); + Schedule(() => + { + if (!Scroll.UserScrolling) + ScrollToSelected(true); + + BeatmapSetsChanged?.Invoke(); + }); }); /// diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index b85e868b89..7e4c5fad72 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; namespace osu.Game.Screens.Select.Carousel { @@ -36,7 +37,21 @@ namespace osu.Game.Screens.Select.Carousel { i.State.ValueChanged += state => ChildItemStateChanged(i, state.NewValue); i.ChildID = ++currentChildID; - InternalChildren.Add(i); + + if (lastCriteria != null) + { + i.Filter(lastCriteria); + + int index = InternalChildren.BinarySearch(i, criteriaComparer); + if (index < 0) index = ~index; // BinarySearch hacks multiple return values with 2's complement. + + InternalChildren.Insert(index, i); + } + else + { + // criteria may be null for initial population. the filtering will be applied post-add. + InternalChildren.Add(i); + } } public CarouselGroup(List items = null) @@ -62,14 +77,22 @@ namespace osu.Game.Screens.Select.Carousel }; } + private Comparer criteriaComparer; + + [CanBeNull] + private FilterCriteria lastCriteria; + public override void Filter(FilterCriteria criteria) { base.Filter(criteria); InternalChildren.ForEach(c => c.Filter(criteria)); + // IEnumerable.OrderBy() is used instead of List.Sort() to ensure sorting stability - var criteriaComparer = Comparer.Create((x, y) => x.CompareTo(criteria, y)); + criteriaComparer = Comparer.Create((x, y) => x.CompareTo(criteria, y)); InternalChildren = InternalChildren.OrderBy(c => c, criteriaComparer).ToList(); + + lastCriteria = criteria; } protected virtual void ChildItemStateChanged(CarouselItem item, CarouselItemState value) From bed7b69464ed1b6971ae84ee81885d6ce90c5fed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 13:09:03 +0900 Subject: [PATCH 0121/5199] Apply NRT to `CarouselGroup` --- .../Screens/Select/Carousel/CarouselGroup.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index 7e4c5fad72..6ebe314072 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -3,7 +3,8 @@ using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; + +#nullable enable namespace osu.Game.Screens.Select.Carousel { @@ -12,7 +13,7 @@ namespace osu.Game.Screens.Select.Carousel /// public class CarouselGroup : CarouselItem { - public override DrawableCarouselItem CreateDrawableRepresentation() => null; + public override DrawableCarouselItem? CreateDrawableRepresentation() => null; public IReadOnlyList Children => InternalChildren; @@ -24,6 +25,10 @@ namespace osu.Game.Screens.Select.Carousel /// private ulong currentChildID; + private Comparer? criteriaComparer; + + private FilterCriteria? lastCriteria; + public virtual void RemoveChild(CarouselItem i) { InternalChildren.Remove(i); @@ -54,7 +59,7 @@ namespace osu.Game.Screens.Select.Carousel } } - public CarouselGroup(List items = null) + public CarouselGroup(List? items = null) { if (items != null) InternalChildren = items; @@ -77,11 +82,6 @@ namespace osu.Game.Screens.Select.Carousel }; } - private Comparer criteriaComparer; - - [CanBeNull] - private FilterCriteria lastCriteria; - public override void Filter(FilterCriteria criteria) { base.Filter(criteria); From 5be41a189bb00e22926511b35424db85c992ead6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 14:56:28 +0900 Subject: [PATCH 0122/5199] Expose EF context factory for use in external migration logic --- osu.Game/OsuGameBase.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 5af992f800..3d40126b4f 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -161,6 +161,11 @@ namespace osu.Game private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(global_track_volume_adjust); + /// + /// A legacy EF context factory if migration has not been performed to realm yet. + /// + protected DatabaseContextFactory EFContextFactory { get; private set; } + public OsuGameBase() { UseDevelopmentServer = DebugUtils.IsDebugBuild; @@ -184,19 +189,14 @@ namespace osu.Game Resources.AddStore(new DllResourceStore(OsuResources.ResourceAssembly)); - DatabaseContextFactory efContextFactory = Storage.Exists(DatabaseContextFactory.DATABASE_NAME) - ? new DatabaseContextFactory(Storage) - : null; + if (Storage.Exists(DatabaseContextFactory.DATABASE_NAME)) + dependencies.Cache(EFContextFactory = new DatabaseContextFactory(Storage)); - dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client", efContextFactory)); + dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client", EFContextFactory)); dependencies.Cache(RulesetStore = new RulesetStore(realmFactory, Storage)); dependencies.CacheAs(RulesetStore); - // A non-null context factory means there's still content to migrate. - if (efContextFactory != null) - new EFToRealmMigrator(efContextFactory, realmFactory, LocalConfig, Storage).Run(); - dependencies.CacheAs(Storage); var largeStore = new LargeTextureStore(Host.CreateTextureLoaderStore(new NamespacedResourceStore(Resources, @"Textures"))); From 5622d2ba4f704fe707ea5a9e647ee9859277ed34 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 14:56:49 +0900 Subject: [PATCH 0123/5199] Show realm migration progress at `Loader` --- osu.Game/Database/EFToRealmMigrator.cs | 150 +++++++++++++++++++------ osu.Game/Screens/Loader.cs | 12 +- 2 files changed, 125 insertions(+), 37 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 727815cc4d..85d65fea82 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -4,52 +4,130 @@ using System; using System.IO; using System.Linq; +using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Models; using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Skinning; +using osuTK; using Realms; #nullable enable namespace osu.Game.Database { - internal class EFToRealmMigrator + internal class EFToRealmMigrator : CompositeDrawable { - private readonly DatabaseContextFactory efContextFactory; - private readonly RealmContextFactory realmContextFactory; - private readonly OsuConfigManager config; - private readonly Storage storage; + public bool FinishedMigrating { get; private set; } - public EFToRealmMigrator(DatabaseContextFactory efContextFactory, RealmContextFactory realmContextFactory, OsuConfigManager config, Storage storage) + [Resolved] + private DatabaseContextFactory efContextFactory { get; set; } = null!; + + [Resolved] + private RealmContextFactory realmContextFactory { get; set; } = null!; + + [Resolved] + private OsuConfigManager config { get; set; } = null!; + + [Resolved] + private Storage storage { get; set; } = null!; + + private readonly OsuSpriteText currentOperationText; + + public EFToRealmMigrator() { - this.efContextFactory = efContextFactory; - this.realmContextFactory = realmContextFactory; - this.config = config; - this.storage = storage; + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(10), + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Database migration in progress", + Font = OsuFont.Default.With(size: 40) + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "This could take a few minutes depending on the speed of your disk(s).", + Font = OsuFont.Default.With(size: 30) + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Please keep the window open until this completes!", + Font = OsuFont.Default.With(size: 30) + }, + new LoadingSpinner(true) + { + State = { Value = Visibility.Visible } + }, + currentOperationText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Default.With(size: 30) + }, + } + }, + }; } - public void Run() + protected override void LoadComplete() { + base.LoadComplete(); + + // needs to be run on the update thread because of realm BlockAllOperations. + // maybe we can work around this? not sure.. createBackup(); - using (var ef = efContextFactory.Get()) + Task.Factory.StartNew(() => { - migrateSettings(ef); - migrateSkins(ef); - migrateBeatmaps(ef); - migrateScores(ef); - } + using (var ef = efContextFactory.Get()) + { + migrateSettings(ef); + migrateSkins(ef); + migrateBeatmaps(ef); + migrateScores(ef); + } - // Delete the database permanently. - // Will cause future startups to not attempt migration. - Logger.Log("Migration successful, deleting EF database", LoggingTarget.Database); - efContextFactory.ResetDatabase(); + // Delete the database permanently. + // Will cause future startups to not attempt migration. + log("Migration successful, deleting EF database"); + efContextFactory.ResetDatabase(); + }, TaskCreationOptions.LongRunning).ContinueWith(t => + { + FinishedMigrating = true; + }); + } + + private void log(string message) + { + Logger.Log(message, LoggingTarget.Database); + Scheduler.AddOnce(m => currentOperationText.Text = m, message); } private void migrateBeatmaps(OsuDbContext ef) @@ -62,12 +140,12 @@ namespace osu.Game.Database .Include(s => s.Files).ThenInclude(f => f.FileInfo) .Include(s => s.Metadata); - Logger.Log("Beginning beatmaps migration to realm", LoggingTarget.Database); + log("Beginning beatmaps migration to realm"); // previous entries in EF are removed post migration. if (!existingBeatmapSets.Any()) { - Logger.Log("No beatmaps found to migrate", LoggingTarget.Database); + log("No beatmaps found to migrate"); return; } @@ -75,13 +153,13 @@ namespace osu.Game.Database using (var realm = realmContextFactory.CreateContext()) { - Logger.Log($"Found {count} beatmaps in EF", LoggingTarget.Database); + log($"Found {count} beatmaps in EF"); // only migrate data if the realm database is empty. // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. if (realm.All().Any(s => !s.Protected)) { - Logger.Log("Skipping migration as realm already has beatmaps loaded", LoggingTarget.Database); + log("Skipping migration as realm already has beatmaps loaded"); } else { @@ -96,7 +174,7 @@ namespace osu.Game.Database { transaction.Commit(); transaction = realm.BeginWrite(); - Logger.Log($"Migrated {written}/{count} beatmaps...", LoggingTarget.Database); + log($"Migrated {written}/{count} beatmaps..."); } var realmBeatmapSet = new BeatmapSetInfo @@ -156,7 +234,7 @@ namespace osu.Game.Database transaction.Commit(); } - Logger.Log($"Successfully migrated {count} beatmaps to realm", LoggingTarget.Database); + log($"Successfully migrated {count} beatmaps to realm"); } } } @@ -193,12 +271,12 @@ namespace osu.Game.Database .Include(s => s.Files) .ThenInclude(f => f.FileInfo); - Logger.Log("Beginning scores migration to realm", LoggingTarget.Database); + log("Beginning scores migration to realm"); // previous entries in EF are removed post migration. if (!existingScores.Any()) { - Logger.Log("No scores found to migrate", LoggingTarget.Database); + log("No scores found to migrate"); return; } @@ -206,12 +284,12 @@ namespace osu.Game.Database using (var realm = realmContextFactory.CreateContext()) { - Logger.Log($"Found {count} scores in EF", LoggingTarget.Database); + log($"Found {count} scores in EF"); // only migrate data if the realm database is empty. if (realm.All().Any()) { - Logger.Log("Skipping migration as realm already has scores loaded", LoggingTarget.Database); + log("Skipping migration as realm already has scores loaded"); } else { @@ -226,7 +304,7 @@ namespace osu.Game.Database { transaction.Commit(); transaction = realm.BeginWrite(); - Logger.Log($"Migrated {written}/{count} scores...", LoggingTarget.Database); + log($"Migrated {written}/{count} scores..."); } var beatmap = realm.All().First(b => b.Hash == score.BeatmapInfo.Hash); @@ -270,7 +348,7 @@ namespace osu.Game.Database transaction.Commit(); } - Logger.Log($"Successfully migrated {count} scores to realm", LoggingTarget.Database); + log($"Successfully migrated {count} scores to realm"); } } } @@ -308,7 +386,7 @@ namespace osu.Game.Database // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. if (!realm.All().Any(s => !s.Protected)) { - Logger.Log($"Migrating {existingSkins.Count} skins", LoggingTarget.Database); + log($"Migrating {existingSkins.Count} skins"); foreach (var skin in existingSkins) { @@ -356,7 +434,7 @@ namespace osu.Game.Database if (!existingSettings.Any()) return; - Logger.Log("Beginning settings migration to realm", LoggingTarget.Database); + log("Beginning settings migration to realm"); using (var realm = realmContextFactory.CreateContext()) using (var transaction = realm.BeginWrite()) @@ -364,7 +442,7 @@ namespace osu.Game.Database // only migrate data if the realm database is empty. if (!realm.All().Any()) { - Logger.Log($"Migrating {existingSettings.Count} settings", LoggingTarget.Database); + log($"Migrating {existingSettings.Count} settings"); foreach (var dkb in existingSettings) { diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 41097a4c74..8c4a13f2bd 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -12,6 +12,7 @@ using osu.Game.Screens.Menu; using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Graphics.UserInterface; using IntroSequence = osu.Game.Configuration.IntroSequence; @@ -63,6 +64,11 @@ namespace osu.Game.Screens protected virtual ShaderPrecompiler CreateShaderPrecompiler() => new ShaderPrecompiler(); + [Resolved(canBeNull: true)] + private DatabaseContextFactory efContextFactory { get; set; } + + private EFToRealmMigrator realmMigrator; + public override void OnEntering(IScreen last) { base.OnEntering(last); @@ -70,6 +76,10 @@ namespace osu.Game.Screens LoadComponentAsync(precompiler = CreateShaderPrecompiler(), AddInternal); LoadComponentAsync(loadableScreen = CreateLoadableScreen()); + // A non-null context factory means there's still content to migrate. + if (efContextFactory != null) + LoadComponentAsync(realmMigrator = new EFToRealmMigrator(), AddInternal); + LoadComponentAsync(spinner = new LoadingSpinner(true, true) { Anchor = Anchor.BottomRight, @@ -86,7 +96,7 @@ namespace osu.Game.Screens private void checkIfLoaded() { - if (loadableScreen.LoadState != LoadState.Ready || !precompiler.FinishedCompiling) + if (loadableScreen.LoadState != LoadState.Ready || !precompiler.FinishedCompiling || realmMigrator?.FinishedMigrating == false) { Schedule(checkIfLoaded); return; From 3bcdce128c5927a90f718b0ef54d76678272648a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 15:29:21 +0900 Subject: [PATCH 0124/5199] Use dictionary add for safety --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 3cd9253eff..e8171d1512 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -934,7 +934,7 @@ namespace osu.Game.Screens.Select public override void AddChild(CarouselItem i) { CarouselBeatmapSet set = (CarouselBeatmapSet)i; - BeatmapSetsByID[set.BeatmapSet.ID] = set; + BeatmapSetsByID.Add(set.BeatmapSet.ID, set); base.AddChild(i); } From dde10d1ba214691e8f41616a0f3fe5b1949867cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 16:38:07 +0900 Subject: [PATCH 0125/5199] Remove unused `IRealmFactory` interface --- osu.Game/Database/IRealmFactory.cs | 20 -------------------- osu.Game/Database/RealmContextFactory.cs | 2 +- 2 files changed, 1 insertion(+), 21 deletions(-) delete mode 100644 osu.Game/Database/IRealmFactory.cs diff --git a/osu.Game/Database/IRealmFactory.cs b/osu.Game/Database/IRealmFactory.cs deleted file mode 100644 index a957424584..0000000000 --- a/osu.Game/Database/IRealmFactory.cs +++ /dev/null @@ -1,20 +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 Realms; - -namespace osu.Game.Database -{ - public interface IRealmFactory - { - /// - /// The main realm context, bound to the update thread. - /// - Realm Context { get; } - - /// - /// Create a new realm context for use on the current thread. - /// - Realm CreateContext(); - } -} diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 50e456a0c8..c1b159eac7 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -30,7 +30,7 @@ namespace osu.Game.Database /// /// A factory which provides both the main (update thread bound) realm context and creates contexts for async usage. /// - public class RealmContextFactory : IDisposable, IRealmFactory + public class RealmContextFactory : IDisposable { private readonly Storage storage; From a59105635e36c31baf63217ea74d472ec09dc239 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 16:40:20 +0900 Subject: [PATCH 0126/5199] Make `CreateContext` private --- osu.Game/Database/RealmContextFactory.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index c1b159eac7..5c9d2d7c5a 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -72,13 +72,13 @@ namespace osu.Game.Database get { if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException(@$"Use {nameof(CreateContext)} when performing realm operations from a non-update thread"); + throw new InvalidOperationException(@$"Use {nameof(createContext)} when performing realm operations from a non-update thread"); lock (contextLock) { if (context == null) { - context = CreateContext(); + context = createContext(); Logger.Log(@$"Opened realm ""{context.Config.DatabasePath}"" at version {context.Config.SchemaVersion}"); } @@ -124,7 +124,7 @@ namespace osu.Game.Database private void cleanupPendingDeletions() { - using (var realm = CreateContext()) + using (var realm = createContext()) using (var transaction = realm.BeginWrite()) { var pendingDeleteScores = realm.All().Where(s => s.DeletePending); @@ -182,7 +182,7 @@ namespace osu.Game.Database if (ThreadSafety.IsUpdateThread) return action(Context); - using (var realm = CreateContext()) + using (var realm = createContext()) return action(realm); } @@ -199,12 +199,12 @@ namespace osu.Game.Database action(Context); else { - using (var realm = CreateContext()) + using (var realm = createContext()) action(realm); } } - public Realm CreateContext() + private Realm createContext() { if (isDisposed) throw new ObjectDisposedException(nameof(RealmContextFactory)); From da0a803e6c2df35194fd5a21e98f5940955d9f5f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 17:08:02 +0900 Subject: [PATCH 0127/5199] Add `RealmContextFactory.Write` helper method --- osu.Game/Database/RealmContextFactory.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 5c9d2d7c5a..ea33fec2a0 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -204,6 +204,24 @@ namespace osu.Game.Database } } + /// + /// Write changes to realm. + /// + /// + /// Handles correct context management and transaction committing automatically. + /// + /// The work to run. + public void Write(Action action) + { + if (ThreadSafety.IsUpdateThread) + Context.Write(action); + else + { + using (var realm = createContext()) + realm.Write(action); + } + } + private Realm createContext() { if (isDisposed) From 114c9e8c1f985b2df61cd8e65e9a3bef9f94bb57 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 17:08:20 +0900 Subject: [PATCH 0128/5199] Update all usages of `CreateContext` to use either `Run` or `Write` --- .../Beatmaps/IO/BeatmapImportHelper.cs | 3 +- osu.Game.Tests/Database/GeneralUsageTests.cs | 20 ++-- osu.Game.Tests/Database/RealmLiveTests.cs | 57 +++++----- .../Database/TestRealmKeyBindingStore.cs | 27 ++--- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 4 +- .../Visual/Ranking/TestSceneResultsScreen.cs | 4 +- .../TestSceneDeleteLocalScore.cs | 4 +- osu.Game/Beatmaps/BeatmapManager.cs | 26 ++--- osu.Game/Beatmaps/BeatmapModelManager.cs | 4 +- osu.Game/Database/EFToRealmMigrator.cs | 104 +++++++++-------- osu.Game/Database/RealmLive.cs | 7 +- osu.Game/Input/RealmKeyBindingStore.cs | 36 +++--- .../Sections/Input/KeyBindingsSubsection.cs | 5 +- .../Configuration/RulesetConfigManager.cs | 18 +-- osu.Game/Rulesets/RulesetStore.cs | 107 +++++++++--------- osu.Game/Scoring/ScoreManager.cs | 11 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 6 +- osu.Game/Skinning/SkinManager.cs | 19 ++-- osu.Game/Skinning/SkinModelManager.cs | 4 +- osu.Game/Stores/RealmArchiveModelImporter.cs | 4 +- osu.Game/Stores/RealmFileStore.cs | 7 +- 22 files changed, 230 insertions(+), 250 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs b/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs index 44f6943871..7aa2dc7093 100644 --- a/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs +++ b/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs @@ -55,8 +55,7 @@ namespace osu.Game.Tests.Beatmaps.IO { var realmContextFactory = osu.Dependencies.Get(); - using (var realm = realmContextFactory.CreateContext()) - BeatmapImporterTests.EnsureLoaded(realm, timeout); + realmContextFactory.Run(realm => BeatmapImporterTests.EnsureLoaded(realm, timeout)); // TODO: add back some extra checks outside of the realm ones? // var set = queryBeatmapSets().First(); diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index 0961ad71e4..9ebe94b383 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Database [Test] public void TestConstructRealm() { - RunTestWithRealm((realmFactory, _) => { realmFactory.CreateContext().Refresh(); }); + RunTestWithRealm((realmFactory, _) => { realmFactory.Run(realm => realm.Refresh()); }); } [Test] @@ -46,23 +46,21 @@ namespace osu.Game.Tests.Database { bool callbackRan = false; - using (var context = realmFactory.CreateContext()) + realmFactory.Run(realm => { - var subscription = context.All().QueryAsyncWithNotifications((sender, changes, error) => + var subscription = realm.All().QueryAsyncWithNotifications((sender, changes, error) => { - using (realmFactory.CreateContext()) + realmFactory.Run(_ => { callbackRan = true; - } + }); }); // Force the callback above to run. - using (realmFactory.CreateContext()) - { - } + realmFactory.Run(r => r.Refresh()); subscription?.Dispose(); - } + }); Assert.IsTrue(callbackRan); }); @@ -78,12 +76,12 @@ namespace osu.Game.Tests.Database Task.Factory.StartNew(() => { - using (realmFactory.CreateContext()) + realmFactory.Run(_ => { hasThreadedUsage.Set(); stopThreadedUsage.Wait(); - } + }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler); hasThreadedUsage.Wait(); diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 187fcd3ca7..2f16df4624 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -23,9 +23,9 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmFactory, _) => { - ILive beatmap = realmFactory.CreateContext().Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realmFactory); + ILive beatmap = realmFactory.Run(realm => realm.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realmFactory)); - ILive beatmap2 = realmFactory.CreateContext().All().First().ToLive(realmFactory); + ILive beatmap2 = realmFactory.Run(realm => realm.All().First().ToLive(realmFactory)); Assert.AreEqual(beatmap, beatmap2); }); @@ -38,14 +38,14 @@ namespace osu.Game.Tests.Database { var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()); - ILive liveBeatmap; + ILive? liveBeatmap = null; - using (var context = realmFactory.CreateContext()) + realmFactory.Run(realm => { - context.Write(r => r.Add(beatmap)); + realm.Write(r => r.Add(beatmap)); liveBeatmap = beatmap.ToLive(realmFactory); - } + }); using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target")) { @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Database storage.Migrate(migratedStorage); - Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden)); + Assert.IsFalse(liveBeatmap?.PerformRead(l => l.Hidden)); } }); } @@ -67,8 +67,7 @@ namespace osu.Game.Tests.Database var liveBeatmap = beatmap.ToLive(realmFactory); - using (var context = realmFactory.CreateContext()) - context.Write(r => r.Add(beatmap)); + realmFactory.Run(realm => realm.Write(r => r.Add(beatmap))); Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden)); }); @@ -99,12 +98,12 @@ namespace osu.Game.Tests.Database ILive? liveBeatmap = null; Task.Factory.StartNew(() => { - using (var threadContext = realmFactory.CreateContext()) + realmFactory.Run(threadContext => { var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))); liveBeatmap = beatmap.ToLive(realmFactory); - } + }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); Debug.Assert(liveBeatmap != null); @@ -128,12 +127,12 @@ namespace osu.Game.Tests.Database ILive? liveBeatmap = null; Task.Factory.StartNew(() => { - using (var threadContext = realmFactory.CreateContext()) + realmFactory.Run(threadContext => { var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))); liveBeatmap = beatmap.ToLive(realmFactory); - } + }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); Debug.Assert(liveBeatmap != null); @@ -170,12 +169,12 @@ namespace osu.Game.Tests.Database Task.Factory.StartNew(() => { - using (var threadContext = realmFactory.CreateContext()) + realmFactory.Run(threadContext => { var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))); liveBeatmap = beatmap.ToLive(realmFactory); - } + }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); Debug.Assert(liveBeatmap != null); @@ -189,13 +188,13 @@ namespace osu.Game.Tests.Database }); // Can't be used, even from within a valid context. - using (realmFactory.CreateContext()) + realmFactory.Run(threadContext => { Assert.Throws(() => { var __ = liveBeatmap.Value; }); - } + }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); }); } @@ -208,12 +207,12 @@ namespace osu.Game.Tests.Database ILive? liveBeatmap = null; Task.Factory.StartNew(() => { - using (var threadContext = realmFactory.CreateContext()) + realmFactory.Run(threadContext => { var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))); liveBeatmap = beatmap.ToLive(realmFactory); - } + }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); Debug.Assert(liveBeatmap != null); @@ -235,50 +234,50 @@ namespace osu.Game.Tests.Database { int changesTriggered = 0; - using (var updateThreadContext = realmFactory.CreateContext()) + realmFactory.Run(outerRealm => { - updateThreadContext.All().QueryAsyncWithNotifications(gotChange); + outerRealm.All().QueryAsyncWithNotifications(gotChange); ILive? liveBeatmap = null; Task.Factory.StartNew(() => { - using (var threadContext = realmFactory.CreateContext()) + realmFactory.Run(innerRealm => { var ruleset = CreateRuleset(); - var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata()))); + var beatmap = innerRealm.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata()))); // add a second beatmap to ensure that a full refresh occurs below. // not just a refresh from the resolved Live. - threadContext.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata()))); + innerRealm.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata()))); liveBeatmap = beatmap.ToLive(realmFactory); - } + }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); Debug.Assert(liveBeatmap != null); // not yet seen by main context - Assert.AreEqual(0, updateThreadContext.All().Count()); + Assert.AreEqual(0, outerRealm.All().Count()); Assert.AreEqual(0, changesTriggered); liveBeatmap.PerformRead(resolved => { // retrieval causes an implicit refresh. even changes that aren't related to the retrieval are fired at this point. // ReSharper disable once AccessToDisposedClosure - Assert.AreEqual(2, updateThreadContext.All().Count()); + Assert.AreEqual(2, outerRealm.All().Count()); Assert.AreEqual(1, changesTriggered); // can access properties without a crash. Assert.IsFalse(resolved.Hidden); // ReSharper disable once AccessToDisposedClosure - updateThreadContext.Write(r => + outerRealm.Write(r => { // can use with the main context. r.Remove(resolved); }); }); - } + }); void gotChange(IRealmCollection sender, ChangeSet changes, Exception error) { diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index e3c1d42667..c1041e9fd6 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -60,15 +60,12 @@ namespace osu.Game.Tests.Database KeyBindingContainer testContainer = new TestKeyBindingContainer(); // Add some excess bindings for an action which only supports 1. - using (var realm = realmContextFactory.CreateContext()) - using (var transaction = realm.BeginWrite()) + realmContextFactory.Write(realm => { realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.A))); realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.S))); realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.D))); - - transaction.Commit(); - } + }); Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(3)); @@ -79,13 +76,13 @@ namespace osu.Game.Tests.Database private int queryCount(GlobalAction? match = null) { - using (var realm = realmContextFactory.CreateContext()) + return realmContextFactory.Run(realm => { var results = realm.All(); if (match.HasValue) results = results.Where(k => k.ActionInt == (int)match.Value); return results.Count(); - } + }); } [Test] @@ -95,26 +92,26 @@ namespace osu.Game.Tests.Database keyBindingStore.Register(testContainer, Enumerable.Empty()); - using (var primaryRealm = realmContextFactory.CreateContext()) + realmContextFactory.Run(outerRealm => { - var backBinding = primaryRealm.All().Single(k => k.ActionInt == (int)GlobalAction.Back); + var backBinding = outerRealm.All().Single(k => k.ActionInt == (int)GlobalAction.Back); Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.Escape })); var tsr = ThreadSafeReference.Create(backBinding); - using (var threadedContext = realmContextFactory.CreateContext()) + realmContextFactory.Run(innerRealm => { - var binding = threadedContext.ResolveReference(tsr); - threadedContext.Write(() => binding.KeyCombination = new KeyCombination(InputKey.BackSpace)); - } + var binding = innerRealm.ResolveReference(tsr); + innerRealm.Write(() => binding.KeyCombination = new KeyCombination(InputKey.BackSpace)); + }); Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace })); // check still correct after re-query. - backBinding = primaryRealm.All().Single(k => k.ActionInt == (int)GlobalAction.Back); + backBinding = outerRealm.All().Single(k => k.ActionInt == (int)GlobalAction.Back); Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace })); - } + }); } [TearDown] diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 8c24b2eef8..1d639c6418 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -60,8 +60,8 @@ namespace osu.Game.Tests.Online testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile); testBeatmapSet = testBeatmapInfo.BeatmapSet; - ContextFactory.Context.Write(r => r.RemoveAll()); - ContextFactory.Context.Write(r => r.RemoveAll()); + ContextFactory.Write(r => r.RemoveAll()); + ContextFactory.Write(r => r.RemoveAll()); selectedItem.Value = new PlaylistItem { diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 62500babc1..a77480ee54 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Ranking { base.LoadComplete(); - using (var realm = realmContextFactory.CreateContext()) + realmContextFactory.Run(realm => { var beatmapInfo = realm.All() .Filter($"{nameof(BeatmapInfo.Ruleset)}.{nameof(RulesetInfo.OnlineID)} = $0", 0) @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Ranking if (beatmapInfo != null) Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); - } + }); } [Test] diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 1e14e4b3e5..f43354514b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -122,11 +122,11 @@ namespace osu.Game.Tests.Visual.UserInterface [SetUp] public void Setup() => Schedule(() => { - using (var realm = realmFactory.CreateContext()) + realmFactory.Run(realm => { // Due to soft deletions, we can re-use deleted scores between test runs scoreManager.Undelete(realm.All().Where(s => s.DeletePending).ToList()); - } + }); leaderboard.Scores = null; leaderboard.FinishTransforms(true); // After setting scores, we may be waiting for transforms to expire drawables diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index cc765657cc..a9340e1250 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -153,14 +153,16 @@ namespace osu.Game.Beatmaps public void RestoreAll() { - using (var realm = contextFactory.CreateContext()) - using (var transaction = realm.BeginWrite()) + contextFactory.Run(realm => { - foreach (var beatmap in realm.All().Where(b => b.Hidden)) - beatmap.Hidden = false; + using (var transaction = realm.BeginWrite()) + { + foreach (var beatmap in realm.All().Where(b => b.Hidden)) + beatmap.Hidden = false; - transaction.Commit(); - } + transaction.Commit(); + } + }); } /// @@ -169,8 +171,7 @@ namespace osu.Game.Beatmaps /// A list of available . public List GetAllUsableBeatmapSets() { - using (var context = contextFactory.CreateContext()) - return context.All().Where(b => !b.DeletePending).Detach(); + return contextFactory.Run(realm => realm.All().Where(b => !b.DeletePending).Detach()); } /// @@ -235,21 +236,20 @@ namespace osu.Game.Beatmaps public void Delete(Expression>? filter = null, bool silent = false) { - using (var context = contextFactory.CreateContext()) + contextFactory.Run(realm => { - var items = context.All().Where(s => !s.DeletePending && !s.Protected); + var items = realm.All().Where(s => !s.DeletePending && !s.Protected); if (filter != null) items = items.Where(filter); beatmapModelManager.Delete(items.ToList(), silent); - } + }); } public void UndeleteAll() { - using (var context = contextFactory.CreateContext()) - beatmapModelManager.Undelete(context.All().Where(s => s.DeletePending).ToList()); + contextFactory.Run(realm => beatmapModelManager.Undelete(realm.All().Where(s => s.DeletePending).ToList())); } public void Undelete(List items, bool silent = false) diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index a4ba13a88d..44d6af5b73 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -103,10 +103,10 @@ namespace osu.Game.Beatmaps public void Update(BeatmapSetInfo item) { - ContextFactory.Run(realm => + ContextFactory.Write(realm => { var existing = realm.Find(item.ID); - realm.Write(r => item.CopyChangesToRealm(existing)); + item.CopyChangesToRealm(existing); }); } } diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 0f726f8ee5..58321efcc9 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -73,7 +73,7 @@ namespace osu.Game.Database int count = existingBeatmapSets.Count(); - using (var realm = realmContextFactory.CreateContext()) + realmContextFactory.Run(realm => { Logger.Log($"Found {count} beatmaps in EF", LoggingTarget.Database); @@ -160,7 +160,7 @@ namespace osu.Game.Database Logger.Log($"Successfully migrated {count} beatmaps to realm", LoggingTarget.Database); } - } + }); } private BeatmapMetadata getBestMetadata(EFBeatmapMetadata? beatmapMetadata, EFBeatmapMetadata? beatmapSetMetadata) @@ -206,7 +206,7 @@ namespace osu.Game.Database int count = existingScores.Count(); - using (var realm = realmContextFactory.CreateContext()) + realmContextFactory.Run(realm => { Logger.Log($"Found {count} scores in EF", LoggingTarget.Database); @@ -276,7 +276,7 @@ namespace osu.Game.Database Logger.Log($"Successfully migrated {count} scores to realm", LoggingTarget.Database); } - } + }); } private void migrateSkins(OsuDbContext db) @@ -307,37 +307,39 @@ namespace osu.Game.Database break; } - using (var realm = realmContextFactory.CreateContext()) - using (var transaction = realm.BeginWrite()) + realmContextFactory.Run(realm => { - // only migrate data if the realm database is empty. - // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. - if (!realm.All().Any(s => !s.Protected)) + using (var transaction = realm.BeginWrite()) { - Logger.Log($"Migrating {existingSkins.Count} skins", LoggingTarget.Database); - - foreach (var skin in existingSkins) + // only migrate data if the realm database is empty. + // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. + if (!realm.All().Any(s => !s.Protected)) { - var realmSkin = new SkinInfo + Logger.Log($"Migrating {existingSkins.Count} skins", LoggingTarget.Database); + + foreach (var skin in existingSkins) { - Name = skin.Name, - Creator = skin.Creator, - Hash = skin.Hash, - Protected = false, - InstantiationInfo = skin.InstantiationInfo, - }; + var realmSkin = new SkinInfo + { + Name = skin.Name, + Creator = skin.Creator, + Hash = skin.Hash, + Protected = false, + InstantiationInfo = skin.InstantiationInfo, + }; - migrateFiles(skin, realm, realmSkin); + migrateFiles(skin, realm, realmSkin); - realm.Add(realmSkin); + realm.Add(realmSkin); - if (skin.ID == userSkinInt) - userSkinChoice.Value = realmSkin.ID.ToString(); + if (skin.ID == userSkinInt) + userSkinChoice.Value = realmSkin.ID.ToString(); + } } - } - transaction.Commit(); - } + transaction.Commit(); + } + }); } private static void migrateFiles(IHasFiles fileSource, Realm realm, IHasRealmFiles realmObject) where T : INamedFileInfo @@ -365,36 +367,38 @@ namespace osu.Game.Database Logger.Log("Beginning settings migration to realm", LoggingTarget.Database); ensureBackup(); - using (var realm = realmContextFactory.CreateContext()) - using (var transaction = realm.BeginWrite()) + realmContextFactory.Run(realm => { - // only migrate data if the realm database is empty. - if (!realm.All().Any()) + using (var transaction = realm.BeginWrite()) { - Logger.Log($"Migrating {existingSettings.Count} settings", LoggingTarget.Database); - - foreach (var dkb in existingSettings) + // only migrate data if the realm database is empty. + if (!realm.All().Any()) { - if (dkb.RulesetID == null) - continue; + Logger.Log($"Migrating {existingSettings.Count} settings", LoggingTarget.Database); - string? shortName = getRulesetShortNameFromLegacyID(dkb.RulesetID.Value); - - if (string.IsNullOrEmpty(shortName)) - continue; - - realm.Add(new RealmRulesetSetting + foreach (var dkb in existingSettings) { - Key = dkb.Key, - Value = dkb.StringValue, - RulesetName = shortName, - Variant = dkb.Variant ?? 0, - }); - } - } + if (dkb.RulesetID == null) + continue; - transaction.Commit(); - } + string? shortName = getRulesetShortNameFromLegacyID(dkb.RulesetID.Value); + + if (string.IsNullOrEmpty(shortName)) + continue; + + realm.Add(new RealmRulesetSetting + { + Key = dkb.Key, + Value = dkb.StringValue, + RulesetName = shortName, + Variant = dkb.Variant ?? 0, + }); + } + } + + transaction.Commit(); + } + }); } private string? getRulesetShortNameFromLegacyID(long rulesetId) => diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index 6594224666..05367160f3 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -51,8 +51,7 @@ namespace osu.Game.Database return; } - using (var realm = realmFactory.CreateContext()) - perform(realm.Find(ID)); + realmFactory.Run(realm => perform(realm.Find(ID))); } /// @@ -64,7 +63,7 @@ namespace osu.Game.Database if (!IsManaged) return perform(data); - using (var realm = realmFactory.CreateContext()) + return realmFactory.Run(realm => { var returnData = perform(realm.Find(ID)); @@ -72,7 +71,7 @@ namespace osu.Game.Database throw new InvalidOperationException(@$"Managed realm objects should not exit the scope of {nameof(PerformRead)}."); return returnData; - } + }); } /// diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 99f5752cfb..60f7eb2198 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -34,7 +34,7 @@ namespace osu.Game.Input { List combinations = new List(); - using (var context = realmFactory.CreateContext()) + realmFactory.Run(context => { foreach (var action in context.All().Where(b => string.IsNullOrEmpty(b.RulesetName) && (GlobalAction)b.ActionInt == globalAction)) { @@ -44,7 +44,7 @@ namespace osu.Game.Input if (str.Length > 0) combinations.Add(str); } - } + }); return combinations; } @@ -56,24 +56,26 @@ namespace osu.Game.Input /// The rulesets to populate defaults from. public void Register(KeyBindingContainer container, IEnumerable rulesets) { - using (var realm = realmFactory.CreateContext()) - using (var transaction = realm.BeginWrite()) + realmFactory.Run(realm => { - // intentionally flattened to a list rather than querying against the IQueryable, as nullable fields being queried against aren't indexed. - // this is much faster as a result. - var existingBindings = realm.All().ToList(); - - insertDefaults(realm, existingBindings, container.DefaultKeyBindings); - - foreach (var ruleset in rulesets) + using (var transaction = realm.BeginWrite()) { - var instance = ruleset.CreateInstance(); - foreach (int variant in instance.AvailableVariants) - insertDefaults(realm, existingBindings, instance.GetDefaultKeyBindings(variant), ruleset.ShortName, variant); - } + // intentionally flattened to a list rather than querying against the IQueryable, as nullable fields being queried against aren't indexed. + // this is much faster as a result. + var existingBindings = realm.All().ToList(); - transaction.Commit(); - } + insertDefaults(realm, existingBindings, container.DefaultKeyBindings); + + foreach (var ruleset in rulesets) + { + var instance = ruleset.CreateInstance(); + foreach (int variant in instance.AvailableVariants) + insertDefaults(realm, existingBindings, instance.GetDefaultKeyBindings(variant), ruleset.ShortName, variant); + } + + transaction.Commit(); + } + }); } private void insertDefaults(Realm realm, List existingBindings, IEnumerable defaults, string? rulesetName = null, int? variant = null) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs index 94c7c66538..9075dfefd4 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs @@ -34,10 +34,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input { string rulesetName = Ruleset?.ShortName; - List bindings; + List bindings = null; - using (var realm = realmFactory.CreateContext()) - bindings = realm.All().Where(b => b.RulesetName == rulesetName && b.Variant == variant).Detach(); + realmFactory.Run(realm => bindings = realm.All().Where(b => b.RulesetName == rulesetName && b.Variant == variant).Detach()); foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) { diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index 17678775e9..60a6b70221 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -56,21 +56,15 @@ namespace osu.Game.Rulesets.Configuration pendingWrites.Clear(); } - if (realmFactory == null) - return true; - - using (var context = realmFactory.CreateContext()) + realmFactory?.Write(realm => { - context.Write(realm => + foreach (var c in changed) { - foreach (var c in changed) - { - var setting = realm.All().First(s => s.RulesetName == rulesetName && s.Variant == variant && s.Key == c.ToString()); + var setting = realm.All().First(s => s.RulesetName == rulesetName && s.Variant == variant && s.Key == c.ToString()); - setting.Value = ConfigStore[c].ToString(); - } - }); - } + setting.Value = ConfigStore[c].ToString(); + } + }); return true; } diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index c675fbbf63..a9e5ff797c 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -100,74 +100,71 @@ namespace osu.Game.Rulesets private void addMissingRulesets() { - using (var context = realmFactory.CreateContext()) + realmFactory.Write(realm => { - context.Write(realm => + var rulesets = realm.All(); + + List instances = loadedAssemblies.Values + .Select(r => Activator.CreateInstance(r) as Ruleset) + .Where(r => r != null) + .Select(r => r.AsNonNull()) + .ToList(); + + // add all legacy rulesets first to ensure they have exclusive choice of primary key. + foreach (var r in instances.Where(r => r is ILegacyRuleset)) { - var rulesets = realm.All(); + if (realm.All().FirstOrDefault(rr => rr.OnlineID == r.RulesetInfo.OnlineID) == null) + realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); + } - List instances = loadedAssemblies.Values - .Select(r => Activator.CreateInstance(r) as Ruleset) - .Where(r => r != null) - .Select(r => r.AsNonNull()) - .ToList(); - - // add all legacy rulesets first to ensure they have exclusive choice of primary key. - foreach (var r in instances.Where(r => r is ILegacyRuleset)) + // add any other rulesets which have assemblies present but are not yet in the database. + foreach (var r in instances.Where(r => !(r is ILegacyRuleset))) + { + if (rulesets.FirstOrDefault(ri => ri.InstantiationInfo.Equals(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null) { - if (realm.All().FirstOrDefault(rr => rr.OnlineID == r.RulesetInfo.OnlineID) == null) + var existingSameShortName = rulesets.FirstOrDefault(ri => ri.ShortName == r.RulesetInfo.ShortName); + + if (existingSameShortName != null) + { + // even if a matching InstantiationInfo was not found, there may be an existing ruleset with the same ShortName. + // this generally means the user or ruleset provider has renamed their dll but the underlying ruleset is *likely* the same one. + // in such cases, update the instantiation info of the existing entry to point to the new one. + existingSameShortName.InstantiationInfo = r.RulesetInfo.InstantiationInfo; + } + else realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); } + } - // add any other rulesets which have assemblies present but are not yet in the database. - foreach (var r in instances.Where(r => !(r is ILegacyRuleset))) + List detachedRulesets = new List(); + + // perform a consistency check and detach final rulesets from realm for cross-thread runtime usage. + foreach (var r in rulesets.OrderBy(r => r.OnlineID)) + { + try { - if (rulesets.FirstOrDefault(ri => ri.InstantiationInfo.Equals(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null) - { - var existingSameShortName = rulesets.FirstOrDefault(ri => ri.ShortName == r.RulesetInfo.ShortName); + var resolvedType = Type.GetType(r.InstantiationInfo) + ?? throw new RulesetLoadException(@"Type could not be resolved"); - if (existingSameShortName != null) - { - // even if a matching InstantiationInfo was not found, there may be an existing ruleset with the same ShortName. - // this generally means the user or ruleset provider has renamed their dll but the underlying ruleset is *likely* the same one. - // in such cases, update the instantiation info of the existing entry to point to the new one. - existingSameShortName.InstantiationInfo = r.RulesetInfo.InstantiationInfo; - } - else - realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); - } + var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo + ?? throw new RulesetLoadException(@"Instantiation failure"); + + r.Name = instanceInfo.Name; + r.ShortName = instanceInfo.ShortName; + r.InstantiationInfo = instanceInfo.InstantiationInfo; + r.Available = true; + + detachedRulesets.Add(r.Clone()); } - - List detachedRulesets = new List(); - - // perform a consistency check and detach final rulesets from realm for cross-thread runtime usage. - foreach (var r in rulesets.OrderBy(r => r.OnlineID)) + catch (Exception ex) { - try - { - var resolvedType = Type.GetType(r.InstantiationInfo) - ?? throw new RulesetLoadException(@"Type could not be resolved"); - - var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo - ?? throw new RulesetLoadException(@"Instantiation failure"); - - r.Name = instanceInfo.Name; - r.ShortName = instanceInfo.ShortName; - r.InstantiationInfo = instanceInfo.InstantiationInfo; - r.Available = true; - - detachedRulesets.Add(r.Clone()); - } - catch (Exception ex) - { - r.Available = false; - Logger.Log($"Could not load ruleset {r}: {ex.Message}"); - } + r.Available = false; + Logger.Log($"Could not load ruleset {r}: {ex.Message}"); } + } - availableRulesets.AddRange(detachedRulesets); - }); - } + availableRulesets.AddRange(detachedRulesets); + }); } private void loadFromAppDomain() diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index ccf3226792..f895134f97 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -51,8 +51,7 @@ namespace osu.Game.Scoring /// The first result for the provided query, or null if no results were found. public ScoreInfo Query(Expression> query) { - using (var context = contextFactory.CreateContext()) - return context.All().FirstOrDefault(query)?.Detach(); + return contextFactory.Run(realm => realm.All().FirstOrDefault(query)?.Detach()); } /// @@ -255,16 +254,16 @@ namespace osu.Game.Scoring public void Delete([CanBeNull] Expression> filter = null, bool silent = false) { - using (var context = contextFactory.CreateContext()) + contextFactory.Run(realm => { - var items = context.All() - .Where(s => !s.DeletePending); + var items = realm.All() + .Where(s => !s.DeletePending); if (filter != null) items = items.Where(filter); scoreModelManager.Delete(items.ToList(), silent); - } + }); } public void Delete(List items, bool silent = false) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 75ad0511e6..fe3d407a64 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -178,8 +178,7 @@ namespace osu.Game.Screens.Select if (!loadedTestBeatmaps) { - using (var realm = realmFactory.CreateContext()) - loadBeatmapSets(getBeatmapSets(realm)); + realmFactory.Run(realm => loadBeatmapSets(getBeatmapSets(realm))); } } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 49f2ea5d64..da52b43ab6 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -147,7 +147,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (Scope == BeatmapLeaderboardScope.Local) { - using (var realm = realmFactory.CreateContext()) + realmFactory.Run(realm => { var scores = realm.All() .AsEnumerable() @@ -171,9 +171,9 @@ namespace osu.Game.Screens.Select.Leaderboards scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken) .ContinueWith(ordered => scoresCallback?.Invoke(ordered.GetResultSafely()), TaskContinuationOptions.OnlyOnRanToCompletion); + }); - return null; - } + return null; } if (api?.IsLoggedIn != true) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 3f6e5754fb..82bcd3b292 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -87,17 +87,14 @@ namespace osu.Game.Skinning }; // Ensure the default entries are present. - using (var context = contextFactory.CreateContext()) - using (var transaction = context.BeginWrite()) + contextFactory.Write(realm => { foreach (var skin in defaultSkins) { - if (context.Find(skin.SkinInfo.ID) == null) - context.Add(skin.SkinInfo.Value); + if (realm.Find(skin.SkinInfo.ID) == null) + realm.Add(skin.SkinInfo.Value); } - - transaction.Commit(); - } + }); CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = skin.NewValue.PerformRead(GetSkin); @@ -292,10 +289,10 @@ namespace osu.Game.Skinning public void Delete([CanBeNull] Expression> filter = null, bool silent = false) { - using (var context = contextFactory.CreateContext()) + contextFactory.Run(realm => { - var items = context.All() - .Where(s => !s.Protected && !s.DeletePending); + var items = realm.All() + .Where(s => !s.Protected && !s.DeletePending); if (filter != null) items = items.Where(filter); @@ -306,7 +303,7 @@ namespace osu.Game.Skinning scheduler.Add(() => CurrentSkinInfo.Value = Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged()); skinModelManager.Delete(items.ToList(), silent); - } + }); } #endregion diff --git a/osu.Game/Skinning/SkinModelManager.cs b/osu.Game/Skinning/SkinModelManager.cs index b8313f63a3..a1926913a9 100644 --- a/osu.Game/Skinning/SkinModelManager.cs +++ b/osu.Game/Skinning/SkinModelManager.cs @@ -205,7 +205,7 @@ namespace osu.Game.Skinning private void populateMissingHashes() { - using (var realm = ContextFactory.CreateContext()) + ContextFactory.Run(realm => { var skinsWithoutHashes = realm.All().Where(i => !i.Protected && string.IsNullOrEmpty(i.Hash)).ToArray(); @@ -221,7 +221,7 @@ namespace osu.Game.Skinning Logger.Error(e, $"Existing skin {skin} has been deleted during hash recomputation due to being invalid"); } } - } + }); } private Skin createInstance(SkinInfo item) => item.CreateInstance(skinResources); diff --git a/osu.Game/Stores/RealmArchiveModelImporter.cs b/osu.Game/Stores/RealmArchiveModelImporter.cs index 2ea7aecc94..3d8e9f2703 100644 --- a/osu.Game/Stores/RealmArchiveModelImporter.cs +++ b/osu.Game/Stores/RealmArchiveModelImporter.cs @@ -320,7 +320,7 @@ namespace osu.Game.Stores /// An optional cancellation token. public virtual Task?> Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { - using (var realm = ContextFactory.CreateContext()) + return ContextFactory.Run(realm => { cancellationToken.ThrowIfCancellationRequested(); @@ -414,7 +414,7 @@ namespace osu.Game.Stores } return Task.FromResult((ILive?)item.ToLive(ContextFactory)); - } + }); } private string computeHashFast(ArchiveReader reader) diff --git a/osu.Game/Stores/RealmFileStore.cs b/osu.Game/Stores/RealmFileStore.cs index f9abbda4c0..ca371e29be 100644 --- a/osu.Game/Stores/RealmFileStore.cs +++ b/osu.Game/Stores/RealmFileStore.cs @@ -92,8 +92,7 @@ namespace osu.Game.Stores int removedFiles = 0; // can potentially be run asynchronously, although we will need to consider operation order for disk deletion vs realm removal. - using (var realm = realmFactory.CreateContext()) - using (var transaction = realm.BeginWrite()) + realmFactory.Write(realm => { // TODO: consider using a realm native query to avoid iterating all files (https://github.com/realm/realm-dotnet/issues/2659#issuecomment-927823707) var files = realm.All().ToList(); @@ -116,9 +115,7 @@ namespace osu.Game.Stores Logger.Error(e, $@"Could not delete databased file {file.Hash}"); } } - - transaction.Commit(); - } + }); Logger.Log($@"Finished realm file store cleanup ({removedFiles} of {totalFiles} deleted)"); } From d2655c082546033490792d6f3e4d1806ba964e8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 17:27:30 +0900 Subject: [PATCH 0129/5199] Fix `RealmLive` not necessarily being in refreshed state due to potentially using update context --- osu.Game/Database/RealmLive.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index 05367160f3..75d84d7cf1 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -51,7 +51,21 @@ namespace osu.Game.Database return; } - realmFactory.Run(realm => perform(realm.Find(ID))); + realmFactory.Run(realm => + { + var found = realm.Find(ID); + + if (found == null) + { + // It may be that we access this from the update thread before a refresh has taken place. + // To ensure that behaviour matches what we'd expect (the object *is* available), force + // a refresh to bring in any off-thread changes immediately. + realm.Refresh(); + found = realm.Find(ID); + } + + perform(found); + }); } /// From 81b5717ae793e334fdcf8efd72d20debfb49e9ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 17:33:03 +0900 Subject: [PATCH 0130/5199] Fix `RealmLive` failing to retrieve due to lack of refresh --- osu.Game/Database/RealmLive.cs | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index 75d84d7cf1..df5e165f8e 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -53,18 +53,7 @@ namespace osu.Game.Database realmFactory.Run(realm => { - var found = realm.Find(ID); - - if (found == null) - { - // It may be that we access this from the update thread before a refresh has taken place. - // To ensure that behaviour matches what we'd expect (the object *is* available), force - // a refresh to bring in any off-thread changes immediately. - realm.Refresh(); - found = realm.Find(ID); - } - - perform(found); + perform(retrieveFromID(realm, ID)); }); } @@ -79,7 +68,7 @@ namespace osu.Game.Database return realmFactory.Run(realm => { - var returnData = perform(realm.Find(ID)); + var returnData = perform(retrieveFromID(realm, ID)); if (returnData is RealmObjectBase realmObject && realmObject.IsManaged) throw new InvalidOperationException(@$"Managed realm objects should not exit the scope of {nameof(PerformRead)}."); @@ -119,6 +108,22 @@ namespace osu.Game.Database } } + private T retrieveFromID(Realm realm, Guid id) + { + var found = realm.Find(ID); + + if (found == null) + { + // It may be that we access this from the update thread before a refresh has taken place. + // To ensure that behaviour matches what we'd expect (the object *is* available), force + // a refresh to bring in any off-thread changes immediately. + realm.Refresh(); + found = realm.Find(ID); + } + + return found; + } + public bool Equals(ILive? other) => ID == other?.ID; public override string ToString() => PerformRead(i => i.ToString()); From 495636538fc23101ec641bdf66eb0ba8b3f6a88c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 17:33:26 +0900 Subject: [PATCH 0131/5199] Add forced refresh on `GetAllUsableBeatmapSets()` This is commonly used in tests in a way where it's not feasible to guarantee correct results unless a refresh is called. This method shouldn't really be used outside of tests anyway, but that's for a folow-up effort. --- osu.Game/Beatmaps/BeatmapManager.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index a9340e1250..43e4b482bd 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -171,7 +171,11 @@ namespace osu.Game.Beatmaps /// A list of available . public List GetAllUsableBeatmapSets() { - return contextFactory.Run(realm => realm.All().Where(b => !b.DeletePending).Detach()); + return contextFactory.Run(realm => + { + realm.Refresh(); + return realm.All().Where(b => !b.DeletePending).Detach(); + }); } /// From 2006620a2c02d60c83792803b4276cb163a3b4a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 17:55:27 +0900 Subject: [PATCH 0132/5199] Fix `IntroScreen` retrieving and iterating all realm beatmap sets --- osu.Game/Screens/Menu/IntroScreen.cs | 82 ++++++++++++++-------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index d98cb8056f..db9d9b83c4 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -21,6 +21,7 @@ using osu.Game.Overlays; using osu.Game.Screens.Backgrounds; using osuTK; using osuTK.Graphics; +using Realms; namespace osu.Game.Screens.Menu { @@ -93,58 +94,57 @@ namespace osu.Game.Screens.Menu MenuMusic = config.GetBindable(OsuSetting.MenuMusic); seeya = audio.Samples.Get(SeeyaSampleName); - ILive setInfo = null; - // if the user has requested not to play theme music, we should attempt to find a random beatmap from their collection. if (!MenuMusic.Value) { - var sets = beatmaps.GetAllUsableBeatmapSets(); - - if (sets.Count > 0) + realmContextFactory.Run(realm => { - setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID); - setInfo?.PerformRead(s => + var sets = realm.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); + + int setCount = sets.Count; + + if (sets.Any()) + { + var found = sets[RNG.Next(0, setCount - 1)].Beatmaps.FirstOrDefault(); + + if (found != null) + initialBeatmap = beatmaps.GetWorkingBeatmap(found); + } + }); + + // we generally want a song to be playing on startup, so use the intro music even if a user has specified not to if no other track is available. + if (initialBeatmap == null) + { + if (!loadThemedIntro()) + { + // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state. + // this could happen if a user has nuked their files store. for now, reimport to repair this. + var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).GetResultSafely(); + + import?.PerformWrite(b => b.Protected = true); + + loadThemedIntro(); + } + } + + bool loadThemedIntro() + { + var setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapHash); + + if (setInfo == null) + return false; + + setInfo.PerformRead(s => { if (s.Beatmaps.Count == 0) return; - initialBeatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps[0]); + initialBeatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps.First()); }); + + return UsingThemedIntro = initialBeatmap != null; } } - - // we generally want a song to be playing on startup, so use the intro music even if a user has specified not to if no other track is available. - if (setInfo == null) - { - if (!loadThemedIntro()) - { - // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state. - // this could happen if a user has nuked their files store. for now, reimport to repair this. - var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).GetResultSafely(); - - import?.PerformWrite(b => b.Protected = true); - - loadThemedIntro(); - } - } - - bool loadThemedIntro() - { - setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapHash); - - if (setInfo == null) - return false; - - setInfo.PerformRead(s => - { - if (s.Beatmaps.Count == 0) - return; - - initialBeatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps.First()); - }); - - return UsingThemedIntro = initialBeatmap != null; - } } public override void OnResuming(IScreen last) From 18bf690a30bc9d5c5c6d53133469fc462bcf62ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 18:13:21 +0900 Subject: [PATCH 0133/5199] Add `Register` method for subscription purposes and update safeties --- osu.Game/Database/RealmContextFactory.cs | 28 ++++++++++++++++++++++ osu.Game/Database/RealmObjectExtensions.cs | 6 ++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 888ffb1dd5..169333bff8 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -88,6 +88,10 @@ namespace osu.Game.Database } } + internal static bool CurrentThreadSubscriptionsAllowed => current_thread_subscriptions_allowed.Value; + + private static readonly ThreadLocal current_thread_subscriptions_allowed = new ThreadLocal(); + /// /// Construct a new instance of a realm context factory. /// @@ -222,6 +226,30 @@ namespace osu.Game.Database } } + /// + /// Run work on realm that will be run every time the update thread realm context gets recycled. + /// + /// The work to run. + public void Register(Action action) + { + if (!ThreadSafety.IsUpdateThread && context != null) + throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); + + if (ThreadSafety.IsUpdateThread) + { + current_thread_subscriptions_allowed.Value = true; + action(Context); + current_thread_subscriptions_allowed.Value = false; + } + else + { + current_thread_subscriptions_allowed.Value = true; + using (var realm = createContext()) + action(realm); + current_thread_subscriptions_allowed.Value = false; + } + } + private Realm createContext() { if (isDisposed) diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index c25aeab336..c30e1699b9 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Runtime.Serialization; using AutoMapper; using AutoMapper.Internal; -using osu.Framework.Development; using osu.Game.Beatmaps; using osu.Game.Input.Bindings; using osu.Game.Models; @@ -272,9 +271,8 @@ namespace osu.Game.Database public static IDisposable? QueryAsyncWithNotifications(this IRealmCollection collection, NotificationCallbackDelegate callback) where T : RealmObjectBase { - // Subscriptions can only work on the main thread. - if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException("Cannot subscribe for realm notifications from a non-update thread."); + if (!RealmContextFactory.CurrentThreadSubscriptionsAllowed) + throw new InvalidOperationException($"Make sure to call {nameof(RealmContextFactory)}.{nameof(RealmContextFactory.Register)}"); return collection.SubscribeForNotifications(callback); } From 45aea9add5209953d7e76a093a74f5ad91732a60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 18:50:25 +0900 Subject: [PATCH 0134/5199] Implement full subscription flow --- osu.Game/Database/RealmContextFactory.cs | 46 +++++++++++++++++------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 169333bff8..62717eb880 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -80,6 +82,10 @@ namespace osu.Game.Database { context = createContext(); Logger.Log(@$"Opened realm ""{context.Config.DatabasePath}"" at version {context.Config.SchemaVersion}"); + + // Resubscribe any subscriptions + foreach (var action in subscriptionActions.Keys) + registerSubscription(action); } // creating a context will ensure our schema is up-to-date and migrated. @@ -226,26 +232,42 @@ namespace osu.Game.Database } } + private readonly Dictionary, IDisposable?> subscriptionActions = new Dictionary, IDisposable?>(); + /// /// Run work on realm that will be run every time the update thread realm context gets recycled. /// - /// The work to run. - public void Register(Action action) + /// The work to run. Return value should be an from QueryAsyncWithNotifications, or an to clean up any bindings. + /// An which should be disposed to unsubscribe any inner subscription. + public IDisposable Register(Func action) { - if (!ThreadSafety.IsUpdateThread && context != null) - throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); + if (!ThreadSafety.IsUpdateThread) + throw new InvalidOperationException(@$"{nameof(Register)} must be called from the update thread."); - if (ThreadSafety.IsUpdateThread) + subscriptionActions.Add(action, null); + registerSubscription(action); + + return new InvokeOnDisposal(() => { - current_thread_subscriptions_allowed.Value = true; - action(Context); - current_thread_subscriptions_allowed.Value = false; - } - else + // TODO: this likely needs to be run on the update thread. + if (subscriptionActions.TryGetValue(action, out var unsubscriptionAction)) + { + unsubscriptionAction?.Dispose(); + subscriptionActions.Remove(action); + } + }); + } + + private void registerSubscription(Func action) + { + Debug.Assert(ThreadSafety.IsUpdateThread); + + lock (contextLock) { + Debug.Assert(context != null); + current_thread_subscriptions_allowed.Value = true; - using (var realm = createContext()) - action(realm); + subscriptionActions[action] = action(context); current_thread_subscriptions_allowed.Value = false; } } From 1f157d729dbc0d1bc2cdbed374b41bbef650e121 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 19:39:49 +0900 Subject: [PATCH 0135/5199] Update existing subscriptions to new style Fix missing detach calls in `MusicController` --- osu.Game.Tests/Database/GeneralUsageTests.cs | 3 ++- osu.Game.Tests/Database/RealmLiveTests.cs | 4 ++- osu.Game/Database/RealmContextFactory.cs | 5 +--- .../Bindings/DatabasedKeyBindingContainer.cs | 26 +++++++++--------- osu.Game/Online/BeatmapDownloadTracker.cs | 4 +-- .../OnlinePlayBeatmapAvailabilityTracker.cs | 4 +-- osu.Game/Online/ScoreDownloadTracker.cs | 4 +-- osu.Game/Overlays/MusicController.cs | 17 +++++++----- .../Sections/DebugSettings/MemorySettings.cs | 4 +++ .../Overlays/Settings/Sections/SkinSection.cs | 22 +++++++-------- osu.Game/Screens/Select/BeatmapCarousel.cs | 17 ++++++------ .../Screens/Select/Carousel/TopLocalRank.cs | 25 ++++++++--------- .../Select/Leaderboards/BeatmapLeaderboard.cs | 27 ++++++++++--------- osu.Game/Screens/Spectate/SpectatorScreen.cs | 20 +++++++------- 14 files changed, 98 insertions(+), 84 deletions(-) diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index 9ebe94b383..c82c1b6e59 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Database { bool callbackRan = false; - realmFactory.Run(realm => + realmFactory.Register(realm => { var subscription = realm.All().QueryAsyncWithNotifications((sender, changes, error) => { @@ -60,6 +60,7 @@ namespace osu.Game.Tests.Database realmFactory.Run(r => r.Refresh()); subscription?.Dispose(); + return null; }); Assert.IsTrue(callbackRan); diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 2f16df4624..5549c140f6 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -234,7 +234,7 @@ namespace osu.Game.Tests.Database { int changesTriggered = 0; - realmFactory.Run(outerRealm => + realmFactory.Register(outerRealm => { outerRealm.All().QueryAsyncWithNotifications(gotChange); ILive? liveBeatmap = null; @@ -277,6 +277,8 @@ namespace osu.Game.Tests.Database r.Remove(resolved); }); }); + + return null; }); void gotChange(IRealmCollection sender, ChangeSet changes, Exception error) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 62717eb880..c11ff61039 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -244,7 +244,6 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException(@$"{nameof(Register)} must be called from the update thread."); - subscriptionActions.Add(action, null); registerSubscription(action); return new InvokeOnDisposal(() => @@ -264,10 +263,8 @@ namespace osu.Game.Database lock (contextLock) { - Debug.Assert(context != null); - current_thread_subscriptions_allowed.Value = true; - subscriptionActions[action] = action(context); + subscriptionActions[action] = action(Context); current_thread_subscriptions_allowed.Value = false; } } diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 03b069d431..5a9797ffce 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -23,7 +23,6 @@ namespace osu.Game.Input.Bindings private readonly int? variant; private IDisposable realmSubscription; - private IQueryable realmKeyBindings; [Resolved] private RealmContextFactory realmFactory { get; set; } @@ -47,22 +46,25 @@ namespace osu.Game.Input.Bindings throw new InvalidOperationException($"{nameof(variant)} can not be null when a non-null {nameof(ruleset)} is provided."); } + private IQueryable realmKeyBindings + { + get + { + string rulesetName = ruleset?.ShortName; + return realmFactory.Context.All() + .Where(b => b.RulesetName == rulesetName && b.Variant == variant); + } + } + protected override void LoadComplete() { - string rulesetName = ruleset?.ShortName; - - realmKeyBindings = realmFactory.Context.All() - .Where(b => b.RulesetName == rulesetName && b.Variant == variant); - - realmSubscription = realmKeyBindings + realmSubscription = realmFactory.Register(realm => realmKeyBindings .QueryAsyncWithNotifications((sender, changes, error) => { - // first subscription ignored as we are handling this in LoadComplete. - if (changes == null) - return; - + // The first fire of this is a bit redundant as this is being called in base.LoadComplete, + // but this is safest in case the subscription is restored after a context recycle. ReloadMappings(); - }); + })); base.LoadComplete(); } diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index be5bdea6f1..d50ab5a347 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -42,7 +42,7 @@ namespace osu.Game.Online // Used to interact with manager classes that don't support interface types. Will eventually be replaced. var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID }; - realmSubscription = realmContextFactory.Context.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending).QueryAsyncWithNotifications((items, changes, ___) => + realmSubscription = realmContextFactory.Register(realm => realm.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending).QueryAsyncWithNotifications((items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); @@ -54,7 +54,7 @@ namespace osu.Game.Online attachDownload(Downloader.GetExistingDownload(beatmapSetInfo)); }); } - }); + })); } private void downloadBegan(ArchiveDownloadRequest request) => Schedule(() => diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index 1f77b1d383..fdcf2b39c6 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -78,13 +78,13 @@ namespace osu.Game.Online.Rooms // handles changes to hash that didn't occur from the import process (ie. a user editing the beatmap in the editor, somehow). realmSubscription?.Dispose(); - realmSubscription = filteredBeatmaps().QueryAsyncWithNotifications((items, changes, ___) => + realmSubscription = realmContextFactory.Register(realm => filteredBeatmaps().QueryAsyncWithNotifications((items, changes, ___) => { if (changes == null) return; Scheduler.AddOnce(updateAvailability); - }); + })); }, true); } diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index b34586567d..de1d6fd94a 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -47,7 +47,7 @@ namespace osu.Game.Online Downloader.DownloadBegan += downloadBegan; Downloader.DownloadFailed += downloadFailed; - realmSubscription = realmContextFactory.Context.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending).QueryAsyncWithNotifications((items, changes, ___) => + realmSubscription = realmContextFactory.Register(realm => realm.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending).QueryAsyncWithNotifications((items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); @@ -59,7 +59,7 @@ namespace osu.Game.Online attachDownload(Downloader.GetExistingDownload(scoreInfo)); }); } - }); + })); } private void downloadBegan(ArchiveDownloadRequest request) => Schedule(() => diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 70f8332295..01a2c9d354 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -80,26 +80,31 @@ namespace osu.Game.Overlays mods.BindValueChanged(_ => ResetTrackAdjustments(), true); } + private IQueryable availableBeatmaps => realmFactory.Context + .All() + .Where(s => !s.DeletePending); + protected override void LoadComplete() { base.LoadComplete(); - var availableBeatmaps = realmFactory.Context - .All() - .Where(s => !s.DeletePending); - // ensure we're ready before completing async load. // probably not a good way of handling this (as there is a period we aren't watching for changes until the realm subscription finishes up. foreach (var s in availableBeatmaps) - beatmapSets.Add(s); + beatmapSets.Add(s.Detach()); - beatmapSubscription = availableBeatmaps.QueryAsyncWithNotifications(beatmapsChanged); + beatmapSubscription = realmFactory.Register(realm => availableBeatmaps.QueryAsyncWithNotifications(beatmapsChanged)); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) { if (changes == null) + { + beatmapSets.Clear(); + foreach (var s in sender) + beatmapSets.Add(s.Detach()); return; + } foreach (int i in changes.InsertedIndices) beatmapSets.Insert(i, sender[i].Detach()); diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index 8d4fc5fc9f..84a54b208c 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -33,6 +33,10 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings using (realmFactory.BlockAllOperations()) { } + + // retrieve context to revive realm subscriptions. + // TODO: should we do this from OsuGame or RealmContextFactory or something? Answer: yes. + var _ = realmFactory.Context; } }, }; diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 0fa6d78d4b..11a7275168 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -50,7 +50,12 @@ namespace osu.Game.Overlays.Settings.Sections private RealmContextFactory realmFactory { get; set; } private IDisposable realmSubscription; - private IQueryable realmSkins; + + private IQueryable realmSkins => + realmFactory.Context.All() + .Where(s => !s.DeletePending) + .OrderByDescending(s => s.Protected) // protected skins should be at the top. + .ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase); [BackgroundDependencyLoader(permitNulls: true)] private void load(OsuConfigManager config, [CanBeNull] SkinEditorOverlay skinEditor) @@ -78,20 +83,13 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown.Current = dropdownBindable; - realmSkins = realmFactory.Context.All() - .Where(s => !s.DeletePending) - .OrderByDescending(s => s.Protected) // protected skins should be at the top. - .ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase); - - realmSubscription = realmSkins + realmSubscription = realmFactory.Register(realm => realmSkins .QueryAsyncWithNotifications((sender, changes, error) => { - if (changes == null) - return; - - // Eventually this should be handling the individual changes rather than refreshing the whole dropdown. + // The first fire of this is a bit redundant due to the call below, + // but this is safest in case the subscription is restored after a context recycle. updateItems(); - }); + })); updateItems(); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index f8cee2704b..6f3f467170 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -190,13 +190,13 @@ namespace osu.Game.Screens.Select { base.LoadComplete(); - subscriptionSets = getBeatmapSets(realmFactory.Context).QueryAsyncWithNotifications(beatmapSetsChanged); - subscriptionBeatmaps = realmFactory.Context.All().Where(b => !b.Hidden).QueryAsyncWithNotifications(beatmapsChanged); + subscriptionSets = realmFactory.Register(realm => getBeatmapSets(realm).QueryAsyncWithNotifications(beatmapSetsChanged)); + subscriptionBeatmaps = realmFactory.Register(realm => realm.All().Where(b => !b.Hidden).QueryAsyncWithNotifications(beatmapsChanged)); // Can't use main subscriptions because we can't lookup deleted indices. // https://github.com/realm/realm-dotnet/discussions/2634#discussioncomment-1605595. - subscriptionDeletedSets = realmFactory.Context.All().Where(s => s.DeletePending && !s.Protected).QueryAsyncWithNotifications(deletedBeatmapSetsChanged); - subscriptionHiddenBeatmaps = realmFactory.Context.All().Where(b => b.Hidden).QueryAsyncWithNotifications(beatmapsChanged); + subscriptionDeletedSets = realmFactory.Register(realm => realm.All().Where(s => s.DeletePending && !s.Protected).QueryAsyncWithNotifications(deletedBeatmapSetsChanged)); + subscriptionHiddenBeatmaps = realmFactory.Register(realm => realm.All().Where(b => b.Hidden).QueryAsyncWithNotifications(beatmapsChanged)); } private void deletedBeatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) @@ -552,10 +552,11 @@ namespace osu.Game.Screens.Select private void signalBeatmapsLoaded() { - Debug.Assert(BeatmapSetsLoaded == false); - - BeatmapSetsChanged?.Invoke(); - BeatmapSetsLoaded = true; + if (!BeatmapSetsLoaded) + { + BeatmapSetsChanged?.Invoke(); + BeatmapSetsLoaded = true; + } itemsCache.Invalidate(); } diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index 7ac99f4935..ee3930364b 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -48,18 +48,19 @@ namespace osu.Game.Screens.Select.Carousel ruleset.BindValueChanged(_ => { scoreSubscription?.Dispose(); - scoreSubscription = realmFactory.Context.All() - .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" - + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" - + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" - + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName) - .OrderByDescending(s => s.TotalScore) - .QueryAsyncWithNotifications((items, changes, ___) => - { - Rank = items.FirstOrDefault()?.Rank; - // Required since presence is changed via IsPresent override - Invalidate(Invalidation.Presence); - }); + scoreSubscription = realmFactory.Register(realm => + realm.All() + .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" + + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" + + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" + + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName) + .OrderByDescending(s => s.TotalScore) + .QueryAsyncWithNotifications((items, changes, ___) => + { + Rank = items.FirstOrDefault()?.Rank; + // Required since presence is changed via IsPresent override + Invalidate(Invalidation.Presence); + })); }, true); } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index da52b43ab6..954c2a6413 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -44,9 +44,13 @@ namespace osu.Game.Screens.Select.Leaderboards beatmapInfo = value; Scores = null; - UpdateScores(); - if (IsLoaded) - refreshRealmSubscription(); + if (IsOnlineScope) + UpdateScores(); + else + { + if (IsLoaded) + refreshRealmSubscription(); + } } } @@ -109,15 +113,14 @@ namespace osu.Game.Screens.Select.Leaderboards if (beatmapInfo == null) return; - scoreSubscription = realmFactory.Context.All() - .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID) - .QueryAsyncWithNotifications((_, changes, ___) => - { - if (changes == null) - return; - - RefreshScores(); - }); + scoreSubscription = realmFactory.Register(realm => + realm.All() + .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID) + .QueryAsyncWithNotifications((_, changes, ___) => + { + if (!IsOnlineScope) + RefreshScores(); + })); } protected override void Reset() diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index dd586bdd37..9fa5bb8562 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -79,17 +79,17 @@ namespace osu.Game.Screens.Spectate playingUserStates.BindTo(spectatorClient.PlayingUserStates); playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true); - realmSubscription = realmContextFactory.Context - .All() - .Where(s => !s.DeletePending) - .QueryAsyncWithNotifications((items, changes, ___) => - { - if (changes?.InsertedIndices == null) - return; + realmSubscription = realmContextFactory.Register(realm => + realm.All() + .Where(s => !s.DeletePending) + .QueryAsyncWithNotifications((items, changes, ___) => + { + if (changes?.InsertedIndices == null) + return; - foreach (int c in changes.InsertedIndices) - beatmapUpdated(items[c]); - }); + foreach (int c in changes.InsertedIndices) + beatmapUpdated(items[c]); + })); foreach ((int id, var _) in userMap) spectatorClient.WatchUser(id); From 63226f7def9f10d10460e43be72a139852f83cc8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 19:56:32 +0900 Subject: [PATCH 0136/5199] Remove pointless initial `MusicController` beatmap set population Looks to pass tests and all usages look safe enough. --- osu.Game/Overlays/MusicController.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 01a2c9d354..c19a069343 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -87,12 +87,6 @@ namespace osu.Game.Overlays protected override void LoadComplete() { base.LoadComplete(); - - // ensure we're ready before completing async load. - // probably not a good way of handling this (as there is a period we aren't watching for changes until the realm subscription finishes up. - foreach (var s in availableBeatmaps) - beatmapSets.Add(s.Detach()); - beatmapSubscription = realmFactory.Register(realm => availableBeatmaps.QueryAsyncWithNotifications(beatmapsChanged)); } From a86c0014fe3093069a23f7014c537d31f6fdf8bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 20:07:43 +0900 Subject: [PATCH 0137/5199] Remove unnecessary exception/check --- osu.Game/Overlays/MusicController.cs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index c19a069343..0671e6d2c7 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -30,16 +30,7 @@ namespace osu.Game.Overlays [Resolved] private BeatmapManager beatmaps { get; set; } - public IBindableList BeatmapSets - { - get - { - if (LoadState < LoadState.Ready) - throw new InvalidOperationException($"{nameof(BeatmapSets)} should not be accessed before the music controller is loaded."); - - return beatmapSets; - } - } + public IBindableList BeatmapSets => beatmapSets; /// /// Point in time after which the current track will be restarted on triggering a "previous track" action. From 3b11235d3c65c7416e8e092d0a8d4aeb45c0c85c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 20:45:10 +0900 Subject: [PATCH 0138/5199] Fix potentially cyclic subscription registration if a subscription's delegate accesses `Context` --- osu.Game/Database/RealmContextFactory.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index c11ff61039..d9c66ccc69 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -88,6 +88,8 @@ namespace osu.Game.Database registerSubscription(action); } + Debug.Assert(context != null); + // creating a context will ensure our schema is up-to-date and migrated. return context; } @@ -261,10 +263,13 @@ namespace osu.Game.Database { Debug.Assert(ThreadSafety.IsUpdateThread); + // Get context outside of flag update to ensure beyond doubt this can't be cyclic. + var realm = Context; + lock (contextLock) { current_thread_subscriptions_allowed.Value = true; - subscriptionActions[action] = action(Context); + subscriptionActions[action] = action(realm); current_thread_subscriptions_allowed.Value = false; } } From 9b63f15e68577a2d2b88d2613a6f61aff554097a Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Fri, 21 Jan 2022 13:58:30 +0100 Subject: [PATCH 0139/5199] Add failing test --- .../Visual/Menus/TestSceneLoginPanel.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs index 4754a73f83..642cc68de5 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs @@ -8,6 +8,8 @@ using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Overlays.Login; +using osu.Game.Users.Drawables; +using osuTK.Input; namespace osu.Game.Tests.Visual.Menus { @@ -15,6 +17,7 @@ namespace osu.Game.Tests.Visual.Menus public class TestSceneLoginPanel : OsuManualInputManagerTestScene { private LoginPanel loginPanel; + private int hideCount; [SetUpSteps] public void SetUpSteps() @@ -26,6 +29,7 @@ namespace osu.Game.Tests.Visual.Menus Anchor = Anchor.Centre, Origin = Anchor.Centre, Width = 0.5f, + RequestHide = () => hideCount++, }); }); } @@ -51,5 +55,22 @@ namespace osu.Game.Tests.Visual.Menus AddStep("enter password", () => loginPanel.ChildrenOfType().First().Text = "password"); AddStep("submit", () => loginPanel.ChildrenOfType().First(b => b.Text.ToString() == "Sign in").TriggerClick()); } + + [Test] + public void TestClickingOnFlagClosesPanel() + { + AddStep("reset hide count", () => hideCount = 0); + + AddStep("logout", () => API.Logout()); + AddStep("enter password", () => loginPanel.ChildrenOfType().First().Text = "password"); + AddStep("submit", () => loginPanel.ChildrenOfType().First(b => b.Text.ToString() == "Sign in").TriggerClick()); + + AddStep("click on flag", () => + { + InputManager.MoveMouseTo(loginPanel.ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + }); + AddAssert("hide requested", () => hideCount == 1); + } } } From 529610ee2e9c0d73c3a9c72c0531d4b716264644 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Fri, 21 Jan 2022 14:01:49 +0100 Subject: [PATCH 0140/5199] Call the UserPanel `Action` when clicking on the flag --- osu.Game/Users/Drawables/UpdateableFlag.cs | 8 ++++++++ osu.Game/Users/ExtendedUserPanel.cs | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Users/Drawables/UpdateableFlag.cs b/osu.Game/Users/Drawables/UpdateableFlag.cs index 7db834bf83..e5debc0683 100644 --- a/osu.Game/Users/Drawables/UpdateableFlag.cs +++ b/osu.Game/Users/Drawables/UpdateableFlag.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 osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -23,6 +24,12 @@ namespace osu.Game.Users.Drawables /// public bool ShowPlaceholderOnNull = true; + /// + /// Perform an action in addition to showing the country ranking. + /// This should be used to perform auxiliary tasks and not as a primary action for clicking a flag (to maintain a consistent UX). + /// + public Action Action; + public UpdateableFlag(Country country = null) { Country = country; @@ -52,6 +59,7 @@ namespace osu.Game.Users.Drawables protected override bool OnClick(ClickEvent e) { + Action?.Invoke(); rankingsOverlay?.ShowCountry(Country); return true; } diff --git a/osu.Game/Users/ExtendedUserPanel.cs b/osu.Game/Users/ExtendedUserPanel.cs index fc5e1eca5f..d0f693c37c 100644 --- a/osu.Game/Users/ExtendedUserPanel.cs +++ b/osu.Game/Users/ExtendedUserPanel.cs @@ -53,7 +53,8 @@ namespace osu.Game.Users protected UpdateableFlag CreateFlag() => new UpdateableFlag(User.Country) { - Size = new Vector2(39, 26) + Size = new Vector2(39, 26), + Action = Action, }; protected SpriteIcon CreateStatusIcon() => statusIcon = new SpriteIcon From ad3a01dc06215e17458357189cac15b9c603d093 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 22:40:18 +0900 Subject: [PATCH 0141/5199] Use a more reliable method of reviving the update thread realm after blocking --- osu.Game/Database/RealmContextFactory.cs | 44 +++++++++++-------- .../Sections/DebugSettings/MemorySettings.cs | 4 -- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index d9c66ccc69..6fb9a2996b 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -69,30 +69,29 @@ namespace osu.Game.Database private Realm? context; - public Realm Context + public Realm Context => ensureUpdateContext(); + + private Realm ensureUpdateContext() { - get + if (!ThreadSafety.IsUpdateThread) + throw new InvalidOperationException(@$"Use {nameof(createContext)} when performing realm operations from a non-update thread"); + + lock (contextLock) { - if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException(@$"Use {nameof(createContext)} when performing realm operations from a non-update thread"); - - lock (contextLock) + if (context == null) { - if (context == null) - { - context = createContext(); - Logger.Log(@$"Opened realm ""{context.Config.DatabasePath}"" at version {context.Config.SchemaVersion}"); + context = createContext(); + Logger.Log(@$"Opened realm ""{context.Config.DatabasePath}"" at version {context.Config.SchemaVersion}"); - // Resubscribe any subscriptions - foreach (var action in subscriptionActions.Keys) - registerSubscription(action); - } - - Debug.Assert(context != null); - - // creating a context will ensure our schema is up-to-date and migrated. - return context; + // Resubscribe any subscriptions + foreach (var action in subscriptionActions.Keys) + registerSubscription(action); } + + Debug.Assert(context != null); + + // creating a context will ensure our schema is up-to-date and migrated. + return context; } } @@ -506,6 +505,8 @@ namespace osu.Game.Database if (isDisposed) throw new ObjectDisposedException(nameof(RealmContextFactory)); + SynchronizationContext syncContext; + try { contextCreationLock.Wait(); @@ -515,6 +516,8 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread && context != null) throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); + syncContext = SynchronizationContext.Current; + Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); context?.Dispose(); @@ -553,6 +556,9 @@ namespace osu.Game.Database { factory.contextCreationLock.Release(); Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); + + // Post back to the update thread to revive any subscriptions. + syncContext?.Post(_ => ensureUpdateContext(), null); }); } diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index 84a54b208c..8d4fc5fc9f 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -33,10 +33,6 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings using (realmFactory.BlockAllOperations()) { } - - // retrieve context to revive realm subscriptions. - // TODO: should we do this from OsuGame or RealmContextFactory or something? Answer: yes. - var _ = realmFactory.Context; } }, }; From 81b07dee563b9ef01da5b8571061c09ccf0b417d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 21 Jan 2022 17:41:33 +0300 Subject: [PATCH 0142/5199] Introduce `IExpandable` interface --- osu.Game/Overlays/IExpandable.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 osu.Game/Overlays/IExpandable.cs diff --git a/osu.Game/Overlays/IExpandable.cs b/osu.Game/Overlays/IExpandable.cs new file mode 100644 index 0000000000..770ac97847 --- /dev/null +++ b/osu.Game/Overlays/IExpandable.cs @@ -0,0 +1,19 @@ +// 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.Bindables; +using osu.Framework.Graphics; + +namespace osu.Game.Overlays +{ + /// + /// An interface for drawables with ability to expand/contract. + /// + public interface IExpandable : IDrawable + { + /// + /// Whether this drawable is in an expanded state. + /// + BindableBool Expanded { get; } + } +} From a2b6bc9e5334e4357c10a5a6553486b2d9bd5e24 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 18:10:00 +0900 Subject: [PATCH 0143/5199] Add benchmark coverage of variuos methods of reading properties from realm --- osu.Game.Benchmarks/BenchmarkRealmReads.cs | 139 +++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 osu.Game.Benchmarks/BenchmarkRealmReads.cs diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs new file mode 100644 index 0000000000..5b5bdf595d --- /dev/null +++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs @@ -0,0 +1,139 @@ +// 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 System.Threading; +using BenchmarkDotNet.Attributes; +using osu.Framework.Testing; +using osu.Framework.Threading; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Rulesets.Osu; +using osu.Game.Tests.Resources; + +namespace osu.Game.Benchmarks +{ + public class BenchmarkRealmReads : BenchmarkTest + { + private TemporaryNativeStorage storage; + private RealmContextFactory realmFactory; + private UpdateThread updateThread; + + [Params(1, 100, 1000)] + public int ReadsPerFetch { get; set; } + + public override void SetUp() + { + storage = new TemporaryNativeStorage("realm-benchmark"); + storage.DeleteDirectory(string.Empty); + + realmFactory = new RealmContextFactory(storage, "client"); + + using (var context = realmFactory.CreateContext()) + context.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo }))); + + updateThread = new UpdateThread(() => { }, null); + updateThread.Start(); + } + + [Benchmark] + public void BenchmarkDirectPropertyRead() + { + using (var context = realmFactory.CreateContext()) + { + var beatmapSet = context.All().First(); + + for (int i = 0; i < ReadsPerFetch; i++) + { + string _ = beatmapSet.Beatmaps.First().Hash; + } + } + } + + [Benchmark] + public void BenchmarkDirectPropertyReadUpdateThread() + { + var done = new ManualResetEventSlim(); + + updateThread.Scheduler.Add(() => + { + try + { + var beatmapSet = realmFactory.Context.All().First(); + + for (int i = 0; i < ReadsPerFetch; i++) + { + string _ = beatmapSet.Beatmaps.First().Hash; + } + } + finally + { + done.Set(); + } + }); + + done.Wait(); + } + + [Benchmark] + public void BenchmarkRealmLivePropertyRead() + { + using (var context = realmFactory.CreateContext()) + { + var beatmapSet = context.All().First().ToLive(realmFactory); + + for (int i = 0; i < ReadsPerFetch; i++) + { + string _ = beatmapSet.PerformRead(b => b.Beatmaps.First().Hash); + } + } + } + + [Benchmark] + public void BenchmarkRealmLivePropertyReadUpdateThread() + { + var done = new ManualResetEventSlim(); + + updateThread.Scheduler.Add(() => + { + try + { + var beatmapSet = realmFactory.Context.All().First().ToLive(realmFactory); + + for (int i = 0; i < ReadsPerFetch; i++) + { + string _ = beatmapSet.PerformRead(b => b.Beatmaps.First().Hash); + } + } + finally + { + done.Set(); + } + }); + + done.Wait(); + } + + [Benchmark] + public void BenchmarkDetachedPropertyRead() + { + using (var context = realmFactory.CreateContext()) + { + var beatmapSet = context.All().First().Detach(); + + for (int i = 0; i < ReadsPerFetch; i++) + { + string _ = beatmapSet.Beatmaps.First().Hash; + } + } + } + + [GlobalCleanup] + public void Cleanup() + { + realmFactory?.Dispose(); + storage?.Dispose(); + updateThread?.Exit(); + } + } +} From 8ef50ff42dab85eaae680274ccd7d3b25a78de2f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Jan 2022 17:28:01 +0900 Subject: [PATCH 0144/5199] Add safety to ensure `RealmLiveUnmanaged` is not used with managed instances --- osu.Game/Database/RealmLiveUnmanaged.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Database/RealmLiveUnmanaged.cs b/osu.Game/Database/RealmLiveUnmanaged.cs index ea50ccc1ff..97f2faa656 100644 --- a/osu.Game/Database/RealmLiveUnmanaged.cs +++ b/osu.Game/Database/RealmLiveUnmanaged.cs @@ -21,6 +21,9 @@ namespace osu.Game.Database /// The realm data. public RealmLiveUnmanaged(T data) { + if (data.IsManaged) + throw new InvalidOperationException($"Cannot use {nameof(RealmLiveUnmanaged)} with managed instances"); + Value = data; } From 9946003069f7666f74bc383ac1371043cf6d0b35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 05:09:40 +0900 Subject: [PATCH 0145/5199] Update osu.Game/Screens/Menu/IntroScreen.cs Co-authored-by: Salman Ahmed --- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index db9d9b83c4..10be90a5f0 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -103,7 +103,7 @@ namespace osu.Game.Screens.Menu int setCount = sets.Count; - if (sets.Any()) + if (setCount > 0) { var found = sets[RNG.Next(0, setCount - 1)].Beatmaps.FirstOrDefault(); From 855ef3fa92ef76e53d2db54eeddf7ec70482e248 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 11:50:47 +0900 Subject: [PATCH 0146/5199] Create backup before any realm contexts are used --- osu.Game/Database/EFToRealmMigrator.cs | 16 ---------------- osu.Game/OsuGameBase.cs | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 85d65fea82..1e21107628 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -100,10 +100,6 @@ namespace osu.Game.Database { base.LoadComplete(); - // needs to be run on the update thread because of realm BlockAllOperations. - // maybe we can work around this? not sure.. - createBackup(); - Task.Factory.StartNew(() => { using (var ef = efContextFactory.Get()) @@ -470,17 +466,5 @@ namespace osu.Game.Database private string? getRulesetShortNameFromLegacyID(long rulesetId) => efContextFactory.Get().RulesetInfo.FirstOrDefault(r => r.ID == rulesetId)?.ShortName; - - private void createBackup() - { - string migration = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; - - efContextFactory.CreateBackup($"client.{migration}.db"); - realmContextFactory.CreateBackup($"client.{migration}.realm"); - - using (var source = storage.GetStream("collection.db")) - using (var destination = storage.GetStream($"collection.{migration}.db", FileAccess.Write, FileMode.CreateNew)) - source.CopyTo(destination); - } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 3d40126b4f..710a7be8d4 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -197,6 +197,21 @@ namespace osu.Game dependencies.Cache(RulesetStore = new RulesetStore(realmFactory, Storage)); dependencies.CacheAs(RulesetStore); + // Backup is taken here rather than in EFToRealmMigrator to avoid recycling realm contexts + // after initial usages below. It can be moved once a direction is established for handling re-subscription. + // See https://github.com/ppy/osu/pull/16547 for more discussion. + if (EFContextFactory != null) + { + string migration = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; + + EFContextFactory.CreateBackup($"client.{migration}.db"); + realmFactory.CreateBackup($"client.{migration}.realm"); + + using (var source = Storage.GetStream("collection.db")) + using (var destination = Storage.GetStream($"collection.{migration}.db", FileAccess.Write, FileMode.CreateNew)) + source.CopyTo(destination); + } + dependencies.CacheAs(Storage); var largeStore = new LargeTextureStore(Host.CreateTextureLoaderStore(new NamespacedResourceStore(Resources, @"Textures"))); From daed0b04dc97e8a50375f6955451bd3175af6946 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 11:54:40 +0900 Subject: [PATCH 0147/5199] Remove using statements --- osu.Game/Database/EFToRealmMigrator.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 1e21107628..f5b338062a 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.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; -using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; From b23f4674b12251a94fd8bb0f054adcdc60f8abf2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 12:02:18 +0900 Subject: [PATCH 0148/5199] Update outdated exception message Co-authored-by: Salman Ahmed --- osu.Game/Database/RealmContextFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 888ffb1dd5..ea6a4b9636 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -72,7 +72,7 @@ namespace osu.Game.Database get { if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException(@$"Use {nameof(createContext)} when performing realm operations from a non-update thread"); + throw new InvalidOperationException(@$"Use {nameof(Run)}/{nameof(Write)} when performing realm operations from a non-update thread"); lock (contextLock) { From 7025191fdd45767321565cf28bfbb403b29148ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 12:02:44 +0900 Subject: [PATCH 0149/5199] Move target field outside of `Run` usage Co-authored-by: Salman Ahmed --- .../Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs index 9075dfefd4..2ee3372f80 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input List bindings = null; - realmFactory.Run(realm => bindings = realm.All().Where(b => b.RulesetName == rulesetName && b.Variant == variant).Detach()); + bindings = realmFactory.Run(realm => realm.All().Where(b => b.RulesetName == rulesetName && b.Variant == variant).Detach()); foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) { From a89954d67f0b1f3b77edab3b8b8f7e71c1db1de8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 12:12:13 +0900 Subject: [PATCH 0150/5199] Update benchmarks in line with new structure --- osu.Game.Benchmarks/BenchmarkRealmReads.cs | 24 ++++++++++++---------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs index 5b5bdf595d..bb22fab51c 100644 --- a/osu.Game.Benchmarks/BenchmarkRealmReads.cs +++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs @@ -29,8 +29,10 @@ namespace osu.Game.Benchmarks realmFactory = new RealmContextFactory(storage, "client"); - using (var context = realmFactory.CreateContext()) - context.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo }))); + realmFactory.Run(realm => + { + realm.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo }))); + }); updateThread = new UpdateThread(() => { }, null); updateThread.Start(); @@ -39,15 +41,15 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkDirectPropertyRead() { - using (var context = realmFactory.CreateContext()) + realmFactory.Run(realm => { - var beatmapSet = context.All().First(); + var beatmapSet = realm.All().First(); for (int i = 0; i < ReadsPerFetch; i++) { string _ = beatmapSet.Beatmaps.First().Hash; } - } + }); } [Benchmark] @@ -78,15 +80,15 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkRealmLivePropertyRead() { - using (var context = realmFactory.CreateContext()) + realmFactory.Run(realm => { - var beatmapSet = context.All().First().ToLive(realmFactory); + var beatmapSet = realm.All().First().ToLive(realmFactory); for (int i = 0; i < ReadsPerFetch; i++) { string _ = beatmapSet.PerformRead(b => b.Beatmaps.First().Hash); } - } + }); } [Benchmark] @@ -117,15 +119,15 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkDetachedPropertyRead() { - using (var context = realmFactory.CreateContext()) + realmFactory.Run(realm => { - var beatmapSet = context.All().First().Detach(); + var beatmapSet = realm.All().First().Detach(); for (int i = 0; i < ReadsPerFetch; i++) { string _ = beatmapSet.Beatmaps.First().Hash; } - } + }); } [GlobalCleanup] From c9db0181d0611554f68e70ac4ed2da1fc96e9550 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 12:24:05 +0900 Subject: [PATCH 0151/5199] Attempt to fix test failures on windows due to context being held open --- osu.Game.Tests/Database/RealmLiveTests.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 2f16df4624..7b1cf763d6 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -47,6 +47,11 @@ namespace osu.Game.Tests.Database liveBeatmap = beatmap.ToLive(realmFactory); }); + using (realmFactory.BlockAllOperations()) + { + // recycle realm before migrating + } + using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target")) { migratedStorage.DeleteDirectory(string.Empty); From 25dbe6b27c092e5ce992e7cb4d7663cf3e8656df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 12:58:30 +0900 Subject: [PATCH 0152/5199] Fix unused null assignment --- .../Settings/Sections/Input/KeyBindingsSubsection.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs index 2ee3372f80..5b8a52240e 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs @@ -34,9 +34,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input { string rulesetName = Ruleset?.ShortName; - List bindings = null; - - bindings = realmFactory.Run(realm => realm.All().Where(b => b.RulesetName == rulesetName && b.Variant == variant).Detach()); + var bindings = realmFactory.Run(realm => realm.All() + .Where(b => b.RulesetName == rulesetName && b.Variant == variant) + .Detach()); foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) { From c99f2278797abd9c2e259b422ccd602ae14eea30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Jan 2022 13:48:49 +0100 Subject: [PATCH 0153/5199] Remove no longer used resolved storage --- osu.Game/Database/EFToRealmMigrator.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index f5b338062a..161c12e35e 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -38,9 +38,6 @@ namespace osu.Game.Database [Resolved] private OsuConfigManager config { get; set; } = null!; - [Resolved] - private Storage storage { get; set; } = null!; - private readonly OsuSpriteText currentOperationText; public EFToRealmMigrator() From 7e68371d28b9ae109a8e89039bf1db1ed801436b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Jan 2022 14:20:28 +0100 Subject: [PATCH 0154/5199] Move log statement about migration completed closer to rest of migration code --- osu.Game/Database/DatabaseContextFactory.cs | 4 ---- osu.Game/Database/EFToRealmMigrator.cs | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index 635c4373cd..c84edbfb81 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -5,7 +5,6 @@ using System.IO; using System.Linq; using System.Threading; using Microsoft.EntityFrameworkCore.Storage; -using osu.Framework.Development; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Statistics; @@ -151,9 +150,6 @@ namespace osu.Game.Database { Logger.Log($"Creating full EF database backup at {backupFilename}", LoggingTarget.Database); - if (DebugUtils.IsDebugBuild) - Logger.Log("Your development database has been fully migrated to realm. If you switch back to a pre-realm branch and need your previous database, rename the backup file back to \"client.db\".\n\nNote that doing this can potentially leave your file store in a bad state.", level: LogLevel.Important); - using (var source = storage.GetStream(DATABASE_NAME)) using (var destination = storage.GetStream(backupFilename, FileAccess.Write, FileMode.CreateNew)) source.CopyTo(destination); diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index c224399dbc..bbbdac352e 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using osu.Framework.Allocation; +using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; @@ -108,6 +109,9 @@ namespace osu.Game.Database // Will cause future startups to not attempt migration. log("Migration successful, deleting EF database"); efContextFactory.ResetDatabase(); + + if (DebugUtils.IsDebugBuild) + Logger.Log("Your development database has been fully migrated to realm. If you switch back to a pre-realm branch and need your previous database, rename the backup file back to \"client.db\".\n\nNote that doing this can potentially leave your file store in a bad state.", level: LogLevel.Important); }, TaskCreationOptions.LongRunning).ContinueWith(t => { FinishedMigrating = true; From b2d1bd029d9bd463714b69256b906caad62c1813 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 22 Jan 2022 16:17:46 +0300 Subject: [PATCH 0155/5199] Turn on high poll rate when tournament chat is expanded --- osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs b/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs index fe22d1e76d..a5ead6c2f0 100644 --- a/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs +++ b/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tournament.Components if (manager == null) { - AddInternal(manager = new ChannelManager()); + AddInternal(manager = new ChannelManager { HighPollRate = { Value = true } }); Channel.BindTo(manager.CurrentChannel); } From 955bab926fac76513a660737a42e48e01cfd9bc0 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Sat, 22 Jan 2022 19:38:56 +0100 Subject: [PATCH 0156/5199] Separate the settings for each modes radiuses --- .../Mods/CatchModFlashlight.cs | 19 ++++++++-- .../Mods/ManiaModFlashlight.cs | 19 ++++++++-- .../Mods/OsuModFlashlight.cs | 36 ++++++++++++------- .../Mods/TaikoModFlashlight.cs | 19 ++++++++-- osu.Game/Rulesets/Mods/ModFlashlight.cs | 20 ++--------- 5 files changed, 75 insertions(+), 38 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 9d9fa5aed4..f75772b04e 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -3,6 +3,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Configuration; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Mods; @@ -15,8 +16,22 @@ namespace osu.Game.Rulesets.Catch.Mods { public override double ScoreMultiplier => 1.12; - public override bool DefaultComboDependency => true; - public override float DefaultRadius => 350; + [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] + public override BindableBool ChangeRadius { get; } = new BindableBool + { + Default = true, + Value = true + }; + + [SettingSource("Initial radius", "Initial radius of the flashlight area.")] + public override BindableNumber InitialRadius { get; } = new BindableNumber + { + MinValue = 150f, + MaxValue = 600f, + Default = 350f, + Value = 350f, + Precision = 5f + }; public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value); diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 4fff736c57..a6a3c3be73 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Layout; +using osu.Game.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; using osuTK; @@ -16,8 +17,22 @@ namespace osu.Game.Rulesets.Mania.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModHidden) }; - public override bool DefaultComboDependency => false; - public override float DefaultRadius => 180; + [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] + public override BindableBool ChangeRadius { get; } = new BindableBool + { + Default = false, + Value = false + }; + + [SettingSource("Initial radius", "Initial radius of the flashlight area.")] + public override BindableNumber InitialRadius { get; } = new BindableNumber + { + MinValue = 0f, + MaxValue = 230f, + Default = 50f, + Value = 50f, + Precision = 5f + }; public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index f381d14ffe..e2a6d0f0dc 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -20,14 +20,32 @@ namespace osu.Game.Rulesets.Osu.Mods { public override double ScoreMultiplier => 1.12; - public override bool DefaultComboDependency => true; - - //private const float default_flashlight_size = 180; - public override float DefaultRadius => 180; - private const double default_follow_delay = 120; + [SettingSource("Follow delay", "Milliseconds until the flashlight reaches the cursor")] + public BindableNumber FollowDelay { get; } = new BindableDouble(default_follow_delay) + { + MinValue = default_follow_delay, + MaxValue = default_follow_delay * 10, + Precision = default_follow_delay, + }; + [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] + public override BindableBool ChangeRadius { get; } = new BindableBool + { + Default = true, + Value = true + }; + + [SettingSource("Initial radius", "Initial radius of the flashlight area.")] + public override BindableNumber InitialRadius { get; } = new BindableNumber + { + MinValue = 90f, + MaxValue = 360f, + Default = 180f, + Value = 180f, + Precision = 5f + }; private OsuFlashlight flashlight; @@ -39,14 +57,6 @@ namespace osu.Game.Rulesets.Osu.Mods s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange; } - [SettingSource("Follow delay", "Milliseconds until the flashlight reaches the cursor")] - public BindableNumber FollowDelay { get; } = new BindableDouble(default_follow_delay) - { - MinValue = default_follow_delay, - MaxValue = default_follow_delay * 10, - Precision = default_follow_delay, - }; - private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition { public double FollowDelay { private get; set; } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 76f7c45b75..71c9d777ec 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -4,6 +4,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Layout; +using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.UI; @@ -16,10 +17,22 @@ namespace osu.Game.Rulesets.Taiko.Mods { public override double ScoreMultiplier => 1.12; - public override bool DefaultComboDependency => true; + [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] + public override BindableBool ChangeRadius { get; } = new BindableBool + { + Default = true, + Value = true + }; - //private const float default_flashlight_size = 250; - public override float DefaultRadius => 250; + [SettingSource("Initial radius", "Initial radius of the flashlight area.")] + public override BindableNumber InitialRadius { get; } = new BindableNumber + { + MinValue = 0f, + MaxValue = 400f, + Default = 250f, + Value = 250f, + Precision = 5f + }; public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value); diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index c218ab45fe..51006d96e8 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -34,26 +34,10 @@ namespace osu.Game.Rulesets.Mods public override string Description => "Restricted view area."; [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] - public Bindable ChangeRadius { get; private set; } + public abstract BindableBool ChangeRadius { get; } [SettingSource("Initial radius", "Initial radius of the flashlight area.")] - public BindableNumber InitialRadius { get; private set; } - - public abstract float DefaultRadius { get; } - - public abstract bool DefaultComboDependency { get; } - - internal ModFlashlight() - { - InitialRadius = new BindableFloat(DefaultRadius) - { - MinValue = DefaultRadius * .5f, - MaxValue = DefaultRadius * 1.5f, - Precision = 5f, - }; - - ChangeRadius = new BindableBool(DefaultComboDependency); - } + public abstract BindableNumber InitialRadius { get; } } public abstract class ModFlashlight : ModFlashlight, IApplicableToDrawableRuleset, IApplicableToScoreProcessor From 735414bc49381df02d6fb243c25054ba8f35e204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Jan 2022 17:27:27 +0100 Subject: [PATCH 0157/5199] Replace `TimeSignatures` enum with struct for storage of arbitrary meter --- .../Skinning/Default/CirclePiece.cs | 2 +- .../Formats/LegacyBeatmapDecoderTest.cs | 6 +-- .../NonVisual/BarLineGeneratorTest.cs | 6 +-- .../TestSceneNightcoreBeatContainer.cs | 4 +- .../ControlPoints/TimingControlPoint.cs | 4 +- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 4 +- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/Timing/TimeSignature.cs | 45 +++++++++++++++++++ osu.Game/Beatmaps/Timing/TimeSignatures.cs | 4 +- osu.Game/Rulesets/Mods/Metronome.cs | 2 +- osu.Game/Rulesets/Mods/ModNightcore.cs | 10 ++--- osu.Game/Rulesets/Objects/BarLineGenerator.cs | 6 +-- .../Timeline/TimelineTickDisplay.cs | 2 +- .../RowAttributes/TimingRowAttribute.cs | 2 +- osu.Game/Screens/Edit/Timing/TimingSection.cs | 11 +++-- osu.Game/Screens/Menu/MenuSideFlashes.cs | 4 +- osu.Game/Screens/Menu/OsuLogo.cs | 2 +- 17 files changed, 84 insertions(+), 32 deletions(-) create mode 100644 osu.Game/Beatmaps/Timing/TimeSignature.cs diff --git a/osu.Game.Rulesets.Taiko/Skinning/Default/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Default/CirclePiece.cs index 8ca996159b..a106c4f629 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Default/CirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Default/CirclePiece.cs @@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default if (!effectPoint.KiaiMode) return; - if (beatIndex % (int)timingPoint.TimeSignature != 0) + if (beatIndex % timingPoint.TimeSignature.Numerator != 0) return; double duration = timingPoint.BeatLength * 2; diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 6ec14e6351..0459753b28 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -178,17 +178,17 @@ namespace osu.Game.Tests.Beatmaps.Formats var timingPoint = controlPoints.TimingPointAt(0); Assert.AreEqual(956, timingPoint.Time); Assert.AreEqual(329.67032967033, timingPoint.BeatLength); - Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature); + Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature); timingPoint = controlPoints.TimingPointAt(48428); Assert.AreEqual(956, timingPoint.Time); Assert.AreEqual(329.67032967033d, timingPoint.BeatLength); - Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature); + Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature); timingPoint = controlPoints.TimingPointAt(119637); Assert.AreEqual(119637, timingPoint.Time); Assert.AreEqual(659.340659340659, timingPoint.BeatLength); - Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature); + Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature); var difficultyPoint = controlPoints.DifficultyPointAt(0); Assert.AreEqual(0, difficultyPoint.Time); diff --git a/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs b/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs index 834c05fd08..6ae8231deb 100644 --- a/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs +++ b/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.NonVisual const int beat_length_numerator = 2000; const int beat_length_denominator = 7; - const TimeSignatures signature = TimeSignatures.SimpleQuadruple; + TimeSignature signature = TimeSignature.SimpleQuadruple; var beatmap = new Beatmap { @@ -49,7 +49,7 @@ namespace osu.Game.Tests.NonVisual for (int i = 0; i * beat_length_denominator < barLines.Count; i++) { var barLine = barLines[i * beat_length_denominator]; - int expectedTime = beat_length_numerator * (int)signature * i; + int expectedTime = beat_length_numerator * signature.Numerator * i; // every seventh bar's start time should be at least greater than the whole number we expect. // It cannot be less, as that can affect overlapping scroll algorithms @@ -60,7 +60,7 @@ namespace osu.Game.Tests.NonVisual Assert.IsTrue(Precision.AlmostEquals(barLine.StartTime, expectedTime)); // check major/minor lines for good measure too - Assert.AreEqual(i % (int)signature == 0, barLine.Major); + Assert.AreEqual(i % signature.Numerator == 0, barLine.Major); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs index 951ee1489d..759e4fa4ec 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs @@ -24,8 +24,8 @@ namespace osu.Game.Tests.Visual.Gameplay Add(new ModNightcore.NightcoreBeatContainer()); - AddStep("change signature to quadruple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignatures.SimpleQuadruple)); - AddStep("change signature to triple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignatures.SimpleTriple)); + AddStep("change signature to quadruple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignature.SimpleQuadruple)); + AddStep("change signature to triple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignature.SimpleTriple)); } } } diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index ec20328fab..922439fcb8 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -13,7 +13,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time signature at this control point. /// - public readonly Bindable TimeSignatureBindable = new Bindable(TimeSignatures.SimpleQuadruple) { Default = TimeSignatures.SimpleQuadruple }; + public readonly Bindable TimeSignatureBindable = new Bindable(TimeSignature.SimpleQuadruple); /// /// Default length of a beat in milliseconds. Used whenever there is no beatmap or track playing. @@ -35,7 +35,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time signature at this control point. /// - public TimeSignatures TimeSignature + public TimeSignature TimeSignature { get => TimeSignatureBindable.Value; set => TimeSignatureBindable.Value = value; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 893eb8ab78..8f3f05aa9f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -340,9 +340,9 @@ namespace osu.Game.Beatmaps.Formats double beatLength = Parsing.ParseDouble(split[1].Trim()); double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1; - TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple; + TimeSignature timeSignature = TimeSignature.SimpleQuadruple; if (split.Length >= 3) - timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)Parsing.ParseInt(split[2]); + timeSignature = split[2][0] == '0' ? TimeSignature.SimpleQuadruple : new TimeSignature(Parsing.ParseInt(split[2])); LegacySampleBank sampleSet = defaultSampleBank; if (split.Length >= 4) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index ebdc882d2f..4cf6d3335f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -227,7 +227,7 @@ namespace osu.Game.Beatmaps.Formats if (effectPoint.OmitFirstBarLine) effectFlags |= LegacyEffectFlags.OmitFirstBarLine; - writer.Write(FormattableString.Invariant($"{(int)legacyControlPoints.TimingPointAt(time).TimeSignature},")); + writer.Write(FormattableString.Invariant($"{legacyControlPoints.TimingPointAt(time).TimeSignature.Numerator},")); writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},")); writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample)},")); writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},")); diff --git a/osu.Game/Beatmaps/Timing/TimeSignature.cs b/osu.Game/Beatmaps/Timing/TimeSignature.cs new file mode 100644 index 0000000000..5bfeea5e9b --- /dev/null +++ b/osu.Game/Beatmaps/Timing/TimeSignature.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Beatmaps.Timing +{ + /// + /// Stores the time signature of a track. + /// For now, the lower numeral can only be 4; support for other denominators can be considered at a later date. + /// + public class TimeSignature : IEquatable + { + /// + /// The numerator of a signature. + /// + public int Numerator { get; } + + // TODO: support time signatures with a denominator other than 4 + // this in particular requires a new beatmap format. + + public TimeSignature(int numerator) + { + if (numerator < 1) + throw new ArgumentOutOfRangeException(nameof(numerator), numerator, "The numerator of a time signature must be positive."); + + Numerator = numerator; + } + + public static TimeSignature SimpleTriple => new TimeSignature(3); + public static TimeSignature SimpleQuadruple => new TimeSignature(4); + + public override string ToString() => $"{Numerator}/4"; + + public bool Equals(TimeSignature other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return Numerator == other.Numerator; + } + + public override int GetHashCode() => Numerator; + } +} diff --git a/osu.Game/Beatmaps/Timing/TimeSignatures.cs b/osu.Game/Beatmaps/Timing/TimeSignatures.cs index 33e6342ae6..d783d3f9ec 100644 --- a/osu.Game/Beatmaps/Timing/TimeSignatures.cs +++ b/osu.Game/Beatmaps/Timing/TimeSignatures.cs @@ -1,11 +1,13 @@ // 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.ComponentModel; namespace osu.Game.Beatmaps.Timing { - public enum TimeSignatures + [Obsolete("Use osu.Game.Beatmaps.Timing.TimeSignature instead.")] + public enum TimeSignatures // can be removed 20220722 { [Description("4/4")] SimpleQuadruple = 4, diff --git a/osu.Game/Rulesets/Mods/Metronome.cs b/osu.Game/Rulesets/Mods/Metronome.cs index 8b6d86c45f..b85a341577 100644 --- a/osu.Game/Rulesets/Mods/Metronome.cs +++ b/osu.Game/Rulesets/Mods/Metronome.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Mods if (!IsBeatSyncedWithTrack) return; - int timeSignature = (int)timingPoint.TimeSignature; + int timeSignature = timingPoint.TimeSignature.Numerator; // play metronome from one measure before the first object. if (BeatSyncClock.CurrentTime < firstHitTime - timingPoint.BeatLength * timeSignature) diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index a44967c21c..993efead33 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Mods { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); - int beatsPerBar = (int)timingPoint.TimeSignature; + int beatsPerBar = timingPoint.TimeSignature.Numerator; int segmentLength = beatsPerBar * Divisor * bars_per_segment; if (!IsBeatSyncedWithTrack) @@ -102,14 +102,14 @@ namespace osu.Game.Rulesets.Mods playBeatFor(beatIndex % segmentLength, timingPoint.TimeSignature); } - private void playBeatFor(int beatIndex, TimeSignatures signature) + private void playBeatFor(int beatIndex, TimeSignature signature) { if (beatIndex == 0) finishSample?.Play(); - switch (signature) + switch (signature.Numerator) { - case TimeSignatures.SimpleTriple: + case 3: switch (beatIndex % 6) { case 0: @@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Mods break; - case TimeSignatures.SimpleQuadruple: + case 4: switch (beatIndex % 4) { case 0: diff --git a/osu.Game/Rulesets/Objects/BarLineGenerator.cs b/osu.Game/Rulesets/Objects/BarLineGenerator.cs index e78aa5a5a0..d71a499119 100644 --- a/osu.Game/Rulesets/Objects/BarLineGenerator.cs +++ b/osu.Game/Rulesets/Objects/BarLineGenerator.cs @@ -41,9 +41,9 @@ namespace osu.Game.Rulesets.Objects int currentBeat = 0; // Stop on the beat before the next timing point, or if there is no next timing point stop slightly past the last object - double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time - currentTimingPoint.BeatLength : lastHitTime + currentTimingPoint.BeatLength * (int)currentTimingPoint.TimeSignature; + double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time - currentTimingPoint.BeatLength : lastHitTime + currentTimingPoint.BeatLength * currentTimingPoint.TimeSignature.Numerator; - double barLength = currentTimingPoint.BeatLength * (int)currentTimingPoint.TimeSignature; + double barLength = currentTimingPoint.BeatLength * currentTimingPoint.TimeSignature.Numerator; for (double t = currentTimingPoint.Time; Precision.DefinitelyBigger(endTime, t); t += barLength, currentBeat++) { @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Objects BarLines.Add(new TBarLine { StartTime = t, - Major = currentBeat % (int)currentTimingPoint.TimeSignature == 0 + Major = currentBeat % currentTimingPoint.TimeSignature.Numerator == 0 }); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 1415014e59..cc4041394d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -125,7 +125,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (beat == 0 && i == 0) nextMinTick = float.MinValue; - int indexInBar = beat % ((int)point.TimeSignature * beatDivisor.Value); + int indexInBar = beat % (point.TimeSignature.Numerator * beatDivisor.Value); int divisor = BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value); var colour = BindableBeatDivisor.GetColourFor(divisor, colours); diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs index ab840e56a7..f8ec4aef25 100644 --- a/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs +++ b/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs @@ -13,7 +13,7 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes public class TimingRowAttribute : RowAttribute { private readonly BindableNumber beatLength; - private readonly Bindable timeSignature; + private readonly Bindable timeSignature; private OsuSpriteText text; public TimingRowAttribute(TimingControlPoint timing) diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index a0bb9ac506..e0b09ce980 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.Edit.Timing internal class TimingSection : Section { private SettingsSlider bpmSlider; - private SettingsEnumDropdown timeSignature; + private SettingsDropdown timeSignature; private BPMTextBox bpmTextEntry; [BackgroundDependencyLoader] @@ -25,9 +25,14 @@ namespace osu.Game.Screens.Edit.Timing { bpmTextEntry = new BPMTextBox(), bpmSlider = new BPMSlider(), - timeSignature = new SettingsEnumDropdown + timeSignature = new SettingsDropdown { - LabelText = "Time Signature" + LabelText = "Time Signature", + Items = new[] + { + TimeSignature.SimpleTriple, + TimeSignature.SimpleQuadruple + } }, }); } diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs index bdcd3020f8..cd0c75c1a1 100644 --- a/osu.Game/Screens/Menu/MenuSideFlashes.cs +++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs @@ -94,9 +94,9 @@ namespace osu.Game.Screens.Menu if (beatIndex < 0) return; - if (effectPoint.KiaiMode ? beatIndex % 2 == 0 : beatIndex % (int)timingPoint.TimeSignature == 0) + if (effectPoint.KiaiMode ? beatIndex % 2 == 0 : beatIndex % timingPoint.TimeSignature.Numerator == 0) flash(leftBox, timingPoint.BeatLength, effectPoint.KiaiMode, amplitudes); - if (effectPoint.KiaiMode ? beatIndex % 2 == 1 : beatIndex % (int)timingPoint.TimeSignature == 0) + if (effectPoint.KiaiMode ? beatIndex % 2 == 1 : beatIndex % timingPoint.TimeSignature.Numerator == 0) flash(rightBox, timingPoint.BeatLength, effectPoint.KiaiMode, amplitudes); } diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index f9388097ac..c82efe2d32 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -282,7 +282,7 @@ namespace osu.Game.Screens.Menu { this.Delay(early_activation).Schedule(() => { - if (beatIndex % (int)timingPoint.TimeSignature == 0) + if (beatIndex % timingPoint.TimeSignature.Numerator == 0) sampleDownbeat.Play(); else sampleBeat.Play(); From f39f2c93b52526a3e5d5aa06e18d0f0ac85020c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Jan 2022 18:54:14 +0100 Subject: [PATCH 0158/5199] Add control for arbitrary-numerator time signatures --- .../Editing/TestSceneLabelledTimeSignature.cs | 88 ++++++++++++++++++ .../Edit/Timing/LabelledTimeSignature.cs | 93 +++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs create mode 100644 osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs b/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs new file mode 100644 index 0000000000..cedbb5025f --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs @@ -0,0 +1,88 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Beatmaps.Timing; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Edit.Timing; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneLabelledTimeSignature : OsuManualInputManagerTestScene + { + private LabelledTimeSignature timeSignature; + + private void createLabelledTimeSignature(TimeSignature initial) => AddStep("create labelled time signature", () => + { + Child = timeSignature = new LabelledTimeSignature + { + Label = "Time Signature", + RelativeSizeAxes = Axes.None, + Width = 400, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Current = { Value = initial } + }; + }); + + private OsuTextBox numeratorTextBox => timeSignature.ChildrenOfType().Single(); + + [Test] + public void TestInitialValue() + { + createLabelledTimeSignature(TimeSignature.SimpleTriple); + AddAssert("current is 3/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleTriple)); + } + + [Test] + public void TestChangeViaCurrent() + { + createLabelledTimeSignature(TimeSignature.SimpleQuadruple); + AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); + + AddStep("set current to 5/4", () => timeSignature.Current.Value = new TimeSignature(5)); + + AddAssert("current is 5/4", () => timeSignature.Current.Value.Equals(new TimeSignature(5))); + AddAssert("numerator is 5", () => numeratorTextBox.Current.Value == "5"); + + AddStep("set current to 3/4", () => timeSignature.Current.Value = TimeSignature.SimpleTriple); + + AddAssert("current is 3/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleTriple)); + AddAssert("numerator is 5", () => numeratorTextBox.Current.Value == "3"); + } + + [Test] + public void TestChangeNumerator() + { + createLabelledTimeSignature(TimeSignature.SimpleQuadruple); + AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); + + AddStep("focus text box", () => InputManager.ChangeFocus(numeratorTextBox)); + + AddStep("set numerator to 7", () => numeratorTextBox.Current.Value = "7"); + AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); + + AddStep("drop focus", () => InputManager.ChangeFocus(null)); + AddAssert("current is 7/4", () => timeSignature.Current.Value.Equals(new TimeSignature(7))); + } + + [Test] + public void TestInvalidChangeRollbackOnCommit() + { + createLabelledTimeSignature(TimeSignature.SimpleQuadruple); + AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); + + AddStep("focus text box", () => InputManager.ChangeFocus(numeratorTextBox)); + + AddStep("set numerator to 0", () => numeratorTextBox.Current.Value = "0"); + AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); + + AddStep("drop focus", () => InputManager.ChangeFocus(null)); + AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); + AddAssert("numerator is 4", () => numeratorTextBox.Current.Value == "4"); + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs b/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs new file mode 100644 index 0000000000..52aece75ad --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs @@ -0,0 +1,93 @@ +// 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.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Beatmaps.Timing; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class LabelledTimeSignature : LabelledComponent + { + public LabelledTimeSignature() + : base(false) + { + } + + protected override TimeSignatureBox CreateComponent() => new TimeSignatureBox(); + + internal class TimeSignatureBox : CompositeDrawable, IHasCurrentValue + { + private readonly BindableWithCurrent current = new BindableWithCurrent(TimeSignature.SimpleQuadruple); + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private OsuNumberBox numeratorBox; + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Both; + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + numeratorBox = new OsuNumberBox + { + Width = 40, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + CornerRadius = CORNER_RADIUS, + CommitOnFocusLost = true + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding(10), + Text = "/4", + Font = OsuFont.Default.With(size: 20) + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(_ => updateFromCurrent(), true); + numeratorBox.OnCommit += (_, __) => updateFromNumeratorBox(); + } + + private void updateFromCurrent() + { + numeratorBox.Current.Value = Current.Value.Numerator.ToString(); + } + + private void updateFromNumeratorBox() + { + if (int.TryParse(numeratorBox.Current.Value, out int numerator) && numerator > 0) + Current.Value = new TimeSignature(numerator); + else + { + // trigger `Current` change to restore the numerator box's text to a valid value. + Current.TriggerChange(); + } + } + } + } +} From 54f7b1b8d0cec1638c67492fadf7c7fca8577f5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Jan 2022 18:54:28 +0100 Subject: [PATCH 0159/5199] Use new time signature control on timing screen --- osu.Game/Screens/Edit/Timing/TimingSection.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index e0b09ce980..cd0b56d338 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Timing; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays.Settings; @@ -15,7 +14,7 @@ namespace osu.Game.Screens.Edit.Timing internal class TimingSection : Section { private SettingsSlider bpmSlider; - private SettingsDropdown timeSignature; + private LabelledTimeSignature timeSignature; private BPMTextBox bpmTextEntry; [BackgroundDependencyLoader] @@ -25,15 +24,10 @@ namespace osu.Game.Screens.Edit.Timing { bpmTextEntry = new BPMTextBox(), bpmSlider = new BPMSlider(), - timeSignature = new SettingsDropdown + timeSignature = new LabelledTimeSignature { - LabelText = "Time Signature", - Items = new[] - { - TimeSignature.SimpleTriple, - TimeSignature.SimpleQuadruple - } - }, + Label = "Time Signature" + } }); } From 62a2bccd7655f78a66d74a7d77d4dec73f311c3a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 21 Jan 2022 17:41:45 +0300 Subject: [PATCH 0160/5199] Abstractify expansion logic from `ExpandingButtonContainer` --- osu.Game/Overlays/ExpandingButtonContainer.cs | 136 ++---------------- .../Overlays/ExpandingControlContainer.cs | 124 ++++++++++++++++ osu.Game/Overlays/IExpandingContainer.cs | 16 +++ osu.Game/Overlays/SettingsPanel.cs | 2 +- 4 files changed, 150 insertions(+), 128 deletions(-) create mode 100644 osu.Game/Overlays/ExpandingControlContainer.cs create mode 100644 osu.Game/Overlays/IExpandingContainer.cs diff --git a/osu.Game/Overlays/ExpandingButtonContainer.cs b/osu.Game/Overlays/ExpandingButtonContainer.cs index 4eb8c47a1f..d7ff285707 100644 --- a/osu.Game/Overlays/ExpandingButtonContainer.cs +++ b/osu.Game/Overlays/ExpandingButtonContainer.cs @@ -1,141 +1,23 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Linq; -using osu.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input.Events; -using osu.Framework.Testing; -using osu.Framework.Threading; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; -using osuTK; namespace osu.Game.Overlays { - public abstract class ExpandingButtonContainer : Container, IStateful + /// + /// An with a long hover expansion delay for buttons. + /// + /// + /// Mostly used for buttons with explanatory labels, in which the label would display after a "long hover". + /// + public class ExpandingButtonContainer : ExpandingControlContainer { - private readonly float contractedWidth; - private readonly float expandedWidth; - - public event Action StateChanged; - - protected override Container Content => FillFlow; - - protected FillFlowContainer FillFlow { get; } - protected ExpandingButtonContainer(float contractedWidth, float expandedWidth) + : base(contractedWidth, expandedWidth) { - this.contractedWidth = contractedWidth; - this.expandedWidth = expandedWidth; - - RelativeSizeAxes = Axes.Y; - Width = contractedWidth; - - InternalChildren = new Drawable[] - { - new SidebarScrollContainer - { - Children = new[] - { - FillFlow = new FillFlowContainer - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - } - } - }, - }; } - private ScheduledDelegate expandEvent; - private ExpandedState state; - - protected override bool OnHover(HoverEvent e) - { - queueExpandIfHovering(); - return true; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - expandEvent?.Cancel(); - hoveredButton = null; - State = ExpandedState.Contracted; - - base.OnHoverLost(e); - } - - protected override bool OnMouseMove(MouseMoveEvent e) - { - queueExpandIfHovering(); - return base.OnMouseMove(e); - } - - private class SidebarScrollContainer : OsuScrollContainer - { - public SidebarScrollContainer() - { - RelativeSizeAxes = Axes.Both; - ScrollbarVisible = false; - } - } - - public ExpandedState State - { - get => state; - set - { - expandEvent?.Cancel(); - - if (state == value) return; - - state = value; - - switch (state) - { - default: - this.ResizeTo(new Vector2(contractedWidth, Height), 500, Easing.OutQuint); - break; - - case ExpandedState.Expanded: - this.ResizeTo(new Vector2(expandedWidth, Height), 500, Easing.OutQuint); - break; - } - - StateChanged?.Invoke(State); - } - } - - private Drawable hoveredButton; - - private void queueExpandIfHovering() - { - // if the same button is hovered, let the scheduled expand play out.. - if (hoveredButton?.IsHovered == true) - return; - - // ..otherwise check whether a new button is hovered, and if so, queue a new hover operation. - - // usually we wouldn't use ChildrenOfType in implementations, but this is the simplest way - // to handle cases like the editor where the buttons may be nested within a child hierarchy. - hoveredButton = FillFlow.ChildrenOfType().FirstOrDefault(c => c.IsHovered); - - expandEvent?.Cancel(); - - if (hoveredButton?.IsHovered == true && State != ExpandedState.Expanded) - expandEvent = Scheduler.AddDelayed(() => State = ExpandedState.Expanded, 750); - } - } - - public enum ExpandedState - { - Contracted, - Expanded, + protected override double HoverExpansionDelay => 750; } } diff --git a/osu.Game/Overlays/ExpandingControlContainer.cs b/osu.Game/Overlays/ExpandingControlContainer.cs new file mode 100644 index 0000000000..8e02cab923 --- /dev/null +++ b/osu.Game/Overlays/ExpandingControlContainer.cs @@ -0,0 +1,124 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Framework.Testing; +using osu.Framework.Threading; +using osu.Game.Graphics.Containers; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Overlays +{ + /// + /// Represents a with the ability to expand/contract when hovering the controls within it. + /// + /// The type of UI control to lookup for hover expansion. + public class ExpandingControlContainer : Container, IExpandingContainer + where TControl : class, IDrawable + { + private readonly float contractedWidth; + private readonly float expandedWidth; + + public BindableBool Expanded { get; } = new BindableBool(); + + /// + /// Delay before the container switches to expanded state from hover. + /// + protected virtual double HoverExpansionDelay => 0; + + protected override Container Content => FillFlow; + + protected FillFlowContainer FillFlow { get; } + + protected ExpandingControlContainer(float contractedWidth, float expandedWidth) + { + this.contractedWidth = contractedWidth; + this.expandedWidth = expandedWidth; + + RelativeSizeAxes = Axes.Y; + Width = contractedWidth; + + InternalChild = new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, + Child = FillFlow = new FillFlowContainer + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }, + }; + } + + private ScheduledDelegate hoverExpandEvent; + private TControl activeControl; + + protected override void LoadComplete() + { + base.LoadComplete(); + + Expanded.BindValueChanged(v => + { + this.ResizeWidthTo(v.NewValue ? expandedWidth : contractedWidth, 500, Easing.OutQuint); + }, true); + } + + protected override void Update() + { + base.Update(); + + // if the container was expanded from hovering over a control, we have to check per-frame whether we can contract it back. + // that's because contracting the container depends not only on whether it's no longer hovered, + // but also on whether the hovered control is no longer in a dragged state (if it was). + if (hoverExpandEvent != null && !IsHovered && (activeControl == null || !isControlActive(activeControl))) + { + hoverExpandEvent?.Cancel(); + + Expanded.Value = false; + hoverExpandEvent = null; + activeControl = null; + } + } + + protected override bool OnHover(HoverEvent e) + { + queueExpandIfHovering(); + return true; + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + queueExpandIfHovering(); + return base.OnMouseMove(e); + } + + private void queueExpandIfHovering() + { + // if the same control is hovered or dragged, let the scheduled expand play out.. + if (activeControl != null && isControlActive(activeControl)) + return; + + // ..otherwise check whether a new control is hovered, and if so, queue a new hover operation. + hoverExpandEvent?.Cancel(); + + // usually we wouldn't use ChildrenOfType in implementations, but this is the simplest way + // to handle cases like the editor where the controls may be nested within a child hierarchy. + activeControl = FillFlow.ChildrenOfType().FirstOrDefault(isControlActive); + + if (activeControl != null && !Expanded.Value) + hoverExpandEvent = Scheduler.AddDelayed(() => Expanded.Value = true, HoverExpansionDelay); + } + + /// + /// Whether the given control is currently active, by checking whether it's hovered or dragged. + /// + private bool isControlActive(TControl control) => control.IsHovered || control.IsDragged; + } +} diff --git a/osu.Game/Overlays/IExpandingContainer.cs b/osu.Game/Overlays/IExpandingContainer.cs new file mode 100644 index 0000000000..ec5f0c90f4 --- /dev/null +++ b/osu.Game/Overlays/IExpandingContainer.cs @@ -0,0 +1,16 @@ +// 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.Containers; + +namespace osu.Game.Overlays +{ + /// + /// A target expanding container that should be resolved by children s to propagate state changes. + /// + [Cached(typeof(IExpandingContainer))] + public interface IExpandingContainer : IContainer, IExpandable + { + } +} diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index ba7118cffe..b11b6fde27 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -265,7 +265,7 @@ namespace osu.Game.Overlays return; SectionsContainer.ScrollTo(section); - Sidebar.State = ExpandedState.Contracted; + Sidebar.Expanded.Value = false; }, }; } From 326f12f8477da2838a368e921329d1ee726e4c44 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 21 Jan 2022 17:43:05 +0300 Subject: [PATCH 0161/5199] Add `IExpandable` support for `SettingsToolboxGroup` --- osu.Game/Overlays/SettingsToolboxGroup.cs | 80 +++++++++++-------- .../Screens/Play/HUD/PlayerSettingsOverlay.cs | 2 +- 2 files changed, 49 insertions(+), 33 deletions(-) diff --git a/osu.Game/Overlays/SettingsToolboxGroup.cs b/osu.Game/Overlays/SettingsToolboxGroup.cs index ca0980a9c9..9e7223df9d 100644 --- a/osu.Game/Overlays/SettingsToolboxGroup.cs +++ b/osu.Game/Overlays/SettingsToolboxGroup.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; @@ -18,7 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays { - public abstract class SettingsToolboxGroup : Container + public class SettingsToolboxGroup : Container, IExpandable { private const float transition_duration = 250; private const int container_width = 270; @@ -34,30 +35,7 @@ namespace osu.Game.Overlays private readonly FillFlowContainer content; private readonly IconButton button; - private bool expanded = true; - - public bool Expanded - { - get => expanded; - set - { - if (expanded == value) return; - - expanded = value; - - content.ClearTransforms(); - - if (expanded) - content.AutoSizeAxes = Axes.Y; - else - { - content.AutoSizeAxes = Axes.None; - content.ResizeHeightTo(0, transition_duration, Easing.OutQuint); - } - - updateExpanded(); - } - } + public BindableBool Expanded { get; } = new BindableBool(true); private Color4 expandedColour; @@ -67,7 +45,7 @@ namespace osu.Game.Overlays /// Create a new instance. /// /// The title to be displayed in the header of this group. - protected SettingsToolboxGroup(string title) + public SettingsToolboxGroup(string title) { AutoSizeAxes = Axes.Y; Width = container_width; @@ -115,7 +93,7 @@ namespace osu.Game.Overlays Position = new Vector2(-15, 0), Icon = FontAwesome.Solid.Bars, Scale = new Vector2(0.75f), - Action = () => Expanded = !Expanded, + Action = () => Expanded.Toggle(), }, } }, @@ -155,23 +133,58 @@ namespace osu.Game.Overlays headerText.FadeTo(headerText.DrawWidth < DrawWidth ? 1 : 0, 150, Easing.OutQuint); } + [Resolved(canBeNull: true)] + private IExpandingContainer expandingContainer { get; set; } + + private bool expandedByContainer; + protected override void LoadComplete() { base.LoadComplete(); - this.Delay(600).FadeTo(inactive_alpha, fade_duration, Easing.OutQuint); - updateExpanded(); + expandingContainer?.Expanded.BindValueChanged(containerExpanded => + { + if (containerExpanded.NewValue && !Expanded.Value) + { + Expanded.Value = true; + expandedByContainer = true; + } + else if (!containerExpanded.NewValue && expandedByContainer) + { + Expanded.Value = false; + expandedByContainer = false; + } + + updateActiveState(); + }, true); + + Expanded.BindValueChanged(v => + { + content.ClearTransforms(); + + if (v.NewValue) + content.AutoSizeAxes = Axes.Y; + else + { + content.AutoSizeAxes = Axes.None; + content.ResizeHeightTo(0, transition_duration, Easing.OutQuint); + } + + button.FadeColour(Expanded.Value ? expandedColour : Color4.White, 200, Easing.InOutQuint); + }, true); + + this.Delay(600).Schedule(updateActiveState); } protected override bool OnHover(HoverEvent e) { - this.FadeIn(fade_duration, Easing.OutQuint); + updateActiveState(); return false; } protected override void OnHoverLost(HoverLostEvent e) { - this.FadeTo(inactive_alpha, fade_duration, Easing.OutQuint); + updateActiveState(); base.OnHoverLost(e); } @@ -181,7 +194,10 @@ namespace osu.Game.Overlays expandedColour = colours.Yellow; } - private void updateExpanded() => button.FadeColour(expanded ? expandedColour : Color4.White, 200, Easing.InOutQuint); + private void updateActiveState() + { + this.FadeTo(IsHovered || expandingContainer?.Expanded.Value == true ? 1 : inactive_alpha, fade_duration, Easing.OutQuint); + } protected override Container Content => content; diff --git a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs index ffcbb06fb3..807b4989c7 100644 --- a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs +++ b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Play.HUD //CollectionSettings = new CollectionSettings(), //DiscussionSettings = new DiscussionSettings(), PlaybackSettings = new PlaybackSettings(), - VisualSettings = new VisualSettings { Expanded = false } + VisualSettings = new VisualSettings { Expanded = { Value = false } } } }; } From f4c7a332c343b1d23df835914150fe85992ef7ee Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 21 Jan 2022 17:45:08 +0300 Subject: [PATCH 0162/5199] Add `IExpandable` support for `SettingsItem` --- osu.Game/Overlays/Settings/ISettingsItem.cs | 9 +- osu.Game/Overlays/Settings/SettingsItem.cs | 91 ++++++++++++++++----- 2 files changed, 78 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/Settings/ISettingsItem.cs b/osu.Game/Overlays/Settings/ISettingsItem.cs index e7afa48502..fe21f0664a 100644 --- a/osu.Game/Overlays/Settings/ISettingsItem.cs +++ b/osu.Game/Overlays/Settings/ISettingsItem.cs @@ -2,12 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Graphics; namespace osu.Game.Overlays.Settings { - public interface ISettingsItem : IDrawable, IDisposable + /// + /// A non-generic interface for s. + /// + public interface ISettingsItem : IExpandable, IDisposable { + /// + /// Invoked when the setting value has changed. + /// event Action SettingChanged; } } diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index e709be1343..cc8d5b36d0 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -30,7 +30,7 @@ namespace osu.Game.Overlays.Settings protected readonly FillFlowContainer FlowContent; - private SpriteText labelText; + private SpriteText label; private OsuTextFlowContainer warningText; @@ -42,21 +42,34 @@ namespace osu.Game.Overlays.Settings [Resolved] private OsuColour colours { get; set; } + private LocalisableString labelText; + public virtual LocalisableString LabelText { - get => labelText?.Text ?? string.Empty; + get => labelText; set { - if (labelText == null) - { - // construct lazily for cases where the label is not needed (may be provided by the Control). - FlowContent.Insert(-1, labelText = new OsuSpriteText()); + ensureLabelCreated(); - updateDisabled(); - } + labelText = value; + updateLabelText(); + } + } - labelText.Text = value; - updateLayout(); + private LocalisableString? contractedLabelText; + + /// + /// Text to be displayed in place of when this is in a contracted state. + /// + public LocalisableString? ContractedLabelText + { + get => contractedLabelText; + set + { + ensureLabelCreated(); + + contractedLabelText = value; + updateLabelText(); } } @@ -90,6 +103,10 @@ namespace osu.Game.Overlays.Settings set => controlWithCurrent.Current = value; } + public BindableBool Expanded { get; } = new BindableBool(true); + + public event Action SettingChanged; + public virtual IEnumerable FilterTerms => Keywords == null ? new[] { LabelText.ToString() } : new List(Keywords) { LabelText.ToString() }.ToArray(); public IEnumerable Keywords { get; set; } @@ -101,8 +118,6 @@ namespace osu.Game.Overlays.Settings public bool FilteringActive { get; set; } - public event Action SettingChanged; - protected SettingsItem() { RelativeSizeAxes = Axes.X; @@ -151,23 +166,59 @@ namespace osu.Game.Overlays.Settings Anchor = Anchor.Centre, Origin = Anchor.Centre }); - updateLayout(); } } - private void updateLayout() - { - bool hasLabel = labelText != null && !string.IsNullOrEmpty(labelText.Text.ToString()); + [Resolved(canBeNull: true)] + private IExpandingContainer expandingContainer { get; set; } - // if the settings item is providing a label, the default value indicator should be centred vertically to the left of the label. + protected override void LoadComplete() + { + base.LoadComplete(); + + expandingContainer?.Expanded.BindValueChanged(containerExpanded => Expanded.Value = containerExpanded.NewValue, true); + + Expanded.BindValueChanged(v => + { + updateLabelText(); + + Control.FadeTo(v.NewValue ? 1 : 0, 500, Easing.OutQuint); + Control.BypassAutoSizeAxes = v.NewValue ? Axes.None : Axes.Both; + }, true); + + FinishTransforms(true); + } + + private void ensureLabelCreated() + { + if (label != null) + return; + + // construct lazily for cases where the label is not needed (may be provided by the Control). + FlowContent.Insert(-1, label = new OsuSpriteText()); + + updateDisabled(); + } + + private void updateLabelText() + { + if (label != null) + { + if (contractedLabelText is LocalisableString contractedText) + label.Text = Expanded.Value ? labelText : contractedText; + else + label.Text = labelText; + } + + // if the settings item is providing a non-empty label, the default value indicator should be centred vertically to the left of the label. // otherwise, it should be centred vertically to the left of the main control of the settings item. - defaultValueIndicatorContainer.Height = hasLabel ? labelText.DrawHeight : Control.DrawHeight; + defaultValueIndicatorContainer.Height = !string.IsNullOrEmpty(label?.Text.ToString()) ? label.DrawHeight : Control.DrawHeight; } private void updateDisabled() { - if (labelText != null) - labelText.Alpha = controlWithCurrent.Current.Disabled ? 0.3f : 1; + if (label != null) + label.Alpha = controlWithCurrent.Current.Disabled ? 0.3f : 1; } } } From 648e7f6bbc4883d591ed7c01994f511006caa770 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 21 Jan 2022 17:47:11 +0300 Subject: [PATCH 0163/5199] Handle controls with dragging state wrapped in underlying components I'm not 100% sure about this approach but it'll do for now. --- osu.Game/Overlays/ExpandingControlContainer.cs | 2 +- osu.Game/Overlays/Settings/ISettingsItem.cs | 5 +++++ osu.Game/Overlays/Settings/SettingsItem.cs | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/ExpandingControlContainer.cs b/osu.Game/Overlays/ExpandingControlContainer.cs index 8e02cab923..2accd63fb9 100644 --- a/osu.Game/Overlays/ExpandingControlContainer.cs +++ b/osu.Game/Overlays/ExpandingControlContainer.cs @@ -119,6 +119,6 @@ namespace osu.Game.Overlays /// /// Whether the given control is currently active, by checking whether it's hovered or dragged. /// - private bool isControlActive(TControl control) => control.IsHovered || control.IsDragged; + private bool isControlActive(TControl control) => control.IsHovered || control.IsDragged || (control is ISettingsItem item && item.IsControlDragged); } } diff --git a/osu.Game/Overlays/Settings/ISettingsItem.cs b/osu.Game/Overlays/Settings/ISettingsItem.cs index fe21f0664a..20e2f48f96 100644 --- a/osu.Game/Overlays/Settings/ISettingsItem.cs +++ b/osu.Game/Overlays/Settings/ISettingsItem.cs @@ -14,5 +14,10 @@ namespace osu.Game.Overlays.Settings /// Invoked when the setting value has changed. /// event Action SettingChanged; + + /// + /// Returns whether the UI control is currently in a dragged state. + /// + bool IsControlDragged { get; } } } diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index cc8d5b36d0..29980dc5a8 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -105,6 +105,8 @@ namespace osu.Game.Overlays.Settings public BindableBool Expanded { get; } = new BindableBool(true); + public bool IsControlDragged => Control.IsDragged; + public event Action SettingChanged; public virtual IEnumerable FilterTerms => Keywords == null ? new[] { LabelText.ToString() } : new List(Keywords) { LabelText.ToString() }.ToArray(); From 6b35c0fe01795d664fc3599085a4ec2aa17d614d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 21 Jan 2022 18:39:58 +0300 Subject: [PATCH 0164/5199] Add test scene for `ExpandingControlContainer` --- .../TestSceneExpandingControlContainer.cs | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneExpandingControlContainer.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingControlContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingControlContainer.cs new file mode 100644 index 0000000000..d75089ceac --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingControlContainer.cs @@ -0,0 +1,183 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osu.Game.Overlays.Settings; +using osu.Game.Overlays.Settings.Sections; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneExpandingControlContainer : OsuManualInputManagerTestScene + { + private TestExpandingContainer container; + private SettingsToolboxGroup toolboxGroup; + + private SettingsSlider slider1; + private SettingsSlider slider2; + + [SetUp] + public void SetUp() => Schedule(() => + { + Child = container = new TestExpandingContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Height = 0.33f, + Child = toolboxGroup = new SettingsToolboxGroup("sliders") + { + RelativeSizeAxes = Axes.X, + Width = 1, + Children = new Drawable[] + { + slider1 = new SettingsSlider + { + Current = new BindableFloat + { + Default = 1.0f, + MinValue = 1.0f, + MaxValue = 10.0f, + Precision = 0.01f, + }, + }, + slider2 = new SettingsSlider + { + Current = new BindableDouble + { + Default = 1.0, + MinValue = 1.0, + MaxValue = 10.0, + Precision = 0.01, + }, + }, + } + } + }; + + slider1.Current.BindValueChanged(v => + { + slider1.LabelText = $"Slider One ({v.NewValue:0.##x})"; + slider1.ContractedLabelText = $"S. 1. ({v.NewValue:0.##x})"; + }, true); + + slider2.Current.BindValueChanged(v => + { + slider2.LabelText = $"Slider Two ({v.NewValue:N2})"; + slider2.ContractedLabelText = $"S. 2. ({v.NewValue:N2})"; + }, true); + }); + + [Test] + public void TestDisplay() + { + AddStep("switch to contracted", () => container.Expanded.Value = false); + AddStep("switch to expanded", () => container.Expanded.Value = true); + AddStep("set left origin", () => container.Origin = Anchor.CentreLeft); + AddStep("set centre origin", () => container.Origin = Anchor.Centre); + AddStep("set right origin", () => container.Origin = Anchor.CentreRight); + } + + /// + /// Tests hovering over controls expands the parenting container appropriately and does not contract until hover is lost from container. + /// + [Test] + public void TestHoveringControlExpandsContainer() + { + AddAssert("ensure container contracted", () => !container.Expanded.Value); + + AddStep("hover slider", () => InputManager.MoveMouseTo(slider1)); + AddAssert("container expanded", () => container.Expanded.Value); + AddAssert("controls expanded", () => slider1.Expanded.Value && slider2.Expanded.Value); + + AddStep("hover group top", () => InputManager.MoveMouseTo(toolboxGroup.ScreenSpaceDrawQuad.TopLeft + new Vector2(5))); + AddAssert("container still expanded", () => container.Expanded.Value); + AddAssert("controls still expanded", () => slider1.Expanded.Value && slider2.Expanded.Value); + + AddStep("hover away", () => InputManager.MoveMouseTo(Vector2.Zero)); + AddAssert("container contracted", () => !container.Expanded.Value); + AddAssert("controls contracted", () => !slider1.Expanded.Value && !slider2.Expanded.Value); + } + + /// + /// Tests dragging a UI control (e.g. ) outside its parenting container does not contract it until dragging is finished. + /// + [Test] + public void TestDraggingControlOutsideDoesntContractContainer() + { + AddStep("hover slider", () => InputManager.MoveMouseTo(slider1)); + AddAssert("container expanded", () => container.Expanded.Value); + + AddStep("hover slider nub", () => InputManager.MoveMouseTo(slider1.ChildrenOfType().Single())); + AddStep("hold slider nub", () => InputManager.PressButton(MouseButton.Left)); + AddStep("drag outside container", () => InputManager.MoveMouseTo(Vector2.Zero)); + AddAssert("container still expanded", () => container.Expanded.Value); + + AddStep("release slider nub", () => InputManager.ReleaseButton(MouseButton.Left)); + AddAssert("container contracted", () => !container.Expanded.Value); + } + + /// + /// Tests expanding a container will expand underlying groups if contracted. + /// + [Test] + public void TestExpandingContainerExpandsContractedGroup() + { + AddStep("contract group", () => toolboxGroup.Expanded.Value = false); + + AddStep("expand container", () => container.Expanded.Value = true); + AddAssert("group expanded", () => toolboxGroup.Expanded.Value); + AddAssert("controls expanded", () => slider1.Expanded.Value && slider2.Expanded.Value); + + AddStep("contract container", () => container.Expanded.Value = false); + AddAssert("group contracted", () => !toolboxGroup.Expanded.Value); + AddAssert("controls contracted", () => !slider1.Expanded.Value && !slider2.Expanded.Value); + } + + /// + /// Tests contracting a container does not contract underlying groups if expanded by user (i.e. by setting directly). + /// + [Test] + public void TestContractingContainerDoesntContractUserExpandedGroup() + { + AddAssert("ensure group expanded", () => toolboxGroup.Expanded.Value); + + AddStep("expand container", () => container.Expanded.Value = true); + AddAssert("group still expanded", () => toolboxGroup.Expanded.Value); + AddAssert("controls expanded", () => slider1.Expanded.Value && slider2.Expanded.Value); + + AddStep("contract container", () => container.Expanded.Value = false); + AddAssert("group still expanded", () => toolboxGroup.Expanded.Value); + AddAssert("controls contracted", () => !slider1.Expanded.Value && !slider2.Expanded.Value); + } + + /// + /// Tests expanding a container via does not get contracted by losing hover. + /// + [Test] + public void TestExpandingContainerDoesntGetContractedByHover() + { + AddStep("expand container", () => container.Expanded.Value = true); + + AddStep("hover control", () => InputManager.MoveMouseTo(slider1)); + AddAssert("container still expanded", () => container.Expanded.Value); + + AddStep("hover away", () => InputManager.MoveMouseTo(Vector2.Zero)); + AddAssert("container still expanded", () => container.Expanded.Value); + } + + private class TestExpandingContainer : ExpandingControlContainer + { + public TestExpandingContainer() + : base(120, 250) + { + } + } + } +} From 48aa1677dcd28e0477bb84ae17075ba53d46e56e Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 23 Jan 2022 11:01:30 +0800 Subject: [PATCH 0165/5199] Include hit results of nested hit objects in statistics of perfect score --- .../Difficulty/PerformanceBreakdownCalculator.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs index 5cf63d0102..46342b237c 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -1,12 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -64,7 +66,7 @@ namespace osu.Game.Rulesets.Difficulty // create statistics assuming all hit objects have perfect hit result var statistics = beatmap.HitObjects - .Select(ho => ho.CreateJudgement().MaxResult) + .SelectMany(getPerfectHitResults) .GroupBy(hr => hr, (hr, list) => (hitResult: hr, count: list.Count())) .ToDictionary(pair => pair.hitResult, pair => pair.count); perfectPlay.Statistics = statistics; @@ -89,5 +91,13 @@ namespace osu.Game.Rulesets.Difficulty return ruleset.CreatePerformanceCalculator(difficulty.Value.Attributes, perfectPlay)?.Calculate(); }, cancellationToken); } + + private IEnumerable getPerfectHitResults(HitObject hitObject) + { + foreach (HitObject nested in hitObject.NestedHitObjects) + yield return nested.CreateJudgement().MaxResult; + + yield return hitObject.CreateJudgement().MaxResult; + } } } From f53ce5aedfd17d18eaa9a882e14707636a806a92 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 23 Jan 2022 11:11:12 +0800 Subject: [PATCH 0166/5199] Fix max combo calculation in osu diffcalc --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index c5b1baaad1..c80b19e1d3 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -63,8 +63,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty double drainRate = beatmap.Difficulty.DrainRate; int maxCombo = beatmap.HitObjects.Count; - // Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above) - maxCombo += beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1); + // Add the ticks + tail of the slider + // 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above) + // an additional 1 is subtracted if only nested objects are judged because the hit result of the entire slider would not contribute to combo + maxCombo += beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1 - (s.OnlyJudgeNestedObjects ? 1 : 0)); int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle); int sliderCount = beatmap.HitObjects.Count(h => h is Slider); From 44311c1f4e3e18548f9b574de968468d52f8c282 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 23 Jan 2022 11:25:22 +0800 Subject: [PATCH 0167/5199] Add tests for diffcalc max combo --- .../CatchDifficultyCalculatorTest.cs | 12 +++++------ .../ManiaDifficultyCalculatorTest.cs | 12 +++++------ .../OsuDifficultyCalculatorTest.cs | 21 ++++++++++++------- .../TaikoDifficultyCalculatorTest.cs | 16 +++++++------- .../Beatmaps/DifficultyCalculatorTest.cs | 7 +++++-- 5 files changed, 38 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs index 7e8d567fbe..48d46636df 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs @@ -14,13 +14,13 @@ namespace osu.Game.Rulesets.Catch.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Catch"; - [TestCase(4.0505463516206195d, "diffcalc-test")] - public void Test(double expected, string name) - => base.Test(expected, name); + [TestCase(4.0505463516206195d, 127, "diffcalc-test")] + public void Test(double expectedStarRating, int expectedMaxCombo, string name) + => base.Test(expectedStarRating, expectedMaxCombo, name); - [TestCase(5.1696411260785498d, "diffcalc-test")] - public void TestClockRateAdjusted(double expected, string name) - => Test(expected, name, new CatchModDoubleTime()); + [TestCase(5.1696411260785498d, 127, "diffcalc-test")] + public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name) + => Test(expectedStarRating, expectedMaxCombo, name, new CatchModDoubleTime()); protected override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new CatchDifficultyCalculator(new CatchRuleset().RulesetInfo, beatmap); diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs index 6ec49d7634..715614a201 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs @@ -14,13 +14,13 @@ namespace osu.Game.Rulesets.Mania.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; - [TestCase(2.3449735700206298d, "diffcalc-test")] - public void Test(double expected, string name) - => base.Test(expected, name); + [TestCase(2.3449735700206298d, 151, "diffcalc-test")] + public void Test(double expectedStarRating, int expectedMaxCombo, string name) + => base.Test(expectedStarRating, expectedMaxCombo, name); - [TestCase(2.7879104989252959d, "diffcalc-test")] - public void TestClockRateAdjusted(double expected, string name) - => Test(expected, name, new ManiaModDoubleTime()); + [TestCase(2.7879104989252959d, 151, "diffcalc-test")] + public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name) + => Test(expectedStarRating, expectedMaxCombo, name, new ManiaModDoubleTime()); protected override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new ManiaDifficultyCalculator(new ManiaRuleset().RulesetInfo, beatmap); diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index b7984e6995..df577ea8d3 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -15,15 +15,20 @@ namespace osu.Game.Rulesets.Osu.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; - [TestCase(6.6972307565739273d, "diffcalc-test")] - [TestCase(1.4484754139145539d, "zero-length-sliders")] - public void Test(double expected, string name) - => base.Test(expected, name); + [TestCase(6.6972307565739273d, 206, "diffcalc-test")] + [TestCase(1.4484754139145539d, 45, "zero-length-sliders")] + public void Test(double expectedStarRating, int expectedMaxCombo, string name) + => base.Test(expectedStarRating, expectedMaxCombo, name); - [TestCase(8.9382559208689809d, "diffcalc-test")] - [TestCase(1.7548875851757628d, "zero-length-sliders")] - public void TestClockRateAdjusted(double expected, string name) - => Test(expected, name, new OsuModDoubleTime()); + [TestCase(8.9382559208689809d, 206, "diffcalc-test")] + [TestCase(1.7548875851757628d, 45, "zero-length-sliders")] + public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name) + => Test(expectedStarRating, expectedMaxCombo, name, new OsuModDoubleTime()); + + [TestCase(6.6972307218715166d, 239, "diffcalc-test")] + [TestCase(1.4484754139145537d, 54, "zero-length-sliders")] + public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name) + => Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic()); protected override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new OsuDifficultyCalculator(new OsuRuleset().RulesetInfo, beatmap); diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs index 2b1cbc580e..226da7df09 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs @@ -14,15 +14,15 @@ namespace osu.Game.Rulesets.Taiko.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko"; - [TestCase(2.2420075288523802d, "diffcalc-test")] - [TestCase(2.2420075288523802d, "diffcalc-test-strong")] - public void Test(double expected, string name) - => base.Test(expected, name); + [TestCase(2.2420075288523802d, 200, "diffcalc-test")] + [TestCase(2.2420075288523802d, 200, "diffcalc-test-strong")] + public void Test(double expectedStarRating, int expectedMaxCombo, string name) + => base.Test(expectedStarRating, expectedMaxCombo, name); - [TestCase(3.134084469440479d, "diffcalc-test")] - [TestCase(3.134084469440479d, "diffcalc-test-strong")] - public void TestClockRateAdjusted(double expected, string name) - => Test(expected, name, new TaikoModDoubleTime()); + [TestCase(3.134084469440479d, 200, "diffcalc-test")] + [TestCase(3.134084469440479d, 200, "diffcalc-test-strong")] + public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name) + => Test(expectedStarRating, expectedMaxCombo, name, new TaikoModDoubleTime()); protected override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new TaikoDifficultyCalculator(new TaikoRuleset().RulesetInfo, beatmap); diff --git a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs index 9f8811c7f9..ed00c7959b 100644 --- a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs +++ b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs @@ -22,10 +22,13 @@ namespace osu.Game.Tests.Beatmaps protected abstract string ResourceAssembly { get; } - protected void Test(double expected, string name, params Mod[] mods) + protected void Test(double expectedStarRating, int expectedMaxCombo, string name, params Mod[] mods) { + var attributes = CreateDifficultyCalculator(getBeatmap(name)).Calculate(mods); + // 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)); + Assert.That(attributes.StarRating, Is.EqualTo(expectedStarRating).Within(0.00001)); + Assert.That(attributes.MaxCombo, Is.EqualTo(expectedMaxCombo)); } private IWorkingBeatmap getBeatmap(string name) From 74a55ead7711108c4d6b856e11433b476459c35a Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 23 Jan 2022 13:00:54 +0800 Subject: [PATCH 0168/5199] Simplify combo counting logic --- .../Difficulty/OsuDifficultyCalculator.cs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index c80b19e1d3..d04d0872d8 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -9,6 +9,7 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Skills; using osu.Game.Rulesets.Osu.Mods; @@ -62,11 +63,20 @@ namespace osu.Game.Rulesets.Osu.Difficulty double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; double drainRate = beatmap.Difficulty.DrainRate; - int maxCombo = beatmap.HitObjects.Count; - // Add the ticks + tail of the slider - // 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above) - // an additional 1 is subtracted if only nested objects are judged because the hit result of the entire slider would not contribute to combo - maxCombo += beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1 - (s.OnlyJudgeNestedObjects ? 1 : 0)); + int maxCombo = 0; + + void countCombo(HitObject ho) + { + if (ho.CreateJudgement().MaxResult.AffectsCombo()) + maxCombo++; + } + + foreach (HitObject ho in beatmap.HitObjects) + { + countCombo(ho); + foreach (HitObject nested in ho.NestedHitObjects) + countCombo(nested); + } int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle); int sliderCount = beatmap.HitObjects.Count(h => h is Slider); From 1ea5a2e6a75301341355f7a934b6b79a5f282d7c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 23 Jan 2022 10:11:07 +0300 Subject: [PATCH 0169/5199] Fix incorrect assert step name --- osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs b/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs index cedbb5025f..b34974dfc7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("set current to 3/4", () => timeSignature.Current.Value = TimeSignature.SimpleTriple); AddAssert("current is 3/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleTriple)); - AddAssert("numerator is 5", () => numeratorTextBox.Current.Value == "3"); + AddAssert("numerator is 3", () => numeratorTextBox.Current.Value == "3"); } [Test] From e4758c9dbbee726593078de2c111fd243b4db582 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 23 Jan 2022 10:13:32 +0300 Subject: [PATCH 0170/5199] Mark `LabelledTimeSignature` as public --- osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs b/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs index 52aece75ad..66bd341393 100644 --- a/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs +++ b/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs @@ -14,7 +14,7 @@ using osu.Game.Graphics.UserInterfaceV2; namespace osu.Game.Screens.Edit.Timing { - internal class LabelledTimeSignature : LabelledComponent + public class LabelledTimeSignature : LabelledComponent { public LabelledTimeSignature() : base(false) @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Edit.Timing protected override TimeSignatureBox CreateComponent() => new TimeSignatureBox(); - internal class TimeSignatureBox : CompositeDrawable, IHasCurrentValue + public class TimeSignatureBox : CompositeDrawable, IHasCurrentValue { private readonly BindableWithCurrent current = new BindableWithCurrent(TimeSignature.SimpleQuadruple); From a5493ce0d1f70b6db0d9ce846ed14c42f360946e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 17:51:32 +0900 Subject: [PATCH 0171/5199] Fix incorrect nesting of statements causing completely broken logic --- osu.Game/Screens/Menu/IntroScreen.cs | 54 ++++++++++++++-------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 10be90a5f0..e66ecc74e1 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -99,51 +99,51 @@ namespace osu.Game.Screens.Menu { realmContextFactory.Run(realm => { - var sets = realm.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); + var usableBeatmapSets = realm.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); - int setCount = sets.Count; + int setCount = usableBeatmapSets.Count; if (setCount > 0) { - var found = sets[RNG.Next(0, setCount - 1)].Beatmaps.FirstOrDefault(); + var found = usableBeatmapSets[RNG.Next(0, setCount - 1)].Beatmaps.FirstOrDefault(); if (found != null) initialBeatmap = beatmaps.GetWorkingBeatmap(found); } }); + } - // we generally want a song to be playing on startup, so use the intro music even if a user has specified not to if no other track is available. - if (initialBeatmap == null) + // we generally want a song to be playing on startup, so use the intro music even if a user has specified not to if no other track is available. + if (initialBeatmap == null) + { + if (!loadThemedIntro()) { - if (!loadThemedIntro()) - { - // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state. - // this could happen if a user has nuked their files store. for now, reimport to repair this. - var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).GetResultSafely(); + // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state. + // this could happen if a user has nuked their files store. for now, reimport to repair this. + var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).GetResultSafely(); - import?.PerformWrite(b => b.Protected = true); + import?.PerformWrite(b => b.Protected = true); - loadThemedIntro(); - } + loadThemedIntro(); } + } - bool loadThemedIntro() + bool loadThemedIntro() + { + var setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapHash); + + if (setInfo == null) + return false; + + setInfo.PerformRead(s => { - var setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapHash); + if (s.Beatmaps.Count == 0) + return; - if (setInfo == null) - return false; + initialBeatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps.First()); + }); - setInfo.PerformRead(s => - { - if (s.Beatmaps.Count == 0) - return; - - initialBeatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps.First()); - }); - - return UsingThemedIntro = initialBeatmap != null; - } + return UsingThemedIntro = initialBeatmap != null; } } From 70a120ea8a5e7694c068624b6495fa857f95a4e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 18:00:24 +0900 Subject: [PATCH 0172/5199] Add missing lock coverage when using `subscriptionActions` dictionary --- osu.Game/Database/RealmContextFactory.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 6fb9a2996b..606871337c 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -249,11 +249,13 @@ namespace osu.Game.Database return new InvokeOnDisposal(() => { - // TODO: this likely needs to be run on the update thread. - if (subscriptionActions.TryGetValue(action, out var unsubscriptionAction)) + lock (contextLock) { - unsubscriptionAction?.Dispose(); - subscriptionActions.Remove(action); + if (subscriptionActions.TryGetValue(action, out var unsubscriptionAction)) + { + unsubscriptionAction?.Dispose(); + subscriptionActions.Remove(action); + } } }); } From bd0eda7e908e7366198e0007d6db9434faded441 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 18:01:39 +0900 Subject: [PATCH 0173/5199] Use method instead of property for realm query retrieval --- .../Bindings/DatabasedKeyBindingContainer.cs | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 5a9797ffce..ac33c64391 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -46,19 +46,16 @@ namespace osu.Game.Input.Bindings throw new InvalidOperationException($"{nameof(variant)} can not be null when a non-null {nameof(ruleset)} is provided."); } - private IQueryable realmKeyBindings + private IQueryable queryRealmKeyBindings() { - get - { - string rulesetName = ruleset?.ShortName; - return realmFactory.Context.All() - .Where(b => b.RulesetName == rulesetName && b.Variant == variant); - } + string rulesetName = ruleset?.ShortName; + return realmFactory.Context.All() + .Where(b => b.RulesetName == rulesetName && b.Variant == variant); } protected override void LoadComplete() { - realmSubscription = realmFactory.Register(realm => realmKeyBindings + realmSubscription = realmFactory.Register(realm => queryRealmKeyBindings() .QueryAsyncWithNotifications((sender, changes, error) => { // The first fire of this is a bit redundant as this is being called in base.LoadComplete, @@ -80,11 +77,11 @@ namespace osu.Game.Input.Bindings { var defaults = DefaultKeyBindings.ToList(); - List newBindings = realmKeyBindings.Detach() - // this ordering is important to ensure that we read entries from the database in the order - // enforced by DefaultKeyBindings. allow for song select to handle actions that may otherwise - // have been eaten by the music controller due to query order. - .OrderBy(b => defaults.FindIndex(d => (int)d.Action == b.ActionInt)).ToList(); + List newBindings = queryRealmKeyBindings().Detach() + // this ordering is important to ensure that we read entries from the database in the order + // enforced by DefaultKeyBindings. allow for song select to handle actions that may otherwise + // have been eaten by the music controller due to query order. + .OrderBy(b => defaults.FindIndex(d => (int)d.Action == b.ActionInt)).ToList(); // In the case no bindings were found in the database, presume this usage is for a non-databased ruleset. // This actually should never be required and can be removed if it is ever deemed to cause a problem. From f39ff1eacb3b5179f70aa47f0a892d23e2651e7d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 18:13:28 +0900 Subject: [PATCH 0174/5199] Add unregistration on blocking This is the first part of the requirement of sending a `ChangeSet` event to ensure correct state during blocking time --- osu.Game/Database/RealmContextFactory.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 606871337c..3e0f278e30 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -247,6 +247,8 @@ namespace osu.Game.Database registerSubscription(action); + // This token is returned to the consumer only. + // It will cause the registration to be permanently removed. return new InvokeOnDisposal(() => { lock (contextLock) @@ -269,12 +271,27 @@ namespace osu.Game.Database lock (contextLock) { + Debug.Assert(!customSubscriptionActions.TryGetValue(action, out var found) || found == null); + current_thread_subscriptions_allowed.Value = true; subscriptionActions[action] = action(realm); current_thread_subscriptions_allowed.Value = false; } } + /// + /// Unregister all subscriptions when the realm context is to be recycled. + /// Subscriptions will still remain and will be re-subscribed when the realm context returns. + /// + private void unregisterAllSubscriptions() + { + foreach (var action in subscriptionActions) + { + action.Value?.Dispose(); + subscriptionActions[action.Key] = null; + } + } + private Realm createContext() { if (isDisposed) @@ -519,6 +536,7 @@ namespace osu.Game.Database throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); syncContext = SynchronizationContext.Current; + unregisterAllSubscriptions(); Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); From 61cef42be977be4fe3cd612122de1f52556e03d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 19:42:26 +0900 Subject: [PATCH 0175/5199] Proof of concept realm subscriptions via `Register` --- osu.Game/Database/EmptyRealmSet.cs | 74 ++++++++++++++++++++ osu.Game/Database/RealmContextFactory.cs | 33 +++++++-- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 +- osu.Game/Screens/Spectate/SpectatorScreen.cs | 21 +++--- 4 files changed, 112 insertions(+), 20 deletions(-) create mode 100644 osu.Game/Database/EmptyRealmSet.cs diff --git a/osu.Game/Database/EmptyRealmSet.cs b/osu.Game/Database/EmptyRealmSet.cs new file mode 100644 index 0000000000..2fecfcbe07 --- /dev/null +++ b/osu.Game/Database/EmptyRealmSet.cs @@ -0,0 +1,74 @@ +// 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; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using Realms; +using Realms.Schema; + +#nullable enable + +namespace osu.Game.Database +{ + public class EmptyRealmSet : IRealmCollection + { + private static List emptySet => new List(); + + public IEnumerator GetEnumerator() + { + return emptySet.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)emptySet).GetEnumerator(); + } + + public int Count => emptySet.Count; + + public T this[int index] => emptySet[index]; + + public event NotifyCollectionChangedEventHandler? CollectionChanged + { + add => throw new NotImplementedException(); + remove => throw new NotImplementedException(); + } + + public event PropertyChangedEventHandler? PropertyChanged + { + add => throw new NotImplementedException(); + remove => throw new NotImplementedException(); + } + + public int IndexOf(object item) + { + return emptySet.IndexOf((T)item); + } + + public bool Contains(object item) + { + return emptySet.Contains((T)item); + } + + public IRealmCollection Freeze() + { + throw new NotImplementedException(); + } + + public IDisposable SubscribeForNotifications(NotificationCallbackDelegate callback) + { + throw new NotImplementedException(); + } + + public bool IsValid => throw new NotImplementedException(); + + public Realm Realm => throw new NotImplementedException(); + + public ObjectSchema ObjectSchema => throw new NotImplementedException(); + + public bool IsFrozen => throw new NotImplementedException(); + } +} diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 3e0f278e30..32f7ac99c1 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -84,7 +84,7 @@ namespace osu.Game.Database Logger.Log(@$"Opened realm ""{context.Config.DatabasePath}"" at version {context.Config.SchemaVersion}"); // Resubscribe any subscriptions - foreach (var action in subscriptionActions.Keys) + foreach (var action in customSubscriptionActions.Keys) registerSubscription(action); } @@ -233,7 +233,22 @@ namespace osu.Game.Database } } - private readonly Dictionary, IDisposable?> subscriptionActions = new Dictionary, IDisposable?>(); + private readonly Dictionary, IDisposable?> customSubscriptionActions = new Dictionary, IDisposable?>(); + + private readonly Dictionary, Action> realmSubscriptionsResetMap = new Dictionary, Action>(); + + public IDisposable Register(Func> query, NotificationCallbackDelegate onChanged) + where T : RealmObjectBase + { + if (!ThreadSafety.IsUpdateThread) + throw new InvalidOperationException(@$"{nameof(Register)} must be called from the update thread."); + + realmSubscriptionsResetMap.Add(action, () => onChanged(new EmptyRealmSet(), null, null)); + + return Register(action); + + IDisposable? action(Realm realm) => query(realm).QueryAsyncWithNotifications(onChanged); + } /// /// Run work on realm that will be run every time the update thread realm context gets recycled. @@ -253,10 +268,11 @@ namespace osu.Game.Database { lock (contextLock) { - if (subscriptionActions.TryGetValue(action, out var unsubscriptionAction)) + if (customSubscriptionActions.TryGetValue(action, out var unsubscriptionAction)) { unsubscriptionAction?.Dispose(); - subscriptionActions.Remove(action); + customSubscriptionActions.Remove(action); + realmSubscriptionsResetMap.Remove(action); } } }); @@ -274,7 +290,7 @@ namespace osu.Game.Database Debug.Assert(!customSubscriptionActions.TryGetValue(action, out var found) || found == null); current_thread_subscriptions_allowed.Value = true; - subscriptionActions[action] = action(realm); + customSubscriptionActions[action] = action(realm); current_thread_subscriptions_allowed.Value = false; } } @@ -285,10 +301,13 @@ namespace osu.Game.Database /// private void unregisterAllSubscriptions() { - foreach (var action in subscriptionActions) + foreach (var action in realmSubscriptionsResetMap.Values) + action(); + + foreach (var action in customSubscriptionActions) { action.Value?.Dispose(); - subscriptionActions[action.Key] = null; + customSubscriptionActions[action.Key] = null; } } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 6f3f467170..10ba23985e 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -190,7 +190,7 @@ namespace osu.Game.Screens.Select { base.LoadComplete(); - subscriptionSets = realmFactory.Register(realm => getBeatmapSets(realm).QueryAsyncWithNotifications(beatmapSetsChanged)); + subscriptionSets = realmFactory.Register(getBeatmapSets, beatmapSetsChanged); subscriptionBeatmaps = realmFactory.Register(realm => realm.All().Where(b => !b.Hidden).QueryAsyncWithNotifications(beatmapsChanged)); // Can't use main subscriptions because we can't lookup deleted indices. @@ -274,7 +274,7 @@ namespace osu.Game.Screens.Select } } - private IRealmCollection getBeatmapSets(Realm realm) => realm.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); + private IQueryable getBeatmapSets(Realm realm) => realm.All().Where(s => !s.DeletePending && !s.Protected); public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => removeBeatmapSet(beatmapSet.ID); diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 9fa5bb8562..904648f727 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -17,6 +17,7 @@ using osu.Game.Online.Spectator; using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Scoring; +using Realms; namespace osu.Game.Screens.Spectate { @@ -79,23 +80,21 @@ namespace osu.Game.Screens.Spectate playingUserStates.BindTo(spectatorClient.PlayingUserStates); playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true); - realmSubscription = realmContextFactory.Register(realm => - realm.All() - .Where(s => !s.DeletePending) - .QueryAsyncWithNotifications((items, changes, ___) => - { - if (changes?.InsertedIndices == null) - return; - - foreach (int c in changes.InsertedIndices) - beatmapUpdated(items[c]); - })); + realmSubscription = realmContextFactory.Register( + realm => realm.All().Where(s => !s.DeletePending), beatmapsChanged); foreach ((int id, var _) in userMap) spectatorClient.WatchUser(id); })); } + private void beatmapsChanged(IRealmCollection items, ChangeSet changes, Exception ___) + { + if (changes?.InsertedIndices == null) return; + + foreach (int c in changes.InsertedIndices) beatmapUpdated(items[c]); + } + private void beatmapUpdated(BeatmapSetInfo beatmapSet) { foreach ((int userId, _) in userMap) From e9e3e024a19d4867673152efa06f89842937d032 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 19:50:29 +0900 Subject: [PATCH 0176/5199] Update all usages of `QueryAsyncWithNotifications` to use new `Register` pathway --- .../Bindings/DatabasedKeyBindingContainer.cs | 13 +++++----- osu.Game/Online/BeatmapDownloadTracker.cs | 4 ++-- .../OnlinePlayBeatmapAvailabilityTracker.cs | 4 ++-- osu.Game/Online/ScoreDownloadTracker.cs | 4 ++-- osu.Game/Overlays/MusicController.cs | 2 +- .../Overlays/Settings/Sections/SkinSection.cs | 19 +++++++-------- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 ++--- .../Screens/Select/Carousel/TopLocalRank.cs | 24 +++++++++---------- .../Select/Leaderboards/BeatmapLeaderboard.cs | 14 +++++------ 9 files changed, 44 insertions(+), 46 deletions(-) diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index ac33c64391..4ad5693867 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -55,13 +55,12 @@ namespace osu.Game.Input.Bindings protected override void LoadComplete() { - realmSubscription = realmFactory.Register(realm => queryRealmKeyBindings() - .QueryAsyncWithNotifications((sender, changes, error) => - { - // The first fire of this is a bit redundant as this is being called in base.LoadComplete, - // but this is safest in case the subscription is restored after a context recycle. - ReloadMappings(); - })); + realmSubscription = realmFactory.Register(realm => queryRealmKeyBindings(), (sender, changes, error) => + { + // The first fire of this is a bit redundant as this is being called in base.LoadComplete, + // but this is safest in case the subscription is restored after a context recycle. + ReloadMappings(); + }); base.LoadComplete(); } diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index d50ab5a347..70599a167b 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -42,7 +42,7 @@ namespace osu.Game.Online // Used to interact with manager classes that don't support interface types. Will eventually be replaced. var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID }; - realmSubscription = realmContextFactory.Register(realm => realm.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending).QueryAsyncWithNotifications((items, changes, ___) => + realmSubscription = realmContextFactory.Register(realm => realm.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); @@ -54,7 +54,7 @@ namespace osu.Game.Online attachDownload(Downloader.GetExistingDownload(beatmapSetInfo)); }); } - })); + }); } private void downloadBegan(ArchiveDownloadRequest request) => Schedule(() => diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index fdcf2b39c6..8dd28f5417 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -78,13 +78,13 @@ namespace osu.Game.Online.Rooms // handles changes to hash that didn't occur from the import process (ie. a user editing the beatmap in the editor, somehow). realmSubscription?.Dispose(); - realmSubscription = realmContextFactory.Register(realm => filteredBeatmaps().QueryAsyncWithNotifications((items, changes, ___) => + realmSubscription = realmContextFactory.Register(realm => filteredBeatmaps(), (items, changes, ___) => { if (changes == null) return; Scheduler.AddOnce(updateAvailability); - })); + }); }, true); } diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index de1d6fd94a..72e95bd6df 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -47,7 +47,7 @@ namespace osu.Game.Online Downloader.DownloadBegan += downloadBegan; Downloader.DownloadFailed += downloadFailed; - realmSubscription = realmContextFactory.Register(realm => realm.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending).QueryAsyncWithNotifications((items, changes, ___) => + realmSubscription = realmContextFactory.Register(realm => realm.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending), (items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); @@ -59,7 +59,7 @@ namespace osu.Game.Online attachDownload(Downloader.GetExistingDownload(scoreInfo)); }); } - })); + }); } private void downloadBegan(ArchiveDownloadRequest request) => Schedule(() => diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 01a2c9d354..24d907285a 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -93,7 +93,7 @@ namespace osu.Game.Overlays foreach (var s in availableBeatmaps) beatmapSets.Add(s.Detach()); - beatmapSubscription = realmFactory.Register(realm => availableBeatmaps.QueryAsyncWithNotifications(beatmapsChanged)); + beatmapSubscription = realmFactory.Register(realm => availableBeatmaps, beatmapsChanged); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 11a7275168..c767edec71 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Settings.Sections private IDisposable realmSubscription; - private IQueryable realmSkins => + private IQueryable queryRealmSkins() => realmFactory.Context.All() .Where(s => !s.DeletePending) .OrderByDescending(s => s.Protected) // protected skins should be at the top. @@ -83,13 +83,12 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown.Current = dropdownBindable; - realmSubscription = realmFactory.Register(realm => realmSkins - .QueryAsyncWithNotifications((sender, changes, error) => - { - // The first fire of this is a bit redundant due to the call below, - // but this is safest in case the subscription is restored after a context recycle. - updateItems(); - })); + realmSubscription = realmFactory.Register(realm => queryRealmSkins(), (sender, changes, error) => + { + // The first fire of this is a bit redundant due to the call below, + // but this is safest in case the subscription is restored after a context recycle. + updateItems(); + }); updateItems(); @@ -129,9 +128,9 @@ namespace osu.Game.Overlays.Settings.Sections private void updateItems() { - int protectedCount = realmSkins.Count(s => s.Protected); + int protectedCount = queryRealmSkins().Count(s => s.Protected); - skinItems = realmSkins.ToLive(realmFactory); + skinItems = queryRealmSkins().ToLive(realmFactory); skinItems.Insert(protectedCount, random_skin_info); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 10ba23985e..889a6f5b79 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -191,12 +191,12 @@ namespace osu.Game.Screens.Select base.LoadComplete(); subscriptionSets = realmFactory.Register(getBeatmapSets, beatmapSetsChanged); - subscriptionBeatmaps = realmFactory.Register(realm => realm.All().Where(b => !b.Hidden).QueryAsyncWithNotifications(beatmapsChanged)); + subscriptionBeatmaps = realmFactory.Register(realm => realm.All().Where(b => !b.Hidden), beatmapsChanged); // Can't use main subscriptions because we can't lookup deleted indices. // https://github.com/realm/realm-dotnet/discussions/2634#discussioncomment-1605595. - subscriptionDeletedSets = realmFactory.Register(realm => realm.All().Where(s => s.DeletePending && !s.Protected).QueryAsyncWithNotifications(deletedBeatmapSetsChanged)); - subscriptionHiddenBeatmaps = realmFactory.Register(realm => realm.All().Where(b => b.Hidden).QueryAsyncWithNotifications(beatmapsChanged)); + subscriptionDeletedSets = realmFactory.Register(realm => realm.All().Where(s => s.DeletePending && !s.Protected), deletedBeatmapSetsChanged); + subscriptionHiddenBeatmaps = realmFactory.Register(realm => realm.All().Where(b => b.Hidden), beatmapsChanged); } private void deletedBeatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index ee3930364b..fc2188e597 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -49,18 +49,18 @@ namespace osu.Game.Screens.Select.Carousel { scoreSubscription?.Dispose(); scoreSubscription = realmFactory.Register(realm => - realm.All() - .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" - + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" - + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" - + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName) - .OrderByDescending(s => s.TotalScore) - .QueryAsyncWithNotifications((items, changes, ___) => - { - Rank = items.FirstOrDefault()?.Rank; - // Required since presence is changed via IsPresent override - Invalidate(Invalidation.Presence); - })); + realm.All() + .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" + + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" + + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" + + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName) + .OrderByDescending(s => s.TotalScore), + (items, changes, ___) => + { + Rank = items.FirstOrDefault()?.Rank; + // Required since presence is changed via IsPresent override + Invalidate(Invalidation.Presence); + }); }, true); } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 954c2a6413..463f878e2a 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -114,13 +114,13 @@ namespace osu.Game.Screens.Select.Leaderboards return; scoreSubscription = realmFactory.Register(realm => - realm.All() - .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID) - .QueryAsyncWithNotifications((_, changes, ___) => - { - if (!IsOnlineScope) - RefreshScores(); - })); + realm.All() + .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID), + (_, changes, ___) => + { + if (!IsOnlineScope) + RefreshScores(); + }); } protected override void Reset() From db8639435538108982acffad2d9e8da710dd71e7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 20:27:35 +0900 Subject: [PATCH 0177/5199] Fix `TestResources` returning a test `BeatmapSetInfo` that can't be laoded directly into realm --- osu.Game.Tests/Resources/TestResources.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index d2cab09ac9..81b624f908 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -80,7 +80,10 @@ namespace osu.Game.Tests.Resources public static BeatmapSetInfo CreateTestBeatmapSetInfo(int? difficultyCount = null, RulesetInfo[] rulesets = null) { int j = 0; - RulesetInfo getRuleset() => rulesets?[j++ % rulesets.Length] ?? new OsuRuleset().RulesetInfo; + + rulesets ??= new[] { new OsuRuleset().RulesetInfo }; + + RulesetInfo getRuleset() => rulesets?[j++ % rulesets.Length]; int setId = Interlocked.Increment(ref importId); From 0709a2ac9be3376193a4aeb266b497683b1bde7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 20:28:13 +0900 Subject: [PATCH 0178/5199] Add test coverage of realm subscription scenarios --- .../RealmSubscriptionRegistrationTests.cs | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs new file mode 100644 index 0000000000..16e9f31c7b --- /dev/null +++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs @@ -0,0 +1,138 @@ +// 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; +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Tests.Resources; +using Realms; + +#nullable enable + +namespace osu.Game.Tests.Database +{ + [TestFixture] + public class RealmSubscriptionRegistrationTests : RealmTest + { + [Test] + public void TestSubscriptionWithContextLoss() + { + IEnumerable? resolvedItems = null; + ChangeSet? lastChanges = null; + + RunTestWithRealm((realmFactory, _) => + { + realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + + var registration = realmFactory.Register(realm => realm.All(), onChanged); + + testEventsArriving(true); + + // All normal until here. + // Now let's yank the main realm context. + resolvedItems = null; + lastChanges = null; + + using (realmFactory.BlockAllOperations()) + Assert.That(resolvedItems, Is.Empty); + + realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + + testEventsArriving(true); + + // Now let's try unsubscribing. + resolvedItems = null; + lastChanges = null; + + registration.Dispose(); + + realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + + testEventsArriving(false); + + // And make sure even after another context loss we don't get firings. + using (realmFactory.BlockAllOperations()) + Assert.That(resolvedItems, Is.Null); + + realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + + testEventsArriving(false); + + void testEventsArriving(bool shouldArrive) + { + realmFactory.Run(realm => realm.Refresh()); + + if (shouldArrive) + Assert.That(resolvedItems, Has.One.Items); + else + Assert.That(resolvedItems, Is.Null); + + realmFactory.Write(realm => + { + realm.RemoveAll(); + realm.RemoveAll(); + }); + + realmFactory.Run(realm => realm.Refresh()); + + if (shouldArrive) + Assert.That(lastChanges?.DeletedIndices, Has.One.Items); + else + Assert.That(lastChanges, Is.Null); + } + }); + + void onChanged(IRealmCollection sender, ChangeSet? changes, Exception error) + { + if (changes == null) + resolvedItems = sender; + + lastChanges = changes; + } + } + + [Test] + public void TestCustomRegisterWithContextLoss() + { + RunTestWithRealm((realmFactory, _) => + { + BeatmapSetInfo? beatmapSetInfo = null; + + realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + + var subscription = realmFactory.Register(realm => + { + beatmapSetInfo = realm.All().First(); + + return new InvokeOnDisposal(() => beatmapSetInfo = null); + }); + + Assert.That(beatmapSetInfo, Is.Not.Null); + + using (realmFactory.BlockAllOperations()) + { + // custom disposal action fired when context lost. + Assert.That(beatmapSetInfo, Is.Null); + } + + // re-registration after context restore. + realmFactory.Run(realm => realm.Refresh()); + Assert.That(beatmapSetInfo, Is.Not.Null); + + subscription.Dispose(); + + Assert.That(beatmapSetInfo, Is.Null); + + using (realmFactory.BlockAllOperations()) + Assert.That(beatmapSetInfo, Is.Null); + + realmFactory.Run(realm => realm.Refresh()); + Assert.That(beatmapSetInfo, Is.Null); + }); + } + } +} From 5e7993c35afaae58a3dec41dddd0463b4e07a53a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 20:38:34 +0900 Subject: [PATCH 0179/5199] Post disposal to synchronisation context --- osu.Game/Database/RealmContextFactory.cs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 32f7ac99c1..26943c1951 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -260,19 +260,29 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException(@$"{nameof(Register)} must be called from the update thread."); + var syncContext = SynchronizationContext.Current; + registerSubscription(action); // This token is returned to the consumer only. // It will cause the registration to be permanently removed. return new InvokeOnDisposal(() => { - lock (contextLock) + if (ThreadSafety.IsUpdateThread) + unsubscribe(); + else + syncContext.Post(_ => unsubscribe(), null); + + void unsubscribe() { - if (customSubscriptionActions.TryGetValue(action, out var unsubscriptionAction)) + lock (contextLock) { - unsubscriptionAction?.Dispose(); - customSubscriptionActions.Remove(action); - realmSubscriptionsResetMap.Remove(action); + if (customSubscriptionActions.TryGetValue(action, out var unsubscriptionAction)) + { + unsubscriptionAction?.Dispose(); + customSubscriptionActions.Remove(action); + realmSubscriptionsResetMap.Remove(action); + } } } }); From 249f0f9697f828649c1734eeec281e0b0876ba77 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 23:15:39 +0900 Subject: [PATCH 0180/5199] Add more lengthy comment explaining cyclic avoidance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Database/RealmContextFactory.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 26943c1951..c8fa298f91 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -292,7 +292,9 @@ namespace osu.Game.Database { Debug.Assert(ThreadSafety.IsUpdateThread); - // Get context outside of flag update to ensure beyond doubt this can't be cyclic. + // Retrieve context outside of flag update to ensure that the context is constructed, + // as attempting to access it inside the subscription if it's not constructed would lead to + // cyclic invocations of the subscription callback. var realm = Context; lock (contextLock) From deb167086212faa8d6c51fe911bcd54c1cc85c73 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 23:17:33 +0900 Subject: [PATCH 0181/5199] Use `Array.Empty` instead of constructed list --- osu.Game/Database/EmptyRealmSet.cs | 42 +++++------------------------- 1 file changed, 7 insertions(+), 35 deletions(-) diff --git a/osu.Game/Database/EmptyRealmSet.cs b/osu.Game/Database/EmptyRealmSet.cs index 2fecfcbe07..b7f27ba035 100644 --- a/osu.Game/Database/EmptyRealmSet.cs +++ b/osu.Game/Database/EmptyRealmSet.cs @@ -15,21 +15,14 @@ namespace osu.Game.Database { public class EmptyRealmSet : IRealmCollection { - private static List emptySet => new List(); - - public IEnumerator GetEnumerator() - { - return emptySet.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)emptySet).GetEnumerator(); - } + private IList emptySet => Array.Empty(); + public IEnumerator GetEnumerator() => emptySet.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => emptySet.GetEnumerator(); public int Count => emptySet.Count; - public T this[int index] => emptySet[index]; + public int IndexOf(object item) => emptySet.IndexOf((T)item); + public bool Contains(object item) => emptySet.Contains((T)item); public event NotifyCollectionChangedEventHandler? CollectionChanged { @@ -43,32 +36,11 @@ namespace osu.Game.Database remove => throw new NotImplementedException(); } - public int IndexOf(object item) - { - return emptySet.IndexOf((T)item); - } - - public bool Contains(object item) - { - return emptySet.Contains((T)item); - } - - public IRealmCollection Freeze() - { - throw new NotImplementedException(); - } - - public IDisposable SubscribeForNotifications(NotificationCallbackDelegate callback) - { - throw new NotImplementedException(); - } - + public IRealmCollection Freeze() => throw new NotImplementedException(); + public IDisposable SubscribeForNotifications(NotificationCallbackDelegate callback) => throw new NotImplementedException(); public bool IsValid => throw new NotImplementedException(); - public Realm Realm => throw new NotImplementedException(); - public ObjectSchema ObjectSchema => throw new NotImplementedException(); - public bool IsFrozen => throw new NotImplementedException(); } } From 351c766ea1f211b8eebe8ff58cb7d5fd0e813739 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 23:20:03 +0900 Subject: [PATCH 0182/5199] Fix one remaining instance of realm query as property --- osu.Game/Overlays/MusicController.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 24d907285a..8caa69e78c 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -80,9 +80,10 @@ namespace osu.Game.Overlays mods.BindValueChanged(_ => ResetTrackAdjustments(), true); } - private IQueryable availableBeatmaps => realmFactory.Context - .All() - .Where(s => !s.DeletePending); + private IQueryable queryRealmBeatmapSets() => + realmFactory.Context + .All() + .Where(s => !s.DeletePending); protected override void LoadComplete() { @@ -90,10 +91,10 @@ namespace osu.Game.Overlays // ensure we're ready before completing async load. // probably not a good way of handling this (as there is a period we aren't watching for changes until the realm subscription finishes up. - foreach (var s in availableBeatmaps) + foreach (var s in queryRealmBeatmapSets()) beatmapSets.Add(s.Detach()); - beatmapSubscription = realmFactory.Register(realm => availableBeatmaps, beatmapsChanged); + beatmapSubscription = realmFactory.Register(realm => queryRealmBeatmapSets(), beatmapsChanged); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) From 4e5a1f27a8c35f462385dfecdeffe9e11d8c14b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 15:14:53 +0100 Subject: [PATCH 0183/5199] Initialise `Simple{Triple,Quadruple}` only once ever rather than create every time --- osu.Game/Beatmaps/Timing/TimeSignature.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Timing/TimeSignature.cs b/osu.Game/Beatmaps/Timing/TimeSignature.cs index 5bfeea5e9b..eebbcc34cd 100644 --- a/osu.Game/Beatmaps/Timing/TimeSignature.cs +++ b/osu.Game/Beatmaps/Timing/TimeSignature.cs @@ -27,8 +27,8 @@ namespace osu.Game.Beatmaps.Timing Numerator = numerator; } - public static TimeSignature SimpleTriple => new TimeSignature(3); - public static TimeSignature SimpleQuadruple => new TimeSignature(4); + public static TimeSignature SimpleTriple { get; } = new TimeSignature(3); + public static TimeSignature SimpleQuadruple { get; } = new TimeSignature(4); public override string ToString() => $"{Numerator}/4"; From bd748686fad63ccb40f13ae9498a53f2c3f47570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 15:20:51 +0100 Subject: [PATCH 0184/5199] Adjust spacing of time signature numerator input box --- osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs b/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs index 66bd341393..51b58bd3dc 100644 --- a/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs +++ b/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs @@ -57,8 +57,12 @@ namespace osu.Game.Screens.Edit.Timing { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Margin = new MarginPadding(10), - Text = "/4", + Margin = new MarginPadding + { + Left = 5, + Right = CONTENT_PADDING_HORIZONTAL + }, + Text = "/ 4", Font = OsuFont.Default.With(size: 20) } } From e236f5d604ee30d92fffccac17702ea4d341abc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 20:28:19 +0100 Subject: [PATCH 0185/5199] Add failing test coverage for correct beatmap filename generation on save --- osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index f89be0adf3..bf3b46c6f7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -46,6 +46,7 @@ namespace osu.Game.Tests.Visual.Editing editorBeatmap.BeatmapInfo.Metadata.Artist = "artist"; editorBeatmap.BeatmapInfo.Metadata.Title = "title"; }); + AddStep("Set author", () => editorBeatmap.BeatmapInfo.Metadata.Author.Username = "author"); AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName = "difficulty"); AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint())); @@ -64,6 +65,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("Save", () => InputManager.Keys(PlatformAction.Save)); checkMutations(); + AddAssert("Beatmap has correct .osu file path", () => editorBeatmap.BeatmapInfo.Path == "artist - title (author) [difficulty].osu"); AddStep("Exit", () => InputManager.Key(Key.Escape)); @@ -88,6 +90,7 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1); AddAssert("Beatmap has correct overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty == 7); AddAssert("Beatmap has correct metadata", () => editorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && editorBeatmap.BeatmapInfo.Metadata.Title == "title"); + AddAssert("Beatmap has correct author", () => editorBeatmap.BeatmapInfo.Metadata.Author.Username == "author"); AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName == "difficulty"); } } From 838a9f69ed078e6e954ca393e1aa5d6fee9ac4b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 19:41:41 +0100 Subject: [PATCH 0186/5199] Fix saved beatmap filename depending on `ToString()` implementation --- osu.Game/Beatmaps/BeatmapModelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 44d6af5b73..ead86c1059 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -88,7 +88,7 @@ namespace osu.Game.Beatmaps private static string getFilename(BeatmapInfo beatmapInfo) { var metadata = beatmapInfo.Metadata; - return $"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.DifficultyName}].osu".GetValidArchiveContentFilename(); + return $"{metadata.Artist} - {metadata.Title} ({metadata.Author.Username}) [{beatmapInfo.DifficultyName}].osu".GetValidArchiveContentFilename(); } /// From ed84ae0ac0d8f57f66ee7e2360b3259e6a786e2c Mon Sep 17 00:00:00 2001 From: mk-56 Date: Mon, 24 Jan 2022 00:42:43 +0100 Subject: [PATCH 0187/5199] Adjust values to Bdach's refined taste --- .../Mods/CatchModFlashlight.cs | 22 ++++++++++++------- .../Mods/ManiaModFlashlight.cs | 20 +++++++++++------ .../Mods/OsuModFlashlight.cs | 22 ++++++++++++------- .../Mods/TaikoModFlashlight.cs | 22 ++++++++++++------- osu.Game/Rulesets/Mods/ModFlashlight.cs | 13 +++++++---- 5 files changed, 64 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index f75772b04e..e8f1ebdd10 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -26,14 +26,20 @@ namespace osu.Game.Rulesets.Catch.Mods [SettingSource("Initial radius", "Initial radius of the flashlight area.")] public override BindableNumber InitialRadius { get; } = new BindableNumber { - MinValue = 150f, - MaxValue = 600f, - Default = 350f, - Value = 350f, - Precision = 5f + MinValue = 0.4f, + MaxValue = 1.7f, + Default = 1f, + Value = 1f, + Precision = 0.1f }; - public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value); + protected override BindableNumber ModeMultiplier { get; } = new BindableNumber + { + Default = 350, + Value = 350, + }; + + public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, ModeMultiplier.Value); private CatchPlayfield playfield; @@ -47,8 +53,8 @@ namespace osu.Game.Rulesets.Catch.Mods { private readonly CatchPlayfield playfield; - public CatchFlashlight(CatchPlayfield playfield, bool isRadiusBasedOnCombo, float initialRadius) - : base(isRadiusBasedOnCombo, initialRadius) + public CatchFlashlight(CatchPlayfield playfield, bool isRadiusBasedOnCombo, float initialRadius, float modeMultiplier) + : base(isRadiusBasedOnCombo, initialRadius, modeMultiplier) { this.playfield = playfield; FlashlightSize = new Vector2(0, GetRadiusFor(0)); diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index a6a3c3be73..9ca1a72584 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -28,20 +28,26 @@ namespace osu.Game.Rulesets.Mania.Mods public override BindableNumber InitialRadius { get; } = new BindableNumber { MinValue = 0f, - MaxValue = 230f, - Default = 50f, - Value = 50f, - Precision = 5f + MaxValue = 4.5f, + Default = 1f, + Value = 1f, + Precision = 0.1f }; - public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value); + protected override BindableNumber ModeMultiplier { get; } = new BindableNumber + { + Default = 50, + Value = 50, + }; + + public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value, ModeMultiplier.Value); private class ManiaFlashlight : Flashlight { private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); - public ManiaFlashlight(bool isRadiusBasedOnCombo, float initialRadius) - : base(isRadiusBasedOnCombo, initialRadius) + public ManiaFlashlight(bool isRadiusBasedOnCombo, float initialRadius, float modeMultiplier) + : base(isRadiusBasedOnCombo, initialRadius, modeMultiplier) { FlashlightSize = new Vector2(DrawWidth, GetRadiusFor(0)); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index e2a6d0f0dc..c6cf5ce4b5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -40,16 +40,22 @@ namespace osu.Game.Rulesets.Osu.Mods [SettingSource("Initial radius", "Initial radius of the flashlight area.")] public override BindableNumber InitialRadius { get; } = new BindableNumber { - MinValue = 90f, - MaxValue = 360f, - Default = 180f, - Value = 180f, - Precision = 5f + MinValue = 0.5f, + MaxValue = 2f, + Default = 1f, + Value = 1f, + Precision = 0.1f + }; + + protected override BindableNumber ModeMultiplier { get; } = new BindableNumber + { + Default = 180, + Value = 180, }; private OsuFlashlight flashlight; - public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value); + public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value, ModeMultiplier.Value); public void ApplyToDrawableHitObject(DrawableHitObject drawable) { @@ -64,8 +70,8 @@ namespace osu.Game.Rulesets.Osu.Mods //public float InitialRadius { private get; set; } public bool ChangeRadius { private get; set; } - public OsuFlashlight(bool isRadiusBasedOnCombo, float initialRadius, double followDelay) - : base(isRadiusBasedOnCombo, initialRadius) + public OsuFlashlight(bool isRadiusBasedOnCombo, float initialRadius, double followDelay, float modeMultiplier) + : base(isRadiusBasedOnCombo, initialRadius, modeMultiplier) { FollowDelay = followDelay; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 71c9d777ec..f235698b55 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -27,14 +27,20 @@ namespace osu.Game.Rulesets.Taiko.Mods [SettingSource("Initial radius", "Initial radius of the flashlight area.")] public override BindableNumber InitialRadius { get; } = new BindableNumber { - MinValue = 0f, - MaxValue = 400f, - Default = 250f, - Value = 250f, - Precision = 5f + MinValue = 0, + MaxValue = 1.66f, + Default = 1f, + Value = 1f, + Precision = 0.1f }; - public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value); + protected override BindableNumber ModeMultiplier { get; } = new BindableNumber + { + Default = 250, + Value = 250, + }; + + public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, ModeMultiplier.Value); private TaikoPlayfield playfield; @@ -49,8 +55,8 @@ namespace osu.Game.Rulesets.Taiko.Mods private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); private readonly TaikoPlayfield taikoPlayfield; - public TaikoFlashlight(TaikoPlayfield taikoPlayfield, bool isRadiusBasedOnCombo, float initialRadius) - : base(isRadiusBasedOnCombo, initialRadius) + public TaikoFlashlight(TaikoPlayfield taikoPlayfield, bool isRadiusBasedOnCombo, float initialRadius, float modeMultiplier) + : base(isRadiusBasedOnCombo, initialRadius, modeMultiplier) { this.taikoPlayfield = taikoPlayfield; FlashlightSize = getSizeFor(0); diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 51006d96e8..d16f310582 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -38,6 +38,8 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Initial radius", "Initial radius of the flashlight area.")] public abstract BindableNumber InitialRadius { get; } + + protected abstract BindableNumber ModeMultiplier { get; } } public abstract class ModFlashlight : ModFlashlight, IApplicableToDrawableRuleset, IApplicableToScoreProcessor @@ -100,10 +102,13 @@ namespace osu.Game.Rulesets.Mods public readonly float InitialRadius; - protected Flashlight(bool isRadiusBasedOnCombo, float initialRadius) + public readonly float ModeMultiplier; + + protected Flashlight(bool isRadiusBasedOnCombo, float initialRadius, float modeMultiplier) { IsRadiusBasedOnCombo = isRadiusBasedOnCombo; InitialRadius = initialRadius; + ModeMultiplier = modeMultiplier; } [BackgroundDependencyLoader] @@ -142,12 +147,12 @@ namespace osu.Game.Rulesets.Mods if (IsRadiusBasedOnCombo) { if (combo > 200) - return InitialRadius * 0.8f; + return InitialRadius * 0.8f * ModeMultiplier; else if (combo > 100) - return InitialRadius * 0.9f; + return InitialRadius * 0.9f * ModeMultiplier; } - return InitialRadius; + return InitialRadius * ModeMultiplier; } private Vector2 flashlightPosition; From 997c13f64367f07fcd81f0107408774d2f0fd241 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 13:36:51 +0900 Subject: [PATCH 0188/5199] Add locking over `realmSubscriptionsResetMap` for sanity --- osu.Game/Database/RealmContextFactory.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index c8fa298f91..522a5fdfd9 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -243,9 +243,11 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException(@$"{nameof(Register)} must be called from the update thread."); - realmSubscriptionsResetMap.Add(action, () => onChanged(new EmptyRealmSet(), null, null)); - - return Register(action); + lock (contextLock) + { + realmSubscriptionsResetMap.Add(action, () => onChanged(new EmptyRealmSet(), null, null)); + return Register(action); + } IDisposable? action(Realm realm) => query(realm).QueryAsyncWithNotifications(onChanged); } From d7a9c5fd410e370f89e83ef1bd3475a418ffe4ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 14:36:48 +0900 Subject: [PATCH 0189/5199] Add settings buttons to allow temporarily blocking realm access --- .../Sections/DebugSettings/MemorySettings.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index 8d4fc5fc9f..f058c274e4 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -1,6 +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 System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Localisation; @@ -17,6 +19,9 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings [BackgroundDependencyLoader] private void load(GameHost host, RealmContextFactory realmFactory) { + SettingsButton blockAction; + SettingsButton unblockAction; + Children = new Drawable[] { new SettingsButton @@ -35,6 +40,43 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings } } }, + blockAction = new SettingsButton + { + Text = "Block realm", + }, + unblockAction = new SettingsButton + { + Text = "Unblock realm", + }, + }; + + blockAction.Action = () => + { + var blocking = realmFactory.BlockAllOperations(); + blockAction.Enabled.Value = false; + + // As a safety measure, unblock after 10 seconds. + // This is to handle the case where a dev may block, but then something on the update thread + // accesses realm and blocks for eternity. + Task.Factory.StartNew(() => + { + Thread.Sleep(10000); + unblock(); + }); + + unblockAction.Action = unblock; + + void unblock() + { + blocking?.Dispose(); + blocking = null; + + Scheduler.Add(() => + { + blockAction.Enabled.Value = true; + unblockAction.Action = null; + }); + } }; } } From 40aa8731900bd7f856e5f5b417aa85897bf79f0f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 14:37:36 +0900 Subject: [PATCH 0190/5199] Rename register methods to better explain their purpose --- osu.Game.Tests/Database/GeneralUsageTests.cs | 2 +- osu.Game.Tests/Database/RealmLiveTests.cs | 2 +- .../RealmSubscriptionRegistrationTests.cs | 4 ++-- osu.Game/Database/RealmContextFactory.cs | 18 +++++++++--------- osu.Game/Database/RealmObjectExtensions.cs | 2 +- .../Bindings/DatabasedKeyBindingContainer.cs | 2 +- osu.Game/Online/BeatmapDownloadTracker.cs | 2 +- .../OnlinePlayBeatmapAvailabilityTracker.cs | 2 +- osu.Game/Online/ScoreDownloadTracker.cs | 2 +- osu.Game/Overlays/MusicController.cs | 2 +- .../Overlays/Settings/Sections/SkinSection.cs | 2 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 8 ++++---- .../Screens/Select/Carousel/TopLocalRank.cs | 2 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- osu.Game/Screens/Spectate/SpectatorScreen.cs | 2 +- 15 files changed, 27 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index c82c1b6e59..3c62153d9e 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Database { bool callbackRan = false; - realmFactory.Register(realm => + realmFactory.RegisterCustomSubscription(realm => { var subscription = realm.All().QueryAsyncWithNotifications((sender, changes, error) => { diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 0e6ad910d9..d53fcb9ac7 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -239,7 +239,7 @@ namespace osu.Game.Tests.Database { int changesTriggered = 0; - realmFactory.Register(outerRealm => + realmFactory.RegisterCustomSubscription(outerRealm => { outerRealm.All().QueryAsyncWithNotifications(gotChange); ILive? liveBeatmap = null; diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs index 16e9f31c7b..1799b95905 100644 --- a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs +++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Database { realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); - var registration = realmFactory.Register(realm => realm.All(), onChanged); + var registration = realmFactory.RegisterForNotifications(realm => realm.All(), onChanged); testEventsArriving(true); @@ -104,7 +104,7 @@ namespace osu.Game.Tests.Database realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); - var subscription = realmFactory.Register(realm => + var subscription = realmFactory.RegisterCustomSubscription(realm => { beatmapSetInfo = realm.All().First(); diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 522a5fdfd9..697caf8cd3 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -63,6 +63,10 @@ namespace osu.Game.Database private readonly ThreadLocal currentThreadCanCreateContexts = new ThreadLocal(); + private readonly Dictionary, IDisposable?> customSubscriptionActions = new Dictionary, IDisposable?>(); + + private readonly Dictionary, Action> realmSubscriptionsResetMap = new Dictionary, Action>(); + private static readonly GlobalStatistic contexts_created = GlobalStatistics.Get(@"Realm", @"Contexts (Created)"); private readonly object contextLock = new object(); @@ -233,20 +237,16 @@ namespace osu.Game.Database } } - private readonly Dictionary, IDisposable?> customSubscriptionActions = new Dictionary, IDisposable?>(); - - private readonly Dictionary, Action> realmSubscriptionsResetMap = new Dictionary, Action>(); - - public IDisposable Register(Func> query, NotificationCallbackDelegate onChanged) + public IDisposable RegisterForNotifications(Func> query, NotificationCallbackDelegate onChanged) where T : RealmObjectBase { if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException(@$"{nameof(Register)} must be called from the update thread."); + throw new InvalidOperationException(@$"{nameof(RegisterForNotifications)} must be called from the update thread."); lock (contextLock) { realmSubscriptionsResetMap.Add(action, () => onChanged(new EmptyRealmSet(), null, null)); - return Register(action); + return RegisterCustomSubscription(action); } IDisposable? action(Realm realm) => query(realm).QueryAsyncWithNotifications(onChanged); @@ -257,10 +257,10 @@ namespace osu.Game.Database /// /// The work to run. Return value should be an from QueryAsyncWithNotifications, or an to clean up any bindings. /// An which should be disposed to unsubscribe any inner subscription. - public IDisposable Register(Func action) + public IDisposable RegisterCustomSubscription(Func action) { if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException(@$"{nameof(Register)} must be called from the update thread."); + throw new InvalidOperationException(@$"{nameof(RegisterForNotifications)} must be called from the update thread."); var syncContext = SynchronizationContext.Current; diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index c30e1699b9..d9026d165d 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -272,7 +272,7 @@ namespace osu.Game.Database where T : RealmObjectBase { if (!RealmContextFactory.CurrentThreadSubscriptionsAllowed) - throw new InvalidOperationException($"Make sure to call {nameof(RealmContextFactory)}.{nameof(RealmContextFactory.Register)}"); + throw new InvalidOperationException($"Make sure to call {nameof(RealmContextFactory)}.{nameof(RealmContextFactory.RegisterForNotifications)}"); return collection.SubscribeForNotifications(callback); } diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 4ad5693867..9cd9865441 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -55,7 +55,7 @@ namespace osu.Game.Input.Bindings protected override void LoadComplete() { - realmSubscription = realmFactory.Register(realm => queryRealmKeyBindings(), (sender, changes, error) => + realmSubscription = realmFactory.RegisterForNotifications(realm => queryRealmKeyBindings(), (sender, changes, error) => { // The first fire of this is a bit redundant as this is being called in base.LoadComplete, // but this is safest in case the subscription is restored after a context recycle. diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index 70599a167b..3f45afb6b2 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -42,7 +42,7 @@ namespace osu.Game.Online // Used to interact with manager classes that don't support interface types. Will eventually be replaced. var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID }; - realmSubscription = realmContextFactory.Register(realm => realm.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, changes, ___) => + realmSubscription = realmContextFactory.RegisterForNotifications(realm => realm.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index 8dd28f5417..28c41bec33 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -78,7 +78,7 @@ namespace osu.Game.Online.Rooms // handles changes to hash that didn't occur from the import process (ie. a user editing the beatmap in the editor, somehow). realmSubscription?.Dispose(); - realmSubscription = realmContextFactory.Register(realm => filteredBeatmaps(), (items, changes, ___) => + realmSubscription = realmContextFactory.RegisterForNotifications(realm => filteredBeatmaps(), (items, changes, ___) => { if (changes == null) return; diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index 72e95bd6df..64ceee9fe6 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -47,7 +47,7 @@ namespace osu.Game.Online Downloader.DownloadBegan += downloadBegan; Downloader.DownloadFailed += downloadFailed; - realmSubscription = realmContextFactory.Register(realm => realm.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending), (items, changes, ___) => + realmSubscription = realmContextFactory.RegisterForNotifications(realm => realm.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending), (items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 8caa69e78c..8df7ed9736 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -94,7 +94,7 @@ namespace osu.Game.Overlays foreach (var s in queryRealmBeatmapSets()) beatmapSets.Add(s.Detach()); - beatmapSubscription = realmFactory.Register(realm => queryRealmBeatmapSets(), beatmapsChanged); + beatmapSubscription = realmFactory.RegisterForNotifications(realm => queryRealmBeatmapSets(), beatmapsChanged); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index c767edec71..6e5cd0da2d 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -83,7 +83,7 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown.Current = dropdownBindable; - realmSubscription = realmFactory.Register(realm => queryRealmSkins(), (sender, changes, error) => + realmSubscription = realmFactory.RegisterForNotifications(realm => queryRealmSkins(), (sender, changes, error) => { // The first fire of this is a bit redundant due to the call below, // but this is safest in case the subscription is restored after a context recycle. diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 889a6f5b79..5a6295fe74 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -190,13 +190,13 @@ namespace osu.Game.Screens.Select { base.LoadComplete(); - subscriptionSets = realmFactory.Register(getBeatmapSets, beatmapSetsChanged); - subscriptionBeatmaps = realmFactory.Register(realm => realm.All().Where(b => !b.Hidden), beatmapsChanged); + subscriptionSets = realmFactory.RegisterForNotifications(getBeatmapSets, beatmapSetsChanged); + subscriptionBeatmaps = realmFactory.RegisterForNotifications(realm => realm.All().Where(b => !b.Hidden), beatmapsChanged); // Can't use main subscriptions because we can't lookup deleted indices. // https://github.com/realm/realm-dotnet/discussions/2634#discussioncomment-1605595. - subscriptionDeletedSets = realmFactory.Register(realm => realm.All().Where(s => s.DeletePending && !s.Protected), deletedBeatmapSetsChanged); - subscriptionHiddenBeatmaps = realmFactory.Register(realm => realm.All().Where(b => b.Hidden), beatmapsChanged); + subscriptionDeletedSets = realmFactory.RegisterForNotifications(realm => realm.All().Where(s => s.DeletePending && !s.Protected), deletedBeatmapSetsChanged); + subscriptionHiddenBeatmaps = realmFactory.RegisterForNotifications(realm => realm.All().Where(b => b.Hidden), beatmapsChanged); } private void deletedBeatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index fc2188e597..c03d464ef6 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Select.Carousel ruleset.BindValueChanged(_ => { scoreSubscription?.Dispose(); - scoreSubscription = realmFactory.Register(realm => + scoreSubscription = realmFactory.RegisterForNotifications(realm => realm.All() .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 463f878e2a..5f288b972b 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (beatmapInfo == null) return; - scoreSubscription = realmFactory.Register(realm => + scoreSubscription = realmFactory.RegisterForNotifications(realm => realm.All() .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID), (_, changes, ___) => diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 904648f727..4abde56ea6 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -80,7 +80,7 @@ namespace osu.Game.Screens.Spectate playingUserStates.BindTo(spectatorClient.PlayingUserStates); playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true); - realmSubscription = realmContextFactory.Register( + realmSubscription = realmContextFactory.RegisterForNotifications( realm => realm.All().Where(s => !s.DeletePending), beatmapsChanged); foreach ((int id, var _) in userMap) From cb319cebdbae8cf70e4b750b1bd80ce655e41183 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 14:48:55 +0900 Subject: [PATCH 0191/5199] Refactor naming and add more comments to help understanding in `RealmContextFactory` subscription logic --- osu.Game/Database/RealmContextFactory.cs | 39 ++++++++++++++++-------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 697caf8cd3..0137e22e94 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -63,8 +63,22 @@ namespace osu.Game.Database private readonly ThreadLocal currentThreadCanCreateContexts = new ThreadLocal(); - private readonly Dictionary, IDisposable?> customSubscriptionActions = new Dictionary, IDisposable?>(); + /// + /// Holds a map of functions registered via and and a coinciding action which when triggered, + /// will unregister the subscription from realm. + /// + /// Put another way, the key is an action which registers the subscription with realm. The returned from the action is stored as the value and only + /// used internally. + /// + /// Entries in this dictionary are only removed when a consumer signals that the subscription should be permanently ceased (via their own ). + /// + private readonly Dictionary, IDisposable?> customSubscriptionsResetMap = new Dictionary, IDisposable?>(); + /// + /// Holds a map of functions registered via and a coinciding action which when triggered, + /// fires a change set event with an empty collection. This is used to inform subscribers when a realm context goes away, and ensure they don't use invalidated + /// managed realm objects from a previous firing. + /// private readonly Dictionary, Action> realmSubscriptionsResetMap = new Dictionary, Action>(); private static readonly GlobalStatistic contexts_created = GlobalStatistics.Get(@"Realm", @"Contexts (Created)"); @@ -88,7 +102,7 @@ namespace osu.Game.Database Logger.Log(@$"Opened realm ""{context.Config.DatabasePath}"" at version {context.Config.SchemaVersion}"); // Resubscribe any subscriptions - foreach (var action in customSubscriptionActions.Keys) + foreach (var action in customSubscriptionsResetMap.Keys) registerSubscription(action); } @@ -245,11 +259,12 @@ namespace osu.Game.Database lock (contextLock) { + Func action = realm => query(realm).QueryAsyncWithNotifications(onChanged); + + // Store an action which is used when blocking to ensure consumers don't use results of a stale changeset firing. realmSubscriptionsResetMap.Add(action, () => onChanged(new EmptyRealmSet(), null, null)); return RegisterCustomSubscription(action); } - - IDisposable? action(Realm realm) => query(realm).QueryAsyncWithNotifications(onChanged); } /// @@ -266,8 +281,8 @@ namespace osu.Game.Database registerSubscription(action); - // This token is returned to the consumer only. - // It will cause the registration to be permanently removed. + // This token is returned to the consumer. + // When disposed, it will cause the registration to be permanently ceased (unsubscribed with realm and unregistered by this class). return new InvokeOnDisposal(() => { if (ThreadSafety.IsUpdateThread) @@ -279,10 +294,10 @@ namespace osu.Game.Database { lock (contextLock) { - if (customSubscriptionActions.TryGetValue(action, out var unsubscriptionAction)) + if (customSubscriptionsResetMap.TryGetValue(action, out var unsubscriptionAction)) { unsubscriptionAction?.Dispose(); - customSubscriptionActions.Remove(action); + customSubscriptionsResetMap.Remove(action); realmSubscriptionsResetMap.Remove(action); } } @@ -301,10 +316,10 @@ namespace osu.Game.Database lock (contextLock) { - Debug.Assert(!customSubscriptionActions.TryGetValue(action, out var found) || found == null); + Debug.Assert(!customSubscriptionsResetMap.TryGetValue(action, out var found) || found == null); current_thread_subscriptions_allowed.Value = true; - customSubscriptionActions[action] = action(realm); + customSubscriptionsResetMap[action] = action(realm); current_thread_subscriptions_allowed.Value = false; } } @@ -318,10 +333,10 @@ namespace osu.Game.Database foreach (var action in realmSubscriptionsResetMap.Values) action(); - foreach (var action in customSubscriptionActions) + foreach (var action in customSubscriptionsResetMap) { action.Value?.Dispose(); - customSubscriptionActions[action.Key] = null; + customSubscriptionsResetMap[action.Key] = null; } } From 1e483ece322f4c78c4ff8eab95c1993d388a1f9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 16:40:16 +0900 Subject: [PATCH 0192/5199] Avoid adding "exit all screens" step when running tests interactively --- osu.Game/Tests/Visual/ScreenTestScene.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/ScreenTestScene.cs b/osu.Game/Tests/Visual/ScreenTestScene.cs index c44a848275..b6f6ca6daa 100644 --- a/osu.Game/Tests/Visual/ScreenTestScene.cs +++ b/osu.Game/Tests/Visual/ScreenTestScene.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; @@ -48,7 +49,11 @@ namespace osu.Game.Tests.Visual public virtual void SetUpSteps() => addExitAllScreensStep(); [TearDownSteps] - public virtual void TearDownSteps() => addExitAllScreensStep(); + public virtual void TearDownSteps() + { + if (DebugUtils.IsNUnitRunning) + addExitAllScreensStep(); + } private void addExitAllScreensStep() { From e22aea0613698af3fa5870fd745764306a9273dd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 17:05:49 +0900 Subject: [PATCH 0193/5199] Apply same fix to `OsuGameTestScene` --- osu.Game/Tests/Visual/OsuGameTestScene.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index 6a11bd3fea..ebbd9bb5dd 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -72,8 +73,11 @@ namespace osu.Game.Tests.Visual [TearDownSteps] public void TearDownSteps() { - AddStep("exit game", () => Game.Exit()); - AddUntilStep("wait for game exit", () => Game.Parent == null); + if (DebugUtils.IsNUnitRunning) + { + AddStep("exit game", () => Game.Exit()); + AddUntilStep("wait for game exit", () => Game.Parent == null); + } } protected void CreateGame() From 161a2a321ef5ecc93dfeb9d5a99af66f7881be2a Mon Sep 17 00:00:00 2001 From: mk-56 Date: Mon, 24 Jan 2022 09:07:07 +0100 Subject: [PATCH 0194/5199] Remove bindable from ModeMultiplier --- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 8 ++------ osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs | 8 ++------ osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 8 ++------ osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 8 ++------ osu.Game/Rulesets/Mods/ModFlashlight.cs | 2 +- 5 files changed, 9 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index e8f1ebdd10..4fbbf63abf 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -33,13 +33,9 @@ namespace osu.Game.Rulesets.Catch.Mods Precision = 0.1f }; - protected override BindableNumber ModeMultiplier { get; } = new BindableNumber - { - Default = 350, - Value = 350, - }; + protected override float ModeMultiplier => 350; - public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, ModeMultiplier.Value); + public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, ModeMultiplier); private CatchPlayfield playfield; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 9ca1a72584..61f73a1ee5 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -34,13 +34,9 @@ namespace osu.Game.Rulesets.Mania.Mods Precision = 0.1f }; - protected override BindableNumber ModeMultiplier { get; } = new BindableNumber - { - Default = 50, - Value = 50, - }; + protected override float ModeMultiplier => 50; - public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value, ModeMultiplier.Value); + public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value, ModeMultiplier); private class ManiaFlashlight : Flashlight { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index c6cf5ce4b5..75299863a9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -47,15 +47,11 @@ namespace osu.Game.Rulesets.Osu.Mods Precision = 0.1f }; - protected override BindableNumber ModeMultiplier { get; } = new BindableNumber - { - Default = 180, - Value = 180, - }; + protected override float ModeMultiplier => 180; private OsuFlashlight flashlight; - public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value, ModeMultiplier.Value); + public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value, ModeMultiplier); public void ApplyToDrawableHitObject(DrawableHitObject drawable) { diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index f235698b55..65a173b491 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -34,13 +34,9 @@ namespace osu.Game.Rulesets.Taiko.Mods Precision = 0.1f }; - protected override BindableNumber ModeMultiplier { get; } = new BindableNumber - { - Default = 250, - Value = 250, - }; + protected override float ModeMultiplier => 250; - public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, ModeMultiplier.Value); + public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, ModeMultiplier); private TaikoPlayfield playfield; diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index d16f310582..7b980e8097 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Initial radius", "Initial radius of the flashlight area.")] public abstract BindableNumber InitialRadius { get; } - protected abstract BindableNumber ModeMultiplier { get; } + protected abstract float ModeMultiplier { get; } } public abstract class ModFlashlight : ModFlashlight, IApplicableToDrawableRuleset, IApplicableToScoreProcessor From 52cd906af6552ecb099d40454d10e2e266a4e92f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 17:45:31 +0900 Subject: [PATCH 0195/5199] Move context retrieval inside lock --- osu.Game/Database/RealmContextFactory.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 0137e22e94..be484329bc 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -309,13 +309,13 @@ namespace osu.Game.Database { Debug.Assert(ThreadSafety.IsUpdateThread); - // Retrieve context outside of flag update to ensure that the context is constructed, - // as attempting to access it inside the subscription if it's not constructed would lead to - // cyclic invocations of the subscription callback. - var realm = Context; - lock (contextLock) { + // Retrieve context outside of flag update to ensure that the context is constructed, + // as attempting to access it inside the subscription if it's not constructed would lead to + // cyclic invocations of the subscription callback. + var realm = Context; + Debug.Assert(!customSubscriptionsResetMap.TryGetValue(action, out var found) || found == null); current_thread_subscriptions_allowed.Value = true; From abf14f09820bb8f5859604135174f36f0e9e7cc5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 17:46:53 +0900 Subject: [PATCH 0196/5199] Lock unregistration for sanity --- osu.Game/Database/RealmContextFactory.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index be484329bc..5dd4e88ccd 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -330,13 +330,16 @@ namespace osu.Game.Database /// private void unregisterAllSubscriptions() { - foreach (var action in realmSubscriptionsResetMap.Values) - action(); - - foreach (var action in customSubscriptionsResetMap) + lock (contextLock) { - action.Value?.Dispose(); - customSubscriptionsResetMap[action.Key] = null; + foreach (var action in realmSubscriptionsResetMap.Values) + action(); + + foreach (var action in customSubscriptionsResetMap) + { + action.Value?.Dispose(); + customSubscriptionsResetMap[action.Key] = null; + } } } From f4e7211ef1356f6598f4ad6038d896c771f39cbd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 17:52:36 +0900 Subject: [PATCH 0197/5199] Add xmldoc for `RegisterForNotifications` --- osu.Game/Database/RealmContextFactory.cs | 27 +++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 5dd4e88ccd..f8470b8c73 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -251,7 +251,28 @@ namespace osu.Game.Database } } - public IDisposable RegisterForNotifications(Func> query, NotificationCallbackDelegate onChanged) + /// + /// Subscribe to a realm collection and begin watching for asynchronous changes. + /// + /// + /// This adds osu! specific thread and managed state safety checks on top of . + /// + /// In addition to the documented realm behaviour, we have the additional requirement of handling subscriptions over potential context loss. + /// When this happens, callback events will be automatically fired: + /// - On context loss, a callback with an empty collection and null will be invoked. + /// - On context revival, a standard initial realm callback will arrive, with null and an up-to-date collection. + /// + /// The to observe for changes. + /// Type of the elements in the list. + /// + /// The callback to be invoked with the updated . + /// + /// A subscription token. It must be kept alive for as long as you want to receive change notifications. + /// To stop receiving notifications, call . + /// + /// May be null in the case the provided collection is not managed. + /// + public IDisposable RegisterForNotifications(Func> query, NotificationCallbackDelegate callback) where T : RealmObjectBase { if (!ThreadSafety.IsUpdateThread) @@ -259,10 +280,10 @@ namespace osu.Game.Database lock (contextLock) { - Func action = realm => query(realm).QueryAsyncWithNotifications(onChanged); + Func action = realm => query(realm).QueryAsyncWithNotifications(callback); // Store an action which is used when blocking to ensure consumers don't use results of a stale changeset firing. - realmSubscriptionsResetMap.Add(action, () => onChanged(new EmptyRealmSet(), null, null)); + realmSubscriptionsResetMap.Add(action, () => callback(new EmptyRealmSet(), null, null)); return RegisterCustomSubscription(action); } } From bf5bf8d1fd147f1a0e27756321512eaa0676e3d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 17:58:53 +0900 Subject: [PATCH 0198/5199] Rename dictionaries to match methods --- osu.Game/Database/RealmContextFactory.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index f8470b8c73..738d0a70a9 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -79,7 +79,7 @@ namespace osu.Game.Database /// fires a change set event with an empty collection. This is used to inform subscribers when a realm context goes away, and ensure they don't use invalidated /// managed realm objects from a previous firing. /// - private readonly Dictionary, Action> realmSubscriptionsResetMap = new Dictionary, Action>(); + private readonly Dictionary, Action> notificationsResetMap = new Dictionary, Action>(); private static readonly GlobalStatistic contexts_created = GlobalStatistics.Get(@"Realm", @"Contexts (Created)"); @@ -283,7 +283,7 @@ namespace osu.Game.Database Func action = realm => query(realm).QueryAsyncWithNotifications(callback); // Store an action which is used when blocking to ensure consumers don't use results of a stale changeset firing. - realmSubscriptionsResetMap.Add(action, () => callback(new EmptyRealmSet(), null, null)); + notificationsResetMap.Add(action, () => callback(new EmptyRealmSet(), null, null)); return RegisterCustomSubscription(action); } } @@ -319,7 +319,7 @@ namespace osu.Game.Database { unsubscriptionAction?.Dispose(); customSubscriptionsResetMap.Remove(action); - realmSubscriptionsResetMap.Remove(action); + notificationsResetMap.Remove(action); } } } @@ -353,7 +353,7 @@ namespace osu.Game.Database { lock (contextLock) { - foreach (var action in realmSubscriptionsResetMap.Values) + foreach (var action in notificationsResetMap.Values) action(); foreach (var action in customSubscriptionsResetMap) From e3083c2477ae80b23f5c17c9360c7aa51b07dc88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 18:05:30 +0900 Subject: [PATCH 0199/5199] Fix copy pasted xmldoc --- osu.Game/Database/RealmContextFactory.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 738d0a70a9..3956738147 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -264,14 +264,12 @@ namespace osu.Game.Database /// /// The to observe for changes. /// Type of the elements in the list. - /// /// The callback to be invoked with the updated . /// /// A subscription token. It must be kept alive for as long as you want to receive change notifications. /// To stop receiving notifications, call . - /// - /// May be null in the case the provided collection is not managed. /// + /// public IDisposable RegisterForNotifications(Func> query, NotificationCallbackDelegate callback) where T : RealmObjectBase { From e3ba7d9ba56c334b2fe815ca1d15e59539bd9f65 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Mon, 24 Jan 2022 10:24:40 +0100 Subject: [PATCH 0200/5199] Wiggle mod expansion Free dlc! --- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 31 ++++++++++++++++------ 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index ff6ba6e121..e0b09213b7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; @@ -22,8 +24,21 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform) }; - private const int wiggle_duration = 90; // (ms) Higher = fewer wiggles - private const int wiggle_strength = 10; // Higher = stronger wiggles + [SettingSource("Wiggle strength", "something")] + public BindableDouble WiggleStrength { get; } = new BindableDouble(10) + { + MinValue = 1f, + MaxValue = 15f, + Precision = .5f + }; + + [SettingSource("Wiggle duration", "milliseconds per wiggle")] + public BindableInt WiggleDuration { get; } = new BindableInt(90) + { + MinValue = 40, + MaxValue = 300, + Precision = 5 + }; protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) => drawableOnApplyCustomUpdateState(hitObject, state); @@ -42,18 +57,18 @@ namespace osu.Game.Rulesets.Osu.Mods Random objRand = new Random((int)osuObject.StartTime); // Wiggle all objects during TimePreempt - int amountWiggles = (int)osuObject.TimePreempt / wiggle_duration; + int amountWiggles = (int)osuObject.TimePreempt / WiggleDuration.Value; void wiggle() { float nextAngle = (float)(objRand.NextDouble() * 2 * Math.PI); - float nextDist = (float)(objRand.NextDouble() * wiggle_strength); - drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), wiggle_duration); + float nextDist = (float)(objRand.NextDouble() * WiggleStrength.Value); + drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), WiggleDuration.Value); } for (int i = 0; i < amountWiggles; i++) { - using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * wiggle_duration)) + using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * WiggleDuration.Value)) wiggle(); } @@ -61,11 +76,11 @@ namespace osu.Game.Rulesets.Osu.Mods if (!(osuObject is IHasDuration endTime)) return; - amountWiggles = (int)(endTime.Duration / wiggle_duration); + amountWiggles = (int)(endTime.Duration / WiggleDuration.Value); for (int i = 0; i < amountWiggles; i++) { - using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * wiggle_duration)) + using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * WiggleDuration.Value)) wiggle(); } } From b0919722ac1c77fb484a7175e3f90cf325d03f9e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 18:24:25 +0900 Subject: [PATCH 0201/5199] Guard against potential exception while blocking realm --- .../Sections/DebugSettings/MemorySettings.cs | 48 +++++++++++-------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index f058c274e4..c5854981e6 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -1,11 +1,13 @@ // 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.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Localisation; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Database; using osu.Game.Localisation; @@ -52,30 +54,38 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings blockAction.Action = () => { - var blocking = realmFactory.BlockAllOperations(); - blockAction.Enabled.Value = false; - - // As a safety measure, unblock after 10 seconds. - // This is to handle the case where a dev may block, but then something on the update thread - // accesses realm and blocks for eternity. - Task.Factory.StartNew(() => + try { - Thread.Sleep(10000); - unblock(); - }); + var token = realmFactory.BlockAllOperations(); - unblockAction.Action = unblock; + blockAction.Enabled.Value = false; - void unblock() - { - blocking?.Dispose(); - blocking = null; - - Scheduler.Add(() => + // As a safety measure, unblock after 10 seconds. + // This is to handle the case where a dev may block, but then something on the update thread + // accesses realm and blocks for eternity. + Task.Factory.StartNew(() => { - blockAction.Enabled.Value = true; - unblockAction.Action = null; + Thread.Sleep(10000); + unblock(); }); + + unblockAction.Action = unblock; + + void unblock() + { + token?.Dispose(); + token = null; + + Scheduler.Add(() => + { + blockAction.Enabled.Value = true; + unblockAction.Action = null; + }); + } + } + catch (Exception e) + { + Logger.Error(e, "Blocking realm failed"); } }; } From 9afa034296e8f06642c907d44ab3061c076c9815 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 18:36:16 +0900 Subject: [PATCH 0202/5199] Fix attempt to revive update thread realm context from non-update thread --- osu.Game/Database/RealmContextFactory.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 3956738147..778092c543 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -594,7 +594,7 @@ namespace osu.Game.Database if (isDisposed) throw new ObjectDisposedException(nameof(RealmContextFactory)); - SynchronizationContext syncContext; + SynchronizationContext? syncContext = null; try { @@ -602,10 +602,20 @@ namespace osu.Game.Database lock (contextLock) { - if (!ThreadSafety.IsUpdateThread && context != null) - throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); + if (context == null) + { + // null context means the update thread has not yet retrieved its context. + // we don't need to worry about reviving the update context in this case, so don't bother with the SynchronizationContext. + Debug.Assert(!ThreadSafety.IsUpdateThread); + } + else + { + if (!ThreadSafety.IsUpdateThread) + throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); + + syncContext = SynchronizationContext.Current; + } - syncContext = SynchronizationContext.Current; unregisterAllSubscriptions(); Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); From a427e20090fc6ea3a3161f1f90bfa3df35bcf9ef Mon Sep 17 00:00:00 2001 From: mk-56 Date: Mon, 24 Jan 2022 10:38:11 +0100 Subject: [PATCH 0203/5199] Fixes --- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index e0b09213b7..7116ee819c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform) }; - [SettingSource("Wiggle strength", "something")] + [SettingSource("Strength")] public BindableDouble WiggleStrength { get; } = new BindableDouble(10) { MinValue = 1f, @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Mods Precision = .5f }; - [SettingSource("Wiggle duration", "milliseconds per wiggle")] + [SettingSource("Duration", "Milliseconds per wiggle")] public BindableInt WiggleDuration { get; } = new BindableInt(90) { MinValue = 40, From 66c5d77d6388686c733eb229e822d0ad4120dee1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 18:55:15 +0900 Subject: [PATCH 0204/5199] Allow realm migration to run again if interrupted halfway --- osu.Game/Database/EFToRealmMigrator.cs | 258 ++++++++++++------------- osu.Game/Screens/Loader.cs | 15 +- 2 files changed, 140 insertions(+), 133 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index bbbdac352e..edce99e302 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -27,7 +27,9 @@ namespace osu.Game.Database { internal class EFToRealmMigrator : CompositeDrawable { - public bool FinishedMigrating { get; private set; } + public Task MigrationCompleted => migrationCompleted.Task; + + private readonly TaskCompletionSource migrationCompleted = new TaskCompletionSource(); [Resolved] private DatabaseContextFactory efContextFactory { get; set; } = null!; @@ -99,6 +101,17 @@ namespace osu.Game.Database { using (var ef = efContextFactory.Get()) { + realmContextFactory.Write(realm => + { + // Before beginning, ensure realm is in an empty state. + // Migrations which are half-completed could lead to issues if the user tries a second time. + // Note that we only do this for beatmaps and scores since the other migrations are yonks old. + realm.RemoveAll(); + realm.RemoveAll(); + realm.RemoveAll(); + realm.RemoveAll(); + }); + migrateSettings(ef); migrateSkins(ef); migrateBeatmaps(ef); @@ -114,7 +127,7 @@ namespace osu.Game.Database Logger.Log("Your development database has been fully migrated to realm. If you switch back to a pre-realm branch and need your previous database, rename the backup file back to \"client.db\".\n\nNote that doing this can potentially leave your file store in a bad state.", level: LogLevel.Important); }, TaskCreationOptions.LongRunning).ContinueWith(t => { - FinishedMigrating = true; + migrationCompleted.SetResult(true); }); } @@ -149,87 +162,78 @@ namespace osu.Game.Database { log($"Found {count} beatmaps in EF"); - // only migrate data if the realm database is empty. - // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. - if (realm.All().Any(s => !s.Protected)) - { - log("Skipping migration as realm already has beatmaps loaded"); - } - else - { - var transaction = realm.BeginWrite(); - int written = 0; + var transaction = realm.BeginWrite(); + int written = 0; - try + try + { + foreach (var beatmapSet in existingBeatmapSets) { - foreach (var beatmapSet in existingBeatmapSets) + if (++written % 1000 == 0) { - if (++written % 1000 == 0) - { - transaction.Commit(); - transaction = realm.BeginWrite(); - log($"Migrated {written}/{count} beatmaps..."); - } + transaction.Commit(); + transaction = realm.BeginWrite(); + log($"Migrated {written}/{count} beatmaps..."); + } - var realmBeatmapSet = new BeatmapSetInfo + var realmBeatmapSet = new BeatmapSetInfo + { + OnlineID = beatmapSet.OnlineID ?? -1, + DateAdded = beatmapSet.DateAdded, + Status = beatmapSet.Status, + DeletePending = beatmapSet.DeletePending, + Hash = beatmapSet.Hash, + Protected = beatmapSet.Protected, + }; + + migrateFiles(beatmapSet, realm, realmBeatmapSet); + + foreach (var beatmap in beatmapSet.Beatmaps) + { + var ruleset = realm.Find(beatmap.RulesetInfo.ShortName); + var metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata); + + var realmBeatmap = new BeatmapInfo(ruleset, new BeatmapDifficulty(beatmap.BaseDifficulty), metadata) { - OnlineID = beatmapSet.OnlineID ?? -1, - DateAdded = beatmapSet.DateAdded, - Status = beatmapSet.Status, - DeletePending = beatmapSet.DeletePending, - Hash = beatmapSet.Hash, - Protected = beatmapSet.Protected, + DifficultyName = beatmap.DifficultyName, + Status = beatmap.Status, + OnlineID = beatmap.OnlineID ?? -1, + Length = beatmap.Length, + BPM = beatmap.BPM, + Hash = beatmap.Hash, + StarRating = beatmap.StarRating, + MD5Hash = beatmap.MD5Hash, + Hidden = beatmap.Hidden, + AudioLeadIn = beatmap.AudioLeadIn, + StackLeniency = beatmap.StackLeniency, + SpecialStyle = beatmap.SpecialStyle, + LetterboxInBreaks = beatmap.LetterboxInBreaks, + WidescreenStoryboard = beatmap.WidescreenStoryboard, + EpilepsyWarning = beatmap.EpilepsyWarning, + SamplesMatchPlaybackRate = beatmap.SamplesMatchPlaybackRate, + DistanceSpacing = beatmap.DistanceSpacing, + BeatDivisor = beatmap.BeatDivisor, + GridSize = beatmap.GridSize, + TimelineZoom = beatmap.TimelineZoom, + Countdown = beatmap.Countdown, + CountdownOffset = beatmap.CountdownOffset, + MaxCombo = beatmap.MaxCombo, + Bookmarks = beatmap.Bookmarks, + BeatmapSet = realmBeatmapSet, }; - migrateFiles(beatmapSet, realm, realmBeatmapSet); - - foreach (var beatmap in beatmapSet.Beatmaps) - { - var ruleset = realm.Find(beatmap.RulesetInfo.ShortName); - var metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata); - - var realmBeatmap = new BeatmapInfo(ruleset, new BeatmapDifficulty(beatmap.BaseDifficulty), metadata) - { - DifficultyName = beatmap.DifficultyName, - Status = beatmap.Status, - OnlineID = beatmap.OnlineID ?? -1, - Length = beatmap.Length, - BPM = beatmap.BPM, - Hash = beatmap.Hash, - StarRating = beatmap.StarRating, - MD5Hash = beatmap.MD5Hash, - Hidden = beatmap.Hidden, - AudioLeadIn = beatmap.AudioLeadIn, - StackLeniency = beatmap.StackLeniency, - SpecialStyle = beatmap.SpecialStyle, - LetterboxInBreaks = beatmap.LetterboxInBreaks, - WidescreenStoryboard = beatmap.WidescreenStoryboard, - EpilepsyWarning = beatmap.EpilepsyWarning, - SamplesMatchPlaybackRate = beatmap.SamplesMatchPlaybackRate, - DistanceSpacing = beatmap.DistanceSpacing, - BeatDivisor = beatmap.BeatDivisor, - GridSize = beatmap.GridSize, - TimelineZoom = beatmap.TimelineZoom, - Countdown = beatmap.Countdown, - CountdownOffset = beatmap.CountdownOffset, - MaxCombo = beatmap.MaxCombo, - Bookmarks = beatmap.Bookmarks, - BeatmapSet = realmBeatmapSet, - }; - - realmBeatmapSet.Beatmaps.Add(realmBeatmap); - } - - realm.Add(realmBeatmapSet); + realmBeatmapSet.Beatmaps.Add(realmBeatmap); } - } - finally - { - transaction.Commit(); - } - log($"Successfully migrated {count} beatmaps to realm"); + realm.Add(realmBeatmapSet); + } } + finally + { + transaction.Commit(); + } + + log($"Successfully migrated {count} beatmaps to realm"); }); } @@ -280,70 +284,62 @@ namespace osu.Game.Database { log($"Found {count} scores in EF"); - // only migrate data if the realm database is empty. - if (realm.All().Any()) - { - log("Skipping migration as realm already has scores loaded"); - } - else - { - var transaction = realm.BeginWrite(); - int written = 0; + var transaction = realm.BeginWrite(); + int written = 0; - try + try + { + foreach (var score in existingScores) { - foreach (var score in existingScores) + if (++written % 1000 == 0) { - if (++written % 1000 == 0) - { - transaction.Commit(); - transaction = realm.BeginWrite(); - log($"Migrated {written}/{count} scores..."); - } - - var beatmap = realm.All().First(b => b.Hash == score.BeatmapInfo.Hash); - var ruleset = realm.Find(score.Ruleset.ShortName); - var user = new RealmUser - { - OnlineID = score.User.OnlineID, - Username = score.User.Username - }; - - var realmScore = new ScoreInfo(beatmap, ruleset, user) - { - Hash = score.Hash, - DeletePending = score.DeletePending, - OnlineID = score.OnlineID ?? -1, - ModsJson = score.ModsJson, - StatisticsJson = score.StatisticsJson, - TotalScore = score.TotalScore, - MaxCombo = score.MaxCombo, - Accuracy = score.Accuracy, - HasReplay = ((IScoreInfo)score).HasReplay, - Date = score.Date, - PP = score.PP, - Rank = score.Rank, - HitEvents = score.HitEvents, - Passed = score.Passed, - Combo = score.Combo, - Position = score.Position, - Statistics = score.Statistics, - Mods = score.Mods, - APIMods = score.APIMods, - }; - - migrateFiles(score, realm, realmScore); - - realm.Add(realmScore); + transaction.Commit(); + transaction = realm.BeginWrite(); + log($"Migrated {written}/{count} scores..."); } - } - finally - { - transaction.Commit(); - } - log($"Successfully migrated {count} scores to realm"); + var beatmap = realm.All().First(b => b.Hash == score.BeatmapInfo.Hash); + var ruleset = realm.Find(score.Ruleset.ShortName); + var user = new RealmUser + { + OnlineID = score.User.OnlineID, + Username = score.User.Username + }; + + var realmScore = new ScoreInfo(beatmap, ruleset, user) + { + Hash = score.Hash, + DeletePending = score.DeletePending, + OnlineID = score.OnlineID ?? -1, + ModsJson = score.ModsJson, + StatisticsJson = score.StatisticsJson, + TotalScore = score.TotalScore, + MaxCombo = score.MaxCombo, + Accuracy = score.Accuracy, + HasReplay = ((IScoreInfo)score).HasReplay, + Date = score.Date, + PP = score.PP, + Rank = score.Rank, + HitEvents = score.HitEvents, + Passed = score.Passed, + Combo = score.Combo, + Position = score.Position, + Statistics = score.Statistics, + Mods = score.Mods, + APIMods = score.APIMods, + }; + + migrateFiles(score, realm, realmScore); + + realm.Add(realmScore); + } } + finally + { + transaction.Commit(); + } + + log($"Successfully migrated {count} scores to realm"); }); } diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 8c4a13f2bd..a72ba89dfa 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -74,11 +74,22 @@ namespace osu.Game.Screens base.OnEntering(last); LoadComponentAsync(precompiler = CreateShaderPrecompiler(), AddInternal); - LoadComponentAsync(loadableScreen = CreateLoadableScreen()); // A non-null context factory means there's still content to migrate. if (efContextFactory != null) + { LoadComponentAsync(realmMigrator = new EFToRealmMigrator(), AddInternal); + realmMigrator.MigrationCompleted.ContinueWith(_ => Schedule(() => + { + // Delay initial screen loading to ensure that the migration is in a complete and sane state + // before the intro screen may import the game intro beatmap. + LoadComponentAsync(loadableScreen = CreateLoadableScreen()); + })); + } + else + { + LoadComponentAsync(loadableScreen = CreateLoadableScreen()); + } LoadComponentAsync(spinner = new LoadingSpinner(true, true) { @@ -96,7 +107,7 @@ namespace osu.Game.Screens private void checkIfLoaded() { - if (loadableScreen.LoadState != LoadState.Ready || !precompiler.FinishedCompiling || realmMigrator?.FinishedMigrating == false) + if (loadableScreen?.LoadState != LoadState.Ready || !precompiler.FinishedCompiling) { Schedule(checkIfLoaded); return; From 948867898cb36ed2a32ff2e58a6f7c9de64d66d5 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Mon, 24 Jan 2022 11:38:52 +0100 Subject: [PATCH 0205/5199] ModeMultiplier rename --- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 8 ++++---- osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs | 8 ++++---- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 8 ++++---- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 8 ++++---- osu.Game/Rulesets/Mods/ModFlashlight.cs | 14 ++++++-------- 5 files changed, 22 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 4fbbf63abf..d48382a9ee 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -33,9 +33,9 @@ namespace osu.Game.Rulesets.Catch.Mods Precision = 0.1f }; - protected override float ModeMultiplier => 350; + protected virtual float DefaultFlashlightSize => 350; - public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, ModeMultiplier); + public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, DefaultFlashlightSize); private CatchPlayfield playfield; @@ -49,8 +49,8 @@ namespace osu.Game.Rulesets.Catch.Mods { private readonly CatchPlayfield playfield; - public CatchFlashlight(CatchPlayfield playfield, bool isRadiusBasedOnCombo, float initialRadius, float modeMultiplier) - : base(isRadiusBasedOnCombo, initialRadius, modeMultiplier) + public CatchFlashlight(CatchPlayfield playfield, bool isRadiusBasedOnCombo, float initialRadius, float defaultFlashlightSize) + : base(isRadiusBasedOnCombo, initialRadius, defaultFlashlightSize) { this.playfield = playfield; FlashlightSize = new Vector2(0, GetRadiusFor(0)); diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 61f73a1ee5..eb3f60edce 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -34,16 +34,16 @@ namespace osu.Game.Rulesets.Mania.Mods Precision = 0.1f }; - protected override float ModeMultiplier => 50; + protected virtual float DefaultFlashlightSize => 50; - public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value, ModeMultiplier); + public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value, DefaultFlashlightSize); private class ManiaFlashlight : Flashlight { private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); - public ManiaFlashlight(bool isRadiusBasedOnCombo, float initialRadius, float modeMultiplier) - : base(isRadiusBasedOnCombo, initialRadius, modeMultiplier) + public ManiaFlashlight(bool isRadiusBasedOnCombo, float initialRadius, float defaultFlashlightSize) + : base(isRadiusBasedOnCombo, initialRadius, defaultFlashlightSize) { FlashlightSize = new Vector2(DrawWidth, GetRadiusFor(0)); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 75299863a9..6a9d199c54 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -47,11 +47,11 @@ namespace osu.Game.Rulesets.Osu.Mods Precision = 0.1f }; - protected override float ModeMultiplier => 180; + protected virtual float DefaultFlashlightSize => 180; private OsuFlashlight flashlight; - public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value, ModeMultiplier); + public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value, DefaultFlashlightSize); public void ApplyToDrawableHitObject(DrawableHitObject drawable) { @@ -66,8 +66,8 @@ namespace osu.Game.Rulesets.Osu.Mods //public float InitialRadius { private get; set; } public bool ChangeRadius { private get; set; } - public OsuFlashlight(bool isRadiusBasedOnCombo, float initialRadius, double followDelay, float modeMultiplier) - : base(isRadiusBasedOnCombo, initialRadius, modeMultiplier) + public OsuFlashlight(bool isRadiusBasedOnCombo, float initialRadius, double followDelay, float defaultFlashlightSize) + : base(isRadiusBasedOnCombo, initialRadius, defaultFlashlightSize) { FollowDelay = followDelay; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 65a173b491..8de7c859c4 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -34,9 +34,9 @@ namespace osu.Game.Rulesets.Taiko.Mods Precision = 0.1f }; - protected override float ModeMultiplier => 250; + protected virtual float DefaultFlashlightSize => 250; - public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, ModeMultiplier); + public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, DefaultFlashlightSize); private TaikoPlayfield playfield; @@ -51,8 +51,8 @@ namespace osu.Game.Rulesets.Taiko.Mods private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); private readonly TaikoPlayfield taikoPlayfield; - public TaikoFlashlight(TaikoPlayfield taikoPlayfield, bool isRadiusBasedOnCombo, float initialRadius, float modeMultiplier) - : base(isRadiusBasedOnCombo, initialRadius, modeMultiplier) + public TaikoFlashlight(TaikoPlayfield taikoPlayfield, bool isRadiusBasedOnCombo, float initialRadius, float defaultFlashlightSize) + : base(isRadiusBasedOnCombo, initialRadius, defaultFlashlightSize) { this.taikoPlayfield = taikoPlayfield; FlashlightSize = getSizeFor(0); diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 7b980e8097..531ee92b7a 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -38,8 +38,6 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Initial radius", "Initial radius of the flashlight area.")] public abstract BindableNumber InitialRadius { get; } - - protected abstract float ModeMultiplier { get; } } public abstract class ModFlashlight : ModFlashlight, IApplicableToDrawableRuleset, IApplicableToScoreProcessor @@ -102,13 +100,13 @@ namespace osu.Game.Rulesets.Mods public readonly float InitialRadius; - public readonly float ModeMultiplier; + public readonly float DefaultFlashlightSize; - protected Flashlight(bool isRadiusBasedOnCombo, float initialRadius, float modeMultiplier) + protected Flashlight(bool isRadiusBasedOnCombo, float initialRadius, float defaultFlashlightSize) { IsRadiusBasedOnCombo = isRadiusBasedOnCombo; InitialRadius = initialRadius; - ModeMultiplier = modeMultiplier; + DefaultFlashlightSize = defaultFlashlightSize; } [BackgroundDependencyLoader] @@ -147,12 +145,12 @@ namespace osu.Game.Rulesets.Mods if (IsRadiusBasedOnCombo) { if (combo > 200) - return InitialRadius * 0.8f * ModeMultiplier; + return InitialRadius * 0.8f * DefaultFlashlightSize; else if (combo > 100) - return InitialRadius * 0.9f * ModeMultiplier; + return InitialRadius * 0.9f * DefaultFlashlightSize; } - return InitialRadius * ModeMultiplier; + return InitialRadius * DefaultFlashlightSize; } private Vector2 flashlightPosition; From deaff340d2733c544b8e87d78a51d5d69063ee27 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 17:58:06 +0900 Subject: [PATCH 0206/5199] Add test coverage of saving velocity --- .../Editor/TestSceneSliderVelocityAdjust.cs | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs new file mode 100644 index 0000000000..4750c97566 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs @@ -0,0 +1,98 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Diagnostics; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Input; +using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osu.Game.Screens.Edit.Timing; +using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Tests.Editor +{ + public class TestSceneSliderVelocityAdjust : OsuGameTestScene + { + private Screens.Edit.Editor editor => Game.ScreenStack.CurrentScreen as Screens.Edit.Editor; + + private EditorBeatmap editorBeatmap => editor.ChildrenOfType().FirstOrDefault(); + + private EditorClock editorClock => editor.ChildrenOfType().FirstOrDefault(); + + private Slider slider => editorBeatmap.HitObjects.OfType().FirstOrDefault(); + + private TimelineHitObjectBlueprint blueprint => editor.ChildrenOfType().FirstOrDefault(); + + private DifficultyPointPiece difficultyPointPiece => blueprint.ChildrenOfType().First(); + + private IndeterminateSliderWithTextBoxInput velocityTextBox => Game.ChildrenOfType().First().ChildrenOfType>().First(); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); + + private bool editorComponentsReady => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true + && editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true + && editor?.ChildrenOfType().FirstOrDefault()?.IsLoaded == true; + + [TestCase(true)] + [TestCase(false)] + public void TestVelocityChangeSavesCorrectly(bool adjustVelocity) + { + double? velocity = null; + + AddStep("enter editor", () => Game.ScreenStack.Push(new EditorLoader())); + AddUntilStep("wait for editor load", () => editorComponentsReady); + + AddStep("seek to first control point", () => editorClock.Seek(editorBeatmap.ControlPointInfo.TimingPoints.First().Time)); + AddStep("enter slider placement mode", () => InputManager.Key(Key.Number3)); + + AddStep("move mouse to centre", () => InputManager.MoveMouseTo(editor.ChildrenOfType().First().ScreenSpaceDrawQuad.Centre)); + AddStep("start placement", () => InputManager.Click(MouseButton.Left)); + + AddStep("move mouse to bottom right", () => InputManager.MoveMouseTo(editor.ChildrenOfType().First().ScreenSpaceDrawQuad.BottomRight - new Vector2(10))); + AddStep("end placement", () => InputManager.Click(MouseButton.Right)); + + AddStep("exit placement mode", () => InputManager.Key(Key.Number1)); + + AddAssert("slider placed", () => slider != null); + + AddStep("select slider", () => editorBeatmap.SelectedHitObjects.Add(slider)); + + AddAssert("ensure one slider placed", () => slider != null); + + AddStep("store velocity", () => velocity = slider.Velocity); + + if (adjustVelocity) + { + AddStep("open velocity adjust panel", () => difficultyPointPiece.TriggerClick()); + AddStep("change velocity", () => velocityTextBox.Current.Value = 2); + + AddAssert("velocity adjusted", () => + { + Debug.Assert(velocity != null); + return Precision.AlmostEquals(velocity.Value * 2, slider.Velocity); + }); + + AddStep("store velocity", () => velocity = slider.Velocity); + } + + AddStep("save", () => InputManager.Keys(PlatformAction.Save)); + AddStep("exit", () => InputManager.Key(Key.Escape)); + + AddStep("enter editor (again)", () => Game.ScreenStack.Push(new EditorLoader())); + AddUntilStep("wait for editor load", () => editorComponentsReady); + + AddStep("seek to slider", () => editorClock.Seek(slider.StartTime)); + AddAssert("slider has correct velocity", () => slider.Velocity == velocity); + } + } +} From c3758047fd9e2113173ea9bfcba50a575f5b7035 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 19:54:07 +0900 Subject: [PATCH 0207/5199] Don't include nested hit objects' `DifficultyControLPoints` in legacy encoder logic The editor doesn't currently propagate velocity to nested objects. We're not yet sure whether it should or not. For now, let's just ignore nested objects' `DifficultyControlPoints` for simplicity. Note that this only affects osu! ruleset due to the pre-check on `isOsuRuleset`. --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 4cf6d3335f..9d848fd8a4 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -242,12 +242,7 @@ namespace osu.Game.Beatmaps.Formats yield break; foreach (var hitObject in hitObjects) - { yield return hitObject.DifficultyControlPoint; - - foreach (var nested in collectDifficultyControlPoints(hitObject.NestedHitObjects)) - yield return nested; - } } void extractDifficultyControlPoints(IEnumerable hitObjects) From 836cb1ee323416b71d2f649bfd725d098fa8dad9 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Mon, 24 Jan 2022 12:16:29 +0100 Subject: [PATCH 0208/5199] Suggested boundary change --- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 7116ee819c..10806318b0 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -25,11 +25,11 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform) }; [SettingSource("Strength")] - public BindableDouble WiggleStrength { get; } = new BindableDouble(10) + public BindableDouble WiggleStrength { get; } = new BindableDouble(1) { - MinValue = 1f, - MaxValue = 15f, - Precision = .5f + MinValue = .1f, + MaxValue = 2f, + Precision = .1f }; [SettingSource("Duration", "Milliseconds per wiggle")] @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Mods void wiggle() { float nextAngle = (float)(objRand.NextDouble() * 2 * Math.PI); - float nextDist = (float)(objRand.NextDouble() * WiggleStrength.Value); + float nextDist = (float)(objRand.NextDouble() * WiggleStrength.Value * 7); drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), WiggleDuration.Value); } From 6eb2c28e41369be24fbefb14478b62935207992d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 19:59:58 +0900 Subject: [PATCH 0209/5199] Rename `RealmContextFactory` to `RealmAccess` --- osu.Game.Benchmarks/BenchmarkRealmReads.cs | 20 +- .../Beatmaps/IO/BeatmapImportHelper.cs | 4 +- .../Database/BeatmapImporterTests.cs | 258 +++++++++--------- osu.Game.Tests/Database/FileStoreTests.cs | 24 +- osu.Game.Tests/Database/GeneralUsageTests.cs | 22 +- osu.Game.Tests/Database/RealmLiveTests.cs | 60 ++-- .../RealmSubscriptionRegistrationTests.cs | 42 +-- osu.Game.Tests/Database/RealmTest.cs | 34 +-- osu.Game.Tests/Database/RulesetStoreTests.cs | 18 +- .../Database/TestRealmKeyBindingStore.cs | 16 +- .../Gameplay/TestSceneStoryboardSamples.cs | 2 +- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 20 +- .../Background/TestSceneUserDimBackgrounds.cs | 6 +- .../TestSceneManageCollectionsDialog.cs | 6 +- .../Visual/Multiplayer/QueueModeTestScene.cs | 6 +- .../TestSceneDrawableRoomPlaylist.cs | 6 +- .../Multiplayer/TestSceneMultiplayer.cs | 6 +- .../TestSceneMultiplayerMatchSongSelect.cs | 6 +- .../TestSceneMultiplayerMatchSubScreen.cs | 6 +- .../TestSceneMultiplayerPlaylist.cs | 6 +- .../TestSceneMultiplayerQueueList.cs | 6 +- .../TestSceneMultiplayerReadyButton.cs | 6 +- .../TestSceneMultiplayerSpectateButton.cs | 6 +- .../TestScenePlaylistsSongSelect.cs | 6 +- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 6 +- .../TestSceneChangeAndUseGameplayBindings.cs | 2 +- .../TestScenePlaylistsRoomCreation.cs | 6 +- .../Visual/Ranking/TestSceneResultsScreen.cs | 4 +- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 8 +- .../SongSelect/TestSceneFilterControl.cs | 6 +- .../SongSelect/TestScenePlaySongSelect.cs | 6 +- .../SongSelect/TestSceneTopLocalRank.cs | 8 +- .../TestSceneDeleteLocalScore.cs | 12 +- osu.Game/Beatmaps/BeatmapManager.cs | 30 +- osu.Game/Beatmaps/BeatmapModelManager.cs | 8 +- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 2 +- osu.Game/Configuration/SettingsStore.cs | 6 +- osu.Game/Database/EFToRealmMigrator.cs | 12 +- ...{RealmContextFactory.cs => RealmAccess.cs} | 10 +- osu.Game/Database/RealmLive.cs | 14 +- osu.Game/Database/RealmObjectExtensions.cs | 12 +- osu.Game/IO/IStorageResourceProvider.cs | 2 +- .../Bindings/DatabasedKeyBindingContainer.cs | 8 +- osu.Game/Input/RealmKeyBindingStore.cs | 10 +- osu.Game/Online/BeatmapDownloadTracker.cs | 4 +- .../OnlinePlayBeatmapAvailabilityTracker.cs | 10 +- osu.Game/Online/ScoreDownloadTracker.cs | 4 +- osu.Game/OsuGameBase.cs | 22 +- osu.Game/Overlays/MusicController.cs | 10 +- .../Sections/DebugSettings/MemorySettings.cs | 6 +- .../Settings/Sections/Input/KeyBindingRow.cs | 4 +- .../Sections/Input/KeyBindingsSubsection.cs | 8 +- .../Overlays/Settings/Sections/SkinSection.cs | 14 +- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 4 +- .../Configuration/RulesetConfigManager.cs | 12 +- osu.Game/Rulesets/RulesetConfigCache.cs | 8 +- osu.Game/Rulesets/RulesetStore.cs | 8 +- osu.Game/Scoring/ScoreManager.cs | 12 +- osu.Game/Scoring/ScoreModelManager.cs | 6 +- osu.Game/Screens/Menu/IntroScreen.cs | 6 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 14 +- .../Screens/Select/Carousel/TopLocalRank.cs | 4 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 6 +- osu.Game/Screens/Spectate/SpectatorScreen.cs | 4 +- osu.Game/Skinning/Skin.cs | 4 +- osu.Game/Skinning/SkinManager.cs | 22 +- osu.Game/Skinning/SkinModelManager.cs | 6 +- osu.Game/Stores/BeatmapImporter.cs | 6 +- osu.Game/Stores/RealmArchiveModelImporter.cs | 16 +- osu.Game/Stores/RealmArchiveModelManager.cs | 12 +- osu.Game/Stores/RealmFileStore.cs | 8 +- .../Drawables/DrawableStoryboard.cs | 4 +- .../Tests/Beatmaps/HitObjectSampleTest.cs | 2 +- osu.Game/Tests/Visual/EditorTestScene.cs | 14 +- osu.Game/Tests/Visual/OsuTestScene.cs | 6 +- osu.Game/Tests/Visual/SkinnableTestScene.cs | 2 +- 76 files changed, 516 insertions(+), 516 deletions(-) rename osu.Game/Database/{RealmContextFactory.cs => RealmAccess.cs} (98%) diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs index bb22fab51c..412f86bd1c 100644 --- a/osu.Game.Benchmarks/BenchmarkRealmReads.cs +++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs @@ -16,7 +16,7 @@ namespace osu.Game.Benchmarks public class BenchmarkRealmReads : BenchmarkTest { private TemporaryNativeStorage storage; - private RealmContextFactory realmFactory; + private RealmAccess realm; private UpdateThread updateThread; [Params(1, 100, 1000)] @@ -27,9 +27,9 @@ namespace osu.Game.Benchmarks storage = new TemporaryNativeStorage("realm-benchmark"); storage.DeleteDirectory(string.Empty); - realmFactory = new RealmContextFactory(storage, "client"); + realm = new RealmAccess(storage, "client"); - realmFactory.Run(realm => + realm.Run(realm => { realm.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo }))); }); @@ -41,7 +41,7 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkDirectPropertyRead() { - realmFactory.Run(realm => + realm.Run(realm => { var beatmapSet = realm.All().First(); @@ -61,7 +61,7 @@ namespace osu.Game.Benchmarks { try { - var beatmapSet = realmFactory.Context.All().First(); + var beatmapSet = realm.Realm.All().First(); for (int i = 0; i < ReadsPerFetch; i++) { @@ -80,9 +80,9 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkRealmLivePropertyRead() { - realmFactory.Run(realm => + realm.Run(r => { - var beatmapSet = realm.All().First().ToLive(realmFactory); + var beatmapSet = r.All().First().ToLive(realm); for (int i = 0; i < ReadsPerFetch; i++) { @@ -100,7 +100,7 @@ namespace osu.Game.Benchmarks { try { - var beatmapSet = realmFactory.Context.All().First().ToLive(realmFactory); + var beatmapSet = realm.Realm.All().First().ToLive(realm); for (int i = 0; i < ReadsPerFetch; i++) { @@ -119,7 +119,7 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkDetachedPropertyRead() { - realmFactory.Run(realm => + realm.Run(realm => { var beatmapSet = realm.All().First().Detach(); @@ -133,7 +133,7 @@ namespace osu.Game.Benchmarks [GlobalCleanup] public void Cleanup() { - realmFactory?.Dispose(); + realm?.Dispose(); storage?.Dispose(); updateThread?.Exit(); } diff --git a/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs b/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs index 7aa2dc7093..9e440c6bce 100644 --- a/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs +++ b/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs @@ -53,9 +53,9 @@ namespace osu.Game.Tests.Beatmaps.IO private static void ensureLoaded(OsuGameBase osu, int timeout = 60000) { - var realmContextFactory = osu.Dependencies.Get(); + var realm = osu.Dependencies.Get(); - realmContextFactory.Run(realm => BeatmapImporterTests.EnsureLoaded(realm, timeout)); + realm.Run(r => BeatmapImporterTests.EnsureLoaded(r, timeout)); // TODO: add back some extra checks outside of the realm ones? // var set = queryBeatmapSets().First(); diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 227314cffd..a52b21244c 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -38,10 +38,10 @@ namespace osu.Game.Tests.Database [Test] public void TestDetachBeatmapSet() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using (var importer = new BeatmapModelManager(realmFactory, storage)) - using (new RulesetStore(realmFactory, storage)) + using (var importer = new BeatmapModelManager(realm, storage)) + using (new RulesetStore(realm, storage)) { ILive? beatmapSet; @@ -82,10 +82,10 @@ namespace osu.Game.Tests.Database [Test] public void TestUpdateDetachedBeatmapSet() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using (var importer = new BeatmapModelManager(realmFactory, storage)) - using (new RulesetStore(realmFactory, storage)) + using (var importer = new BeatmapModelManager(realm, storage)) + using (new RulesetStore(realm, storage)) { ILive? beatmapSet; @@ -139,53 +139,53 @@ namespace osu.Game.Tests.Database [Test] public void TestImportBeatmapThenCleanup() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using (var importer = new BeatmapModelManager(realmFactory, storage)) - using (new RulesetStore(realmFactory, storage)) + using (var importer = new BeatmapModelManager(realm, storage)) + using (new RulesetStore(realm, storage)) { ILive? imported; using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream())) imported = await importer.Import(reader); - Assert.AreEqual(1, realmFactory.Context.All().Count()); + Assert.AreEqual(1, realm.Realm.All().Count()); Assert.NotNull(imported); Debug.Assert(imported != null); imported.PerformWrite(s => s.DeletePending = true); - Assert.AreEqual(1, realmFactory.Context.All().Count(s => s.DeletePending)); + Assert.AreEqual(1, realm.Realm.All().Count(s => s.DeletePending)); } }); Logger.Log("Running with no work to purge pending deletions"); - RunTestWithRealm((realmFactory, _) => { Assert.AreEqual(0, realmFactory.Context.All().Count()); }); + RunTestWithRealm((realm, _) => { Assert.AreEqual(0, realm.Realm.All().Count()); }); } [Test] public void TestImportWhenClosed() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); - await LoadOszIntoStore(importer, realmFactory.Context); + await LoadOszIntoStore(importer, realm.Realm); }); } [Test] public void TestAccessFileAfterImport() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); var beatmap = imported.Beatmaps.First(); var file = beatmap.File; @@ -198,24 +198,24 @@ namespace osu.Game.Tests.Database [Test] public void TestImportThenDelete() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); - deleteBeatmapSet(imported, realmFactory.Context); + deleteBeatmapSet(imported, realm.Realm); }); } [Test] public void TestImportThenDeleteFromStream() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? tempPath = TestResources.GetTestBeatmapForImport(); @@ -224,7 +224,7 @@ namespace osu.Game.Tests.Database using (var stream = File.OpenRead(tempPath)) { importedSet = await importer.Import(new ImportTask(stream, Path.GetFileName(tempPath))); - EnsureLoaded(realmFactory.Context); + EnsureLoaded(realm.Realm); } Assert.NotNull(importedSet); @@ -233,39 +233,39 @@ namespace osu.Game.Tests.Database Assert.IsTrue(File.Exists(tempPath), "Stream source file somehow went missing"); File.Delete(tempPath); - var imported = realmFactory.Context.All().First(beatmapSet => beatmapSet.ID == importedSet.ID); + var imported = realm.Realm.All().First(beatmapSet => beatmapSet.ID == importedSet.ID); - deleteBeatmapSet(imported, realmFactory.Context); + deleteBeatmapSet(imported, realm.Realm); }); } [Test] public void TestImportThenImport() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); - var imported = await LoadOszIntoStore(importer, realmFactory.Context); - var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); + var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm); // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. Assert.IsTrue(imported.ID == importedSecondTime.ID); Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID); - checkBeatmapSetCount(realmFactory.Context, 1); - checkSingleReferencedFileCount(realmFactory.Context, 18); + checkBeatmapSetCount(realm.Realm, 1); + checkSingleReferencedFileCount(realm.Realm, 18); }); } [Test] public void TestImportThenImportWithReZip() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -274,7 +274,7 @@ namespace osu.Game.Tests.Database try { - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); string hashBefore = hashFile(temp); @@ -292,7 +292,7 @@ namespace osu.Game.Tests.Database var importedSecondTime = await importer.Import(new ImportTask(temp)); - EnsureLoaded(realmFactory.Context); + EnsureLoaded(realm.Realm); Assert.NotNull(importedSecondTime); Debug.Assert(importedSecondTime != null); @@ -311,10 +311,10 @@ namespace osu.Game.Tests.Database [Test] public void TestImportThenImportWithChangedHashedFile() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -323,9 +323,9 @@ namespace osu.Game.Tests.Database try { - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); - await createScoreForBeatmap(realmFactory.Context, imported.Beatmaps.First()); + await createScoreForBeatmap(realm.Realm, imported.Beatmaps.First()); using (var zip = ZipArchive.Open(temp)) zip.WriteToDirectory(extractedFolder); @@ -343,7 +343,7 @@ namespace osu.Game.Tests.Database var importedSecondTime = await importer.Import(new ImportTask(temp)); - EnsureLoaded(realmFactory.Context); + EnsureLoaded(realm.Realm); // check the newly "imported" beatmap is not the original. Assert.NotNull(importedSecondTime); @@ -363,10 +363,10 @@ namespace osu.Game.Tests.Database [Ignore("intentionally broken by import optimisations")] public void TestImportThenImportWithChangedFile() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -375,7 +375,7 @@ namespace osu.Game.Tests.Database try { - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); using (var zip = ZipArchive.Open(temp)) zip.WriteToDirectory(extractedFolder); @@ -392,7 +392,7 @@ namespace osu.Game.Tests.Database var importedSecondTime = await importer.Import(new ImportTask(temp)); - EnsureLoaded(realmFactory.Context); + EnsureLoaded(realm.Realm); Assert.NotNull(importedSecondTime); Debug.Assert(importedSecondTime != null); @@ -411,10 +411,10 @@ namespace osu.Game.Tests.Database [Test] public void TestImportThenImportWithDifferentFilename() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -423,7 +423,7 @@ namespace osu.Game.Tests.Database try { - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); using (var zip = ZipArchive.Open(temp)) zip.WriteToDirectory(extractedFolder); @@ -440,7 +440,7 @@ namespace osu.Game.Tests.Database var importedSecondTime = await importer.Import(new ImportTask(temp)); - EnsureLoaded(realmFactory.Context); + EnsureLoaded(realm.Realm); Assert.NotNull(importedSecondTime); Debug.Assert(importedSecondTime != null); @@ -460,12 +460,12 @@ namespace osu.Game.Tests.Database [Ignore("intentionally broken by import optimisations")] public void TestImportCorruptThenImport() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); var firstFile = imported.Files.First(); @@ -476,7 +476,7 @@ namespace osu.Game.Tests.Database using (var stream = storage.GetStream(firstFile.File.GetStoragePath(), FileAccess.Write, FileMode.Create)) stream.WriteByte(0); - var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context); + var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm); using (var stream = storage.GetStream(firstFile.File.GetStoragePath())) Assert.AreEqual(stream.Length, originalLength, "Corruption was not fixed on second import"); @@ -485,18 +485,18 @@ namespace osu.Game.Tests.Database Assert.IsTrue(imported.ID == importedSecondTime.ID); Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID); - checkBeatmapSetCount(realmFactory.Context, 1); - checkSingleReferencedFileCount(realmFactory.Context, 18); + checkBeatmapSetCount(realm.Realm, 1); + checkSingleReferencedFileCount(realm.Realm, 18); }); } [Test] public void TestModelCreationFailureDoesntReturn() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); var progressNotification = new ImportProgressNotification(); @@ -510,8 +510,8 @@ namespace osu.Game.Tests.Database new ImportTask(zipStream, string.Empty) ); - checkBeatmapSetCount(realmFactory.Context, 0); - checkBeatmapCount(realmFactory.Context, 0); + checkBeatmapSetCount(realm.Realm, 0); + checkBeatmapCount(realm.Realm, 0); Assert.IsEmpty(imported); Assert.AreEqual(ProgressNotificationState.Cancelled, progressNotification.State); @@ -521,7 +521,7 @@ namespace osu.Game.Tests.Database [Test] public void TestRollbackOnFailure() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { int loggedExceptionCount = 0; @@ -531,16 +531,16 @@ namespace osu.Game.Tests.Database Interlocked.Increment(ref loggedExceptionCount); }; - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); - realmFactory.Context.Write(() => imported.Hash += "-changed"); + realm.Realm.Write(() => imported.Hash += "-changed"); - checkBeatmapSetCount(realmFactory.Context, 1); - checkBeatmapCount(realmFactory.Context, 12); - checkSingleReferencedFileCount(realmFactory.Context, 18); + checkBeatmapSetCount(realm.Realm, 1); + checkBeatmapCount(realm.Realm, 12); + checkSingleReferencedFileCount(realm.Realm, 18); string? brokenTempFilename = TestResources.GetTestBeatmapForImport(); @@ -565,10 +565,10 @@ namespace osu.Game.Tests.Database { } - checkBeatmapSetCount(realmFactory.Context, 1); - checkBeatmapCount(realmFactory.Context, 12); + checkBeatmapSetCount(realm.Realm, 1); + checkBeatmapCount(realm.Realm, 12); - checkSingleReferencedFileCount(realmFactory.Context, 18); + checkSingleReferencedFileCount(realm.Realm, 18); Assert.AreEqual(1, loggedExceptionCount); @@ -579,18 +579,18 @@ namespace osu.Game.Tests.Database [Test] public void TestImportThenDeleteThenImportOptimisedPath() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); - deleteBeatmapSet(imported, realmFactory.Context); + deleteBeatmapSet(imported, realm.Realm); Assert.IsTrue(imported.DeletePending); - var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context); + var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm); // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. Assert.IsTrue(imported.ID == importedSecondTime.ID); @@ -603,18 +603,18 @@ namespace osu.Game.Tests.Database [Test] public void TestImportThenDeleteThenImportNonOptimisedPath() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new NonOptimisedBeatmapImporter(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new NonOptimisedBeatmapImporter(realm, storage); + using var store = new RulesetStore(realm, storage); - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); - deleteBeatmapSet(imported, realmFactory.Context); + deleteBeatmapSet(imported, realm.Realm); Assert.IsTrue(imported.DeletePending); - var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context); + var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm); // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. Assert.IsTrue(imported.ID == importedSecondTime.ID); @@ -627,22 +627,22 @@ namespace osu.Game.Tests.Database [Test] public void TestImportThenDeleteThenImportWithOnlineIDsMissing() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); - realmFactory.Context.Write(() => + realm.Realm.Write(() => { foreach (var b in imported.Beatmaps) b.OnlineID = -1; }); - deleteBeatmapSet(imported, realmFactory.Context); + deleteBeatmapSet(imported, realm.Realm); - var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context); + var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm); // check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched) Assert.IsTrue(imported.ID != importedSecondTime.ID); @@ -653,10 +653,10 @@ namespace osu.Game.Tests.Database [Test] public void TestImportWithDuplicateBeatmapIDs() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); var metadata = new BeatmapMetadata { @@ -667,7 +667,7 @@ namespace osu.Game.Tests.Database } }; - var ruleset = realmFactory.Context.All().First(); + var ruleset = realm.Realm.All().First(); var toImport = new BeatmapSetInfo { @@ -699,15 +699,15 @@ namespace osu.Game.Tests.Database [Test] public void TestImportWhenFileOpen() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); using (File.OpenRead(temp)) await importer.Import(temp); - EnsureLoaded(realmFactory.Context); + EnsureLoaded(realm.Realm); File.Delete(temp); Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't"); }); @@ -716,10 +716,10 @@ namespace osu.Game.Tests.Database [Test] public void TestImportWithDuplicateHashes() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -740,7 +740,7 @@ namespace osu.Game.Tests.Database await importer.Import(temp); - EnsureLoaded(realmFactory.Context); + EnsureLoaded(realm.Realm); } finally { @@ -752,10 +752,10 @@ namespace osu.Game.Tests.Database [Test] public void TestImportNestedStructure() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -780,7 +780,7 @@ namespace osu.Game.Tests.Database Assert.NotNull(imported); Debug.Assert(imported != null); - EnsureLoaded(realmFactory.Context); + EnsureLoaded(realm.Realm); Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("subfolder"))), "Files contain common subfolder"); } @@ -794,10 +794,10 @@ namespace osu.Game.Tests.Database [Test] public void TestImportWithIgnoredDirectoryInArchive() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -830,7 +830,7 @@ namespace osu.Game.Tests.Database Assert.NotNull(imported); Debug.Assert(imported != null); - EnsureLoaded(realmFactory.Context); + EnsureLoaded(realm.Realm); Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("__MACOSX"))), "Files contain resource fork folder, which should be ignored"); Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("actual_data"))), "Files contain common subfolder"); @@ -845,22 +845,22 @@ namespace osu.Game.Tests.Database [Test] public void TestUpdateBeatmapInfo() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); await importer.Import(temp); // Update via the beatmap, not the beatmap info, to ensure correct linking - BeatmapSetInfo setToUpdate = realmFactory.Context.All().First(); + BeatmapSetInfo setToUpdate = realm.Realm.All().First(); var beatmapToUpdate = setToUpdate.Beatmaps.First(); - realmFactory.Context.Write(() => beatmapToUpdate.DifficultyName = "updated"); + realm.Realm.Write(() => beatmapToUpdate.DifficultyName = "updated"); - BeatmapInfo updatedInfo = realmFactory.Context.All().First(b => b.ID == beatmapToUpdate.ID); + BeatmapInfo updatedInfo = realm.Realm.All().First(b => b.ID == beatmapToUpdate.ID); Assert.That(updatedInfo.DifficultyName, Is.EqualTo("updated")); }); } @@ -1004,8 +1004,8 @@ namespace osu.Game.Tests.Database public class NonOptimisedBeatmapImporter : BeatmapImporter { - public NonOptimisedBeatmapImporter(RealmContextFactory realmFactory, Storage storage) - : base(realmFactory, storage) + public NonOptimisedBeatmapImporter(RealmAccess realm, Storage storage) + : base(realm, storage) { } diff --git a/osu.Game.Tests/Database/FileStoreTests.cs b/osu.Game.Tests/Database/FileStoreTests.cs index 3cb4705381..008aa3d833 100644 --- a/osu.Game.Tests/Database/FileStoreTests.cs +++ b/osu.Game.Tests/Database/FileStoreTests.cs @@ -19,10 +19,10 @@ namespace osu.Game.Tests.Database [Test] public void TestImportFile() { - RunTestWithRealm((realmFactory, storage) => + RunTestWithRealm((realmAccess, storage) => { - var realm = realmFactory.Context; - var files = new RealmFileStore(realmFactory, storage); + var realm = realmAccess.Context; + var files = new RealmFileStore(realmAccess, storage); var testData = new MemoryStream(new byte[] { 0, 1, 2, 3 }); @@ -36,10 +36,10 @@ namespace osu.Game.Tests.Database [Test] public void TestImportSameFileTwice() { - RunTestWithRealm((realmFactory, storage) => + RunTestWithRealm((realmAccess, storage) => { - var realm = realmFactory.Context; - var files = new RealmFileStore(realmFactory, storage); + var realm = realmAccess.Context; + var files = new RealmFileStore(realmAccess, storage); var testData = new MemoryStream(new byte[] { 0, 1, 2, 3 }); @@ -53,10 +53,10 @@ namespace osu.Game.Tests.Database [Test] public void TestDontPurgeReferenced() { - RunTestWithRealm((realmFactory, storage) => + RunTestWithRealm((realmAccess, storage) => { - var realm = realmFactory.Context; - var files = new RealmFileStore(realmFactory, storage); + var realm = realmAccess.Context; + var files = new RealmFileStore(realmAccess, storage); var file = realm.Write(() => files.Add(new MemoryStream(new byte[] { 0, 1, 2, 3 }), realm)); @@ -92,10 +92,10 @@ namespace osu.Game.Tests.Database [Test] public void TestPurgeUnreferenced() { - RunTestWithRealm((realmFactory, storage) => + RunTestWithRealm((realmAccess, storage) => { - var realm = realmFactory.Context; - var files = new RealmFileStore(realmFactory, storage); + var realm = realmAccess.Context; + var files = new RealmFileStore(realmAccess, storage); var file = realm.Write(() => files.Add(new MemoryStream(new byte[] { 0, 1, 2, 3 }), realm)); diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index 3c62153d9e..2533c832e6 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -21,15 +21,15 @@ namespace osu.Game.Tests.Database [Test] public void TestConstructRealm() { - RunTestWithRealm((realmFactory, _) => { realmFactory.Run(realm => realm.Refresh()); }); + RunTestWithRealm((realm, _) => { realm.Run(r => r.Refresh()); }); } [Test] public void TestBlockOperations() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { - using (realmFactory.BlockAllOperations()) + using (realm.BlockAllOperations()) { } }); @@ -42,22 +42,22 @@ namespace osu.Game.Tests.Database [Test] public void TestNestedContextCreationWithSubscription() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { bool callbackRan = false; - realmFactory.RegisterCustomSubscription(realm => + realm.RegisterCustomSubscription(r => { - var subscription = realm.All().QueryAsyncWithNotifications((sender, changes, error) => + var subscription = r.All().QueryAsyncWithNotifications((sender, changes, error) => { - realmFactory.Run(_ => + realm.Run(_ => { callbackRan = true; }); }); // Force the callback above to run. - realmFactory.Run(r => r.Refresh()); + realm.Run(rr => rr.Refresh()); subscription?.Dispose(); return null; @@ -70,14 +70,14 @@ namespace osu.Game.Tests.Database [Test] public void TestBlockOperationsWithContention() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { ManualResetEventSlim stopThreadedUsage = new ManualResetEventSlim(); ManualResetEventSlim hasThreadedUsage = new ManualResetEventSlim(); Task.Factory.StartNew(() => { - realmFactory.Run(_ => + realm.Run(_ => { hasThreadedUsage.Set(); @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Database Assert.Throws(() => { - using (realmFactory.BlockAllOperations()) + using (realm.BlockAllOperations()) { } }); diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index d53fcb9ac7..2e3f708f79 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -21,11 +21,11 @@ namespace osu.Game.Tests.Database [Test] public void TestLiveEquality() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { - ILive beatmap = realmFactory.Run(realm => realm.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realmFactory)); + ILive beatmap = realm.Run(r => r.Write(_ => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realm)); - ILive beatmap2 = realmFactory.Run(realm => realm.All().First().ToLive(realmFactory)); + ILive beatmap2 = realm.Run(r => r.All().First().ToLive(realm)); Assert.AreEqual(beatmap, beatmap2); }); @@ -34,20 +34,20 @@ namespace osu.Game.Tests.Database [Test] public void TestAccessAfterStorageMigrate() { - RunTestWithRealm((realmFactory, storage) => + RunTestWithRealm((realm, storage) => { var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()); ILive? liveBeatmap = null; - realmFactory.Run(realm => + realm.Run(r => { - realm.Write(r => r.Add(beatmap)); + r.Write(_ => r.Add(beatmap)); - liveBeatmap = beatmap.ToLive(realmFactory); + liveBeatmap = beatmap.ToLive(realm); }); - using (realmFactory.BlockAllOperations()) + using (realm.BlockAllOperations()) { // recycle realm before migrating } @@ -66,13 +66,13 @@ namespace osu.Game.Tests.Database [Test] public void TestAccessAfterAttach() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()); - var liveBeatmap = beatmap.ToLive(realmFactory); + var liveBeatmap = beatmap.ToLive(realm); - realmFactory.Run(realm => realm.Write(r => r.Add(beatmap))); + realm.Run(r => r.Write(_ => r.Add(beatmap))); Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden)); }); @@ -98,16 +98,16 @@ namespace osu.Game.Tests.Database [Test] public void TestScopedReadWithoutContext() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { ILive? liveBeatmap = null; Task.Factory.StartNew(() => { - realmFactory.Run(threadContext => + realm.Run(threadContext => { var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))); - liveBeatmap = beatmap.ToLive(realmFactory); + liveBeatmap = beatmap.ToLive(realm); }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); @@ -127,16 +127,16 @@ namespace osu.Game.Tests.Database [Test] public void TestScopedWriteWithoutContext() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { ILive? liveBeatmap = null; Task.Factory.StartNew(() => { - realmFactory.Run(threadContext => + realm.Run(threadContext => { var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))); - liveBeatmap = beatmap.ToLive(realmFactory); + liveBeatmap = beatmap.ToLive(realm); }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); @@ -153,10 +153,10 @@ namespace osu.Game.Tests.Database [Test] public void TestValueAccessNonManaged() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()); - var liveBeatmap = beatmap.ToLive(realmFactory); + var liveBeatmap = beatmap.ToLive(realm); Assert.DoesNotThrow(() => { @@ -168,17 +168,17 @@ namespace osu.Game.Tests.Database [Test] public void TestValueAccessWithOpenContextFails() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { ILive? liveBeatmap = null; Task.Factory.StartNew(() => { - realmFactory.Run(threadContext => + realm.Run(threadContext => { var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))); - liveBeatmap = beatmap.ToLive(realmFactory); + liveBeatmap = beatmap.ToLive(realm); }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); @@ -193,7 +193,7 @@ namespace osu.Game.Tests.Database }); // Can't be used, even from within a valid context. - realmFactory.Run(threadContext => + realm.Run(threadContext => { Assert.Throws(() => { @@ -207,16 +207,16 @@ namespace osu.Game.Tests.Database [Test] public void TestValueAccessWithoutOpenContextFails() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { ILive? liveBeatmap = null; Task.Factory.StartNew(() => { - realmFactory.Run(threadContext => + realm.Run(threadContext => { var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))); - liveBeatmap = beatmap.ToLive(realmFactory); + liveBeatmap = beatmap.ToLive(realm); }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); @@ -235,18 +235,18 @@ namespace osu.Game.Tests.Database [Test] public void TestLiveAssumptions() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { int changesTriggered = 0; - realmFactory.RegisterCustomSubscription(outerRealm => + realm.RegisterCustomSubscription(outerRealm => { outerRealm.All().QueryAsyncWithNotifications(gotChange); ILive? liveBeatmap = null; Task.Factory.StartNew(() => { - realmFactory.Run(innerRealm => + realm.Run(innerRealm => { var ruleset = CreateRuleset(); var beatmap = innerRealm.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata()))); @@ -255,7 +255,7 @@ namespace osu.Game.Tests.Database // not just a refresh from the resolved Live. innerRealm.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata()))); - liveBeatmap = beatmap.ToLive(realmFactory); + liveBeatmap = beatmap.ToLive(realm); }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs index 1799b95905..d62ce3b585 100644 --- a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs +++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs @@ -24,11 +24,11 @@ namespace osu.Game.Tests.Database IEnumerable? resolvedItems = null; ChangeSet? lastChanges = null; - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { - realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); - var registration = realmFactory.RegisterForNotifications(realm => realm.All(), onChanged); + var registration = realm.RegisterForNotifications(r => r.All(), onChanged); testEventsArriving(true); @@ -37,10 +37,10 @@ namespace osu.Game.Tests.Database resolvedItems = null; lastChanges = null; - using (realmFactory.BlockAllOperations()) + using (realm.BlockAllOperations()) Assert.That(resolvedItems, Is.Empty); - realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); testEventsArriving(true); @@ -50,34 +50,34 @@ namespace osu.Game.Tests.Database registration.Dispose(); - realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); testEventsArriving(false); // And make sure even after another context loss we don't get firings. - using (realmFactory.BlockAllOperations()) + using (realm.BlockAllOperations()) Assert.That(resolvedItems, Is.Null); - realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); testEventsArriving(false); void testEventsArriving(bool shouldArrive) { - realmFactory.Run(realm => realm.Refresh()); + realm.Run(r => r.Refresh()); if (shouldArrive) Assert.That(resolvedItems, Has.One.Items); else Assert.That(resolvedItems, Is.Null); - realmFactory.Write(realm => + realm.Write(r => { - realm.RemoveAll(); - realm.RemoveAll(); + r.RemoveAll(); + r.RemoveAll(); }); - realmFactory.Run(realm => realm.Refresh()); + realm.Run(r => r.Refresh()); if (shouldArrive) Assert.That(lastChanges?.DeletedIndices, Has.One.Items); @@ -98,39 +98,39 @@ namespace osu.Game.Tests.Database [Test] public void TestCustomRegisterWithContextLoss() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { BeatmapSetInfo? beatmapSetInfo = null; - realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); - var subscription = realmFactory.RegisterCustomSubscription(realm => + var subscription = realm.RegisterCustomSubscription(r => { - beatmapSetInfo = realm.All().First(); + beatmapSetInfo = r.All().First(); return new InvokeOnDisposal(() => beatmapSetInfo = null); }); Assert.That(beatmapSetInfo, Is.Not.Null); - using (realmFactory.BlockAllOperations()) + using (realm.BlockAllOperations()) { // custom disposal action fired when context lost. Assert.That(beatmapSetInfo, Is.Null); } // re-registration after context restore. - realmFactory.Run(realm => realm.Refresh()); + realm.Run(r => r.Refresh()); Assert.That(beatmapSetInfo, Is.Not.Null); subscription.Dispose(); Assert.That(beatmapSetInfo, Is.Null); - using (realmFactory.BlockAllOperations()) + using (realm.BlockAllOperations()) Assert.That(beatmapSetInfo, Is.Null); - realmFactory.Run(realm => realm.Refresh()); + realm.Run(r => r.Refresh()); Assert.That(beatmapSetInfo, Is.Null); }); } diff --git a/osu.Game.Tests/Database/RealmTest.cs b/osu.Game.Tests/Database/RealmTest.cs index 0cee165f75..c2339dd9ad 100644 --- a/osu.Game.Tests/Database/RealmTest.cs +++ b/osu.Game.Tests/Database/RealmTest.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Database storage.DeleteDirectory(string.Empty); } - protected void RunTestWithRealm(Action testAction, [CallerMemberName] string caller = "") + protected void RunTestWithRealm(Action testAction, [CallerMemberName] string caller = "") { using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller)) { @@ -39,22 +39,22 @@ namespace osu.Game.Tests.Database // ReSharper disable once AccessToDisposedClosure var testStorage = new OsuStorage(host, storage.GetStorageForDirectory(caller)); - using (var realmFactory = new RealmContextFactory(testStorage, "client")) + using (var realm = new RealmAccess(testStorage, "client")) { - Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}"); - testAction(realmFactory, testStorage); + Logger.Log($"Running test using realm file {testStorage.GetFullPath(realm.Filename)}"); + testAction(realm, testStorage); - realmFactory.Dispose(); + realm.Dispose(); - Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}"); - realmFactory.Compact(); - Logger.Log($"Final database size after compact: {getFileSize(testStorage, realmFactory)}"); + Logger.Log($"Final database size: {getFileSize(testStorage, realm)}"); + realm.Compact(); + Logger.Log($"Final database size after compact: {getFileSize(testStorage, realm)}"); } })); } } - protected void RunTestWithRealmAsync(Func testAction, [CallerMemberName] string caller = "") + protected void RunTestWithRealmAsync(Func testAction, [CallerMemberName] string caller = "") { using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller)) { @@ -62,15 +62,15 @@ namespace osu.Game.Tests.Database { var testStorage = storage.GetStorageForDirectory(caller); - using (var realmFactory = new RealmContextFactory(testStorage, "client")) + using (var realm = new RealmAccess(testStorage, "client")) { - Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}"); - await testAction(realmFactory, testStorage); + Logger.Log($"Running test using realm file {testStorage.GetFullPath(realm.Filename)}"); + await testAction(realm, testStorage); - realmFactory.Dispose(); + realm.Dispose(); - Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}"); - realmFactory.Compact(); + Logger.Log($"Final database size: {getFileSize(testStorage, realm)}"); + realm.Compact(); } })); } @@ -138,11 +138,11 @@ namespace osu.Game.Tests.Database } } - private static long getFileSize(Storage testStorage, RealmContextFactory realmFactory) + private static long getFileSize(Storage testStorage, RealmAccess realm) { try { - using (var stream = testStorage.GetStream(realmFactory.Filename)) + using (var stream = testStorage.GetStream(realm.Filename)) return stream?.Length ?? 0; } catch diff --git a/osu.Game.Tests/Database/RulesetStoreTests.cs b/osu.Game.Tests/Database/RulesetStoreTests.cs index 4416da6f92..7544142b70 100644 --- a/osu.Game.Tests/Database/RulesetStoreTests.cs +++ b/osu.Game.Tests/Database/RulesetStoreTests.cs @@ -12,37 +12,37 @@ namespace osu.Game.Tests.Database [Test] public void TestCreateStore() { - RunTestWithRealm((realmFactory, storage) => + RunTestWithRealm((realm, storage) => { - var rulesets = new RulesetStore(realmFactory, storage); + var rulesets = new RulesetStore(realm, storage); Assert.AreEqual(4, rulesets.AvailableRulesets.Count()); - Assert.AreEqual(4, realmFactory.Context.All().Count()); + Assert.AreEqual(4, realm.Realm.All().Count()); }); } [Test] public void TestCreateStoreTwiceDoesntAddRulesetsAgain() { - RunTestWithRealm((realmFactory, storage) => + RunTestWithRealm((realm, storage) => { - var rulesets = new RulesetStore(realmFactory, storage); - var rulesets2 = new RulesetStore(realmFactory, storage); + var rulesets = new RulesetStore(realm, storage); + var rulesets2 = new RulesetStore(realm, storage); Assert.AreEqual(4, rulesets.AvailableRulesets.Count()); Assert.AreEqual(4, rulesets2.AvailableRulesets.Count()); Assert.AreEqual(rulesets.AvailableRulesets.First(), rulesets2.AvailableRulesets.First()); - Assert.AreEqual(4, realmFactory.Context.All().Count()); + Assert.AreEqual(4, realm.Realm.All().Count()); }); } [Test] public void TestRetrievedRulesetsAreDetached() { - RunTestWithRealm((realmFactory, storage) => + RunTestWithRealm((realm, storage) => { - var rulesets = new RulesetStore(realmFactory, storage); + var rulesets = new RulesetStore(realm, storage); Assert.IsFalse(rulesets.AvailableRulesets.First().IsManaged); Assert.IsFalse(rulesets.GetRuleset(0)?.IsManaged); diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index c1041e9fd6..4b8816f142 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Database private RealmKeyBindingStore keyBindingStore; - private RealmContextFactory realmContextFactory; + private RealmAccess realm; [SetUp] public void SetUp() @@ -33,8 +33,8 @@ namespace osu.Game.Tests.Database storage = new NativeStorage(directory.FullName); - realmContextFactory = new RealmContextFactory(storage, "test"); - keyBindingStore = new RealmKeyBindingStore(realmContextFactory, new ReadableKeyCombinationProvider()); + realm = new RealmAccess(storage, "test"); + keyBindingStore = new RealmKeyBindingStore(realm, new ReadableKeyCombinationProvider()); } [Test] @@ -60,7 +60,7 @@ namespace osu.Game.Tests.Database KeyBindingContainer testContainer = new TestKeyBindingContainer(); // Add some excess bindings for an action which only supports 1. - realmContextFactory.Write(realm => + realm.Write(realm => { realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.A))); realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.S))); @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Database private int queryCount(GlobalAction? match = null) { - return realmContextFactory.Run(realm => + return realm.Run(realm => { var results = realm.All(); if (match.HasValue) @@ -92,7 +92,7 @@ namespace osu.Game.Tests.Database keyBindingStore.Register(testContainer, Enumerable.Empty()); - realmContextFactory.Run(outerRealm => + realm.Run(outerRealm => { var backBinding = outerRealm.All().Single(k => k.ActionInt == (int)GlobalAction.Back); @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Database var tsr = ThreadSafeReference.Create(backBinding); - realmContextFactory.Run(innerRealm => + realm.Run(innerRealm => { var binding = innerRealm.ResolveReference(tsr); innerRealm.Write(() => binding.KeyCombination = new KeyCombination(InputKey.BackSpace)); @@ -117,7 +117,7 @@ namespace osu.Game.Tests.Database [TearDown] public void TearDown() { - realmContextFactory.Dispose(); + realm.Dispose(); storage.DeleteDirectory(string.Empty); } diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index f0ebd7a8cc..88862ea28b 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -261,7 +261,7 @@ namespace osu.Game.Tests.Gameplay public AudioManager AudioManager => Audio; public IResourceStore Files => null; public new IResourceStore Resources => base.Resources; - public RealmContextFactory RealmContextFactory => null; + public RealmAccess RealmAccess => null; public IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore) => null; #endregion diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 1d639c6418..e35e4d9b15 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -45,8 +45,8 @@ namespace osu.Game.Tests.Online [BackgroundDependencyLoader] private void load(AudioManager audio, GameHost host) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, Access, rulesets, API, audio, Resources, host, Beatmap.Default)); Dependencies.CacheAs(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API, host)); } @@ -60,8 +60,8 @@ namespace osu.Game.Tests.Online testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile); testBeatmapSet = testBeatmapInfo.BeatmapSet; - ContextFactory.Write(r => r.RemoveAll()); - ContextFactory.Write(r => r.RemoveAll()); + Access.Write(r => r.RemoveAll()); + Access.Write(r => r.RemoveAll()); selectedItem.Value = new PlaylistItem { @@ -166,22 +166,22 @@ namespace osu.Game.Tests.Online public Task> CurrentImportTask { get; private set; } - public TestBeatmapManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null) - : base(storage, contextFactory, rulesets, api, audioManager, resources, host, defaultBeatmap) + public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null) + : base(storage, realm, rulesets, api, audioManager, resources, host, defaultBeatmap) { } - protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue) + protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue) { - return new TestBeatmapModelManager(this, storage, contextFactory, rulesets, onlineLookupQueue); + return new TestBeatmapModelManager(this, storage, realm, rulesets, onlineLookupQueue); } internal class TestBeatmapModelManager : BeatmapModelManager { private readonly TestBeatmapManager testBeatmapManager; - public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, RealmContextFactory databaseContextFactory, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) - : base(databaseContextFactory, storage, beatmapOnlineLookupQueue) + public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) + : base(databaseAccess, storage, beatmapOnlineLookupQueue) { this.testBeatmapManager = testBeatmapManager; } diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 4ab4c08353..fbfa7eda6a 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -47,10 +47,10 @@ namespace osu.Game.Tests.Visual.Background [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(new OsuConfigManager(LocalStorage)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(Access); manager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 18572ac211..fd0645a1e9 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -36,9 +36,9 @@ namespace osu.Game.Tests.Visual.Collections [BackgroundDependencyLoader] private void load(GameHost host) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesets, null, Audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index d4282ff21e..07b2bdcba3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -47,9 +47,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 99c867b014..0bf1d60ac5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -43,9 +43,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 373b165acc..063d886729 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -61,9 +61,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 15ebe0ee00..8c79c468d7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -42,9 +42,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); beatmaps = new List(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 012a2fd960..77e2c9c714 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -38,9 +38,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index d547b42891..4270818b1a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -33,9 +33,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); } [SetUp] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 965b142ed7..56e64292c6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -38,9 +38,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 1c346e09d5..afb60b62aa 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -40,9 +40,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 221732910b..79b29f0eca 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -41,9 +41,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 0b0006e437..6ed57e9899 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -34,9 +34,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); var beatmapSet = TestResources.CreateTestBeatmapSetInfo(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 39cde0ad87..bd95b297d4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -42,9 +42,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs index 0f314242b4..7cb29895eb 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Navigation .ChildrenOfType().SingleOrDefault(); private RealmKeyBinding firstOsuRulesetKeyBindings => Game.Dependencies - .Get().Context + .Get().Context .All() .AsEnumerable() .First(k => k.RulesetName == "osu" && k.ActionInt == 0); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index bc9f759bdd..716e3a535d 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -40,9 +40,9 @@ namespace osu.Game.Tests.Visual.Playlists [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); } [SetUpSteps] diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index a77480ee54..13b4af5223 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -36,13 +36,13 @@ namespace osu.Game.Tests.Visual.Ranking private BeatmapManager beatmaps { get; set; } [Resolved] - private RealmContextFactory realmContextFactory { get; set; } + private RealmAccess realm { get; set; } protected override void LoadComplete() { base.LoadComplete(); - realmContextFactory.Run(realm => + realm.Run(realm => { var beatmapInfo = realm.All() .Filter($"{nameof(BeatmapInfo.Ruleset)}.{nameof(RulesetInfo.OnlineID)} = $0", 0) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 2e1a66be5f..2f5594379b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -42,10 +42,10 @@ namespace osu.Game.Tests.Visual.SongSelect { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory)); - dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, ContextFactory, Scheduler)); - Dependencies.Cache(ContextFactory); + dependencies.Cache(rulesetStore = new RulesetStore(Access)); + dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); + dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Access, Scheduler)); + Dependencies.Cache(Access); return dependencies; } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index ca8e9d2eff..4868a4a075 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -36,9 +36,9 @@ namespace osu.Game.Tests.Visual.SongSelect [BackgroundDependencyLoader] private void load(GameHost host) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesets, null, Audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 6295a52bdd..d8a39dda01 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -47,9 +47,9 @@ namespace osu.Game.Tests.Visual.SongSelect { // These DI caches are required to ensure for interactive runs this test scene doesn't nuke all user beatmaps in the local install. // At a point we have isolated interactive test runs enough, this can likely be removed. - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(ContextFactory); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, defaultBeatmap = Beatmap.Default)); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(Access); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, defaultBeatmap = Beatmap.Default)); Dependencies.Cache(music = new MusicController()); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs index 3aa5a759e6..a0657ffdf6 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs @@ -28,10 +28,10 @@ namespace osu.Game.Tests.Visual.SongSelect [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, ContextFactory, Scheduler)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, Access, Scheduler)); + Dependencies.Cache(Access); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index f43354514b..2278d3f8bf 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.UserInterface private BeatmapInfo beatmapInfo; [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } [Cached] private readonly DialogOverlay dialogOverlay; @@ -87,10 +87,10 @@ namespace osu.Game.Tests.Visual.UserInterface { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory)); - dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, ContextFactory, Scheduler)); - Dependencies.Cache(ContextFactory); + dependencies.Cache(rulesetStore = new RulesetStore(Access)); + dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); + dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, Access, Scheduler)); + Dependencies.Cache(Access); var imported = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).GetResultSafely(); @@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.UserInterface [SetUp] public void Setup() => Schedule(() => { - realmFactory.Run(realm => + realm.Run(realm => { // Due to soft deletions, we can re-use deleted scores between test runs scoreManager.Undelete(realm.All().Where(s => s.DeletePending).ToList()); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 43e4b482bd..ddc1d054cc 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -41,11 +41,11 @@ namespace osu.Game.Beatmaps private readonly WorkingBeatmapCache workingBeatmapCache; private readonly BeatmapOnlineLookupQueue? onlineBeatmapLookupQueue; - private readonly RealmContextFactory contextFactory; + private readonly RealmAccess realm; - public BeatmapManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, IAPIProvider? api, AudioManager audioManager, IResourceStore gameResources, GameHost? host = null, WorkingBeatmap? defaultBeatmap = null, bool performOnlineLookups = false) + public BeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider? api, AudioManager audioManager, IResourceStore gameResources, GameHost? host = null, WorkingBeatmap? defaultBeatmap = null, bool performOnlineLookups = false) { - this.contextFactory = contextFactory; + this.realm = realm; if (performOnlineLookups) { @@ -55,11 +55,11 @@ namespace osu.Game.Beatmaps onlineBeatmapLookupQueue = new BeatmapOnlineLookupQueue(api, storage); } - var userResources = new RealmFileStore(contextFactory, storage).Store; + var userResources = new RealmFileStore(realm, storage).Store; BeatmapTrackStore = audioManager.GetTrackStore(userResources); - beatmapModelManager = CreateBeatmapModelManager(storage, contextFactory, rulesets, onlineBeatmapLookupQueue); + beatmapModelManager = CreateBeatmapModelManager(storage, realm, rulesets, onlineBeatmapLookupQueue); workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host); beatmapModelManager.WorkingBeatmapCache = workingBeatmapCache; @@ -70,8 +70,8 @@ namespace osu.Game.Beatmaps return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host); } - protected virtual BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, BeatmapOnlineLookupQueue? onlineLookupQueue) => - new BeatmapModelManager(contextFactory, storage, onlineLookupQueue); + protected virtual BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue? onlineLookupQueue) => + new BeatmapModelManager(realm, storage, onlineLookupQueue); /// /// Create a new . @@ -119,7 +119,7 @@ namespace osu.Game.Beatmaps /// The beatmap difficulty to hide. public void Hide(BeatmapInfo beatmapInfo) { - contextFactory.Run(realm => + realm.Run(realm => { using (var transaction = realm.BeginWrite()) { @@ -138,7 +138,7 @@ namespace osu.Game.Beatmaps /// The beatmap difficulty to restore. public void Restore(BeatmapInfo beatmapInfo) { - contextFactory.Run(realm => + realm.Run(realm => { using (var transaction = realm.BeginWrite()) { @@ -153,7 +153,7 @@ namespace osu.Game.Beatmaps public void RestoreAll() { - contextFactory.Run(realm => + realm.Run(realm => { using (var transaction = realm.BeginWrite()) { @@ -171,7 +171,7 @@ namespace osu.Game.Beatmaps /// A list of available . public List GetAllUsableBeatmapSets() { - return contextFactory.Run(realm => + return realm.Run(realm => { realm.Refresh(); return realm.All().Where(b => !b.DeletePending).Detach(); @@ -185,7 +185,7 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public ILive? QueryBeatmapSet(Expression> query) { - return contextFactory.Run(realm => realm.All().FirstOrDefault(query)?.ToLive(contextFactory)); + return realm.Run(r => r.All().FirstOrDefault(query)?.ToLive(realm)); } #region Delegation to BeatmapModelManager (methods which previously existed locally). @@ -240,7 +240,7 @@ namespace osu.Game.Beatmaps public void Delete(Expression>? filter = null, bool silent = false) { - contextFactory.Run(realm => + realm.Run(realm => { var items = realm.All().Where(s => !s.DeletePending && !s.Protected); @@ -253,7 +253,7 @@ namespace osu.Game.Beatmaps public void UndeleteAll() { - contextFactory.Run(realm => beatmapModelManager.Undelete(realm.All().Where(s => s.DeletePending).ToList())); + realm.Run(realm => beatmapModelManager.Undelete(realm.All().Where(s => s.DeletePending).ToList())); } public void Undelete(List items, bool silent = false) @@ -312,7 +312,7 @@ namespace osu.Game.Beatmaps // If we seem to be missing files, now is a good time to re-fetch. if (importedBeatmap?.BeatmapSet?.Files.Count == 0) { - contextFactory.Run(realm => + realm.Run(realm => { var refetch = realm.Find(importedBeatmap.ID)?.Detach(); diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index ead86c1059..167d77d6f6 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -33,8 +33,8 @@ namespace osu.Game.Beatmaps protected override string[] HashableFileTypes => new[] { ".osu" }; - public BeatmapModelManager(RealmContextFactory contextFactory, Storage storage, BeatmapOnlineLookupQueue? onlineLookupQueue = null) - : base(contextFactory, storage, onlineLookupQueue) + public BeatmapModelManager(RealmAccess realm, Storage storage, BeatmapOnlineLookupQueue? onlineLookupQueue = null) + : base(realm, storage, onlineLookupQueue) { } @@ -98,12 +98,12 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapInfo? QueryBeatmap(Expression> query) { - return ContextFactory.Run(realm => realm.All().FirstOrDefault(query)?.Detach()); + return Access.Run(realm => realm.All().FirstOrDefault(query)?.Detach()); } public void Update(BeatmapSetInfo item) { - ContextFactory.Write(realm => + Access.Write(realm => { var existing = realm.Find(item.ID); item.CopyChangesToRealm(existing); diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index 6947752c47..d3f356bb24 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -100,7 +100,7 @@ namespace osu.Game.Beatmaps TextureStore IBeatmapResourceProvider.LargeTextureStore => largeTextureStore; ITrackStore IBeatmapResourceProvider.Tracks => trackStore; AudioManager IStorageResourceProvider.AudioManager => audioManager; - RealmContextFactory IStorageResourceProvider.RealmContextFactory => null; + RealmAccess IStorageResourceProvider.RealmAccess => null; IResourceStore IStorageResourceProvider.Files => files; IResourceStore IStorageResourceProvider.Resources => resources; IResourceStore IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore underlyingStore) => host?.CreateTextureLoaderStore(underlyingStore); diff --git a/osu.Game/Configuration/SettingsStore.cs b/osu.Game/Configuration/SettingsStore.cs index 2bba20fb09..e5d2d572c8 100644 --- a/osu.Game/Configuration/SettingsStore.cs +++ b/osu.Game/Configuration/SettingsStore.cs @@ -10,11 +10,11 @@ namespace osu.Game.Configuration // 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 readonly RealmContextFactory Realm; + public readonly RealmAccess Realm; - public SettingsStore(RealmContextFactory realmFactory) + public SettingsStore(RealmAccess realm) { - Realm = realmFactory; + Realm = realm; } } } diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index edce99e302..a0787e81e6 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -35,7 +35,7 @@ namespace osu.Game.Database private DatabaseContextFactory efContextFactory { get; set; } = null!; [Resolved] - private RealmContextFactory realmContextFactory { get; set; } = null!; + private RealmAccess realm { get; set; } = null!; [Resolved] private OsuConfigManager config { get; set; } = null!; @@ -101,7 +101,7 @@ namespace osu.Game.Database { using (var ef = efContextFactory.Get()) { - realmContextFactory.Write(realm => + realm.Write(realm => { // Before beginning, ensure realm is in an empty state. // Migrations which are half-completed could lead to issues if the user tries a second time. @@ -158,7 +158,7 @@ namespace osu.Game.Database int count = existingBeatmapSets.Count(); - realmContextFactory.Run(realm => + realm.Run(realm => { log($"Found {count} beatmaps in EF"); @@ -280,7 +280,7 @@ namespace osu.Game.Database int count = existingScores.Count(); - realmContextFactory.Run(realm => + realm.Run(realm => { log($"Found {count} scores in EF"); @@ -369,7 +369,7 @@ namespace osu.Game.Database break; } - realmContextFactory.Run(realm => + realm.Run(realm => { using (var transaction = realm.BeginWrite()) { @@ -428,7 +428,7 @@ namespace osu.Game.Database log("Beginning settings migration to realm"); - realmContextFactory.Run(realm => + realm.Run(realm => { using (var transaction = realm.BeginWrite()) { diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmAccess.cs similarity index 98% rename from osu.Game/Database/RealmContextFactory.cs rename to osu.Game/Database/RealmAccess.cs index 778092c543..2f397639c9 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -32,7 +32,7 @@ namespace osu.Game.Database /// /// A factory which provides both the main (update thread bound) realm context and creates contexts for async usage. /// - public class RealmContextFactory : IDisposable + public class RealmAccess : IDisposable { private readonly Storage storage; @@ -123,7 +123,7 @@ namespace osu.Game.Database /// The game storage which will be used to create the realm backing file. /// The filename to use for the realm backing file. A ".realm" extension will be added automatically if not specified. /// An EF factory used only for migration purposes. - public RealmContextFactory(Storage storage, string filename, IDatabaseContextFactory? efContextFactory = null) + public RealmAccess(Storage storage, string filename, IDatabaseContextFactory? efContextFactory = null) { this.storage = storage; this.efContextFactory = efContextFactory; @@ -365,7 +365,7 @@ namespace osu.Game.Database private Realm createContext() { if (isDisposed) - throw new ObjectDisposedException(nameof(RealmContextFactory)); + throw new ObjectDisposedException(nameof(RealmAccess)); bool tookSemaphoreLock = false; @@ -592,7 +592,7 @@ namespace osu.Game.Database public IDisposable BlockAllOperations() { if (isDisposed) - throw new ObjectDisposedException(nameof(RealmContextFactory)); + throw new ObjectDisposedException(nameof(RealmAccess)); SynchronizationContext? syncContext = null; @@ -652,7 +652,7 @@ namespace osu.Game.Database throw; } - return new InvokeOnDisposal(this, factory => + return new InvokeOnDisposal(this, factory => { factory.contextCreationLock.Release(); Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index df5e165f8e..29159fd5be 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -24,17 +24,17 @@ namespace osu.Game.Database /// private readonly T data; - private readonly RealmContextFactory realmFactory; + private readonly RealmAccess realm; /// /// Construct a new instance of live realm data. /// /// The realm data. - /// The realm factory the data was sourced from. May be null for an unmanaged object. - public RealmLive(T data, RealmContextFactory realmFactory) + /// The realm factory the data was sourced from. May be null for an unmanaged object. + public RealmLive(T data, RealmAccess realm) { this.data = data; - this.realmFactory = realmFactory; + this.realm = realm; ID = data.ID; } @@ -51,7 +51,7 @@ namespace osu.Game.Database return; } - realmFactory.Run(realm => + realm.Run(realm => { perform(retrieveFromID(realm, ID)); }); @@ -66,7 +66,7 @@ namespace osu.Game.Database if (!IsManaged) return perform(data); - return realmFactory.Run(realm => + return realm.Run(realm => { var returnData = perform(retrieveFromID(realm, ID)); @@ -104,7 +104,7 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException($"Can't use {nameof(Value)} on managed objects from non-update threads"); - return realmFactory.Context.Find(ID); + return realm.Realm.Find(ID); } } diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index d9026d165d..d4f8978ac5 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -216,16 +216,16 @@ namespace osu.Game.Database return new RealmLiveUnmanaged(realmObject); } - public static List> ToLive(this IEnumerable realmList, RealmContextFactory realmContextFactory) + public static List> ToLive(this IEnumerable realmList, RealmAccess realm) where T : RealmObject, IHasGuidPrimaryKey { - return realmList.Select(l => new RealmLive(l, realmContextFactory)).Cast>().ToList(); + return realmList.Select(l => new RealmLive(l, realm)).Cast>().ToList(); } - public static ILive ToLive(this T realmObject, RealmContextFactory realmContextFactory) + public static ILive ToLive(this T realmObject, RealmAccess realm) where T : RealmObject, IHasGuidPrimaryKey { - return new RealmLive(realmObject, realmContextFactory); + return new RealmLive(realmObject, realm); } /// @@ -271,8 +271,8 @@ namespace osu.Game.Database public static IDisposable? QueryAsyncWithNotifications(this IRealmCollection collection, NotificationCallbackDelegate callback) where T : RealmObjectBase { - if (!RealmContextFactory.CurrentThreadSubscriptionsAllowed) - throw new InvalidOperationException($"Make sure to call {nameof(RealmContextFactory)}.{nameof(RealmContextFactory.RegisterForNotifications)}"); + if (!RealmAccess.CurrentThreadSubscriptionsAllowed) + throw new InvalidOperationException($"Make sure to call {nameof(RealmAccess)}.{nameof(RealmAccess.RegisterForNotifications)}"); return collection.SubscribeForNotifications(callback); } diff --git a/osu.Game/IO/IStorageResourceProvider.cs b/osu.Game/IO/IStorageResourceProvider.cs index 950b5aae09..b381ac70b0 100644 --- a/osu.Game/IO/IStorageResourceProvider.cs +++ b/osu.Game/IO/IStorageResourceProvider.cs @@ -28,7 +28,7 @@ namespace osu.Game.IO /// /// Access realm. /// - RealmContextFactory RealmContextFactory { get; } + RealmAccess RealmAccess { get; } /// /// Create a texture loader store based on an underlying data store. diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 9cd9865441..d54c049c99 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -25,7 +25,7 @@ namespace osu.Game.Input.Bindings private IDisposable realmSubscription; [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } public override IEnumerable DefaultKeyBindings => ruleset.CreateInstance().GetDefaultKeyBindings(variant ?? 0); @@ -49,13 +49,13 @@ namespace osu.Game.Input.Bindings private IQueryable queryRealmKeyBindings() { string rulesetName = ruleset?.ShortName; - return realmFactory.Context.All() - .Where(b => b.RulesetName == rulesetName && b.Variant == variant); + return realm.Realm.All() + .Where(b => b.RulesetName == rulesetName && b.Variant == variant); } protected override void LoadComplete() { - realmSubscription = realmFactory.RegisterForNotifications(realm => queryRealmKeyBindings(), (sender, changes, error) => + realmSubscription = realm.RegisterForNotifications(realm => queryRealmKeyBindings(), (sender, changes, error) => { // The first fire of this is a bit redundant as this is being called in base.LoadComplete, // but this is safest in case the subscription is restored after a context recycle. diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 60f7eb2198..cccd42a9aa 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -16,12 +16,12 @@ namespace osu.Game.Input { public class RealmKeyBindingStore { - private readonly RealmContextFactory realmFactory; + private readonly RealmAccess realm; private readonly ReadableKeyCombinationProvider keyCombinationProvider; - public RealmKeyBindingStore(RealmContextFactory realmFactory, ReadableKeyCombinationProvider keyCombinationProvider) + public RealmKeyBindingStore(RealmAccess realm, ReadableKeyCombinationProvider keyCombinationProvider) { - this.realmFactory = realmFactory; + this.realm = realm; this.keyCombinationProvider = keyCombinationProvider; } @@ -34,7 +34,7 @@ namespace osu.Game.Input { List combinations = new List(); - realmFactory.Run(context => + realm.Run(context => { foreach (var action in context.All().Where(b => string.IsNullOrEmpty(b.RulesetName) && (GlobalAction)b.ActionInt == globalAction)) { @@ -56,7 +56,7 @@ namespace osu.Game.Input /// The rulesets to populate defaults from. public void Register(KeyBindingContainer container, IEnumerable rulesets) { - realmFactory.Run(realm => + realm.Run(realm => { using (var transaction = realm.BeginWrite()) { diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index 3f45afb6b2..f54dc30620 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -22,7 +22,7 @@ namespace osu.Game.Online private IDisposable? realmSubscription; [Resolved] - private RealmContextFactory realmContextFactory { get; set; } = null!; + private RealmAccess realm { get; set; } = null!; public BeatmapDownloadTracker(IBeatmapSetInfo trackedItem) : base(trackedItem) @@ -42,7 +42,7 @@ namespace osu.Game.Online // Used to interact with manager classes that don't support interface types. Will eventually be replaced. var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID }; - realmSubscription = realmContextFactory.RegisterForNotifications(realm => realm.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(realm => realm.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index 28c41bec33..c562695ac1 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -30,7 +30,7 @@ namespace osu.Game.Online.Rooms protected override bool RequiresChildrenUpdate => true; [Resolved] - private RealmContextFactory realmContextFactory { get; set; } = null!; + private RealmAccess realm { get; set; } = null!; /// /// The availability state of the currently selected playlist item. @@ -78,7 +78,7 @@ namespace osu.Game.Online.Rooms // handles changes to hash that didn't occur from the import process (ie. a user editing the beatmap in the editor, somehow). realmSubscription?.Dispose(); - realmSubscription = realmContextFactory.RegisterForNotifications(realm => filteredBeatmaps(), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(realm => filteredBeatmaps(), (items, changes, ___) => { if (changes == null) return; @@ -128,9 +128,9 @@ namespace osu.Game.Online.Rooms int onlineId = SelectedItem.Value.Beatmap.Value.OnlineID; string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash; - return realmContextFactory.Context - .All() - .Filter("OnlineID == $0 && MD5Hash == $1 && BeatmapSet.DeletePending == false", onlineId, checksum); + return realm.Realm + .All() + .Filter("OnlineID == $0 && MD5Hash == $1 && BeatmapSet.DeletePending == false", onlineId, checksum); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index 64ceee9fe6..81dfc811a4 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -23,7 +23,7 @@ namespace osu.Game.Online private IDisposable? realmSubscription; [Resolved] - private RealmContextFactory realmContextFactory { get; set; } = null!; + private RealmAccess realm { get; set; } = null!; public ScoreDownloadTracker(ScoreInfo trackedItem) : base(trackedItem) @@ -47,7 +47,7 @@ namespace osu.Game.Online Downloader.DownloadBegan += downloadBegan; Downloader.DownloadFailed += downloadFailed; - realmSubscription = realmContextFactory.RegisterForNotifications(realm => realm.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(realm => realm.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending), (items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 710a7be8d4..7a6a126900 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -149,7 +149,7 @@ namespace osu.Game private MultiplayerClient multiplayerClient; - private RealmContextFactory realmFactory; + private RealmAccess realm; protected override Container Content => content; @@ -192,9 +192,9 @@ namespace osu.Game if (Storage.Exists(DatabaseContextFactory.DATABASE_NAME)) dependencies.Cache(EFContextFactory = new DatabaseContextFactory(Storage)); - dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client", EFContextFactory)); + dependencies.Cache(realm = new RealmAccess(Storage, "client", EFContextFactory)); - dependencies.Cache(RulesetStore = new RulesetStore(realmFactory, Storage)); + dependencies.Cache(RulesetStore = new RulesetStore(realm, Storage)); dependencies.CacheAs(RulesetStore); // Backup is taken here rather than in EFToRealmMigrator to avoid recycling realm contexts @@ -205,7 +205,7 @@ namespace osu.Game string migration = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; EFContextFactory.CreateBackup($"client.{migration}.db"); - realmFactory.CreateBackup($"client.{migration}.realm"); + realm.CreateBackup($"client.{migration}.realm"); using (var source = Storage.GetStream("collection.db")) using (var destination = Storage.GetStream($"collection.{migration}.db", FileAccess.Write, FileMode.CreateNew)) @@ -225,7 +225,7 @@ namespace osu.Game Audio.Samples.PlaybackConcurrency = SAMPLE_CONCURRENCY; - dependencies.Cache(SkinManager = new SkinManager(Storage, realmFactory, Host, Resources, Audio, Scheduler)); + dependencies.Cache(SkinManager = new SkinManager(Storage, realm, Host, Resources, Audio, Scheduler)); dependencies.CacheAs(SkinManager); EndpointConfiguration endpoints = UseDevelopmentServer ? (EndpointConfiguration)new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration(); @@ -240,8 +240,8 @@ namespace osu.Game var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realmFactory, Scheduler, Host, () => difficultyCache, LocalConfig)); - dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realmFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, Host, () => difficultyCache, LocalConfig)); + dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true)); dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API)); dependencies.Cache(ScoreDownloader = new ScoreModelDownloader(ScoreManager, API)); @@ -259,7 +259,7 @@ namespace osu.Game dependencies.Cache(scorePerformanceManager); AddInternal(scorePerformanceManager); - dependencies.CacheAs(rulesetConfigCache = new RulesetConfigCache(realmFactory, RulesetStore)); + dependencies.CacheAs(rulesetConfigCache = new RulesetConfigCache(realm, RulesetStore)); var powerStatus = CreateBatteryInfo(); if (powerStatus != null) @@ -303,7 +303,7 @@ namespace osu.Game base.Content.Add(CreateScalingContainer().WithChildren(mainContent)); - KeyBindingStore = new RealmKeyBindingStore(realmFactory, keyCombinationProvider); + KeyBindingStore = new RealmKeyBindingStore(realm, keyCombinationProvider); KeyBindingStore.Register(globalBindings, RulesetStore.AvailableRulesets); dependencies.Cache(globalBindings); @@ -405,7 +405,7 @@ namespace osu.Game Scheduler.Add(() => { - realmBlocker = realmFactory.BlockAllOperations(); + realmBlocker = realm.BlockAllOperations(); readyToRun.Set(); }, false); @@ -483,7 +483,7 @@ namespace osu.Game BeatmapManager?.Dispose(); LocalConfig?.Dispose(); - realmFactory?.Dispose(); + realm?.Dispose(); } } } diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 8df7ed9736..8450446473 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -69,7 +69,7 @@ namespace osu.Game.Overlays public DrawableTrack CurrentTrack { get; private set; } = new DrawableTrack(new TrackVirtual(1000)); [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } [BackgroundDependencyLoader] private void load() @@ -81,9 +81,9 @@ namespace osu.Game.Overlays } private IQueryable queryRealmBeatmapSets() => - realmFactory.Context - .All() - .Where(s => !s.DeletePending); + realm.Realm + .All() + .Where(s => !s.DeletePending); protected override void LoadComplete() { @@ -94,7 +94,7 @@ namespace osu.Game.Overlays foreach (var s in queryRealmBeatmapSets()) beatmapSets.Add(s.Detach()); - beatmapSubscription = realmFactory.RegisterForNotifications(realm => queryRealmBeatmapSets(), beatmapsChanged); + beatmapSubscription = realm.RegisterForNotifications(realm => queryRealmBeatmapSets(), beatmapsChanged); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index c5854981e6..3b94cae171 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings protected override LocalisableString Header => DebugSettingsStrings.MemoryHeader; [BackgroundDependencyLoader] - private void load(GameHost host, RealmContextFactory realmFactory) + private void load(GameHost host, RealmAccess realm) { SettingsButton blockAction; SettingsButton unblockAction; @@ -37,7 +37,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings Action = () => { // Blocking operations implicitly causes a Compact(). - using (realmFactory.BlockAllOperations()) + using (realm.BlockAllOperations()) { } } @@ -56,7 +56,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings { try { - var token = realmFactory.BlockAllOperations(); + var token = realm.BlockAllOperations(); blockAction.Enabled.Value = false; diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index 60aff91301..91883e4f41 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -79,7 +79,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input } [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) @@ -386,7 +386,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input private void updateStoreFromButton(KeyButton button) { - realmFactory.Run(realm => + realm.Run(realm => { var binding = realm.Find(((IHasGuidPrimaryKey)button.KeyBinding).ID); realm.Write(() => binding.KeyCombinationString = button.KeyBinding.KeyCombinationString); diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs index 5b8a52240e..922d371261 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs @@ -30,13 +30,13 @@ namespace osu.Game.Overlays.Settings.Sections.Input } [BackgroundDependencyLoader] - private void load(RealmContextFactory realmFactory) + private void load(RealmAccess realm) { string rulesetName = Ruleset?.ShortName; - var bindings = realmFactory.Run(realm => realm.All() - .Where(b => b.RulesetName == rulesetName && b.Variant == variant) - .Detach()); + var bindings = realm.Run(r => r.All() + .Where(b => b.RulesetName == rulesetName && b.Variant == variant) + .Detach()); foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) { diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 6e5cd0da2d..af3fd5c9bf 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -47,15 +47,15 @@ namespace osu.Game.Overlays.Settings.Sections private SkinManager skins { get; set; } [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } private IDisposable realmSubscription; private IQueryable queryRealmSkins() => - realmFactory.Context.All() - .Where(s => !s.DeletePending) - .OrderByDescending(s => s.Protected) // protected skins should be at the top. - .ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase); + realm.Realm.All() + .Where(s => !s.DeletePending) + .OrderByDescending(s => s.Protected) // protected skins should be at the top. + .ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase); [BackgroundDependencyLoader(permitNulls: true)] private void load(OsuConfigManager config, [CanBeNull] SkinEditorOverlay skinEditor) @@ -83,7 +83,7 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown.Current = dropdownBindable; - realmSubscription = realmFactory.RegisterForNotifications(realm => queryRealmSkins(), (sender, changes, error) => + realmSubscription = realm.RegisterForNotifications(realm => queryRealmSkins(), (sender, changes, error) => { // The first fire of this is a bit redundant due to the call below, // but this is safest in case the subscription is restored after a context recycle. @@ -130,7 +130,7 @@ namespace osu.Game.Overlays.Settings.Sections { int protectedCount = queryRealmSkins().Count(s => s.Protected); - skinItems = queryRealmSkins().ToLive(realmFactory); + skinItems = queryRealmSkins().ToLive(realm); skinItems.Insert(protectedCount, random_skin_info); diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 75bebfa763..c855b76680 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -80,7 +80,7 @@ namespace osu.Game.Overlays.Toolbar protected FillFlowContainer Flow; [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } protected ToolbarButton() : base(HoverSampleSet.Toolbar) @@ -207,7 +207,7 @@ namespace osu.Game.Overlays.Toolbar { if (Hotkey == null) return; - var realmKeyBinding = realmFactory.Context.All().FirstOrDefault(rkb => rkb.RulesetName == null && rkb.ActionInt == (int)Hotkey.Value); + var realmKeyBinding = realm.Realm.All().FirstOrDefault(rkb => rkb.RulesetName == null && rkb.ActionInt == (int)Hotkey.Value); if (realmKeyBinding != null) { diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index 60a6b70221..cfa20e0b87 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Configuration public abstract class RulesetConfigManager : ConfigManager, IRulesetConfigManager where TLookup : struct, Enum { - private readonly RealmContextFactory realmFactory; + private readonly RealmAccess realm; private readonly int variant; @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Configuration protected RulesetConfigManager(SettingsStore store, RulesetInfo ruleset, int? variant = null) { - realmFactory = store?.Realm; + realm = store?.Realm; rulesetName = ruleset.ShortName; @@ -37,10 +37,10 @@ namespace osu.Game.Rulesets.Configuration protected override void PerformLoad() { - if (realmFactory != null) + if (realm != null) { // As long as RulesetConfigCache exists, there is no need to subscribe to realm events. - databasedSettings = realmFactory.Context.All().Where(b => b.RulesetName == rulesetName && b.Variant == variant).ToList(); + databasedSettings = realm.Realm.All().Where(b => b.RulesetName == rulesetName && b.Variant == variant).ToList(); } } @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Configuration pendingWrites.Clear(); } - realmFactory?.Write(realm => + realm?.Write(realm => { foreach (var c in changed) { @@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Configuration Variant = variant, }; - realmFactory?.Context.Write(() => realmFactory.Context.Add(setting)); + realm?.Realm.Write(() => realm.Realm.Add(setting)); databasedSettings.Add(setting); } diff --git a/osu.Game/Rulesets/RulesetConfigCache.cs b/osu.Game/Rulesets/RulesetConfigCache.cs index dee13e74a5..c4f1933cd8 100644 --- a/osu.Game/Rulesets/RulesetConfigCache.cs +++ b/osu.Game/Rulesets/RulesetConfigCache.cs @@ -13,14 +13,14 @@ namespace osu.Game.Rulesets { public class RulesetConfigCache : Component, IRulesetConfigCache { - private readonly RealmContextFactory realmFactory; + private readonly RealmAccess realm; private readonly RulesetStore rulesets; private readonly Dictionary configCache = new Dictionary(); - public RulesetConfigCache(RealmContextFactory realmFactory, RulesetStore rulesets) + public RulesetConfigCache(RealmAccess realm, RulesetStore rulesets) { - this.realmFactory = realmFactory; + this.realm = realm; this.rulesets = rulesets; } @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets { base.LoadComplete(); - var settingsStore = new SettingsStore(realmFactory); + var settingsStore = new SettingsStore(realm); // let's keep things simple for now and just retrieve all the required configs at startup.. foreach (var ruleset in rulesets.AvailableRulesets) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index a9e5ff797c..606bc65599 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets { public class RulesetStore : IDisposable, IRulesetStore { - private readonly RealmContextFactory realmFactory; + private readonly RealmAccess realm; private const string ruleset_library_prefix = @"osu.Game.Rulesets"; @@ -31,9 +31,9 @@ namespace osu.Game.Rulesets private readonly List availableRulesets = new List(); - public RulesetStore(RealmContextFactory realmFactory, Storage? storage = null) + public RulesetStore(RealmAccess realm, Storage? storage = null) { - this.realmFactory = realmFactory; + this.realm = realm; // On android in release configuration assemblies are loaded from the apk directly into memory. // We cannot read assemblies from cwd, so should check loaded assemblies instead. @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets private void addMissingRulesets() { - realmFactory.Write(realm => + realm.Write(realm => { var rulesets = realm.All(); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index f895134f97..e712d170cd 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -25,21 +25,21 @@ namespace osu.Game.Scoring { public class ScoreManager : IModelManager, IModelImporter { - private readonly RealmContextFactory contextFactory; + private readonly RealmAccess realm; private readonly Scheduler scheduler; private readonly Func difficulties; private readonly OsuConfigManager configManager; private readonly ScoreModelManager scoreModelManager; - public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmContextFactory contextFactory, Scheduler scheduler, + public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, Scheduler scheduler, IIpcHost importHost = null, Func difficulties = null, OsuConfigManager configManager = null) { - this.contextFactory = contextFactory; + this.realm = realm; this.scheduler = scheduler; this.difficulties = difficulties; this.configManager = configManager; - scoreModelManager = new ScoreModelManager(rulesets, beatmaps, storage, contextFactory); + scoreModelManager = new ScoreModelManager(rulesets, beatmaps, storage, realm); } public Score GetScore(ScoreInfo score) => scoreModelManager.GetScore(score); @@ -51,7 +51,7 @@ namespace osu.Game.Scoring /// The first result for the provided query, or null if no results were found. public ScoreInfo Query(Expression> query) { - return contextFactory.Run(realm => realm.All().FirstOrDefault(query)?.Detach()); + return realm.Run(realm => realm.All().FirstOrDefault(query)?.Detach()); } /// @@ -254,7 +254,7 @@ namespace osu.Game.Scoring public void Delete([CanBeNull] Expression> filter = null, bool silent = false) { - contextFactory.Run(realm => + realm.Run(realm => { var items = realm.All() .Where(s => !s.DeletePending); diff --git a/osu.Game/Scoring/ScoreModelManager.cs b/osu.Game/Scoring/ScoreModelManager.cs index 5e560effa1..2147ff1ba1 100644 --- a/osu.Game/Scoring/ScoreModelManager.cs +++ b/osu.Game/Scoring/ScoreModelManager.cs @@ -29,8 +29,8 @@ namespace osu.Game.Scoring private readonly RulesetStore rulesets; private readonly Func beatmaps; - public ScoreModelManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmContextFactory contextFactory) - : base(storage, contextFactory) + public ScoreModelManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm) + : base(storage, realm) { this.rulesets = rulesets; this.beatmaps = beatmaps; @@ -74,7 +74,7 @@ namespace osu.Game.Scoring public override bool IsAvailableLocally(ScoreInfo model) { - return ContextFactory.Run(realm => realm.All().Any(s => s.OnlineID == model.OnlineID)); + return Access.Run(realm => realm.All().Any(s => s.OnlineID == model.OnlineID)); } } } diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index e66ecc74e1..fceb083916 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -85,7 +85,7 @@ namespace osu.Game.Screens.Menu private BeatmapManager beatmaps { get; set; } [BackgroundDependencyLoader] - private void load(OsuConfigManager config, Framework.Game game, RealmContextFactory realmContextFactory) + private void load(OsuConfigManager config, Framework.Game game, RealmAccess realm) { // prevent user from changing beatmap while the intro is still running. beatmap = Beatmap.BeginLease(false); @@ -97,9 +97,9 @@ namespace osu.Game.Screens.Menu // if the user has requested not to play theme music, we should attempt to find a random beatmap from their collection. if (!MenuMusic.Value) { - realmContextFactory.Run(realm => + realm.Run(r => { - var usableBeatmapSets = realm.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); + var usableBeatmapSets = r.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); int setCount = usableBeatmapSets.Count; diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 5a6295fe74..8e0fdea0f8 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -179,24 +179,24 @@ namespace osu.Game.Screens.Select if (!loadedTestBeatmaps) { - realmFactory.Run(realm => loadBeatmapSets(getBeatmapSets(realm))); + realm.Run(realm => loadBeatmapSets(getBeatmapSets(realm))); } } [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } protected override void LoadComplete() { base.LoadComplete(); - subscriptionSets = realmFactory.RegisterForNotifications(getBeatmapSets, beatmapSetsChanged); - subscriptionBeatmaps = realmFactory.RegisterForNotifications(realm => realm.All().Where(b => !b.Hidden), beatmapsChanged); + subscriptionSets = realm.RegisterForNotifications(getBeatmapSets, beatmapSetsChanged); + subscriptionBeatmaps = realm.RegisterForNotifications(realm => realm.All().Where(b => !b.Hidden), beatmapsChanged); // Can't use main subscriptions because we can't lookup deleted indices. // https://github.com/realm/realm-dotnet/discussions/2634#discussioncomment-1605595. - subscriptionDeletedSets = realmFactory.RegisterForNotifications(realm => realm.All().Where(s => s.DeletePending && !s.Protected), deletedBeatmapSetsChanged); - subscriptionHiddenBeatmaps = realmFactory.RegisterForNotifications(realm => realm.All().Where(b => b.Hidden), beatmapsChanged); + subscriptionDeletedSets = realm.RegisterForNotifications(realm => realm.All().Where(s => s.DeletePending && !s.Protected), deletedBeatmapSetsChanged); + subscriptionHiddenBeatmaps = realm.RegisterForNotifications(realm => realm.All().Where(b => b.Hidden), beatmapsChanged); } private void deletedBeatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) @@ -231,7 +231,7 @@ namespace osu.Game.Screens.Select foreach (var id in realmSets) { if (!root.BeatmapSetsByID.ContainsKey(id)) - UpdateBeatmapSet(realmFactory.Context.Find(id).Detach()); + UpdateBeatmapSet(realm.Realm.Find(id).Detach()); } foreach (var id in root.BeatmapSetsByID.Keys) diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index c03d464ef6..021dfd06f7 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Select.Carousel private IBindable ruleset { get; set; } [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } [Resolved] private IAPIProvider api { get; set; } @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Select.Carousel ruleset.BindValueChanged(_ => { scoreSubscription?.Dispose(); - scoreSubscription = realmFactory.RegisterForNotifications(realm => + scoreSubscription = realm.RegisterForNotifications(realm => realm.All() .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 5f288b972b..3d262f8b97 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Select.Leaderboards private RulesetStore rulesets { get; set; } [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } private BeatmapInfo beatmapInfo; @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (beatmapInfo == null) return; - scoreSubscription = realmFactory.RegisterForNotifications(realm => + scoreSubscription = realm.RegisterForNotifications(realm => realm.All() .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID), (_, changes, ___) => @@ -150,7 +150,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (Scope == BeatmapLeaderboardScope.Local) { - realmFactory.Run(realm => + realm.Run(realm => { var scores = realm.All() .AsEnumerable() diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 4abde56ea6..3cf9f79611 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Spectate } [Resolved] - private RealmContextFactory realmContextFactory { get; set; } + private RealmAccess realm { get; set; } private IDisposable realmSubscription; @@ -80,7 +80,7 @@ namespace osu.Game.Screens.Spectate playingUserStates.BindTo(spectatorClient.PlayingUserStates); playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true); - realmSubscription = realmContextFactory.RegisterForNotifications( + realmSubscription = realm.RegisterForNotifications( realm => realm.All().Where(s => !s.DeletePending), beatmapsChanged); foreach ((int id, var _) in userMap) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index d606d94b97..3685a26e26 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -43,8 +43,8 @@ namespace osu.Game.Skinning protected Skin(SkinInfo skin, IStorageResourceProvider resources, [CanBeNull] Stream configurationStream = null) { - SkinInfo = resources?.RealmContextFactory != null - ? skin.ToLive(resources.RealmContextFactory) + SkinInfo = resources?.RealmAccess != null + ? skin.ToLive(resources.RealmAccess) // This path should only be used in some tests. : skin.ToLiveUnmanaged(); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 82bcd3b292..66956325da 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -54,7 +54,7 @@ namespace osu.Game.Skinning }; private readonly SkinModelManager skinModelManager; - private readonly RealmContextFactory contextFactory; + private readonly RealmAccess realm; private readonly IResourceStore userFiles; @@ -68,9 +68,9 @@ namespace osu.Game.Skinning /// public Skin DefaultLegacySkin { get; } - public SkinManager(Storage storage, RealmContextFactory contextFactory, GameHost host, IResourceStore resources, AudioManager audio, Scheduler scheduler) + public SkinManager(Storage storage, RealmAccess realm, GameHost host, IResourceStore resources, AudioManager audio, Scheduler scheduler) { - this.contextFactory = contextFactory; + this.realm = realm; this.audio = audio; this.scheduler = scheduler; this.host = host; @@ -78,7 +78,7 @@ namespace osu.Game.Skinning userFiles = new StorageBackedResourceStore(storage.GetStorageForDirectory("files")); - skinModelManager = new SkinModelManager(storage, contextFactory, host, this); + skinModelManager = new SkinModelManager(storage, realm, host, this); var defaultSkins = new[] { @@ -87,7 +87,7 @@ namespace osu.Game.Skinning }; // Ensure the default entries are present. - contextFactory.Write(realm => + realm.Write(realm => { foreach (var skin in defaultSkins) { @@ -110,10 +110,10 @@ namespace osu.Game.Skinning public void SelectRandomSkin() { - contextFactory.Run(realm => + realm.Run(r => { // choose from only user skins, removing the current selection to ensure a new one is chosen. - var randomChoices = realm.All().Where(s => !s.DeletePending && s.ID != CurrentSkinInfo.Value.ID).ToArray(); + var randomChoices = r.All().Where(s => !s.DeletePending && s.ID != CurrentSkinInfo.Value.ID).ToArray(); if (randomChoices.Length == 0) { @@ -123,7 +123,7 @@ namespace osu.Game.Skinning var chosen = randomChoices.ElementAt(RNG.Next(0, randomChoices.Length)); - CurrentSkinInfo.Value = chosen.ToLive(contextFactory); + CurrentSkinInfo.Value = chosen.ToLive(realm); }); } @@ -179,7 +179,7 @@ namespace osu.Game.Skinning /// The first result for the provided query, or null if no results were found. public ILive Query(Expression> query) { - return contextFactory.Run(realm => realm.All().FirstOrDefault(query)?.ToLive(contextFactory)); + return realm.Run(r => r.All().FirstOrDefault(query)?.ToLive(realm)); } public event Action SourceChanged; @@ -234,7 +234,7 @@ namespace osu.Game.Skinning AudioManager IStorageResourceProvider.AudioManager => audio; IResourceStore IStorageResourceProvider.Resources => resources; IResourceStore IStorageResourceProvider.Files => userFiles; - RealmContextFactory IStorageResourceProvider.RealmContextFactory => contextFactory; + RealmAccess IStorageResourceProvider.RealmAccess => realm; IResourceStore IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore underlyingStore) => host.CreateTextureLoaderStore(underlyingStore); #endregion @@ -289,7 +289,7 @@ namespace osu.Game.Skinning public void Delete([CanBeNull] Expression> filter = null, bool silent = false) { - contextFactory.Run(realm => + realm.Run(realm => { var items = realm.All() .Where(s => !s.Protected && !s.DeletePending); diff --git a/osu.Game/Skinning/SkinModelManager.cs b/osu.Game/Skinning/SkinModelManager.cs index a1926913a9..c93cdb17dd 100644 --- a/osu.Game/Skinning/SkinModelManager.cs +++ b/osu.Game/Skinning/SkinModelManager.cs @@ -27,8 +27,8 @@ namespace osu.Game.Skinning private readonly IStorageResourceProvider skinResources; - public SkinModelManager(Storage storage, RealmContextFactory contextFactory, GameHost host, IStorageResourceProvider skinResources) - : base(storage, contextFactory) + public SkinModelManager(Storage storage, RealmAccess realm, GameHost host, IStorageResourceProvider skinResources) + : base(storage, realm) { this.skinResources = skinResources; @@ -205,7 +205,7 @@ namespace osu.Game.Skinning private void populateMissingHashes() { - ContextFactory.Run(realm => + Access.Run(realm => { var skinsWithoutHashes = realm.All().Where(i => !i.Protected && string.IsNullOrEmpty(i.Hash)).ToArray(); diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index 61178014ef..3d241e795c 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -44,8 +44,8 @@ namespace osu.Game.Stores private readonly BeatmapOnlineLookupQueue? onlineLookupQueue; - protected BeatmapImporter(RealmContextFactory contextFactory, Storage storage, BeatmapOnlineLookupQueue? onlineLookupQueue = null) - : base(storage, contextFactory) + protected BeatmapImporter(RealmAccess realm, Storage storage, BeatmapOnlineLookupQueue? onlineLookupQueue = null) + : base(storage, realm) { this.onlineLookupQueue = onlineLookupQueue; } @@ -165,7 +165,7 @@ namespace osu.Game.Stores public override bool IsAvailableLocally(BeatmapSetInfo model) { - return ContextFactory.Run(realm => realm.All().Any(b => b.OnlineID == model.OnlineID)); + return Access.Run(realm => realm.All().Any(b => b.OnlineID == model.OnlineID)); } public override string HumanisedModelName => "beatmap"; diff --git a/osu.Game/Stores/RealmArchiveModelImporter.cs b/osu.Game/Stores/RealmArchiveModelImporter.cs index 3d8e9f2703..23a860791e 100644 --- a/osu.Game/Stores/RealmArchiveModelImporter.cs +++ b/osu.Game/Stores/RealmArchiveModelImporter.cs @@ -59,7 +59,7 @@ namespace osu.Game.Stores protected readonly RealmFileStore Files; - protected readonly RealmContextFactory ContextFactory; + protected readonly RealmAccess Access; /// /// Fired when the user requests to view the resulting import. @@ -71,11 +71,11 @@ namespace osu.Game.Stores /// public Action? PostNotification { protected get; set; } - protected RealmArchiveModelImporter(Storage storage, RealmContextFactory contextFactory) + protected RealmArchiveModelImporter(Storage storage, RealmAccess realm) { - ContextFactory = contextFactory; + Access = realm; - Files = new RealmFileStore(contextFactory, storage); + Files = new RealmFileStore(realm, storage); } /// @@ -320,7 +320,7 @@ namespace osu.Game.Stores /// An optional cancellation token. public virtual Task?> Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { - return ContextFactory.Run(realm => + return Access.Run(realm => { cancellationToken.ThrowIfCancellationRequested(); @@ -352,7 +352,7 @@ namespace osu.Game.Stores transaction.Commit(); } - return Task.FromResult((ILive?)existing.ToLive(ContextFactory)); + return Task.FromResult((ILive?)existing.ToLive(Access)); } LogForModel(item, @"Found existing (optimised) but failed pre-check."); @@ -387,7 +387,7 @@ namespace osu.Game.Stores existing.DeletePending = false; transaction.Commit(); - return Task.FromResult((ILive?)existing.ToLive(ContextFactory)); + return Task.FromResult((ILive?)existing.ToLive(Access)); } LogForModel(item, @"Found existing but failed re-use check."); @@ -413,7 +413,7 @@ namespace osu.Game.Stores throw; } - return Task.FromResult((ILive?)item.ToLive(ContextFactory)); + return Task.FromResult((ILive?)item.ToLive(Access)); }); } diff --git a/osu.Game/Stores/RealmArchiveModelManager.cs b/osu.Game/Stores/RealmArchiveModelManager.cs index 115fbf721d..01eb90d6e8 100644 --- a/osu.Game/Stores/RealmArchiveModelManager.cs +++ b/osu.Game/Stores/RealmArchiveModelManager.cs @@ -24,10 +24,10 @@ namespace osu.Game.Stores { private readonly RealmFileStore realmFileStore; - protected RealmArchiveModelManager(Storage storage, RealmContextFactory contextFactory) - : base(storage, contextFactory) + protected RealmArchiveModelManager(Storage storage, RealmAccess realm) + : base(storage, realm) { - realmFileStore = new RealmFileStore(contextFactory, storage); + realmFileStore = new RealmFileStore(realm, storage); } public void DeleteFile(TModel item, RealmNamedFileUsage file) => @@ -45,7 +45,7 @@ namespace osu.Game.Stores // This method should be removed as soon as all the surrounding pieces support non-detached operations. if (!item.IsManaged) { - var managed = ContextFactory.Context.Find(item.ID); + var managed = Access.Context.Find(item.ID); managed.Realm.Write(() => operation(managed)); item.Files.Clear(); @@ -165,7 +165,7 @@ namespace osu.Game.Stores public bool Delete(TModel item) { - return ContextFactory.Run(realm => + return Access.Run(realm => { if (!item.IsManaged) item = realm.Find(item.ID); @@ -180,7 +180,7 @@ namespace osu.Game.Stores public void Undelete(TModel item) { - ContextFactory.Run(realm => + Access.Run(realm => { if (!item.IsManaged) item = realm.Find(item.ID); diff --git a/osu.Game/Stores/RealmFileStore.cs b/osu.Game/Stores/RealmFileStore.cs index ca371e29be..5edc1be954 100644 --- a/osu.Game/Stores/RealmFileStore.cs +++ b/osu.Game/Stores/RealmFileStore.cs @@ -24,15 +24,15 @@ namespace osu.Game.Stores [ExcludeFromDynamicCompile] public class RealmFileStore { - private readonly RealmContextFactory realmFactory; + private readonly RealmAccess realm; public readonly IResourceStore Store; public readonly Storage Storage; - public RealmFileStore(RealmContextFactory realmFactory, Storage storage) + public RealmFileStore(RealmAccess realm, Storage storage) { - this.realmFactory = realmFactory; + this.realm = realm; Storage = storage.GetStorageForDirectory(@"files"); Store = new StorageBackedResourceStore(Storage); @@ -92,7 +92,7 @@ namespace osu.Game.Stores int removedFiles = 0; // can potentially be run asynchronously, although we will need to consider operation order for disk deletion vs realm removal. - realmFactory.Write(realm => + realm.Write(realm => { // TODO: consider using a realm native query to avoid iterating all files (https://github.com/realm/realm-dotnet/issues/2659#issuecomment-927823707) var files = realm.All().ToList(); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index 3d6240bc98..e6528a83bd 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -77,12 +77,12 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader(true)] - private void load(GameplayClock clock, CancellationToken? cancellationToken, GameHost host, RealmContextFactory realmContextFactory) + private void load(GameplayClock clock, CancellationToken? cancellationToken, GameHost host, RealmAccess realm) { if (clock != null) Clock = clock; - dependencies.Cache(new TextureStore(host.CreateTextureLoaderStore(new RealmFileStore(realmContextFactory, host.Storage).Store), false, scaleAdjust: 1)); + dependencies.Cache(new TextureStore(host.CreateTextureLoaderStore(new RealmFileStore(realm, host.Storage).Store), false, scaleAdjust: 1)); foreach (var layer in Storyboard.Layers) { diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index 10cb210f4d..f7e154b5e7 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -123,7 +123,7 @@ namespace osu.Game.Tests.Beatmaps public IResourceStore Files => userSkinResourceStore; public new IResourceStore Resources => base.Resources; public IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore) => null; - RealmContextFactory IStorageResourceProvider.RealmContextFactory => null; + RealmAccess IStorageResourceProvider.RealmAccess => null; #endregion diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 6cc009514d..a6cac0ec86 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual working = CreateWorkingBeatmap(Ruleset.Value); if (IsolateSavingFromDatabase) - Dependencies.CacheAs(testBeatmapManager = new TestBeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.CacheAs(testBeatmapManager = new TestBeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); } protected override void LoadComplete() @@ -126,14 +126,14 @@ namespace osu.Game.Tests.Visual { public WorkingBeatmap TestBeatmap; - public TestBeatmapManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host, WorkingBeatmap defaultBeatmap) - : base(storage, contextFactory, rulesets, api, audioManager, resources, host, defaultBeatmap) + public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host, WorkingBeatmap defaultBeatmap) + : base(storage, realm, rulesets, api, audioManager, resources, host, defaultBeatmap) { } - protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue) + protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue) { - return new TestBeatmapModelManager(storage, contextFactory, rulesets, onlineLookupQueue); + return new TestBeatmapModelManager(storage, realm, rulesets, onlineLookupQueue); } protected override WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore resources, IResourceStore storage, WorkingBeatmap defaultBeatmap, GameHost host) @@ -157,8 +157,8 @@ namespace osu.Game.Tests.Visual internal class TestBeatmapModelManager : BeatmapModelManager { - public TestBeatmapModelManager(Storage storage, RealmContextFactory databaseContextFactory, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) - : base(databaseContextFactory, storage, beatmapOnlineLookupQueue) + public TestBeatmapModelManager(Storage storage, RealmAccess databaseAccess, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) + : base(databaseAccess, storage, beatmapOnlineLookupQueue) { } diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index da8af49158..33dd1d45b8 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -75,9 +75,9 @@ namespace osu.Game.Tests.Visual /// /// In interactive runs (ie. VisualTests) this will use the user's database if is not set to true. /// - protected RealmContextFactory ContextFactory => contextFactory.Value; + protected RealmAccess Access => contextFactory.Value; - private Lazy contextFactory; + private Lazy contextFactory; /// /// Whether a fresh storage should be initialised per test (method) run. @@ -119,7 +119,7 @@ namespace osu.Game.Tests.Visual Resources = parent.Get().Resources; - contextFactory = new Lazy(() => new RealmContextFactory(LocalStorage, "client")); + contextFactory = new Lazy(() => new RealmAccess(LocalStorage, "client")); RecycleLocalStorage(false); diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index a080f47d66..cd675e467b 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -159,7 +159,7 @@ namespace osu.Game.Tests.Visual public IResourceStore Files => null; public new IResourceStore Resources => base.Resources; public IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore) => host.CreateTextureLoaderStore(underlyingStore); - RealmContextFactory IStorageResourceProvider.RealmContextFactory => null; + RealmAccess IStorageResourceProvider.RealmAccess => null; #endregion From f30894840c709505464cc2b903f4a8870feb7fd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 20:11:36 +0900 Subject: [PATCH 0210/5199] Update terminology to realm "instance" rather than "context" This matches the terminology used by realm themselves, which feels better. --- osu.Game.Tests/Database/FileStoreTests.cs | 8 +- .../TestSceneChangeAndUseGameplayBindings.cs | 2 +- osu.Game/Database/RealmAccess.cs | 131 ++++++++---------- osu.Game/Stores/RealmArchiveModelManager.cs | 2 +- 4 files changed, 67 insertions(+), 76 deletions(-) diff --git a/osu.Game.Tests/Database/FileStoreTests.cs b/osu.Game.Tests/Database/FileStoreTests.cs index 008aa3d833..98b0ed99b5 100644 --- a/osu.Game.Tests/Database/FileStoreTests.cs +++ b/osu.Game.Tests/Database/FileStoreTests.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmAccess, storage) => { - var realm = realmAccess.Context; + var realm = realmAccess.Realm; var files = new RealmFileStore(realmAccess, storage); var testData = new MemoryStream(new byte[] { 0, 1, 2, 3 }); @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmAccess, storage) => { - var realm = realmAccess.Context; + var realm = realmAccess.Realm; var files = new RealmFileStore(realmAccess, storage); var testData = new MemoryStream(new byte[] { 0, 1, 2, 3 }); @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmAccess, storage) => { - var realm = realmAccess.Context; + var realm = realmAccess.Realm; var files = new RealmFileStore(realmAccess, storage); var file = realm.Write(() => files.Add(new MemoryStream(new byte[] { 0, 1, 2, 3 }), realm)); @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmAccess, storage) => { - var realm = realmAccess.Context; + var realm = realmAccess.Realm; var files = new RealmFileStore(realmAccess, storage); var file = realm.Write(() => files.Add(new MemoryStream(new byte[] { 0, 1, 2, 3 }), realm)); diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs index 7cb29895eb..bfcefdbbfe 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Navigation .ChildrenOfType().SingleOrDefault(); private RealmKeyBinding firstOsuRulesetKeyBindings => Game.Dependencies - .Get().Context + .Get().Realm .All() .AsEnumerable() .First(k => k.RulesetName == "osu" && k.ActionInt == 0); diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 2f397639c9..66b4edbe84 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -59,9 +59,9 @@ namespace osu.Game.Database /// /// Lock object which is held during sections, blocking context creation during blocking periods. /// - private readonly SemaphoreSlim contextCreationLock = new SemaphoreSlim(1); + private readonly SemaphoreSlim realmCreationLock = new SemaphoreSlim(1); - private readonly ThreadLocal currentThreadCanCreateContexts = new ThreadLocal(); + private readonly ThreadLocal currentThreadCanCreateRealmInstances = new ThreadLocal(); /// /// Holds a map of functions registered via and and a coinciding action which when triggered, @@ -81,35 +81,35 @@ namespace osu.Game.Database /// private readonly Dictionary, Action> notificationsResetMap = new Dictionary, Action>(); - private static readonly GlobalStatistic contexts_created = GlobalStatistics.Get(@"Realm", @"Contexts (Created)"); + private static readonly GlobalStatistic realm_instances_created = GlobalStatistics.Get(@"Realm", @"Instances (Created)"); - private readonly object contextLock = new object(); + private readonly object realmLock = new object(); - private Realm? context; + private Realm? updateRealm; - public Realm Context => ensureUpdateContext(); + public Realm Realm => ensureUpdateRealm(); - private Realm ensureUpdateContext() + private Realm ensureUpdateRealm() { if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException(@$"Use {nameof(createContext)} when performing realm operations from a non-update thread"); + throw new InvalidOperationException(@$"Use {nameof(getRealmInstance)} when performing realm operations from a non-update thread"); - lock (contextLock) + lock (realmLock) { - if (context == null) + if (updateRealm == null) { - context = createContext(); - Logger.Log(@$"Opened realm ""{context.Config.DatabasePath}"" at version {context.Config.SchemaVersion}"); + updateRealm = getRealmInstance(); + + Logger.Log(@$"Opened realm ""{updateRealm.Config.DatabasePath}"" at version {updateRealm.Config.SchemaVersion}"); // Resubscribe any subscriptions foreach (var action in customSubscriptionsResetMap.Keys) registerSubscription(action); } - Debug.Assert(context != null); + Debug.Assert(updateRealm != null); - // creating a context will ensure our schema is up-to-date and migrated. - return context; + return updateRealm; } } @@ -118,7 +118,7 @@ namespace osu.Game.Database private static readonly ThreadLocal current_thread_subscriptions_allowed = new ThreadLocal(); /// - /// Construct a new instance of a realm context factory. + /// Construct a new instance. /// /// The game storage which will be used to create the realm backing file. /// The filename to use for the realm backing file. A ".realm" extension will be added automatically if not specified. @@ -137,7 +137,7 @@ namespace osu.Game.Database try { - // This method triggers the first `CreateContext` call, which will implicitly run realm migrations and bring the schema up-to-date. + // This method triggers the first `getRealmInstance` call, which will implicitly run realm migrations and bring the schema up-to-date. cleanupPendingDeletions(); } catch (Exception e) @@ -153,7 +153,7 @@ namespace osu.Game.Database private void cleanupPendingDeletions() { - using (var realm = createContext()) + using (var realm = getRealmInstance()) using (var transaction = realm.BeginWrite()) { var pendingDeleteScores = realm.All().Where(s => s.DeletePending); @@ -201,34 +201,28 @@ namespace osu.Game.Database /// /// Run work on realm with a return value. /// - /// - /// Handles correct context management automatically. - /// /// The work to run. /// The return type. public T Run(Func action) { if (ThreadSafety.IsUpdateThread) - return action(Context); + return action(Realm); - using (var realm = createContext()) + using (var realm = getRealmInstance()) return action(realm); } /// /// Run work on realm. /// - /// - /// Handles correct context management automatically. - /// /// The work to run. public void Run(Action action) { if (ThreadSafety.IsUpdateThread) - action(Context); + action(Realm); else { - using (var realm = createContext()) + using (var realm = getRealmInstance()) action(realm); } } @@ -236,17 +230,14 @@ namespace osu.Game.Database /// /// Write changes to realm. /// - /// - /// Handles correct context management and transaction committing automatically. - /// /// The work to run. public void Write(Action action) { if (ThreadSafety.IsUpdateThread) - Context.Write(action); + Realm.Write(action); else { - using (var realm = createContext()) + using (var realm = getRealmInstance()) realm.Write(action); } } @@ -257,10 +248,10 @@ namespace osu.Game.Database /// /// This adds osu! specific thread and managed state safety checks on top of . /// - /// In addition to the documented realm behaviour, we have the additional requirement of handling subscriptions over potential context loss. + /// In addition to the documented realm behaviour, we have the additional requirement of handling subscriptions over potential realm instance recycle. /// When this happens, callback events will be automatically fired: - /// - On context loss, a callback with an empty collection and null will be invoked. - /// - On context revival, a standard initial realm callback will arrive, with null and an up-to-date collection. + /// - On recycle start, a callback with an empty collection and null will be invoked. + /// - On recycle end, a standard initial realm callback will arrive, with null and an up-to-date collection. /// /// The to observe for changes. /// Type of the elements in the list. @@ -276,7 +267,7 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException(@$"{nameof(RegisterForNotifications)} must be called from the update thread."); - lock (contextLock) + lock (realmLock) { Func action = realm => query(realm).QueryAsyncWithNotifications(callback); @@ -287,7 +278,7 @@ namespace osu.Game.Database } /// - /// Run work on realm that will be run every time the update thread realm context gets recycled. + /// Run work on realm that will be run every time the update thread realm instance gets recycled. /// /// The work to run. Return value should be an from QueryAsyncWithNotifications, or an to clean up any bindings. /// An which should be disposed to unsubscribe any inner subscription. @@ -311,7 +302,7 @@ namespace osu.Game.Database void unsubscribe() { - lock (contextLock) + lock (realmLock) { if (customSubscriptionsResetMap.TryGetValue(action, out var unsubscriptionAction)) { @@ -328,12 +319,12 @@ namespace osu.Game.Database { Debug.Assert(ThreadSafety.IsUpdateThread); - lock (contextLock) + lock (realmLock) { - // Retrieve context outside of flag update to ensure that the context is constructed, + // Retrieve realm instance outside of flag update to ensure that the instance is retrieved, // as attempting to access it inside the subscription if it's not constructed would lead to // cyclic invocations of the subscription callback. - var realm = Context; + var realm = Realm; Debug.Assert(!customSubscriptionsResetMap.TryGetValue(action, out var found) || found == null); @@ -344,12 +335,12 @@ namespace osu.Game.Database } /// - /// Unregister all subscriptions when the realm context is to be recycled. - /// Subscriptions will still remain and will be re-subscribed when the realm context returns. + /// Unregister all subscriptions when the realm instance is to be recycled. + /// Subscriptions will still remain and will be re-subscribed when the realm instance returns. /// private void unregisterAllSubscriptions() { - lock (contextLock) + lock (realmLock) { foreach (var action in notificationsResetMap.Values) action(); @@ -362,7 +353,7 @@ namespace osu.Game.Database } } - private Realm createContext() + private Realm getRealmInstance() { if (isDisposed) throw new ObjectDisposedException(nameof(RealmAccess)); @@ -371,20 +362,20 @@ namespace osu.Game.Database try { - if (!currentThreadCanCreateContexts.Value) + if (!currentThreadCanCreateRealmInstances.Value) { - contextCreationLock.Wait(); - currentThreadCanCreateContexts.Value = true; + realmCreationLock.Wait(); + currentThreadCanCreateRealmInstances.Value = true; tookSemaphoreLock = true; } else { - // the semaphore is used to handle blocking of all context creation during certain periods. - // once the semaphore has been taken by this code section, it is safe to create further contexts on the same thread. - // this can happen if a realm subscription is active and triggers a callback which has user code that calls `CreateContext`. + // the semaphore is used to handle blocking of all realm retrieval during certain periods. + // once the semaphore has been taken by this code section, it is safe to retrieve further realm instances on the same thread. + // this can happen if a realm subscription is active and triggers a callback which has user code that calls `Run`. } - contexts_created.Value++; + realm_instances_created.Value++; return Realm.GetInstance(getConfiguration()); } @@ -392,8 +383,8 @@ namespace osu.Game.Database { if (tookSemaphoreLock) { - contextCreationLock.Release(); - currentThreadCanCreateContexts.Value = false; + realmCreationLock.Release(); + currentThreadCanCreateRealmInstances.Value = false; } } } @@ -582,7 +573,7 @@ namespace osu.Game.Database } /// - /// Flush any active contexts and block any further writes. + /// Flush any active realm instances and block any further writes. /// /// /// This should be used in places we need to ensure no ongoing reads/writes are occurring with realm. @@ -598,14 +589,14 @@ namespace osu.Game.Database try { - contextCreationLock.Wait(); + realmCreationLock.Wait(); - lock (contextLock) + lock (realmLock) { - if (context == null) + if (updateRealm == null) { - // null context means the update thread has not yet retrieved its context. - // we don't need to worry about reviving the update context in this case, so don't bother with the SynchronizationContext. + // null realm means the update thread has not yet retrieved its instance. + // we don't need to worry about reviving the update instance in this case, so don't bother with the SynchronizationContext. Debug.Assert(!ThreadSafety.IsUpdateThread); } else @@ -620,8 +611,8 @@ namespace osu.Game.Database Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); - context?.Dispose(); - context = null; + updateRealm?.Dispose(); + updateRealm = null; } const int sleep_length = 200; @@ -648,17 +639,17 @@ namespace osu.Game.Database } catch { - contextCreationLock.Release(); + realmCreationLock.Release(); throw; } return new InvokeOnDisposal(this, factory => { - factory.contextCreationLock.Release(); + factory.realmCreationLock.Release(); Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); // Post back to the update thread to revive any subscriptions. - syncContext?.Post(_ => ensureUpdateContext(), null); + syncContext?.Post(_ => ensureUpdateRealm(), null); }); } @@ -669,16 +660,16 @@ namespace osu.Game.Database public void Dispose() { - lock (contextLock) + lock (realmLock) { - context?.Dispose(); + updateRealm?.Dispose(); } if (!isDisposed) { // intentionally block context creation indefinitely. this ensures that nothing can start consuming a new context after disposal. - contextCreationLock.Wait(); - contextCreationLock.Dispose(); + realmCreationLock.Wait(); + realmCreationLock.Dispose(); isDisposed = true; } diff --git a/osu.Game/Stores/RealmArchiveModelManager.cs b/osu.Game/Stores/RealmArchiveModelManager.cs index 01eb90d6e8..00cd1c2958 100644 --- a/osu.Game/Stores/RealmArchiveModelManager.cs +++ b/osu.Game/Stores/RealmArchiveModelManager.cs @@ -45,7 +45,7 @@ namespace osu.Game.Stores // This method should be removed as soon as all the surrounding pieces support non-detached operations. if (!item.IsManaged) { - var managed = Access.Context.Find(item.ID); + var managed = Access.Realm.Find(item.ID); managed.Realm.Write(() => operation(managed)); item.Files.Clear(); From 6f4c337a5696ef2e51c10511ceeed873cd0834b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 20:52:27 +0900 Subject: [PATCH 0211/5199] Fix a failed `BlockAllOperations` leaving update realm in unretrieved state If the operation timed out on.. ```csharp throw new TimeoutException(@"Took too long to acquire lock"); ``` ..from an update thread, it would not restore the update context. The next call would then fail on the assert that ensures a non-null context in such cases. Can add test coverage if required. --- osu.Game/Database/RealmAccess.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 66b4edbe84..64063664eb 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -595,8 +595,8 @@ namespace osu.Game.Database { if (updateRealm == null) { - // null realm means the update thread has not yet retrieved its instance. - // we don't need to worry about reviving the update instance in this case, so don't bother with the SynchronizationContext. + // null context means the update thread has not yet retrieved its context. + // we don't need to worry about reviving the update context in this case, so don't bother with the SynchronizationContext. Debug.Assert(!ThreadSafety.IsUpdateThread); } else @@ -639,18 +639,19 @@ namespace osu.Game.Database } catch { - realmCreationLock.Release(); + restoreOperation(); throw; } - return new InvokeOnDisposal(this, factory => - { - factory.realmCreationLock.Release(); - Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); + return new InvokeOnDisposal(restoreOperation); + void restoreOperation() + { + Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); + realmCreationLock.Release(); // Post back to the update thread to revive any subscriptions. syncContext?.Post(_ => ensureUpdateRealm(), null); - }); + } } // https://github.com/realm/realm-dotnet/blob/32f4ebcc88b3e80a3b254412665340cd9f3bd6b5/Realm/Realm/Extensions/ReflectionExtensions.cs#L46 From a7c0d507cefa16343475f5aa73a23d666b6b7446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Jan 2022 20:44:25 +0100 Subject: [PATCH 0212/5199] Rename flashlight settings to be more accurate --- .../Mods/CatchModFlashlight.cs | 20 +++++++++---------- .../Mods/ManiaModFlashlight.cs | 20 +++++++++---------- .../Mods/OsuModFlashlight.cs | 20 +++++++++---------- .../Mods/TaikoModFlashlight.cs | 20 +++++++++---------- osu.Game/Rulesets/Mods/ModFlashlight.cs | 8 ++++---- 5 files changed, 44 insertions(+), 44 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index d48382a9ee..e7335dcc34 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -16,15 +16,8 @@ namespace osu.Game.Rulesets.Catch.Mods { public override double ScoreMultiplier => 1.12; - [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] - public override BindableBool ChangeRadius { get; } = new BindableBool - { - Default = true, - Value = true - }; - - [SettingSource("Initial radius", "Initial radius of the flashlight area.")] - public override BindableNumber InitialRadius { get; } = new BindableNumber + [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] + public override BindableNumber SizeMultiplier { get; } = new BindableNumber { MinValue = 0.4f, MaxValue = 1.7f, @@ -33,9 +26,16 @@ namespace osu.Game.Rulesets.Catch.Mods Precision = 0.1f }; + [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] + public override BindableBool ComboBasedSize { get; } = new BindableBool + { + Default = true, + Value = true + }; + protected virtual float DefaultFlashlightSize => 350; - public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, DefaultFlashlightSize); + public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); private CatchPlayfield playfield; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index eb3f60edce..f89c131fea 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -17,15 +17,8 @@ namespace osu.Game.Rulesets.Mania.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModHidden) }; - [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] - public override BindableBool ChangeRadius { get; } = new BindableBool - { - Default = false, - Value = false - }; - - [SettingSource("Initial radius", "Initial radius of the flashlight area.")] - public override BindableNumber InitialRadius { get; } = new BindableNumber + [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] + public override BindableNumber SizeMultiplier { get; } = new BindableNumber { MinValue = 0f, MaxValue = 4.5f, @@ -34,9 +27,16 @@ namespace osu.Game.Rulesets.Mania.Mods Precision = 0.1f }; + [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] + public override BindableBool ComboBasedSize { get; } = new BindableBool + { + Default = false, + Value = false + }; + protected virtual float DefaultFlashlightSize => 50; - public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value, DefaultFlashlightSize); + public override Flashlight CreateFlashlight() => new ManiaFlashlight(ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); private class ManiaFlashlight : Flashlight { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 6a9d199c54..bc915591d0 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -30,15 +30,8 @@ namespace osu.Game.Rulesets.Osu.Mods Precision = default_follow_delay, }; - [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] - public override BindableBool ChangeRadius { get; } = new BindableBool - { - Default = true, - Value = true - }; - - [SettingSource("Initial radius", "Initial radius of the flashlight area.")] - public override BindableNumber InitialRadius { get; } = new BindableNumber + [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] + public override BindableNumber SizeMultiplier { get; } = new BindableNumber { MinValue = 0.5f, MaxValue = 2f, @@ -47,11 +40,18 @@ namespace osu.Game.Rulesets.Osu.Mods Precision = 0.1f }; + [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] + public override BindableBool ComboBasedSize { get; } = new BindableBool + { + Default = true, + Value = true + }; + protected virtual float DefaultFlashlightSize => 180; private OsuFlashlight flashlight; - public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value, DefaultFlashlightSize); + public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ComboBasedSize.Value, SizeMultiplier.Value, FollowDelay.Value, DefaultFlashlightSize); public void ApplyToDrawableHitObject(DrawableHitObject drawable) { diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 8de7c859c4..48e56c8784 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -17,15 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Mods { public override double ScoreMultiplier => 1.12; - [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] - public override BindableBool ChangeRadius { get; } = new BindableBool - { - Default = true, - Value = true - }; - - [SettingSource("Initial radius", "Initial radius of the flashlight area.")] - public override BindableNumber InitialRadius { get; } = new BindableNumber + [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] + public override BindableNumber SizeMultiplier { get; } = new BindableNumber { MinValue = 0, MaxValue = 1.66f, @@ -34,9 +27,16 @@ namespace osu.Game.Rulesets.Taiko.Mods Precision = 0.1f }; + [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] + public override BindableBool ComboBasedSize { get; } = new BindableBool + { + Default = true, + Value = true + }; + protected virtual float DefaultFlashlightSize => 250; - public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, DefaultFlashlightSize); + public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); private TaikoPlayfield playfield; diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 531ee92b7a..d3998bcad4 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -33,11 +33,11 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Restricted view area."; - [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] - public abstract BindableBool ChangeRadius { get; } + [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] + public abstract BindableNumber SizeMultiplier { get; } - [SettingSource("Initial radius", "Initial radius of the flashlight area.")] - public abstract BindableNumber InitialRadius { get; } + [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] + public abstract BindableBool ComboBasedSize { get; } } public abstract class ModFlashlight : ModFlashlight, IApplicableToDrawableRuleset, IApplicableToScoreProcessor From 5874475dffbc5ba6d1bf620d07e026360858a3a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Jan 2022 20:46:54 +0100 Subject: [PATCH 0213/5199] Extract `DefaultFlashlightSize` to base flashlight class --- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 2 +- osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 2 +- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 2 +- osu.Game/Rulesets/Mods/ModFlashlight.cs | 6 ++++++ 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index e7335dcc34..4c5dcf84d2 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Mods Value = true }; - protected virtual float DefaultFlashlightSize => 350; + public override float DefaultFlashlightSize => 350; public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index f89c131fea..03be00c5db 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.Mods Value = false }; - protected virtual float DefaultFlashlightSize => 50; + public override float DefaultFlashlightSize => 50; public override Flashlight CreateFlashlight() => new ManiaFlashlight(ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index bc915591d0..f344bb017e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Mods Value = true }; - protected virtual float DefaultFlashlightSize => 180; + public override float DefaultFlashlightSize => 180; private OsuFlashlight flashlight; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 48e56c8784..576fbb65a0 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Taiko.Mods Value = true }; - protected virtual float DefaultFlashlightSize => 250; + public override float DefaultFlashlightSize => 250; public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index d3998bcad4..f2fd86d549 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -38,6 +38,12 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] public abstract BindableBool ComboBasedSize { get; } + + /// + /// The default size of the flashlight in ruleset-appropriate dimensions. + /// and will apply their adjustments on top of this size. + /// + public abstract float DefaultFlashlightSize { get; } } public abstract class ModFlashlight : ModFlashlight, IApplicableToDrawableRuleset, IApplicableToScoreProcessor From a227af75edb70a847aac468c47371bacc4d3d49f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Jan 2022 20:55:24 +0100 Subject: [PATCH 0214/5199] Simplify flashlight parameter passing flow --- .../Mods/CatchModFlashlight.cs | 10 +++---- .../Mods/ManiaModFlashlight.cs | 10 +++---- .../Mods/OsuModFlashlight.cs | 19 +++++------- .../Mods/TaikoModFlashlight.cs | 8 ++--- osu.Game/Rulesets/Mods/ModFlashlight.cs | 30 +++++++++---------- 5 files changed, 37 insertions(+), 40 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 4c5dcf84d2..6bd914de33 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Catch.Mods public override float DefaultFlashlightSize => 350; - public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); + protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield); private CatchPlayfield playfield; @@ -49,11 +49,11 @@ namespace osu.Game.Rulesets.Catch.Mods { private readonly CatchPlayfield playfield; - public CatchFlashlight(CatchPlayfield playfield, bool isRadiusBasedOnCombo, float initialRadius, float defaultFlashlightSize) - : base(isRadiusBasedOnCombo, initialRadius, defaultFlashlightSize) + public CatchFlashlight(CatchModFlashlight modFlashlight, CatchPlayfield playfield) + : base(modFlashlight) { this.playfield = playfield; - FlashlightSize = new Vector2(0, GetRadiusFor(0)); + FlashlightSize = new Vector2(0, GetSizeFor(0)); } protected override void Update() @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Catch.Mods protected override void OnComboChange(ValueChangedEvent e) { - this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetRadiusFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); + this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "CircularFlashlight"; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 03be00c5db..def3f8b274 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -36,16 +36,16 @@ namespace osu.Game.Rulesets.Mania.Mods public override float DefaultFlashlightSize => 50; - public override Flashlight CreateFlashlight() => new ManiaFlashlight(ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); + protected override Flashlight CreateFlashlight() => new ManiaFlashlight(this); private class ManiaFlashlight : Flashlight { private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); - public ManiaFlashlight(bool isRadiusBasedOnCombo, float initialRadius, float defaultFlashlightSize) - : base(isRadiusBasedOnCombo, initialRadius, defaultFlashlightSize) + public ManiaFlashlight(ManiaModFlashlight modFlashlight) + : base(modFlashlight) { - FlashlightSize = new Vector2(DrawWidth, GetRadiusFor(0)); + FlashlightSize = new Vector2(DrawWidth, GetSizeFor(0)); AddLayout(flashlightProperties); } @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Mania.Mods protected override void OnComboChange(ValueChangedEvent e) { - this.TransformTo(nameof(FlashlightSize), new Vector2(DrawWidth, GetRadiusFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); + this.TransformTo(nameof(FlashlightSize), new Vector2(DrawWidth, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "RectangularFlashlight"; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index f344bb017e..b4eff57c55 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Mods private OsuFlashlight flashlight; - public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ComboBasedSize.Value, SizeMultiplier.Value, FollowDelay.Value, DefaultFlashlightSize); + protected override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(this); public void ApplyToDrawableHitObject(DrawableHitObject drawable) { @@ -61,17 +61,14 @@ namespace osu.Game.Rulesets.Osu.Mods private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition { - public double FollowDelay { private get; set; } + private readonly double followDelay; - //public float InitialRadius { private get; set; } - public bool ChangeRadius { private get; set; } - - public OsuFlashlight(bool isRadiusBasedOnCombo, float initialRadius, double followDelay, float defaultFlashlightSize) - : base(isRadiusBasedOnCombo, initialRadius, defaultFlashlightSize) + public OsuFlashlight(OsuModFlashlight modFlashlight) + : base(modFlashlight) { - FollowDelay = followDelay; + followDelay = modFlashlight.FollowDelay.Value; - FlashlightSize = new Vector2(0, GetRadiusFor(0)); + FlashlightSize = new Vector2(0, GetSizeFor(0)); } public void OnSliderTrackingChange(ValueChangedEvent e) @@ -86,14 +83,14 @@ namespace osu.Game.Rulesets.Osu.Mods var destination = e.MousePosition; FlashlightPosition = Interpolation.ValueAt( - Math.Min(Math.Abs(Clock.ElapsedFrameTime), FollowDelay), position, destination, 0, FollowDelay, Easing.Out); + Math.Min(Math.Abs(Clock.ElapsedFrameTime), followDelay), position, destination, 0, followDelay, Easing.Out); return base.OnMouseMove(e); } protected override void OnComboChange(ValueChangedEvent e) { - this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetRadiusFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); + this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "CircularFlashlight"; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 576fbb65a0..84444dded9 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public override float DefaultFlashlightSize => 250; - public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); + protected override Flashlight CreateFlashlight() => new TaikoFlashlight(this, playfield); private TaikoPlayfield playfield; @@ -51,8 +51,8 @@ namespace osu.Game.Rulesets.Taiko.Mods private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); private readonly TaikoPlayfield taikoPlayfield; - public TaikoFlashlight(TaikoPlayfield taikoPlayfield, bool isRadiusBasedOnCombo, float initialRadius, float defaultFlashlightSize) - : base(isRadiusBasedOnCombo, initialRadius, defaultFlashlightSize) + public TaikoFlashlight(TaikoModFlashlight modFlashlight, TaikoPlayfield taikoPlayfield) + : base(modFlashlight) { this.taikoPlayfield = taikoPlayfield; FlashlightSize = getSizeFor(0); @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.Mods private Vector2 getSizeFor(int combo) { // Preserve flashlight size through the playfield's aspect adjustment. - return new Vector2(0, GetRadiusFor(combo) * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); + return new Vector2(0, GetSizeFor(combo) * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); } protected override void OnComboChange(ValueChangedEvent e) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index f2fd86d549..e6487c6b29 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Mods flashlight.Breaks = drawableRuleset.Beatmap.Breaks; } - public abstract Flashlight CreateFlashlight(); + protected abstract Flashlight CreateFlashlight(); public abstract class Flashlight : Drawable { @@ -102,17 +102,15 @@ namespace osu.Game.Rulesets.Mods public List Breaks; - public readonly bool IsRadiusBasedOnCombo; + private readonly float defaultFlashlightSize; + private readonly float sizeMultiplier; + private readonly bool comboBasedSize; - public readonly float InitialRadius; - - public readonly float DefaultFlashlightSize; - - protected Flashlight(bool isRadiusBasedOnCombo, float initialRadius, float defaultFlashlightSize) + protected Flashlight(ModFlashlight modFlashlight) { - IsRadiusBasedOnCombo = isRadiusBasedOnCombo; - InitialRadius = initialRadius; - DefaultFlashlightSize = defaultFlashlightSize; + defaultFlashlightSize = modFlashlight.DefaultFlashlightSize; + sizeMultiplier = modFlashlight.SizeMultiplier.Value; + comboBasedSize = modFlashlight.ComboBasedSize.Value; } [BackgroundDependencyLoader] @@ -146,17 +144,19 @@ namespace osu.Game.Rulesets.Mods protected abstract string FragmentShader { get; } - protected float GetRadiusFor(int combo) + protected float GetSizeFor(int combo) { - if (IsRadiusBasedOnCombo) + float size = defaultFlashlightSize * sizeMultiplier; + + if (comboBasedSize) { if (combo > 200) - return InitialRadius * 0.8f * DefaultFlashlightSize; + size *= 0.8f; else if (combo > 100) - return InitialRadius * 0.9f * DefaultFlashlightSize; + size *= 0.9f; } - return InitialRadius * DefaultFlashlightSize; + return size; } private Vector2 flashlightPosition; From 4a13c93ca7e98c89ba5938c89c1e00a2856c08c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Jan 2022 20:56:59 +0100 Subject: [PATCH 0215/5199] Disallow zero size multiplier in flashlight implementations --- osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs | 2 +- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index def3f8b274..60de063b24 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mania.Mods [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableNumber SizeMultiplier { get; } = new BindableNumber { - MinValue = 0f, + MinValue = 0.1f, MaxValue = 4.5f, Default = 1f, Value = 1f, diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 84444dded9..bdb30e9ded 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Mods [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableNumber SizeMultiplier { get; } = new BindableNumber { - MinValue = 0, + MinValue = 0.1f, MaxValue = 1.66f, Default = 1f, Value = 1f, From 2375420d4cbce1372fd2848b7de799bee2f281b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Jan 2022 21:16:10 +0100 Subject: [PATCH 0216/5199] Tweak allowable ranges of size multiplier --- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 4 ++-- osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs | 4 ++-- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 6bd914de33..2d92c925d7 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -19,8 +19,8 @@ namespace osu.Game.Rulesets.Catch.Mods [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableNumber SizeMultiplier { get; } = new BindableNumber { - MinValue = 0.4f, - MaxValue = 1.7f, + MinValue = 0.5f, + MaxValue = 1.5f, Default = 1f, Value = 1f, Precision = 0.1f diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 60de063b24..1ee4ea12e3 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Mania.Mods [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableNumber SizeMultiplier { get; } = new BindableNumber { - MinValue = 0.1f, - MaxValue = 4.5f, + MinValue = 0.5f, + MaxValue = 3f, Default = 1f, Value = 1f, Precision = 0.1f diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index bdb30e9ded..fb07c687bb 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Taiko.Mods [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableNumber SizeMultiplier { get; } = new BindableNumber { - MinValue = 0.1f, - MaxValue = 1.66f, + MinValue = 0.5f, + MaxValue = 1.5f, Default = 1f, Value = 1f, Precision = 0.1f From 2d34831b5fd0383c75cf1c4cdc5d49a9b256d892 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 12:54:38 +0900 Subject: [PATCH 0217/5199] Fix `TestMigration` failing due to changes in realm migration logic Fixes failures as seen at https://github.com/ppy/osu/runs/4927916031?check_suite_focus=true. --- .../NonVisual/CustomDataDirectoryTest.cs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 61ef31e07e..834930a05e 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -142,19 +142,28 @@ namespace osu.Game.Tests.NonVisual Assert.That(osuStorage, Is.Not.Null); + // In the following tests, realm files are ignored as + // - in the case of checking the source, interacting with the pipe files (client.realm.note) may + // lead to unexpected behaviour. + // - in the case of checking the destination, the files may have already been recreated by the game + // as part of the standard migration flow. + foreach (string file in osuStorage.IgnoreFiles) { - // avoid touching realm files which may be a pipe and break everything. - // this is also done locally inside OsuStorage via the IgnoreFiles list. - if (file.EndsWith(".ini", StringComparison.Ordinal)) + if (!file.Contains("realm", StringComparison.Ordinal)) + { Assert.That(File.Exists(Path.Combine(originalDirectory, file))); - Assert.That(storage.Exists(file), Is.False); + Assert.That(storage.Exists(file), Is.False, () => $"{file} exists in destination when it was expected to be ignored"); + } } foreach (string dir in osuStorage.IgnoreDirectories) { - Assert.That(Directory.Exists(Path.Combine(originalDirectory, dir))); - Assert.That(storage.ExistsDirectory(dir), Is.False); + if (!dir.Contains("realm", StringComparison.Ordinal)) + { + Assert.That(Directory.Exists(Path.Combine(originalDirectory, dir))); + Assert.That(storage.Exists(dir), Is.False, () => $"{dir} exists in destination when it was expected to be ignored"); + } } Assert.That(new StreamReader(Path.Combine(originalDirectory, "storage.ini")).ReadToEnd().Contains($"FullPath = {customPath}")); From 3e5c9e843606647561eb35541459386ecf54da20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 12:58:15 +0900 Subject: [PATCH 0218/5199] Fix cases of `Access` instead of `Realm` --- .../TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 8 ++++---- .../Visual/Background/TestSceneUserDimBackgrounds.cs | 6 +++--- .../Collections/TestSceneManageCollectionsDialog.cs | 6 +++--- .../Visual/Multiplayer/QueueModeTestScene.cs | 6 +++--- .../Multiplayer/TestSceneDrawableRoomPlaylist.cs | 6 +++--- .../Visual/Multiplayer/TestSceneMultiplayer.cs | 6 +++--- .../TestSceneMultiplayerMatchSongSelect.cs | 6 +++--- .../TestSceneMultiplayerMatchSubScreen.cs | 6 +++--- .../Multiplayer/TestSceneMultiplayerPlaylist.cs | 6 +++--- .../Multiplayer/TestSceneMultiplayerQueueList.cs | 6 +++--- .../Multiplayer/TestSceneMultiplayerReadyButton.cs | 6 +++--- .../TestSceneMultiplayerSpectateButton.cs | 6 +++--- .../Multiplayer/TestScenePlaylistsSongSelect.cs | 6 +++--- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 6 +++--- .../Playlists/TestScenePlaylistsRoomCreation.cs | 6 +++--- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 8 ++++---- .../Visual/SongSelect/TestSceneFilterControl.cs | 6 +++--- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 6 +++--- .../Visual/SongSelect/TestSceneTopLocalRank.cs | 8 ++++---- .../UserInterface/TestSceneDeleteLocalScore.cs | 8 ++++---- osu.Game/Beatmaps/BeatmapModelManager.cs | 4 ++-- osu.Game/Scoring/ScoreModelManager.cs | 2 +- osu.Game/Skinning/SkinModelManager.cs | 2 +- osu.Game/Stores/BeatmapImporter.cs | 2 +- osu.Game/Stores/RealmArchiveModelImporter.cs | 12 ++++++------ osu.Game/Stores/RealmArchiveModelManager.cs | 6 +++--- osu.Game/Tests/Visual/EditorTestScene.cs | 2 +- osu.Game/Tests/Visual/OsuTestScene.cs | 6 +++--- 28 files changed, 82 insertions(+), 82 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index e35e4d9b15..144cbe15c3 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -45,8 +45,8 @@ namespace osu.Game.Tests.Online [BackgroundDependencyLoader] private void load(AudioManager audio, GameHost host) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, Access, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); Dependencies.CacheAs(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API, host)); } @@ -60,8 +60,8 @@ namespace osu.Game.Tests.Online testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile); testBeatmapSet = testBeatmapInfo.BeatmapSet; - Access.Write(r => r.RemoveAll()); - Access.Write(r => r.RemoveAll()); + Realm.Write(r => r.RemoveAll()); + Realm.Write(r => r.RemoveAll()); selectedItem.Value = new PlaylistItem { diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index fbfa7eda6a..40e7c0a844 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -47,10 +47,10 @@ namespace osu.Game.Tests.Visual.Background [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(new OsuConfigManager(LocalStorage)); - Dependencies.Cache(Access); + Dependencies.Cache(Realm); manager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index fd0645a1e9..d4c13059da 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -36,9 +36,9 @@ namespace osu.Game.Tests.Visual.Collections [BackgroundDependencyLoader] private void load(GameHost host) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesets, null, Audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, Audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 07b2bdcba3..4cd19b53a4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -47,9 +47,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 0bf1d60ac5..30ac1302aa 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -43,9 +43,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 063d886729..3f151a0ae8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -61,9 +61,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, API, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 8c79c468d7..7e3c9722cf 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -42,9 +42,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); beatmaps = new List(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 77e2c9c714..9d14d80d07 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -38,9 +38,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 4270818b1a..d970ab6c34 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -33,9 +33,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); } [SetUp] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 56e64292c6..d83421ee3a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -38,9 +38,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, API, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index afb60b62aa..9867e5225e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -40,9 +40,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 79b29f0eca..42ae279667 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -41,9 +41,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 6ed57e9899..d7a0885c95 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -34,9 +34,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); var beatmapSet = TestResources.CreateTestBeatmapSetInfo(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index bd95b297d4..73c67d26d9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -42,9 +42,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index 716e3a535d..d397b37d05 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -40,9 +40,9 @@ namespace osu.Game.Tests.Visual.Playlists [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, API, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); } [SetUpSteps] diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 2f5594379b..5e977d075d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -42,10 +42,10 @@ namespace osu.Game.Tests.Visual.SongSelect { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Cache(rulesetStore = new RulesetStore(Access)); - dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Access, Scheduler)); - Dependencies.Cache(Access); + dependencies.Cache(rulesetStore = new RulesetStore(Realm)); + dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); + dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, Scheduler)); + Dependencies.Cache(Realm); return dependencies; } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 4868a4a075..b384061531 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -36,9 +36,9 @@ namespace osu.Game.Tests.Visual.SongSelect [BackgroundDependencyLoader] private void load(GameHost host) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesets, null, Audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, Audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index d8a39dda01..0dc4556ed9 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -47,9 +47,9 @@ namespace osu.Game.Tests.Visual.SongSelect { // These DI caches are required to ensure for interactive runs this test scene doesn't nuke all user beatmaps in the local install. // At a point we have isolated interactive test runs enough, this can likely be removed. - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(Access); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, defaultBeatmap = Beatmap.Default)); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(Realm); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, defaultBeatmap = Beatmap.Default)); Dependencies.Cache(music = new MusicController()); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs index a0657ffdf6..8e5f76a2eb 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs @@ -28,10 +28,10 @@ namespace osu.Game.Tests.Visual.SongSelect [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, Access, Scheduler)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, Realm, Scheduler)); + Dependencies.Cache(Realm); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 2278d3f8bf..94ce85ef38 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -87,10 +87,10 @@ namespace osu.Game.Tests.Visual.UserInterface { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Cache(rulesetStore = new RulesetStore(Access)); - dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, Access, Scheduler)); - Dependencies.Cache(Access); + dependencies.Cache(rulesetStore = new RulesetStore(Realm)); + dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); + dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, Realm, Scheduler)); + Dependencies.Cache(Realm); var imported = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).GetResultSafely(); diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 167d77d6f6..e8104f2ecb 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -98,12 +98,12 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapInfo? QueryBeatmap(Expression> query) { - return Access.Run(realm => realm.All().FirstOrDefault(query)?.Detach()); + return Realm.Run(realm => realm.All().FirstOrDefault(query)?.Detach()); } public void Update(BeatmapSetInfo item) { - Access.Write(realm => + Realm.Write(realm => { var existing = realm.Find(item.ID); item.CopyChangesToRealm(existing); diff --git a/osu.Game/Scoring/ScoreModelManager.cs b/osu.Game/Scoring/ScoreModelManager.cs index 2147ff1ba1..59102360f9 100644 --- a/osu.Game/Scoring/ScoreModelManager.cs +++ b/osu.Game/Scoring/ScoreModelManager.cs @@ -74,7 +74,7 @@ namespace osu.Game.Scoring public override bool IsAvailableLocally(ScoreInfo model) { - return Access.Run(realm => realm.All().Any(s => s.OnlineID == model.OnlineID)); + return Realm.Run(realm => realm.All().Any(s => s.OnlineID == model.OnlineID)); } } } diff --git a/osu.Game/Skinning/SkinModelManager.cs b/osu.Game/Skinning/SkinModelManager.cs index c93cdb17dd..0af31100a9 100644 --- a/osu.Game/Skinning/SkinModelManager.cs +++ b/osu.Game/Skinning/SkinModelManager.cs @@ -205,7 +205,7 @@ namespace osu.Game.Skinning private void populateMissingHashes() { - Access.Run(realm => + Realm.Run(realm => { var skinsWithoutHashes = realm.All().Where(i => !i.Protected && string.IsNullOrEmpty(i.Hash)).ToArray(); diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index 3d241e795c..a6f20c8d4f 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -165,7 +165,7 @@ namespace osu.Game.Stores public override bool IsAvailableLocally(BeatmapSetInfo model) { - return Access.Run(realm => realm.All().Any(b => b.OnlineID == model.OnlineID)); + return Realm.Run(realm => realm.All().Any(b => b.OnlineID == model.OnlineID)); } public override string HumanisedModelName => "beatmap"; diff --git a/osu.Game/Stores/RealmArchiveModelImporter.cs b/osu.Game/Stores/RealmArchiveModelImporter.cs index 23a860791e..d9ca3f50a3 100644 --- a/osu.Game/Stores/RealmArchiveModelImporter.cs +++ b/osu.Game/Stores/RealmArchiveModelImporter.cs @@ -59,7 +59,7 @@ namespace osu.Game.Stores protected readonly RealmFileStore Files; - protected readonly RealmAccess Access; + protected readonly RealmAccess Realm; /// /// Fired when the user requests to view the resulting import. @@ -73,7 +73,7 @@ namespace osu.Game.Stores protected RealmArchiveModelImporter(Storage storage, RealmAccess realm) { - Access = realm; + Realm = realm; Files = new RealmFileStore(realm, storage); } @@ -320,7 +320,7 @@ namespace osu.Game.Stores /// An optional cancellation token. public virtual Task?> Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { - return Access.Run(realm => + return Realm.Run(realm => { cancellationToken.ThrowIfCancellationRequested(); @@ -352,7 +352,7 @@ namespace osu.Game.Stores transaction.Commit(); } - return Task.FromResult((ILive?)existing.ToLive(Access)); + return Task.FromResult((ILive?)existing.ToLive(Realm)); } LogForModel(item, @"Found existing (optimised) but failed pre-check."); @@ -387,7 +387,7 @@ namespace osu.Game.Stores existing.DeletePending = false; transaction.Commit(); - return Task.FromResult((ILive?)existing.ToLive(Access)); + return Task.FromResult((ILive?)existing.ToLive(Realm)); } LogForModel(item, @"Found existing but failed re-use check."); @@ -413,7 +413,7 @@ namespace osu.Game.Stores throw; } - return Task.FromResult((ILive?)item.ToLive(Access)); + return Task.FromResult((ILive?)item.ToLive(Realm)); }); } diff --git a/osu.Game/Stores/RealmArchiveModelManager.cs b/osu.Game/Stores/RealmArchiveModelManager.cs index 00cd1c2958..57e51b79aa 100644 --- a/osu.Game/Stores/RealmArchiveModelManager.cs +++ b/osu.Game/Stores/RealmArchiveModelManager.cs @@ -45,7 +45,7 @@ namespace osu.Game.Stores // This method should be removed as soon as all the surrounding pieces support non-detached operations. if (!item.IsManaged) { - var managed = Access.Realm.Find(item.ID); + var managed = Realm.Realm.Find(item.ID); managed.Realm.Write(() => operation(managed)); item.Files.Clear(); @@ -165,7 +165,7 @@ namespace osu.Game.Stores public bool Delete(TModel item) { - return Access.Run(realm => + return Realm.Run(realm => { if (!item.IsManaged) item = realm.Find(item.ID); @@ -180,7 +180,7 @@ namespace osu.Game.Stores public void Undelete(TModel item) { - Access.Run(realm => + Realm.Run(realm => { if (!item.IsManaged) item = realm.Find(item.ID); diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index a6cac0ec86..6f751b1736 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual working = CreateWorkingBeatmap(Ruleset.Value); if (IsolateSavingFromDatabase) - Dependencies.CacheAs(testBeatmapManager = new TestBeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.CacheAs(testBeatmapManager = new TestBeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); } protected override void LoadComplete() diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 33dd1d45b8..42e96f80ca 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -75,9 +75,9 @@ namespace osu.Game.Tests.Visual /// /// In interactive runs (ie. VisualTests) this will use the user's database if is not set to true. /// - protected RealmAccess Access => contextFactory.Value; + protected RealmAccess Realm => realm.Value; - private Lazy contextFactory; + private Lazy realm; /// /// Whether a fresh storage should be initialised per test (method) run. @@ -119,7 +119,7 @@ namespace osu.Game.Tests.Visual Resources = parent.Get().Resources; - contextFactory = new Lazy(() => new RealmAccess(LocalStorage, "client")); + realm = new Lazy(() => new RealmAccess(LocalStorage, "client")); RecycleLocalStorage(false); From e23b10e6a5a91ba1d7fd1f746846043c73c0427c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 13:04:05 +0900 Subject: [PATCH 0219/5199] Update remaining cases of clashing variable name in `realm.Run(realm..` --- osu.Game.Benchmarks/BenchmarkRealmReads.cs | 10 ++-- .../Database/TestRealmKeyBindingStore.cs | 4 +- .../Visual/Ranking/TestSceneResultsScreen.cs | 8 ++-- .../TestSceneDeleteLocalScore.cs | 4 +- osu.Game/Beatmaps/BeatmapManager.cs | 34 +++++++------- osu.Game/Database/EFToRealmMigrator.cs | 46 +++++++++---------- osu.Game/Database/RealmLive.cs | 9 ++-- osu.Game/Input/RealmKeyBindingStore.cs | 10 ++-- .../Settings/Sections/Input/KeyBindingRow.cs | 6 +-- osu.Game/Scoring/ScoreManager.cs | 8 ++-- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 10 ++-- osu.Game/Skinning/SkinManager.cs | 6 +-- 13 files changed, 77 insertions(+), 80 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs index 412f86bd1c..bf9467700c 100644 --- a/osu.Game.Benchmarks/BenchmarkRealmReads.cs +++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs @@ -29,7 +29,7 @@ namespace osu.Game.Benchmarks realm = new RealmAccess(storage, "client"); - realm.Run(realm => + realm.Run(r => { realm.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo }))); }); @@ -41,9 +41,9 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkDirectPropertyRead() { - realm.Run(realm => + realm.Run(r => { - var beatmapSet = realm.All().First(); + var beatmapSet = r.All().First(); for (int i = 0; i < ReadsPerFetch; i++) { @@ -119,9 +119,9 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkDetachedPropertyRead() { - realm.Run(realm => + realm.Run(r => { - var beatmapSet = realm.All().First().Detach(); + var beatmapSet = r.All().First().Detach(); for (int i = 0; i < ReadsPerFetch; i++) { diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index 4b8816f142..7b2d2a1278 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -76,9 +76,9 @@ namespace osu.Game.Tests.Database private int queryCount(GlobalAction? match = null) { - return realm.Run(realm => + return realm.Run(r => { - var results = realm.All(); + var results = r.All(); if (match.HasValue) results = results.Where(k => k.ActionInt == (int)match.Value); return results.Count(); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 13b4af5223..988f429ff5 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -42,11 +42,11 @@ namespace osu.Game.Tests.Visual.Ranking { base.LoadComplete(); - realm.Run(realm => + realm.Run(r => { - var beatmapInfo = realm.All() - .Filter($"{nameof(BeatmapInfo.Ruleset)}.{nameof(RulesetInfo.OnlineID)} = $0", 0) - .FirstOrDefault(); + var beatmapInfo = r.All() + .Filter($"{nameof(BeatmapInfo.Ruleset)}.{nameof(RulesetInfo.OnlineID)} = $0", 0) + .FirstOrDefault(); if (beatmapInfo != null) Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 94ce85ef38..55031a9699 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -122,10 +122,10 @@ namespace osu.Game.Tests.Visual.UserInterface [SetUp] public void Setup() => Schedule(() => { - realm.Run(realm => + realm.Run(r => { // Due to soft deletions, we can re-use deleted scores between test runs - scoreManager.Undelete(realm.All().Where(s => s.DeletePending).ToList()); + scoreManager.Undelete(r.All().Where(s => s.DeletePending).ToList()); }); leaderboard.Scores = null; diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index ddc1d054cc..4488301fe5 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -119,12 +119,12 @@ namespace osu.Game.Beatmaps /// The beatmap difficulty to hide. public void Hide(BeatmapInfo beatmapInfo) { - realm.Run(realm => + realm.Run(r => { - using (var transaction = realm.BeginWrite()) + using (var transaction = r.BeginWrite()) { if (!beatmapInfo.IsManaged) - beatmapInfo = realm.Find(beatmapInfo.ID); + beatmapInfo = r.Find(beatmapInfo.ID); beatmapInfo.Hidden = true; transaction.Commit(); @@ -138,12 +138,12 @@ namespace osu.Game.Beatmaps /// The beatmap difficulty to restore. public void Restore(BeatmapInfo beatmapInfo) { - realm.Run(realm => + realm.Run(r => { - using (var transaction = realm.BeginWrite()) + using (var transaction = r.BeginWrite()) { if (!beatmapInfo.IsManaged) - beatmapInfo = realm.Find(beatmapInfo.ID); + beatmapInfo = r.Find(beatmapInfo.ID); beatmapInfo.Hidden = false; transaction.Commit(); @@ -153,11 +153,11 @@ namespace osu.Game.Beatmaps public void RestoreAll() { - realm.Run(realm => + realm.Run(r => { - using (var transaction = realm.BeginWrite()) + using (var transaction = r.BeginWrite()) { - foreach (var beatmap in realm.All().Where(b => b.Hidden)) + foreach (var beatmap in r.All().Where(b => b.Hidden)) beatmap.Hidden = false; transaction.Commit(); @@ -171,10 +171,10 @@ namespace osu.Game.Beatmaps /// A list of available . public List GetAllUsableBeatmapSets() { - return realm.Run(realm => + return realm.Run(r => { - realm.Refresh(); - return realm.All().Where(b => !b.DeletePending).Detach(); + r.Refresh(); + return r.All().Where(b => !b.DeletePending).Detach(); }); } @@ -240,9 +240,9 @@ namespace osu.Game.Beatmaps public void Delete(Expression>? filter = null, bool silent = false) { - realm.Run(realm => + realm.Run(r => { - var items = realm.All().Where(s => !s.DeletePending && !s.Protected); + var items = r.All().Where(s => !s.DeletePending && !s.Protected); if (filter != null) items = items.Where(filter); @@ -253,7 +253,7 @@ namespace osu.Game.Beatmaps public void UndeleteAll() { - realm.Run(realm => beatmapModelManager.Undelete(realm.All().Where(s => s.DeletePending).ToList())); + realm.Run(r => beatmapModelManager.Undelete(r.All().Where(s => s.DeletePending).ToList())); } public void Undelete(List items, bool silent = false) @@ -312,9 +312,9 @@ namespace osu.Game.Beatmaps // If we seem to be missing files, now is a good time to re-fetch. if (importedBeatmap?.BeatmapSet?.Files.Count == 0) { - realm.Run(realm => + realm.Run(r => { - var refetch = realm.Find(importedBeatmap.ID)?.Detach(); + var refetch = r.Find(importedBeatmap.ID)?.Detach(); if (refetch != null) importedBeatmap = refetch; diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index a0787e81e6..349452afce 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -158,11 +158,11 @@ namespace osu.Game.Database int count = existingBeatmapSets.Count(); - realm.Run(realm => + realm.Run(r => { log($"Found {count} beatmaps in EF"); - var transaction = realm.BeginWrite(); + var transaction = r.BeginWrite(); int written = 0; try @@ -172,7 +172,7 @@ namespace osu.Game.Database if (++written % 1000 == 0) { transaction.Commit(); - transaction = realm.BeginWrite(); + transaction = r.BeginWrite(); log($"Migrated {written}/{count} beatmaps..."); } @@ -186,11 +186,11 @@ namespace osu.Game.Database Protected = beatmapSet.Protected, }; - migrateFiles(beatmapSet, realm, realmBeatmapSet); + migrateFiles(beatmapSet, r, realmBeatmapSet); foreach (var beatmap in beatmapSet.Beatmaps) { - var ruleset = realm.Find(beatmap.RulesetInfo.ShortName); + var ruleset = r.Find(beatmap.RulesetInfo.ShortName); var metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata); var realmBeatmap = new BeatmapInfo(ruleset, new BeatmapDifficulty(beatmap.BaseDifficulty), metadata) @@ -225,7 +225,7 @@ namespace osu.Game.Database realmBeatmapSet.Beatmaps.Add(realmBeatmap); } - realm.Add(realmBeatmapSet); + r.Add(realmBeatmapSet); } } finally @@ -280,11 +280,11 @@ namespace osu.Game.Database int count = existingScores.Count(); - realm.Run(realm => + realm.Run(r => { log($"Found {count} scores in EF"); - var transaction = realm.BeginWrite(); + var transaction = r.BeginWrite(); int written = 0; try @@ -294,12 +294,12 @@ namespace osu.Game.Database if (++written % 1000 == 0) { transaction.Commit(); - transaction = realm.BeginWrite(); + transaction = r.BeginWrite(); log($"Migrated {written}/{count} scores..."); } - var beatmap = realm.All().First(b => b.Hash == score.BeatmapInfo.Hash); - var ruleset = realm.Find(score.Ruleset.ShortName); + var beatmap = r.All().First(b => b.Hash == score.BeatmapInfo.Hash); + var ruleset = r.Find(score.Ruleset.ShortName); var user = new RealmUser { OnlineID = score.User.OnlineID, @@ -329,9 +329,9 @@ namespace osu.Game.Database APIMods = score.APIMods, }; - migrateFiles(score, realm, realmScore); + migrateFiles(score, r, realmScore); - realm.Add(realmScore); + r.Add(realmScore); } } finally @@ -369,13 +369,13 @@ namespace osu.Game.Database break; } - realm.Run(realm => + realm.Run(r => { - using (var transaction = realm.BeginWrite()) + using (var transaction = r.BeginWrite()) { // only migrate data if the realm database is empty. - // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. - if (!realm.All().Any(s => !s.Protected)) + // note that this cannot be written as: `r.All().All(s => s.Protected)`, because realm does not support `.All()`. + if (!r.All().Any(s => !s.Protected)) { log($"Migrating {existingSkins.Count} skins"); @@ -390,9 +390,9 @@ namespace osu.Game.Database InstantiationInfo = skin.InstantiationInfo, }; - migrateFiles(skin, realm, realmSkin); + migrateFiles(skin, r, realmSkin); - realm.Add(realmSkin); + r.Add(realmSkin); if (skin.ID == userSkinInt) userSkinChoice.Value = realmSkin.ID.ToString(); @@ -428,12 +428,12 @@ namespace osu.Game.Database log("Beginning settings migration to realm"); - realm.Run(realm => + realm.Run(r => { - using (var transaction = realm.BeginWrite()) + using (var transaction = r.BeginWrite()) { // only migrate data if the realm database is empty. - if (!realm.All().Any()) + if (!r.All().Any()) { log($"Migrating {existingSettings.Count} settings"); @@ -447,7 +447,7 @@ namespace osu.Game.Database if (string.IsNullOrEmpty(shortName)) continue; - realm.Add(new RealmRulesetSetting + r.Add(new RealmRulesetSetting { Key = dkb.Key, Value = dkb.StringValue, diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index 29159fd5be..f06ba1eff9 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -51,10 +51,7 @@ namespace osu.Game.Database return; } - realm.Run(realm => - { - perform(retrieveFromID(realm, ID)); - }); + realm.Run(r => perform(retrieveFromID(r, ID))); } /// @@ -66,9 +63,9 @@ namespace osu.Game.Database if (!IsManaged) return perform(data); - return realm.Run(realm => + return realm.Run(r => { - var returnData = perform(retrieveFromID(realm, ID)); + var returnData = perform(retrieveFromID(r, ID)); if (returnData is RealmObjectBase realmObject && realmObject.IsManaged) throw new InvalidOperationException(@$"Managed realm objects should not exit the scope of {nameof(PerformRead)}."); diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index cccd42a9aa..20971ffca5 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -56,21 +56,21 @@ namespace osu.Game.Input /// The rulesets to populate defaults from. public void Register(KeyBindingContainer container, IEnumerable rulesets) { - realm.Run(realm => + realm.Run(r => { - using (var transaction = realm.BeginWrite()) + using (var transaction = r.BeginWrite()) { // intentionally flattened to a list rather than querying against the IQueryable, as nullable fields being queried against aren't indexed. // this is much faster as a result. - var existingBindings = realm.All().ToList(); + var existingBindings = r.All().ToList(); - insertDefaults(realm, existingBindings, container.DefaultKeyBindings); + insertDefaults(r, existingBindings, container.DefaultKeyBindings); foreach (var ruleset in rulesets) { var instance = ruleset.CreateInstance(); foreach (int variant in instance.AvailableVariants) - insertDefaults(realm, existingBindings, instance.GetDefaultKeyBindings(variant), ruleset.ShortName, variant); + insertDefaults(r, existingBindings, instance.GetDefaultKeyBindings(variant), ruleset.ShortName, variant); } transaction.Commit(); diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index 91883e4f41..2405618917 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -386,10 +386,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input private void updateStoreFromButton(KeyButton button) { - realm.Run(realm => + realm.Run(r => { - var binding = realm.Find(((IHasGuidPrimaryKey)button.KeyBinding).ID); - realm.Write(() => binding.KeyCombinationString = button.KeyBinding.KeyCombinationString); + var binding = r.Find(((IHasGuidPrimaryKey)button.KeyBinding).ID); + r.Write(() => binding.KeyCombinationString = button.KeyBinding.KeyCombinationString); }); } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index e712d170cd..bb26bd3b04 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -51,7 +51,7 @@ namespace osu.Game.Scoring /// The first result for the provided query, or null if no results were found. public ScoreInfo Query(Expression> query) { - return realm.Run(realm => realm.All().FirstOrDefault(query)?.Detach()); + return realm.Run(r => r.All().FirstOrDefault(query)?.Detach()); } /// @@ -254,10 +254,10 @@ namespace osu.Game.Scoring public void Delete([CanBeNull] Expression> filter = null, bool silent = false) { - realm.Run(realm => + realm.Run(r => { - var items = realm.All() - .Where(s => !s.DeletePending); + var items = r.All() + .Where(s => !s.DeletePending); if (filter != null) items = items.Where(filter); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 8e0fdea0f8..b02abd2f7a 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -179,7 +179,7 @@ namespace osu.Game.Screens.Select if (!loadedTestBeatmaps) { - realm.Run(realm => loadBeatmapSets(getBeatmapSets(realm))); + realm.Run(r => loadBeatmapSets(getBeatmapSets(r))); } } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 3d262f8b97..b53aa6f43c 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -150,12 +150,12 @@ namespace osu.Game.Screens.Select.Leaderboards if (Scope == BeatmapLeaderboardScope.Local) { - realm.Run(realm => + realm.Run(r => { - var scores = realm.All() - .AsEnumerable() - // TODO: update to use a realm filter directly (or at least figure out the beatmap part to reduce scope). - .Where(s => !s.DeletePending && s.BeatmapInfo.ID == fetchBeatmapInfo.ID && s.Ruleset.OnlineID == ruleset.Value.ID); + var scores = r.All() + .AsEnumerable() + // TODO: update to use a realm filter directly (or at least figure out the beatmap part to reduce scope). + .Where(s => !s.DeletePending && s.BeatmapInfo.ID == fetchBeatmapInfo.ID && s.Ruleset.OnlineID == ruleset.Value.ID); if (filterMods && !mods.Value.Any()) { diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 66956325da..22fb8ce86f 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -289,10 +289,10 @@ namespace osu.Game.Skinning public void Delete([CanBeNull] Expression> filter = null, bool silent = false) { - realm.Run(realm => + realm.Run(r => { - var items = realm.All() - .Where(s => !s.Protected && !s.DeletePending); + var items = r.All() + .Where(s => !s.Protected && !s.DeletePending); if (filter != null) items = items.Where(filter); From d7342880f5a099e3260695bd04741a61864ff921 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 13:09:47 +0900 Subject: [PATCH 0220/5199] Update remaining cases of clashes with `realm.Write` and `realm.RegisterForNotifications` --- .../Database/TestRealmKeyBindingStore.cs | 8 ++++---- osu.Game/Database/EFToRealmMigrator.cs | 10 +++++----- .../Input/Bindings/DatabasedKeyBindingContainer.cs | 2 +- osu.Game/Online/BeatmapDownloadTracker.cs | 2 +- .../Rooms/OnlinePlayBeatmapAvailabilityTracker.cs | 2 +- osu.Game/Online/ScoreDownloadTracker.cs | 2 +- osu.Game/Overlays/MusicController.cs | 2 +- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 2 +- .../Rulesets/Configuration/RulesetConfigManager.cs | 4 ++-- osu.Game/Rulesets/RulesetStore.cs | 6 +++--- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 +++--- osu.Game/Screens/Select/Carousel/TopLocalRank.cs | 14 +++++++------- .../Select/Leaderboards/BeatmapLeaderboard.cs | 6 +++--- osu.Game/Skinning/SkinManager.cs | 6 +++--- osu.Game/Stores/RealmFileStore.cs | 6 +++--- 15 files changed, 39 insertions(+), 39 deletions(-) diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index 7b2d2a1278..891801865f 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -60,11 +60,11 @@ namespace osu.Game.Tests.Database KeyBindingContainer testContainer = new TestKeyBindingContainer(); // Add some excess bindings for an action which only supports 1. - realm.Write(realm => + realm.Write(r => { - realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.A))); - realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.S))); - realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.D))); + r.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.A))); + r.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.S))); + r.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.D))); }); Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(3)); diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 349452afce..adf91e4a41 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -101,15 +101,15 @@ namespace osu.Game.Database { using (var ef = efContextFactory.Get()) { - realm.Write(realm => + realm.Write(r => { // Before beginning, ensure realm is in an empty state. // Migrations which are half-completed could lead to issues if the user tries a second time. // Note that we only do this for beatmaps and scores since the other migrations are yonks old. - realm.RemoveAll(); - realm.RemoveAll(); - realm.RemoveAll(); - realm.RemoveAll(); + r.RemoveAll(); + r.RemoveAll(); + r.RemoveAll(); + r.RemoveAll(); }); migrateSettings(ef); diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index d54c049c99..3e4a9759a3 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -55,7 +55,7 @@ namespace osu.Game.Input.Bindings protected override void LoadComplete() { - realmSubscription = realm.RegisterForNotifications(realm => queryRealmKeyBindings(), (sender, changes, error) => + realmSubscription = realm.RegisterForNotifications(r => queryRealmKeyBindings(), (sender, changes, error) => { // The first fire of this is a bit redundant as this is being called in base.LoadComplete, // but this is safest in case the subscription is restored after a context recycle. diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index f54dc30620..9f795f007a 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -42,7 +42,7 @@ namespace osu.Game.Online // Used to interact with manager classes that don't support interface types. Will eventually be replaced. var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID }; - realmSubscription = realm.RegisterForNotifications(realm => realm.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(r => r.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index c562695ac1..c67cbade6a 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -78,7 +78,7 @@ namespace osu.Game.Online.Rooms // handles changes to hash that didn't occur from the import process (ie. a user editing the beatmap in the editor, somehow). realmSubscription?.Dispose(); - realmSubscription = realm.RegisterForNotifications(realm => filteredBeatmaps(), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(r => filteredBeatmaps(), (items, changes, ___) => { if (changes == null) return; diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index 81dfc811a4..d7e31c8a59 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -47,7 +47,7 @@ namespace osu.Game.Online Downloader.DownloadBegan += downloadBegan; Downloader.DownloadFailed += downloadFailed; - realmSubscription = realm.RegisterForNotifications(realm => realm.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(r => r.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending), (items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 0f8e390ebf..9dc55d24dd 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -79,7 +79,7 @@ namespace osu.Game.Overlays protected override void LoadComplete() { base.LoadComplete(); - beatmapSubscription = realm.RegisterForNotifications(realm => queryRealmBeatmapSets(), beatmapsChanged); + beatmapSubscription = realm.RegisterForNotifications(r => queryRealmBeatmapSets(), beatmapsChanged); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index af3fd5c9bf..8ab296c0a8 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -83,7 +83,7 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown.Current = dropdownBindable; - realmSubscription = realm.RegisterForNotifications(realm => queryRealmSkins(), (sender, changes, error) => + realmSubscription = realm.RegisterForNotifications(r => queryRealmSkins(), (sender, changes, error) => { // The first fire of this is a bit redundant due to the call below, // but this is safest in case the subscription is restored after a context recycle. diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index cfa20e0b87..30bb95ba72 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -56,11 +56,11 @@ namespace osu.Game.Rulesets.Configuration pendingWrites.Clear(); } - realm?.Write(realm => + realm?.Write(r => { foreach (var c in changed) { - var setting = realm.All().First(s => s.RulesetName == rulesetName && s.Variant == variant && s.Key == c.ToString()); + var setting = r.All().First(s => s.RulesetName == rulesetName && s.Variant == variant && s.Key == c.ToString()); setting.Value = ConfigStore[c].ToString(); } diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 606bc65599..9af9ace7ad 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets { public class RulesetStore : IDisposable, IRulesetStore { - private readonly RealmAccess realm; + private readonly RealmAccess realmAccess; private const string ruleset_library_prefix = @"osu.Game.Rulesets"; @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets public RulesetStore(RealmAccess realm, Storage? storage = null) { - this.realm = realm; + realmAccess = realm; // On android in release configuration assemblies are loaded from the apk directly into memory. // We cannot read assemblies from cwd, so should check loaded assemblies instead. @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets private void addMissingRulesets() { - realm.Write(realm => + realmAccess.Write(realm => { var rulesets = realm.All(); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index b02abd2f7a..dff2c598c3 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -191,12 +191,12 @@ namespace osu.Game.Screens.Select base.LoadComplete(); subscriptionSets = realm.RegisterForNotifications(getBeatmapSets, beatmapSetsChanged); - subscriptionBeatmaps = realm.RegisterForNotifications(realm => realm.All().Where(b => !b.Hidden), beatmapsChanged); + subscriptionBeatmaps = realm.RegisterForNotifications(r => r.All().Where(b => !b.Hidden), beatmapsChanged); // Can't use main subscriptions because we can't lookup deleted indices. // https://github.com/realm/realm-dotnet/discussions/2634#discussioncomment-1605595. - subscriptionDeletedSets = realm.RegisterForNotifications(realm => realm.All().Where(s => s.DeletePending && !s.Protected), deletedBeatmapSetsChanged); - subscriptionHiddenBeatmaps = realm.RegisterForNotifications(realm => realm.All().Where(b => b.Hidden), beatmapsChanged); + subscriptionDeletedSets = realm.RegisterForNotifications(r => r.All().Where(s => s.DeletePending && !s.Protected), deletedBeatmapSetsChanged); + subscriptionHiddenBeatmaps = realm.RegisterForNotifications(r => r.All().Where(b => b.Hidden), beatmapsChanged); } private void deletedBeatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index 021dfd06f7..e1f9c1b508 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -48,13 +48,13 @@ namespace osu.Game.Screens.Select.Carousel ruleset.BindValueChanged(_ => { scoreSubscription?.Dispose(); - scoreSubscription = realm.RegisterForNotifications(realm => - realm.All() - .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" - + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" - + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" - + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName) - .OrderByDescending(s => s.TotalScore), + scoreSubscription = realm.RegisterForNotifications(r => + r.All() + .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" + + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" + + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" + + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName) + .OrderByDescending(s => s.TotalScore), (items, changes, ___) => { Rank = items.FirstOrDefault()?.Rank; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index b53aa6f43c..f25997650b 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -113,9 +113,9 @@ namespace osu.Game.Screens.Select.Leaderboards if (beatmapInfo == null) return; - scoreSubscription = realm.RegisterForNotifications(realm => - realm.All() - .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID), + scoreSubscription = realm.RegisterForNotifications(r => + r.All() + .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID), (_, changes, ___) => { if (!IsOnlineScope) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 22fb8ce86f..344acb7a0f 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -87,12 +87,12 @@ namespace osu.Game.Skinning }; // Ensure the default entries are present. - realm.Write(realm => + realm.Write(r => { foreach (var skin in defaultSkins) { - if (realm.Find(skin.SkinInfo.ID) == null) - realm.Add(skin.SkinInfo.Value); + if (r.Find(skin.SkinInfo.ID) == null) + r.Add(skin.SkinInfo.Value); } }); diff --git a/osu.Game/Stores/RealmFileStore.cs b/osu.Game/Stores/RealmFileStore.cs index 5edc1be954..b5dd3d64e4 100644 --- a/osu.Game/Stores/RealmFileStore.cs +++ b/osu.Game/Stores/RealmFileStore.cs @@ -92,10 +92,10 @@ namespace osu.Game.Stores int removedFiles = 0; // can potentially be run asynchronously, although we will need to consider operation order for disk deletion vs realm removal. - realm.Write(realm => + realm.Write(r => { // TODO: consider using a realm native query to avoid iterating all files (https://github.com/realm/realm-dotnet/issues/2659#issuecomment-927823707) - var files = realm.All().ToList(); + var files = r.All().ToList(); foreach (var file in files) { @@ -108,7 +108,7 @@ namespace osu.Game.Stores { removedFiles++; Storage.Delete(file.GetStoragePath()); - realm.Remove(file); + r.Remove(file); } catch (Exception e) { From bbcc149e2e7cc01045374b7b25be85983658aa0e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 13:42:41 +0900 Subject: [PATCH 0221/5199] During import if files are found to be missing, ensure they are restored This is one step closer to sanity in terms of physical files. As per the comment I have left in place, we really should be checking file sizes or hashes, but to keep things simple and fast I've opted to just cover the "missing file" scenario for now. Ran into this when testing against a foreign `client.realm` by: - Noticing a beatmap doesn't load - Deleting said beatmap - Downloading via beatmap overlay - Beatmap is restored but still doesn't work Note that I've kept the logic where this will undelete an existing import rather than create one from fresh, as I think that is beneficial to the user (ie. it will still keep any linked scores on restore). --- .../Database/BeatmapImporterTests.cs | 32 +++++++++++++++++++ osu.Game/Stores/RealmArchiveModelImporter.cs | 10 ++++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 227314cffd..abab8ae3c6 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -600,6 +600,38 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestImportThenReimportAfterMissingFiles() + { + RunTestWithRealmAsync(async (realmFactory, storage) => + { + using var importer = new BeatmapModelManager(realmFactory, storage); + using var store = new RulesetStore(realmFactory, storage); + + var imported = await LoadOszIntoStore(importer, realmFactory.Context); + + deleteBeatmapSet(imported, realmFactory.Context); + + Assert.IsTrue(imported.DeletePending); + + // intentionally nuke all files + storage.DeleteDirectory("files"); + + Assert.That(imported.Files.All(f => !storage.GetStorageForDirectory("files").Exists(f.File.GetStoragePath()))); + + var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context); + + // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. + Assert.IsTrue(imported.ID == importedSecondTime.ID); + Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID); + Assert.IsFalse(imported.DeletePending); + Assert.IsFalse(importedSecondTime.DeletePending); + + // check that the files now exist, even though they were deleted above. + Assert.That(importedSecondTime.Files.All(f => storage.GetStorageForDirectory("files").Exists(f.File.GetStoragePath()))); + }); + } + [Test] public void TestImportThenDeleteThenImportNonOptimisedPath() { diff --git a/osu.Game/Stores/RealmArchiveModelImporter.cs b/osu.Game/Stores/RealmArchiveModelImporter.cs index 3d8e9f2703..154135ea71 100644 --- a/osu.Game/Stores/RealmArchiveModelImporter.cs +++ b/osu.Game/Stores/RealmArchiveModelImporter.cs @@ -342,7 +342,8 @@ namespace osu.Game.Stores // note that this should really be checking filesizes on disk (of existing files) for some degree of sanity. // or alternatively doing a faster hash check. either of these require database changes and reprocessing of existing files. if (CanSkipImport(existing, item) && - getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f))) + getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f)) && + checkAllFilesExist(existing)) { LogForModel(item, @$"Found existing (optimised) {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); @@ -459,7 +460,6 @@ namespace osu.Game.Stores if (!(prefix.EndsWith('/') || prefix.EndsWith('\\'))) prefix = string.Empty; - // import files to manager foreach (string file in reader.Filenames) yield return (file, file.Substring(prefix.Length).ToStandardisedPath()); } @@ -519,7 +519,11 @@ namespace osu.Game.Stores // for the best or worst, we copy and import files of a new import before checking whether // it is a duplicate. so to check if anything has changed, we can just compare all File IDs. getIDs(existing.Files).SequenceEqual(getIDs(import.Files)) && - getFilenames(existing.Files).SequenceEqual(getFilenames(import.Files)); + getFilenames(existing.Files).SequenceEqual(getFilenames(import.Files)) && + checkAllFilesExist(existing); + + private bool checkAllFilesExist(TModel model) => + model.Files.All(f => Files.Storage.Exists(f.File.GetStoragePath())); /// /// Whether this specified path should be removed after successful import. From df1297ade6fcdf95b9025252aae63cbebcd0ba0b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 13:50:39 +0900 Subject: [PATCH 0222/5199] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index b296c114e9..4e5b9fdbb1 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 758575e74a..af5d8a5920 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 5925581e28..2bcdea61b3 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - + From dd2caea694f0e2782c4c34b8dba3c165ef9136da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 19:10:14 +0900 Subject: [PATCH 0223/5199] Update `GetSuitableHost` usages in line with new `HostOptions` --- osu.Desktop/Program.cs | 2 +- osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs | 3 ++- .../NonVisual/CustomTourneyDirectoryTest.cs | 5 +++-- osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs | 3 ++- osu.Game.Tournament.Tests/TournamentTestRunner.cs | 2 +- osu.Game/Tests/CleanRunHeadlessGameHost.cs | 6 +++++- osu.Game/Tests/VisualTestRunner.cs | 2 +- 7 files changed, 15 insertions(+), 8 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 7ec7d53a7e..ab9935a220 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -55,7 +55,7 @@ namespace osu.Desktop } } - using (DesktopGameHost host = Host.GetSuitableHost(gameName, true)) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { PortableInstallation = true })) { host.ExceptionThrown += handleException; diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index 53e4ef07e7..ee746ba4d5 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -6,6 +6,7 @@ using System.IO; using System.Text; using System.Threading.Tasks; using NUnit.Framework; +using osu.Framework; using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Testing; @@ -155,7 +156,7 @@ namespace osu.Game.Tests.Collections.IO } // Name matches the automatically chosen name from `CleanRunHeadlessGameHost` above, so we end up using the same storage location. - using (HeadlessGameHost host = new TestRunHeadlessGameHost(firstRunName)) + using (HeadlessGameHost host = new TestRunHeadlessGameHost(firstRunName, null)) { try { diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index fc5d3b652f..36d0c6f0f6 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using NUnit.Framework; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; using osu.Framework.Testing; @@ -37,7 +38,7 @@ namespace osu.Game.Tournament.Tests.NonVisual [Test] public void TestCustomDirectory() { - using (HeadlessGameHost host = new TestRunHeadlessGameHost(nameof(TestCustomDirectory))) // don't use clean run as we are writing a config file. + using (HeadlessGameHost host = new TestRunHeadlessGameHost(nameof(TestCustomDirectory), null)) // don't use clean run as we are writing a config file. { string osuDesktopStorage = Path.Combine(host.UserStoragePaths.First(), nameof(TestCustomDirectory)); const string custom_tournament = "custom"; @@ -68,7 +69,7 @@ namespace osu.Game.Tournament.Tests.NonVisual [Test] public void TestMigration() { - using (HeadlessGameHost host = new TestRunHeadlessGameHost(nameof(TestMigration))) // don't use clean run as we are writing test files for migration. + using (HeadlessGameHost host = new TestRunHeadlessGameHost(nameof(TestMigration), null)) // don't use clean run as we are writing test files for migration. { string osuRoot = Path.Combine(host.UserStoragePaths.First(), nameof(TestMigration)); string configFile = Path.Combine(osuRoot, "tournament.ini"); diff --git a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs index 03252e3be6..11b686e3e8 100644 --- a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using NUnit.Framework; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; using osu.Framework.Testing; @@ -19,7 +20,7 @@ namespace osu.Game.Tournament.Tests.NonVisual public void CheckIPCLocation() { // don't use clean run because files are being written before osu! launches. - using (var host = new TestRunHeadlessGameHost(nameof(CheckIPCLocation))) + using (var host = new TestRunHeadlessGameHost(nameof(CheckIPCLocation), null)) { string basePath = Path.Combine(host.UserStoragePaths.First(), nameof(CheckIPCLocation)); diff --git a/osu.Game.Tournament.Tests/TournamentTestRunner.cs b/osu.Game.Tournament.Tests/TournamentTestRunner.cs index 1f63f7c545..23da37a6b7 100644 --- a/osu.Game.Tournament.Tests/TournamentTestRunner.cs +++ b/osu.Game.Tournament.Tests/TournamentTestRunner.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tournament.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { PortableInstallation = true })) { host.Run(new TournamentTestBrowser()); return 0; diff --git a/osu.Game/Tests/CleanRunHeadlessGameHost.cs b/osu.Game/Tests/CleanRunHeadlessGameHost.cs index 754c9044e8..bdb171c528 100644 --- a/osu.Game/Tests/CleanRunHeadlessGameHost.cs +++ b/osu.Game/Tests/CleanRunHeadlessGameHost.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.CompilerServices; +using osu.Framework; using osu.Framework.Testing; namespace osu.Game.Tests @@ -20,7 +21,10 @@ namespace osu.Game.Tests /// Whether to bypass directory cleanup on host disposal. Should be used only if a subsequent test relies on the files still existing. /// The name of the calling method, used for test file isolation and clean-up. public CleanRunHeadlessGameHost(bool bindIPC = false, bool realtime = true, bool bypassCleanup = false, [CallerMemberName] string callingMethodName = @"") - : base($"{callingMethodName}-{Guid.NewGuid()}", bindIPC, realtime, bypassCleanup: bypassCleanup) + : base($"{callingMethodName}-{Guid.NewGuid()}", new HostOptions + { + BindIPC = bindIPC, + }, bypassCleanup: bypassCleanup, realtime: realtime) { } diff --git a/osu.Game/Tests/VisualTestRunner.cs b/osu.Game/Tests/VisualTestRunner.cs index d63b3d48b2..973801099e 100644 --- a/osu.Game/Tests/VisualTestRunner.cs +++ b/osu.Game/Tests/VisualTestRunner.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { PortableInstallation = true, })) { host.Run(new OsuTestBrowser()); return 0; From a5c76a9647c56b1677bd9105c93220ee7ad59441 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 13:56:47 +0900 Subject: [PATCH 0224/5199] Fix a few more cases of "context" terminology usage --- osu.Game/Database/RealmAccess.cs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 66b4edbe84..65e13cf542 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -30,7 +30,7 @@ using Realms.Exceptions; namespace osu.Game.Database { /// - /// A factory which provides both the main (update thread bound) realm context and creates contexts for async usage. + /// A factory which provides safe access to the realm storage backend. /// public class RealmAccess : IDisposable { @@ -57,9 +57,9 @@ namespace osu.Game.Database private const int schema_version = 13; /// - /// Lock object which is held during sections, blocking context creation during blocking periods. + /// Lock object which is held during sections, blocking realm retrieval during blocking periods. /// - private readonly SemaphoreSlim realmCreationLock = new SemaphoreSlim(1); + private readonly SemaphoreSlim realmRetrievalLock = new SemaphoreSlim(1); private readonly ThreadLocal currentThreadCanCreateRealmInstances = new ThreadLocal(); @@ -76,7 +76,7 @@ namespace osu.Game.Database /// /// Holds a map of functions registered via and a coinciding action which when triggered, - /// fires a change set event with an empty collection. This is used to inform subscribers when a realm context goes away, and ensure they don't use invalidated + /// fires a change set event with an empty collection. This is used to inform subscribers when the main realm instance gets recycled, and ensure they don't use invalidated /// managed realm objects from a previous firing. /// private readonly Dictionary, Action> notificationsResetMap = new Dictionary, Action>(); @@ -364,7 +364,7 @@ namespace osu.Game.Database { if (!currentThreadCanCreateRealmInstances.Value) { - realmCreationLock.Wait(); + realmRetrievalLock.Wait(); currentThreadCanCreateRealmInstances.Value = true; tookSemaphoreLock = true; } @@ -383,7 +383,7 @@ namespace osu.Game.Database { if (tookSemaphoreLock) { - realmCreationLock.Release(); + realmRetrievalLock.Release(); currentThreadCanCreateRealmInstances.Value = false; } } @@ -589,7 +589,7 @@ namespace osu.Game.Database try { - realmCreationLock.Wait(); + realmRetrievalLock.Wait(); lock (realmLock) { @@ -639,13 +639,13 @@ namespace osu.Game.Database } catch { - realmCreationLock.Release(); + realmRetrievalLock.Release(); throw; } return new InvokeOnDisposal(this, factory => { - factory.realmCreationLock.Release(); + factory.realmRetrievalLock.Release(); Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); // Post back to the update thread to revive any subscriptions. @@ -667,9 +667,9 @@ namespace osu.Game.Database if (!isDisposed) { - // intentionally block context creation indefinitely. this ensures that nothing can start consuming a new context after disposal. - realmCreationLock.Wait(); - realmCreationLock.Dispose(); + // intentionally block realm retrieval indefinitely. this ensures that nothing can start consuming a new instance after disposal. + realmRetrievalLock.Wait(); + realmRetrievalLock.Dispose(); isDisposed = true; } From 8116806db3fa6b06e96d1f38eac77964a96435a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 14:00:58 +0900 Subject: [PATCH 0225/5199] Add test coverage of calling `BlockAllOperations` a second time after timeout --- osu.Game.Tests/Database/GeneralUsageTests.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index 2533c832e6..8262ef18d4 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -95,6 +95,11 @@ namespace osu.Game.Tests.Database }); stopThreadedUsage.Set(); + + // Ensure we can block a second time after the usage has ended. + using (realm.BlockAllOperations()) + { + } }); } } From 86c844bd58317c2fc5d978a6142ea3ac153794a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 14:15:29 +0900 Subject: [PATCH 0226/5199] Update remaining usages of `GetSuitableHost` in template projects --- .../osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs | 2 +- .../osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs | 2 +- .../osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs | 2 +- .../osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs | 2 +- osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs | 1 - .../NonVisual/CustomTourneyDirectoryTest.cs | 1 - osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs | 1 - 7 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs index 4f810ce17f..03ee7c9204 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true })) { host.Run(new OsuTestBrowser()); return 0; diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs index fd6bd9b714..55c0cf6a3b 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true })) { host.Run(new OsuTestBrowser()); return 0; diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs index 65cfb2bff4..b45505678c 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true })) { host.Run(new OsuTestBrowser()); return 0; diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs index fd6bd9b714..55c0cf6a3b 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true })) { host.Run(new OsuTestBrowser()); return 0; diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index ee746ba4d5..5cbede54f5 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -6,7 +6,6 @@ using System.IO; using System.Text; using System.Threading.Tasks; using NUnit.Framework; -using osu.Framework; using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Testing; diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 36d0c6f0f6..26fb03bed4 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -4,7 +4,6 @@ using System.IO; using System.Linq; using NUnit.Framework; -using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; using osu.Framework.Testing; diff --git a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs index 11b686e3e8..80cc9be5c1 100644 --- a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs @@ -4,7 +4,6 @@ using System.IO; using System.Linq; using NUnit.Framework; -using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; using osu.Framework.Testing; From 5872dabf604f55a1feec63039404780a9d48133b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 14:16:15 +0900 Subject: [PATCH 0227/5199] Fix incorrect flag to options conversion --- osu.Desktop/Program.cs | 2 +- osu.Game.Tournament.Tests/TournamentTestRunner.cs | 2 +- osu.Game/Tests/VisualTestRunner.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index ab9935a220..b944068e78 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -55,7 +55,7 @@ namespace osu.Desktop } } - using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { PortableInstallation = true })) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { BindIPC = true })) { host.ExceptionThrown += handleException; diff --git a/osu.Game.Tournament.Tests/TournamentTestRunner.cs b/osu.Game.Tournament.Tests/TournamentTestRunner.cs index 23da37a6b7..229ab41a1e 100644 --- a/osu.Game.Tournament.Tests/TournamentTestRunner.cs +++ b/osu.Game.Tournament.Tests/TournamentTestRunner.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tournament.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { PortableInstallation = true })) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true })) { host.Run(new TournamentTestBrowser()); return 0; diff --git a/osu.Game/Tests/VisualTestRunner.cs b/osu.Game/Tests/VisualTestRunner.cs index 973801099e..6aa75ec147 100644 --- a/osu.Game/Tests/VisualTestRunner.cs +++ b/osu.Game/Tests/VisualTestRunner.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { PortableInstallation = true, })) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true, })) { host.Run(new OsuTestBrowser()); return 0; From 56d7d814658d1d202e405f237cf74d8a386d078b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 14:47:21 +0900 Subject: [PATCH 0228/5199] Fix broken test due to `SynchronizationContext` never running expected work --- osu.Game.Tests/Database/GeneralUsageTests.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index 8262ef18d4..dc0d42595b 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -87,6 +87,11 @@ namespace osu.Game.Tests.Database hasThreadedUsage.Wait(); + // Usually the host would run the synchronization context work per frame. + // For the sake of keeping this test simple (there's only one update invocation), + // let's replace it so we can ensure work is run immediately. + SynchronizationContext.SetSynchronizationContext(new ImmediateExecuteSynchronizationContext()); + Assert.Throws(() => { using (realm.BlockAllOperations()) @@ -102,5 +107,10 @@ namespace osu.Game.Tests.Database } }); } + + private class ImmediateExecuteSynchronizationContext : SynchronizationContext + { + public override void Post(SendOrPostCallback d, object? state) => d(state); + } } } From 5fb9b58c9b12c2929d07eaccfabf71528f259fe6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 20:23:10 +0900 Subject: [PATCH 0229/5199] Add tracking of total subscriptions --- osu.Game/Database/RealmAccess.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 65e13cf542..fe4c30d7dd 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -83,6 +83,8 @@ namespace osu.Game.Database private static readonly GlobalStatistic realm_instances_created = GlobalStatistics.Get(@"Realm", @"Instances (Created)"); + private static readonly GlobalStatistic total_subscriptions = GlobalStatistics.Get(@"Realm", @"Subscriptions"); + private readonly object realmLock = new object(); private Realm? updateRealm; @@ -289,6 +291,8 @@ namespace osu.Game.Database var syncContext = SynchronizationContext.Current; + total_subscriptions.Value++; + registerSubscription(action); // This token is returned to the consumer. @@ -309,6 +313,7 @@ namespace osu.Game.Database unsubscriptionAction?.Dispose(); customSubscriptionsResetMap.Remove(action); notificationsResetMap.Remove(action); + total_subscriptions.Value--; } } } From ae0fea8e2627f68f211c89a27a9bae04a651b0f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 15:29:45 +0900 Subject: [PATCH 0230/5199] Fix compilation issues due to misnamed fild --- osu.Game.Tests/Database/BeatmapImporterTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 8cb762ed12..b37d2be0bc 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -608,9 +608,9 @@ namespace osu.Game.Tests.Database using var importer = new BeatmapModelManager(realmFactory, storage); using var store = new RulesetStore(realmFactory, storage); - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realmFactory.Realm); - deleteBeatmapSet(imported, realmFactory.Context); + deleteBeatmapSet(imported, realmFactory.Realm); Assert.IsTrue(imported.DeletePending); @@ -619,7 +619,7 @@ namespace osu.Game.Tests.Database Assert.That(imported.Files.All(f => !storage.GetStorageForDirectory("files").Exists(f.File.GetStoragePath()))); - var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context); + var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Realm); // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. Assert.IsTrue(imported.ID == importedSecondTime.ID); From 778d2a71b484bb3220a09db15fc5fc632cb891e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 15:23:51 +0900 Subject: [PATCH 0231/5199] Remove `Task` from the inner-most `Import` method in `RealmArchiveModelImporter` One of my pending work items for post-realm merge. The lowest-level import task is no longer asynchronous, as we don't want it to span multiple threads to allow easier interaction with realm. Removing the `Task` spec simplifies a heap of usages. Individual usages should decide whether they want to run the import asynchronously, by either using an alternative override or spooling up a thread themselves. --- .../Database/BeatmapImporterTests.cs | 4 ++-- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 10 ++++----- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 22 +++++++++---------- .../Gameplay/TestSceneReplayDownloadButton.cs | 2 +- .../Menus/TestSceneMusicActionHandling.cs | 2 +- .../TestSceneDrawableRoomPlaylist.cs | 3 +-- .../TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../TestScenePlaylistsSongSelect.cs | 3 +-- .../Navigation/TestScenePresentBeatmap.cs | 3 +-- .../Navigation/TestScenePresentScore.cs | 5 ++--- .../TestScenePlaylistsRoomCreation.cs | 7 +++--- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 2 +- .../TestSceneBeatmapRecommendations.cs | 3 +-- .../SongSelect/TestScenePlaySongSelect.cs | 13 +++++------ .../TestSceneDeleteLocalScore.cs | 2 +- osu.Game/Beatmaps/BeatmapManager.cs | 5 ++--- osu.Game/Database/IModelImporter.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 2 +- osu.Game/Screens/Play/Player.cs | 8 ++++--- osu.Game/Skinning/SkinManager.cs | 5 ++--- osu.Game/Stores/RealmArchiveModelImporter.cs | 14 +++++++----- 21 files changed, 57 insertions(+), 62 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index b37d2be0bc..69dd2d930a 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -685,7 +685,7 @@ namespace osu.Game.Tests.Database [Test] public void TestImportWithDuplicateBeatmapIDs() { - RunTestWithRealmAsync(async (realm, storage) => + RunTestWithRealm((realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); using var store = new RulesetStore(realm, storage); @@ -718,7 +718,7 @@ namespace osu.Game.Tests.Database } }; - var imported = await importer.Import(toImport); + var imported = importer.Import(toImport); Assert.NotNull(imported); Debug.Assert(imported != null); diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 144cbe15c3..95c15367aa 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Online addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing); AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); - AddUntilStep("wait for import", () => beatmaps.CurrentImportTask?.IsCompleted == true); + AddUntilStep("wait for import", () => beatmaps.CurrentImport != null); addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); } @@ -164,7 +164,7 @@ namespace osu.Game.Tests.Online { public TaskCompletionSource AllowImport = new TaskCompletionSource(); - public Task> CurrentImportTask { get; private set; } + public ILive CurrentImport { get; private set; } public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null) : base(storage, realm, rulesets, api, audioManager, resources, host, defaultBeatmap) @@ -186,10 +186,10 @@ namespace osu.Game.Tests.Online this.testBeatmapManager = testBeatmapManager; } - public override async Task> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public override ILive Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { - await testBeatmapManager.AllowImport.Task.ConfigureAwait(false); - return await (testBeatmapManager.CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken)).ConfigureAwait(false); + testBeatmapManager.AllowImport.Task.WaitSafely(); + return (testBeatmapManager.CurrentImport = base.Import(item, archive, lowPriority, cancellationToken)); } } } diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index dd12c94855..8de9f0a292 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions; @@ -25,7 +24,7 @@ namespace osu.Game.Tests.Scores.IO public class ImportScoreTest : ImportTest { [Test] - public async Task TestBasicImport() + public void TestBasicImport() { using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { @@ -49,7 +48,7 @@ namespace osu.Game.Tests.Scores.IO BeatmapInfo = beatmap.Beatmaps.First() }; - var imported = await LoadScoreIntoOsu(osu, toImport); + var imported = LoadScoreIntoOsu(osu, toImport); Assert.AreEqual(toImport.Rank, imported.Rank); Assert.AreEqual(toImport.TotalScore, imported.TotalScore); @@ -67,7 +66,7 @@ namespace osu.Game.Tests.Scores.IO } [Test] - public async Task TestImportMods() + public void TestImportMods() { using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { @@ -85,7 +84,7 @@ namespace osu.Game.Tests.Scores.IO Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, }; - var imported = await LoadScoreIntoOsu(osu, toImport); + var imported = LoadScoreIntoOsu(osu, toImport); Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock)); Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime)); @@ -98,7 +97,7 @@ namespace osu.Game.Tests.Scores.IO } [Test] - public async Task TestImportStatistics() + public void TestImportStatistics() { using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { @@ -120,7 +119,7 @@ namespace osu.Game.Tests.Scores.IO } }; - var imported = await LoadScoreIntoOsu(osu, toImport); + var imported = LoadScoreIntoOsu(osu, toImport); Assert.AreEqual(toImport.Statistics[HitResult.Perfect], imported.Statistics[HitResult.Perfect]); Assert.AreEqual(toImport.Statistics[HitResult.Miss], imported.Statistics[HitResult.Miss]); @@ -133,7 +132,7 @@ namespace osu.Game.Tests.Scores.IO } [Test] - public async Task TestOnlineScoreIsAvailableLocally() + public void TestOnlineScoreIsAvailableLocally() { using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { @@ -143,7 +142,7 @@ namespace osu.Game.Tests.Scores.IO var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely(); - await LoadScoreIntoOsu(osu, new ScoreInfo + LoadScoreIntoOsu(osu, new ScoreInfo { User = new APIUser { Username = "Test user" }, BeatmapInfo = beatmap.Beatmaps.First(), @@ -168,13 +167,14 @@ namespace osu.Game.Tests.Scores.IO } } - public static async Task LoadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null) + public static ScoreInfo LoadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null) { // clone to avoid attaching the input score to realm. score = score.DeepClone(); var scoreManager = osu.Dependencies.Get(); - await scoreManager.Import(score, archive); + + scoreManager.Import(score, archive); return scoreManager.Query(_ => true); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 8199389b36..a12171401a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -147,7 +147,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded); - AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(true)).GetResultSafely()); + AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(true))); AddUntilStep("state is available", () => downloadButton.State.Value == DownloadState.LocallyAvailable); diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index 3ebc64cd0b..10a82089b3 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Menus Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null; // ensure we have at least two beatmaps available to identify the direction the music controller navigated to. - AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()).WaitSafely(), 5); + AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()), 5); AddStep("import beatmap with track", () => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 30ac1302aa..92accb0cd1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -8,7 +8,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; @@ -158,7 +157,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Debug.Assert(beatmap.BeatmapSet != null); - AddStep("import beatmap", () => imported = manager.Import(beatmap.BeatmapSet).GetResultSafely()); + AddStep("import beatmap", () => imported = manager.Import(beatmap.BeatmapSet)); createPlaylistWithBeatmaps(() => imported.PerformRead(s => s.Beatmaps.Detach())); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 7e3c9722cf..5465061891 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.Multiplayer beatmapSetInfo.Beatmaps.Add(beatmap); } - manager.Import(beatmapSetInfo).WaitSafely(); + manager.Import(beatmapSetInfo); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index d7a0885c95..d933491ab6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -6,7 +6,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Utils; @@ -40,7 +39,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var beatmapSet = TestResources.CreateTestBeatmapSetInfo(); - manager.Import(beatmapSet).WaitSafely(); + manager.Import(beatmapSet); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs index f6c53e76c4..63226de750 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using NUnit.Framework; -using osu.Framework.Extensions; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Extensions; @@ -125,7 +124,7 @@ namespace osu.Game.Tests.Visual.Navigation Ruleset = ruleset ?? new OsuRuleset().RulesetInfo }, } - }).GetResultSafely()?.Value; + })?.Value; }); AddAssert($"import {i} succeeded", () => imported != null); diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index 7bd8110374..7656bf79dc 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using NUnit.Framework; -using osu.Framework.Extensions; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -60,7 +59,7 @@ namespace osu.Game.Tests.Visual.Navigation Ruleset = new OsuRuleset().RulesetInfo }, } - }).GetResultSafely()?.Value; + })?.Value; }); } @@ -135,7 +134,7 @@ namespace osu.Game.Tests.Visual.Navigation BeatmapInfo = beatmap.Beatmaps.First(), Ruleset = ruleset ?? new OsuRuleset().RulesetInfo, User = new GuestUser(), - }).GetResultSafely().Value; + }).Value; }); AddAssert($"import {i} succeeded", () => imported != null); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index d397b37d05..68225f6d64 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -8,7 +8,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; @@ -151,7 +150,7 @@ namespace osu.Game.Tests.Visual.Playlists Debug.Assert(modifiedBeatmap.BeatmapInfo.BeatmapSet != null); - manager.Import(modifiedBeatmap.BeatmapInfo.BeatmapSet).WaitSafely(); + manager.Import(modifiedBeatmap.BeatmapInfo.BeatmapSet); }); // Create the room using the real beatmap values. @@ -196,7 +195,7 @@ namespace osu.Game.Tests.Visual.Playlists Debug.Assert(originalBeatmap.BeatmapInfo.BeatmapSet != null); - manager.Import(originalBeatmap.BeatmapInfo.BeatmapSet).WaitSafely(); + manager.Import(originalBeatmap.BeatmapInfo.BeatmapSet); }); AddUntilStep("match has correct beatmap", () => realHash == match.Beatmap.Value.BeatmapInfo.MD5Hash); @@ -219,7 +218,7 @@ namespace osu.Game.Tests.Visual.Playlists Debug.Assert(beatmap.BeatmapInfo.BeatmapSet != null); - importedBeatmap = manager.Import(beatmap.BeatmapInfo.BeatmapSet).GetResultSafely()?.Value.Detach(); + importedBeatmap = manager.Import(beatmap.BeatmapInfo.BeatmapSet)?.Value.Detach(); }); private class TestPlaylistsRoomSubScreen : PlaylistsRoomSubScreen diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 5e977d075d..e31be1d51a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -180,7 +180,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep(@"Load new scores via manager", () => { foreach (var score in generateSampleScores(beatmapInfo())) - scoreManager.Import(score).WaitSafely(); + scoreManager.Import(score); }); } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index b7bc0c37e1..940d001c5b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Extensions; @@ -184,7 +183,7 @@ namespace osu.Game.Tests.Visual.SongSelect beatmap.DifficultyName = $"SR{i + 1}"; } - return Game.BeatmapManager.Import(beatmapSet).GetResultSafely()?.Value; + return Game.BeatmapManager.Import(beatmapSet)?.Value; } private bool ensureAllBeatmapSetsImported(IEnumerable beatmapSets) => beatmapSets.All(set => set != null); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 0dc4556ed9..630171a4d0 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -8,7 +8,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; @@ -260,7 +259,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import multi-ruleset map", () => { var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - manager.Import(TestResources.CreateTestBeatmapSetInfo(rulesets: usableRulesets)).WaitSafely(); + manager.Import(TestResources.CreateTestBeatmapSetInfo(rulesets: usableRulesets)); }); } else @@ -675,7 +674,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import multi-ruleset map", () => { var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)).WaitSafely(); + manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)); }); int previousSetID = 0; @@ -715,7 +714,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import multi-ruleset map", () => { var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)).WaitSafely(); + manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)); }); DrawableCarouselBeatmapSet set = null; @@ -764,7 +763,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import huge difficulty count map", () => { var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - imported = manager.Import(TestResources.CreateTestBeatmapSetInfo(50, usableRulesets)).GetResultSafely()?.Value; + imported = manager.Import(TestResources.CreateTestBeatmapSetInfo(50, usableRulesets))?.Value; }); AddStep("select the first beatmap of import", () => Beatmap.Value = manager.GetWorkingBeatmap(imported.Beatmaps.First())); @@ -873,7 +872,7 @@ namespace osu.Game.Tests.Visual.SongSelect private void addRulesetImportStep(int id) => AddStep($"import test map for ruleset {id}", () => importForRuleset(id)); - private void importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray())).WaitSafely(); + private void importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray())); private void checkMusicPlaying(bool playing) => AddUntilStep($"music {(playing ? "" : "not ")}playing", () => music.IsPlaying == playing); @@ -903,7 +902,7 @@ namespace osu.Game.Tests.Visual.SongSelect var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); for (int i = 0; i < 10; i++) - manager.Import(TestResources.CreateTestBeatmapSetInfo(difficultyCountPerSet, usableRulesets)).WaitSafely(); + manager.Import(TestResources.CreateTestBeatmapSetInfo(difficultyCountPerSet, usableRulesets)); }); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 55031a9699..4826d2fb33 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -112,7 +112,7 @@ namespace osu.Game.Tests.Visual.UserInterface Ruleset = new OsuRuleset().RulesetInfo, }; - importedScores.Add(scoreManager.Import(score).GetResultSafely().Value); + importedScores.Add(scoreManager.Import(score).Value); } }); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 4488301fe5..a1c1982f00 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -10,7 +10,6 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Audio; using osu.Framework.Audio.Track; -using osu.Framework.Extensions; using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Framework.Testing; @@ -105,7 +104,7 @@ namespace osu.Game.Beatmaps foreach (BeatmapInfo b in beatmapSet.Beatmaps) b.BeatmapSet = beatmapSet; - var imported = beatmapModelManager.Import(beatmapSet).GetResultSafely(); + var imported = beatmapModelManager.Import(beatmapSet); if (imported == null) throw new InvalidOperationException("Failed to import new beatmap"); @@ -295,7 +294,7 @@ namespace osu.Game.Beatmaps return beatmapModelManager.Import(archive, lowPriority, cancellationToken); } - public Task?> Import(BeatmapSetInfo item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public ILive? Import(BeatmapSetInfo item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { return beatmapModelManager.Import(item, archive, lowPriority, cancellationToken); } diff --git a/osu.Game/Database/IModelImporter.cs b/osu.Game/Database/IModelImporter.cs index d00cfb2035..3047a1d30a 100644 --- a/osu.Game/Database/IModelImporter.cs +++ b/osu.Game/Database/IModelImporter.cs @@ -45,7 +45,7 @@ namespace osu.Game.Database /// An optional archive to use for model population. /// Whether this is a low priority import. /// An optional cancellation token. - Task?> Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default); + ILive? Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default); /// /// A user displayable name for the model type associated with this manager. diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index bb26bd3b04..3a842a048a 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -308,7 +308,7 @@ namespace osu.Game.Scoring return scoreModelManager.Import(archive, lowPriority, cancellationToken); } - public Task> Import(ScoreInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public ILive Import(ScoreInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { return scoreModelManager.Import(item, archive, lowPriority, cancellationToken); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4d3201cd27..cfca2d0a3d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1024,11 +1024,11 @@ namespace osu.Game.Screens.Play /// /// The to import. /// The imported score. - protected virtual async Task ImportScore(Score score) + protected virtual Task ImportScore(Score score) { // Replays are already populated and present in the game's database, so should not be re-imported. if (DrawableRuleset.ReplayScore != null) - return; + return Task.CompletedTask; LegacyByteArrayReader replayReader; @@ -1048,7 +1048,7 @@ namespace osu.Game.Screens.Play // conflicts across various systems (ie. solo and multiplayer). importableScore.OnlineID = -1; - var imported = await scoreManager.Import(importableScore, replayReader).ConfigureAwait(false); + var imported = scoreManager.Import(importableScore, replayReader); imported.PerformRead(s => { @@ -1056,6 +1056,8 @@ namespace osu.Game.Screens.Play score.ScoreInfo.Hash = s.Hash; score.ScoreInfo.ID = s.ID; }); + + return Task.CompletedTask; } /// diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 344acb7a0f..47c7bc060a 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -11,7 +11,6 @@ using JetBrains.Annotations; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; @@ -151,7 +150,7 @@ namespace osu.Game.Skinning Name = s.Name + @" (modified)", Creator = s.Creator, InstantiationInfo = s.InstantiationInfo, - }).GetResultSafely(); + }); if (result != null) { @@ -278,7 +277,7 @@ namespace osu.Game.Skinning return skinModelManager.Import(archive, lowPriority, cancellationToken); } - public Task> Import(SkinInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public ILive Import(SkinInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { return skinModelManager.Import(item, archive, lowPriority, cancellationToken); } diff --git a/osu.Game/Stores/RealmArchiveModelImporter.cs b/osu.Game/Stores/RealmArchiveModelImporter.cs index b0f676ecf0..43c1c7c888 100644 --- a/osu.Game/Stores/RealmArchiveModelImporter.cs +++ b/osu.Game/Stores/RealmArchiveModelImporter.cs @@ -250,8 +250,10 @@ namespace osu.Game.Stores return null; } - var scheduledImport = Task.Factory.StartNew(async () => await Import(model, archive, lowPriority, cancellationToken).ConfigureAwait(false), - cancellationToken, TaskCreationOptions.HideScheduler, lowPriority ? import_scheduler_low_priority : import_scheduler).Unwrap(); + var scheduledImport = Task.Factory.StartNew(() => Import(model, archive, lowPriority, cancellationToken), + cancellationToken, + TaskCreationOptions.HideScheduler, + lowPriority ? import_scheduler_low_priority : import_scheduler); return await scheduledImport.ConfigureAwait(false); } @@ -318,7 +320,7 @@ namespace osu.Game.Stores /// An optional archive to use for model population. /// Whether this is a low priority import. /// An optional cancellation token. - public virtual Task?> Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public virtual ILive? Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { return Realm.Run(realm => { @@ -353,7 +355,7 @@ namespace osu.Game.Stores transaction.Commit(); } - return Task.FromResult((ILive?)existing.ToLive(Realm)); + return existing.ToLive(Realm); } LogForModel(item, @"Found existing (optimised) but failed pre-check."); @@ -388,7 +390,7 @@ namespace osu.Game.Stores existing.DeletePending = false; transaction.Commit(); - return Task.FromResult((ILive?)existing.ToLive(Realm)); + return existing.ToLive(Realm); } LogForModel(item, @"Found existing but failed re-use check."); @@ -414,7 +416,7 @@ namespace osu.Game.Stores throw; } - return Task.FromResult((ILive?)item.ToLive(Realm)); + return (ILive?)item.ToLive(Realm); }); } From fc58b202b1d93b98d4d90b22741a5502591d5c3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 15:45:23 +0900 Subject: [PATCH 0232/5199] Fix crash when trying to migrate collection database that doesn't exist --- osu.Game/OsuGameBase.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7a6a126900..09ca4e450d 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -208,8 +208,13 @@ namespace osu.Game realm.CreateBackup($"client.{migration}.realm"); using (var source = Storage.GetStream("collection.db")) - using (var destination = Storage.GetStream($"collection.{migration}.db", FileAccess.Write, FileMode.CreateNew)) - source.CopyTo(destination); + { + if (source != null) + { + using (var destination = Storage.GetStream($"collection.{migration}.db", FileAccess.Write, FileMode.CreateNew)) + source.CopyTo(destination); + } + } } dependencies.CacheAs(Storage); From 1bb1366c9f1c7e3d6d0b43f0bdf7d7f0ce3fe551 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 16:26:06 +0900 Subject: [PATCH 0233/5199] Fix notification reset events potentially arriving out of order if a block operation times out --- osu.Game/Database/RealmAccess.cs | 40 +++++++++++++++----------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index e6e3c9ee41..bf88313663 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -334,25 +334,6 @@ namespace osu.Game.Database } } - /// - /// Unregister all subscriptions when the realm instance is to be recycled. - /// Subscriptions will still remain and will be re-subscribed when the realm instance returns. - /// - private void unregisterAllSubscriptions() - { - lock (realmLock) - { - foreach (var action in notificationsResetMap.Values) - action(); - - foreach (var action in customSubscriptionsResetMap) - { - action.Value?.Dispose(); - customSubscriptionsResetMap[action.Key] = null; - } - } - } - private Realm getRealmInstance() { if (isDisposed) @@ -605,9 +586,16 @@ namespace osu.Game.Database throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); syncContext = SynchronizationContext.Current; - } - unregisterAllSubscriptions(); + // Before disposing the update context, clean up all subscriptions. + // Note that in the case of realm notification subscriptions, this is not really required (they will be cleaned up by disposal). + // In the case of custom subscriptions, we want them to fire before the update realm is disposed in case they do any follow-up work. + foreach (var action in customSubscriptionsResetMap) + { + action.Value?.Dispose(); + customSubscriptionsResetMap[action.Key] = null; + } + } Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); @@ -615,6 +603,16 @@ namespace osu.Game.Database updateRealm = null; } + // In order to ensure events arrive in the correct order, these *must* be fired post disposal of the update realm, + // and must be posted to the synchronization context. + // This is because realm may fire event callbacks between the `unregisterAllSubscriptions` and `updateRealm.Dispose` + // calls above. + syncContext?.Post(_ => + { + foreach (var action in notificationsResetMap.Values) + action(); + }, null); + const int sleep_length = 200; int timeout = 5000; From 5a9524a74e945b6db0d51cdfa71574a63e6ee832 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 24 Jan 2022 02:51:13 +0300 Subject: [PATCH 0234/5199] Decrease default timeline zoom to "6 seconds visible" range --- osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 265f56534f..35a0282611 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -141,7 +141,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { MaxZoom = getZoomLevelForVisibleMilliseconds(500); MinZoom = getZoomLevelForVisibleMilliseconds(10000); - Zoom = getZoomLevelForVisibleMilliseconds(2000); + Zoom = getZoomLevelForVisibleMilliseconds(6000); } }, true); } From 1f9cf00db89f7a80027e665d86a3cfd8e1891b31 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 16:44:44 +0900 Subject: [PATCH 0235/5199] Fix `DatabasedKeyBindingContainer` re-querying realm on receiving notification --- .../Bindings/DatabasedKeyBindingContainer.cs | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 3e4a9759a3..ba129b93e5 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Input.Bindings; using osu.Game.Database; using osu.Game.Rulesets; +using Realms; namespace osu.Game.Input.Bindings { @@ -46,41 +47,36 @@ namespace osu.Game.Input.Bindings throw new InvalidOperationException($"{nameof(variant)} can not be null when a non-null {nameof(ruleset)} is provided."); } - private IQueryable queryRealmKeyBindings() - { - string rulesetName = ruleset?.ShortName; - return realm.Realm.All() - .Where(b => b.RulesetName == rulesetName && b.Variant == variant); - } - protected override void LoadComplete() { - realmSubscription = realm.RegisterForNotifications(r => queryRealmKeyBindings(), (sender, changes, error) => + realmSubscription = realm.RegisterForNotifications(queryRealmKeyBindings, (sender, changes, error) => { // The first fire of this is a bit redundant as this is being called in base.LoadComplete, // but this is safest in case the subscription is restored after a context recycle. - ReloadMappings(); + reloadMappings(sender.AsQueryable()); }); base.LoadComplete(); } - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); + protected override void ReloadMappings() => reloadMappings(queryRealmKeyBindings(realm.Realm)); - realmSubscription?.Dispose(); + private IQueryable queryRealmKeyBindings(Realm realm) + { + string rulesetName = ruleset?.ShortName; + return realm.All() + .Where(b => b.RulesetName == rulesetName && b.Variant == variant); } - protected override void ReloadMappings() + private void reloadMappings(IQueryable realmKeyBindings) { var defaults = DefaultKeyBindings.ToList(); - List newBindings = queryRealmKeyBindings().Detach() - // this ordering is important to ensure that we read entries from the database in the order - // enforced by DefaultKeyBindings. allow for song select to handle actions that may otherwise - // have been eaten by the music controller due to query order. - .OrderBy(b => defaults.FindIndex(d => (int)d.Action == b.ActionInt)).ToList(); + List newBindings = realmKeyBindings.Detach() + // this ordering is important to ensure that we read entries from the database in the order + // enforced by DefaultKeyBindings. allow for song select to handle actions that may otherwise + // have been eaten by the music controller due to query order. + .OrderBy(b => defaults.FindIndex(d => (int)d.Action == b.ActionInt)).ToList(); // In the case no bindings were found in the database, presume this usage is for a non-databased ruleset. // This actually should never be required and can be removed if it is ever deemed to cause a problem. @@ -91,5 +87,12 @@ namespace osu.Game.Input.Bindings else KeyBindings = newBindings; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + realmSubscription?.Dispose(); + } } } From 958cfde60834a0e5a9342c3dfc7f59893e52942d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 23:45:06 +0900 Subject: [PATCH 0236/5199] Stop detaching and exposing beatmaps from `MusicController` --- osu.Game/Overlays/MusicController.cs | 14 ++++++-------- osu.Game/Overlays/NowPlayingOverlay.cs | 1 - 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 9dc55d24dd..335abb53dc 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -30,8 +30,6 @@ namespace osu.Game.Overlays [Resolved] private BeatmapManager beatmaps { get; set; } - public IBindableList BeatmapSets => beatmapSets; - /// /// Point in time after which the current track will be restarted on triggering a "previous track" action. /// @@ -88,12 +86,12 @@ namespace osu.Game.Overlays { beatmapSets.Clear(); foreach (var s in sender) - beatmapSets.Add(s.Detach()); + beatmapSets.Add(s); return; } foreach (int i in changes.InsertedIndices) - beatmapSets.Insert(i, sender[i].Detach()); + beatmapSets.Insert(i, sender[i]); foreach (int i in changes.DeletedIndices.OrderByDescending(i => i)) beatmapSets.RemoveAt(i); @@ -240,7 +238,7 @@ namespace osu.Game.Overlays queuedDirection = TrackChangeDirection.Prev; - var playable = BeatmapSets.TakeWhile(i => !i.Equals(current.BeatmapSetInfo)).LastOrDefault() ?? BeatmapSets.LastOrDefault(); + var playable = beatmapSets.TakeWhile(i => !i.Equals(current.BeatmapSetInfo)).LastOrDefault() ?? beatmapSets.LastOrDefault(); if (playable != null) { @@ -271,7 +269,7 @@ namespace osu.Game.Overlays queuedDirection = TrackChangeDirection.Next; - var playableSet = BeatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).ElementAtOrDefault(1) ?? BeatmapSets.FirstOrDefault(); + var playableSet = beatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).ElementAtOrDefault(1) ?? beatmapSets.FirstOrDefault(); var playableBeatmap = playableSet?.Beatmaps.FirstOrDefault(); if (playableBeatmap != null) @@ -322,8 +320,8 @@ namespace osu.Game.Overlays else { // figure out the best direction based on order in playlist. - int last = BeatmapSets.TakeWhile(b => !b.Equals(current.BeatmapSetInfo)).Count(); - int next = newWorking == null ? -1 : BeatmapSets.TakeWhile(b => !b.Equals(newWorking.BeatmapSetInfo)).Count(); + int last = beatmapSets.TakeWhile(b => !b.Equals(current.BeatmapSetInfo)).Count(); + int next = newWorking == null ? -1 : beatmapSets.TakeWhile(b => !b.Equals(newWorking.BeatmapSetInfo)).Count(); direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next; } diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 4dd23c0008..4617a91885 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -197,7 +197,6 @@ namespace osu.Game.Overlays { dragContainer.Add(playlist); - playlist.BeatmapSets.BindTo(musicController.BeatmapSets); playlist.State.BindValueChanged(s => playlistButton.FadeColour(s.NewValue == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint), true); togglePlaylist(); From 8a4f3a7ce093b02dbbfa655146a30308214330d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 01:18:21 +0900 Subject: [PATCH 0237/5199] Reimplement subscription logic in `PlaylistOverlay` directly --- osu.Game/Overlays/Music/PlaylistOverlay.cs | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 78b2d58dae..245886743a 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.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.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -10,9 +11,11 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics; using osuTK; using osuTK.Graphics; +using Realms; namespace osu.Game.Overlays.Music { @@ -30,6 +33,15 @@ namespace osu.Game.Overlays.Music [Resolved] private BeatmapManager beatmaps { get; set; } + [Resolved] + private RealmContextFactory realmFactory { get; set; } + + private IDisposable beatmapSubscription; + + private IQueryable availableBeatmaps => realmFactory.Context + .All() + .Where(s => !s.DeletePending); + private FilterControl filter; private Playlist list; @@ -91,10 +103,31 @@ namespace osu.Game.Overlays.Music { base.LoadComplete(); + // tests might bind externally, in which case we don't want to involve realm. + if (beatmapSets.Count == 0) + beatmapSubscription = realmFactory.RegisterForNotifications(realm => availableBeatmaps, beatmapsChanged); + list.Items.BindTo(beatmapSets); beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo, true); } + private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) + { + if (changes == null) + { + beatmapSets.Clear(); + // must use AddRange to avoid RearrangeableList sort overhead per add op. + beatmapSets.AddRange(sender); + return; + } + + foreach (int i in changes.InsertedIndices) + beatmapSets.Insert(i, sender[i]); + + foreach (int i in changes.DeletedIndices.OrderByDescending(i => i)) + beatmapSets.RemoveAt(i); + } + protected override void PopIn() { filter.Search.HoldFocus = true; @@ -123,5 +156,11 @@ namespace osu.Game.Overlays.Music beatmap.Value = beatmaps.GetWorkingBeatmap(set.Beatmaps.First()); beatmap.Value.Track.Restart(); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + beatmapSubscription?.Dispose(); + } } } From ace2bd2208eead324a2ad8753b4c33bb6e1d02ea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 01:18:29 +0900 Subject: [PATCH 0238/5199] Apply some initial optimisations to `PlaylistItem` --- osu.Game/Overlays/Music/PlaylistItem.cs | 44 +++++++++++-------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 04c12b8cd7..b3b039148b 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -32,8 +32,6 @@ namespace osu.Game.Overlays.Music : base(item) { Padding = new MarginPadding { Left = 5 }; - - FilterTerms = item.Metadata.GetSearchableTerms(); } [BackgroundDependencyLoader] @@ -46,6 +44,25 @@ namespace osu.Game.Overlays.Music { base.LoadComplete(); + var metadata = Model.Metadata; + + var title = new RomanisableString(metadata.TitleUnicode, metadata.Title); + var artist = new RomanisableString(metadata.ArtistUnicode, metadata.Artist); + + titlePart = text.AddText(title, sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)); + + updateSelectionState(true); + titlePart.DrawablePartsRecreated += _ => updateSelectionState(true); + + text.AddText(@" "); // to separate the title from the artist. + + text.AddText(artist, sprite => + { + sprite.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); + sprite.Colour = colours.Gray9; + sprite.Padding = new MarginPadding { Top = 1 }; + }); + SelectedSet.BindValueChanged(set => { if (set.OldValue?.Equals(Model) != true && set.NewValue?.Equals(Model) != true) @@ -67,27 +84,6 @@ namespace osu.Game.Overlays.Music AutoSizeAxes = Axes.Y, }; - protected override void LoadAsyncComplete() - { - base.LoadAsyncComplete(); - - var title = new RomanisableString(Model.Metadata.TitleUnicode, Model.Metadata.Title); - var artist = new RomanisableString(Model.Metadata.ArtistUnicode, Model.Metadata.Artist); - - titlePart = text.AddText(title, sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)); - updateSelectionState(true); - titlePart.DrawablePartsRecreated += _ => updateSelectionState(true); - - text.AddText(@" "); // to separate the title from the artist. - - text.AddText(artist, sprite => - { - sprite.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); - sprite.Colour = colours.Gray9; - sprite.Padding = new MarginPadding { Top = 1 }; - }); - } - protected override bool OnClick(ClickEvent e) { RequestSelection?.Invoke(Model); @@ -109,7 +105,7 @@ namespace osu.Game.Overlays.Music } } - public IEnumerable FilterTerms { get; } + public IEnumerable FilterTerms => Model.Metadata.GetSearchableTerms(); private bool matchingFilter = true; From 2a786f9ec0302f3ba39561ae5a5d56a0eafa7fe5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 01:49:07 +0900 Subject: [PATCH 0239/5199] Load text only after it comes on screen (and tidy up selection handling logic) --- osu.Game/Overlays/Music/PlaylistItem.cs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index b3b039148b..b31a03a64d 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -50,12 +50,9 @@ namespace osu.Game.Overlays.Music var artist = new RomanisableString(metadata.ArtistUnicode, metadata.Artist); titlePart = text.AddText(title, sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)); - - updateSelectionState(true); titlePart.DrawablePartsRecreated += _ => updateSelectionState(true); text.AddText(@" "); // to separate the title from the artist. - text.AddText(artist, sprite => { sprite.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); @@ -65,24 +62,31 @@ namespace osu.Game.Overlays.Music SelectedSet.BindValueChanged(set => { - if (set.OldValue?.Equals(Model) != true && set.NewValue?.Equals(Model) != true) + bool newSelected = set.NewValue?.Equals(Model) == true; + + if (newSelected == selected) return; + selected = newSelected; updateSelectionState(false); - }, true); + }); + + updateSelectionState(true); } + private bool selected; + private void updateSelectionState(bool instant) { foreach (Drawable s in titlePart.Drawables) - s.FadeColour(SelectedSet.Value?.Equals(Model) == true ? colours.Yellow : Color4.White, instant ? 0 : FADE_DURATION); + s.FadeColour(selected ? colours.Yellow : Color4.White, instant ? 0 : FADE_DURATION); } - protected override Drawable CreateContent() => text = new OsuTextFlowContainer + protected override Drawable CreateContent() => new DelayedLoadWrapper(text = new OsuTextFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - }; + }); protected override bool OnClick(ClickEvent e) { From 83b0e4572a057c520723c3319d7227c1ccf8951f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 02:39:01 +0900 Subject: [PATCH 0240/5199] Fix test failures --- osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs index 62f3b63780..7131c19471 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs @@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("drag to 5th", () => { var item = this.ChildrenOfType().ElementAt(4); - InputManager.MoveMouseTo(item.ScreenSpaceDrawQuad.Centre); + InputManager.MoveMouseTo(item.ScreenSpaceDrawQuad.BottomLeft); }); AddAssert("song 1 is 5th", () => beatmapSets[4].Equals(first)); From 1a776a95877907b7d9304ffa6ac4a529558f1b79 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 04:27:07 +0900 Subject: [PATCH 0241/5199] Completely remove subscription from `MusicController` --- osu.Game/Overlays/Music/PlaylistOverlay.cs | 10 ++-- osu.Game/Overlays/MusicController.cs | 68 ++++------------------ 2 files changed, 16 insertions(+), 62 deletions(-) diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 245886743a..6adcd2a6d6 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -34,13 +34,13 @@ namespace osu.Game.Overlays.Music private BeatmapManager beatmaps { get; set; } [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } private IDisposable beatmapSubscription; - private IQueryable availableBeatmaps => realmFactory.Context - .All() - .Where(s => !s.DeletePending); + private IQueryable availableBeatmaps => realm.Realm + .All() + .Where(s => !s.DeletePending); private FilterControl filter; private Playlist list; @@ -105,7 +105,7 @@ namespace osu.Game.Overlays.Music // tests might bind externally, in which case we don't want to involve realm. if (beatmapSets.Count == 0) - beatmapSubscription = realmFactory.RegisterForNotifications(realm => availableBeatmaps, beatmapsChanged); + beatmapSubscription = realm.RegisterForNotifications(realm => availableBeatmaps, beatmapsChanged); list.Items.BindTo(beatmapSets); beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo, true); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 335abb53dc..5fc0da8891 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -16,7 +16,6 @@ using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Rulesets.Mods; -using Realms; namespace osu.Game.Overlays { @@ -25,8 +24,6 @@ namespace osu.Game.Overlays /// public class MusicController : CompositeDrawable { - private IDisposable beatmapSubscription; - [Resolved] private BeatmapManager beatmaps { get; set; } @@ -35,8 +32,6 @@ namespace osu.Game.Overlays /// private const double restart_cutoff_point = 5000; - private readonly BindableList beatmapSets = new BindableList(); - /// /// Whether the user has requested the track to be paused. Use to determine whether the track is still playing. /// @@ -69,50 +64,11 @@ namespace osu.Game.Overlays mods.BindValueChanged(_ => ResetTrackAdjustments(), true); } - private IQueryable queryRealmBeatmapSets() => - realm.Realm - .All() - .Where(s => !s.DeletePending); - - protected override void LoadComplete() - { - base.LoadComplete(); - beatmapSubscription = realm.RegisterForNotifications(r => queryRealmBeatmapSets(), beatmapsChanged); - } - - private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) - { - if (changes == null) - { - beatmapSets.Clear(); - foreach (var s in sender) - beatmapSets.Add(s); - return; - } - - foreach (int i in changes.InsertedIndices) - beatmapSets.Insert(i, sender[i]); - - foreach (int i in changes.DeletedIndices.OrderByDescending(i => i)) - beatmapSets.RemoveAt(i); - } - /// /// Forcefully reload the current 's track from disk. /// public void ReloadCurrentTrack() => changeTrack(); - /// - /// Change the position of a in the current playlist. - /// - /// The beatmap to move. - /// The new position. - public void ChangeBeatmapSetPosition(BeatmapSetInfo beatmapSetInfo, int index) - { - beatmapSets.Remove(beatmapSetInfo); - beatmapSets.Insert(index, beatmapSetInfo); - } - /// /// Returns whether the beatmap track is playing. /// @@ -238,11 +194,12 @@ namespace osu.Game.Overlays queuedDirection = TrackChangeDirection.Prev; - var playable = beatmapSets.TakeWhile(i => !i.Equals(current.BeatmapSetInfo)).LastOrDefault() ?? beatmapSets.LastOrDefault(); + var playableSet = getBeatmapSets().AsEnumerable().TakeWhile(i => !i.Equals(current.BeatmapSetInfo)).LastOrDefault() + ?? getBeatmapSets().LastOrDefault(); - if (playable != null) + if (playableSet != null) { - changeBeatmap(beatmaps.GetWorkingBeatmap(playable.Beatmaps.First())); + changeBeatmap(beatmaps.GetWorkingBeatmap(playableSet.Beatmaps.First())); restartTrack(); return PreviousTrackResult.Previous; } @@ -269,7 +226,9 @@ namespace osu.Game.Overlays queuedDirection = TrackChangeDirection.Next; - var playableSet = beatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).ElementAtOrDefault(1) ?? beatmapSets.FirstOrDefault(); + var playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current.BeatmapSetInfo)).ElementAtOrDefault(1) + ?? getBeatmapSets().FirstOrDefault(); + var playableBeatmap = playableSet?.Beatmaps.FirstOrDefault(); if (playableBeatmap != null) @@ -293,6 +252,8 @@ namespace osu.Game.Overlays private TrackChangeDirection? queuedDirection; + private IQueryable getBeatmapSets() => realm.Realm.All().Where(s => !s.DeletePending); + private void beatmapChanged(ValueChangedEvent beatmap) => changeBeatmap(beatmap.NewValue); private void changeBeatmap(WorkingBeatmap newWorking) @@ -320,8 +281,8 @@ namespace osu.Game.Overlays else { // figure out the best direction based on order in playlist. - int last = beatmapSets.TakeWhile(b => !b.Equals(current.BeatmapSetInfo)).Count(); - int next = newWorking == null ? -1 : beatmapSets.TakeWhile(b => !b.Equals(newWorking.BeatmapSetInfo)).Count(); + int last = getBeatmapSets().AsEnumerable().TakeWhile(b => !b.Equals(current.BeatmapSetInfo)).Count(); + int next = newWorking == null ? -1 : getBeatmapSets().AsEnumerable().TakeWhile(b => !b.Equals(newWorking.BeatmapSetInfo)).Count(); direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next; } @@ -435,13 +396,6 @@ namespace osu.Game.Overlays mod.ApplyToTrack(CurrentTrack); } } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - beatmapSubscription?.Dispose(); - } } public enum TrackChangeDirection From ffd7877a1e8b6b822fd5f5922111b3d1ae7c027b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 17:41:22 +0900 Subject: [PATCH 0242/5199] Remove synchronization context hacks in realm tests --- osu.Game.Tests/Database/GeneralUsageTests.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index dc0d42595b..8262ef18d4 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -87,11 +87,6 @@ namespace osu.Game.Tests.Database hasThreadedUsage.Wait(); - // Usually the host would run the synchronization context work per frame. - // For the sake of keeping this test simple (there's only one update invocation), - // let's replace it so we can ensure work is run immediately. - SynchronizationContext.SetSynchronizationContext(new ImmediateExecuteSynchronizationContext()); - Assert.Throws(() => { using (realm.BlockAllOperations()) @@ -107,10 +102,5 @@ namespace osu.Game.Tests.Database } }); } - - private class ImmediateExecuteSynchronizationContext : SynchronizationContext - { - public override void Post(SendOrPostCallback d, object? state) => d(state); - } } } From 90a7dd771107f0de718a1fefc286c2d946ee99c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 14:30:00 +0900 Subject: [PATCH 0243/5199] In gameplay bindings test, ensure a selection is made before attempting to enter gameplay --- .../Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs index bfcefdbbfe..6165c9d9b1 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs @@ -50,8 +50,11 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("close settings", () => Game.Settings.Hide()); AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + PushAndConfirm(() => new PlaySongSelect()); + AddUntilStep("wait for selection", () => !Game.Beatmap.IsDefault); + AddStep("enter gameplay", () => InputManager.Key(Key.Enter)); AddUntilStep("wait for gameplay", () => player?.IsBreakTime.Value == false); From c0ed30801686e2a25486fb2a80a9ec2534e2b522 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 14:30:32 +0900 Subject: [PATCH 0244/5199] Use more correct method of deletion in `TestScenePlaySongSelect` --- osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 630171a4d0..458c6130c7 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -64,13 +64,13 @@ namespace osu.Game.Tests.Visual.SongSelect { base.SetUpSteps(); - AddStep("delete all beatmaps", () => + AddStep("reset defaults", () => { Ruleset.Value = new OsuRuleset().RulesetInfo; - manager?.Delete(manager.GetAllUsableBeatmapSets()); - Beatmap.SetDefault(); }); + + AddStep("delete all beatmaps", () => manager?.Delete()); } [Test] From b2b6672095d10a8da9a005dd87d072212893e454 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jan 2022 11:43:05 +0300 Subject: [PATCH 0245/5199] Add failing test asserts --- osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index bf3b46c6f7..96f815621c 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -3,6 +3,7 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Input; using osu.Framework.Testing; using osu.Game.Beatmaps.ControlPoints; @@ -40,6 +41,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("Enter compose mode", () => InputManager.Key(Key.F1)); AddUntilStep("Wait for compose mode load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + AddStep("Set beat divisor", () => editor.Dependencies.Get().Value = 16); AddStep("Set overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty = 7); AddStep("Set artist and title", () => { @@ -88,6 +90,7 @@ namespace osu.Game.Tests.Visual.Editing private void checkMutations() { AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1); + AddAssert("Beatmap has correct beat divisor", () => editorBeatmap.BeatmapInfo.BeatDivisor == 16); AddAssert("Beatmap has correct overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty == 7); AddAssert("Beatmap has correct metadata", () => editorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && editorBeatmap.BeatmapInfo.Metadata.Title == "title"); AddAssert("Beatmap has correct author", () => editorBeatmap.BeatmapInfo.Metadata.Author.Username == "author"); From f7f58b06a1bd107100d3ed0ee23c65bb0925112c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jan 2022 11:43:16 +0300 Subject: [PATCH 0246/5199] Fix beat divisor not saving in editor --- osu.Game/Screens/Edit/Editor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 8c4b458534..b42f629aad 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -158,9 +158,6 @@ namespace osu.Game.Screens.Edit return; } - beatDivisor.Value = playableBeatmap.BeatmapInfo.BeatDivisor; - beatDivisor.BindValueChanged(divisor => playableBeatmap.BeatmapInfo.BeatDivisor = divisor.NewValue); - // Todo: should probably be done at a DrawableRuleset level to share logic with Player. clock = new EditorClock(playableBeatmap, beatDivisor) { IsCoupled = false }; clock.ChangeSource(loadableBeatmap.Track); @@ -178,6 +175,9 @@ namespace osu.Game.Screens.Edit changeHandler = new EditorChangeHandler(editorBeatmap); dependencies.CacheAs(changeHandler); + beatDivisor.Value = editorBeatmap.BeatmapInfo.BeatDivisor; + beatDivisor.BindValueChanged(divisor => editorBeatmap.BeatmapInfo.BeatDivisor = divisor.NewValue); + updateLastSavedHash(); Schedule(() => From a93873e8ca0f9290c5be236b363337d71042824f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jan 2022 13:01:30 +0300 Subject: [PATCH 0247/5199] Recreate test beatmap of `EditorTestScene` on set up --- osu.Game/Tests/Visual/EditorTestScene.cs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 6f751b1736..f7d62a8694 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -43,28 +43,16 @@ namespace osu.Game.Tests.Visual }; private TestBeatmapManager testBeatmapManager; - private WorkingBeatmap working; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio, RulesetStore rulesets) { Add(logo); - working = CreateWorkingBeatmap(Ruleset.Value); - if (IsolateSavingFromDatabase) Dependencies.CacheAs(testBeatmapManager = new TestBeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); } - protected override void LoadComplete() - { - base.LoadComplete(); - - Beatmap.Value = working; - if (testBeatmapManager != null) - testBeatmapManager.TestBeatmap = working; - } - protected virtual bool EditorComponentsReady => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true && Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true; @@ -78,6 +66,11 @@ namespace osu.Game.Tests.Visual protected virtual void LoadEditor() { + Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); + + if (testBeatmapManager != null) + testBeatmapManager.TestBeatmap = Beatmap.Value; + LoadScreen(editorLoader = new TestEditorLoader()); } From 4a9f4eecba45e66428de23094f1d1387199bb450 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 20:49:52 +0900 Subject: [PATCH 0248/5199] Use blocking calls to `SynchronizationContext` to guarantee order of execution --- osu.Game/Database/RealmAccess.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index bf88313663..ffaae9b4ae 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -607,7 +607,7 @@ namespace osu.Game.Database // and must be posted to the synchronization context. // This is because realm may fire event callbacks between the `unregisterAllSubscriptions` and `updateRealm.Dispose` // calls above. - syncContext?.Post(_ => + syncContext?.Send(_ => { foreach (var action in notificationsResetMap.Values) action(); @@ -647,8 +647,14 @@ namespace osu.Game.Database { Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); realmRetrievalLock.Release(); + // Post back to the update thread to revive any subscriptions. - syncContext?.Post(_ => ensureUpdateRealm(), null); + // In the case we are on the update thread, let's also require this to run synchronously. + // This requirement is mostly due to test coverage, but shouldn't cause any harm. + if (ThreadSafety.IsUpdateThread) + syncContext?.Send(_ => ensureUpdateRealm(), null); + else + syncContext?.Post(_ => ensureUpdateRealm(), null); } } From 6c69df815a83f718d59354744321ce99ca5b644a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jan 2022 15:25:28 +0300 Subject: [PATCH 0249/5199] Update editor test scenes to set working beatmap properly --- .../Visual/Editing/TestSceneDifficultySwitching.cs | 8 +++----- .../Visual/Editing/TestSceneEditorBeatmapCreation.cs | 7 ++----- .../Visual/Editing/TestSceneEditorTestGameplay.cs | 5 ++++- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs index 243bb71e26..cf6488f721 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit; +using osu.Game.Storyboards; using osu.Game.Tests.Beatmaps.IO; namespace osu.Game.Tests.Visual.Editing @@ -37,11 +38,8 @@ namespace osu.Game.Tests.Visual.Editing base.SetUpSteps(); } - protected override void LoadEditor() - { - Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First()); - base.LoadEditor(); - } + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) + => beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First()); [Test] public void TestBasicSwitch() diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 2386446e96..e3fb44534b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Setup; +using osu.Game.Storyboards; using osu.Game.Tests.Resources; using SharpCompress.Archives; using SharpCompress.Archives.Zip; @@ -39,11 +40,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("make new beatmap unique", () => EditorBeatmap.Metadata.Title = Guid.NewGuid().ToString()); } - protected override void LoadEditor() - { - Beatmap.Value = new DummyWorkingBeatmap(Audio, null); - base.LoadEditor(); - } + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new DummyWorkingBeatmap(Audio, null); [Test] public void TestCreateNewBeatmap() diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index bb630e5d5c..79afc8cf27 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -17,6 +17,7 @@ using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Game.Screens.Edit.GameplayTest; using osu.Game.Screens.Play; +using osu.Game.Storyboards; using osu.Game.Tests.Beatmaps.IO; using osuTK.Graphics; using osuTK.Input; @@ -43,9 +44,11 @@ namespace osu.Game.Tests.Visual.Editing base.SetUpSteps(); } + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) + => beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0)); + protected override void LoadEditor() { - Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0)); SelectedMods.Value = new[] { new ModCinema() }; base.LoadEditor(); } From cdef67ccd0840053192a151b9d75f303e44ecb07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 23:38:46 +0900 Subject: [PATCH 0250/5199] Log posted notifications To help with test failures and the likes. --- osu.Game/Overlays/NotificationOverlay.cs | 3 +++ osu.Game/Overlays/Notifications/Notification.cs | 3 +++ osu.Game/Overlays/Notifications/ProgressNotification.cs | 2 +- osu.Game/Overlays/Notifications/SimpleNotification.cs | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 8809dec642..e4e3931048 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -12,6 +12,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Localisation; +using osu.Framework.Logging; using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Localisation; @@ -118,6 +119,8 @@ namespace osu.Game.Overlays { ++runningDepth; + Logger.Log($"⚠️ {notification.Text}"); + notification.Closed += notificationClosed; if (notification is IHasCompletionTarget hasCompletionTarget) diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 44203e8ee7..ec6e9e09b3 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; @@ -25,6 +26,8 @@ namespace osu.Game.Overlays.Notifications /// public event Action Closed; + public abstract LocalisableString Text { get; set; } + /// /// Whether this notification should forcefully display itself. /// diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 5b74bff817..4735fcb7c1 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Notifications private LocalisableString text; - public LocalisableString Text + public override LocalisableString Text { get => text; set diff --git a/osu.Game/Overlays/Notifications/SimpleNotification.cs b/osu.Game/Overlays/Notifications/SimpleNotification.cs index c32e40ffc8..b9a1cc6d90 100644 --- a/osu.Game/Overlays/Notifications/SimpleNotification.cs +++ b/osu.Game/Overlays/Notifications/SimpleNotification.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Notifications { private LocalisableString text; - public LocalisableString Text + public override LocalisableString Text { get => text; set From d1cbdf63f09afb381961cc646782d2c01610f80d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jan 2022 10:43:43 +0300 Subject: [PATCH 0251/5199] Add support for reading/saving timeline zoom in editor --- .../Compose/Components/Timeline/Timeline.cs | 29 ++++++++++++++++--- .../Timeline/ZoomableScrollContainer.cs | 9 ++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 35a0282611..5722174490 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -24,7 +24,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Cached] public class Timeline : ZoomableScrollContainer, IPositionSnapProvider { + private const float timeline_height = 72; + private const float timeline_expanded_height = 94; + private readonly Drawable userContent; + public readonly Bindable WaveformVisible = new Bindable(); public readonly Bindable ControlPointsVisible = new Bindable(); @@ -58,8 +62,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Track track; - private const float timeline_height = 72; - private const float timeline_expanded_height = 94; + /// + /// The timeline zoom level at a 1x zoom scale. + /// + private float defaultTimelineZoom; + + private readonly Bindable timelineZoomScale = new BindableDouble(1.0); public Timeline(Drawable userContent) { @@ -84,7 +92,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Bindable waveformOpacity; [BackgroundDependencyLoader] - private void load(IBindable beatmap, OsuColour colours, OsuConfigManager config) + private void load(IBindable beatmap, EditorBeatmap editorBeatmap, OsuColour colours, OsuConfigManager config) { CentreMarker centreMarker; @@ -141,9 +149,16 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { MaxZoom = getZoomLevelForVisibleMilliseconds(500); MinZoom = getZoomLevelForVisibleMilliseconds(10000); - Zoom = getZoomLevelForVisibleMilliseconds(6000); + defaultTimelineZoom = getZoomLevelForVisibleMilliseconds(6000); } }, true); + + timelineZoomScale.Value = editorBeatmap.BeatmapInfo.TimelineZoom; + timelineZoomScale.BindValueChanged(scale => + { + Zoom = (float)(defaultTimelineZoom * scale.NewValue); + editorBeatmap.BeatmapInfo.TimelineZoom = scale.NewValue; + }, true); } protected override void LoadComplete() @@ -201,6 +216,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return base.OnScroll(e); } + protected override void OnZoomChange() + { + base.OnZoomChange(); + timelineZoomScale.Value = Zoom / defaultTimelineZoom; + } + protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index f10eb0d284..6dea99f1c8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -136,11 +136,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { zoomTarget = Math.Clamp(newZoom, MinZoom, MaxZoom); transformZoomTo(zoomTarget, focusPoint, ZoomDuration, ZoomEasing); + + OnZoomChange(); } private void transformZoomTo(float newZoom, float focusPoint, double duration = 0, Easing easing = Easing.None) => this.TransformTo(this.PopulateTransform(new TransformZoom(focusPoint, zoomedContent.DrawWidth, Current), newZoom, duration, easing)); + /// + /// Invoked when the zoom target has changed. + /// + protected virtual void OnZoomChange() + { + } + private class TransformZoom : Transform { /// From ad18bc4983254b389c5b80f707ee8610197a5ceb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jan 2022 17:02:23 +0300 Subject: [PATCH 0252/5199] Update timeline selection test scene with zoom changes --- .../Editing/TestSceneTimelineSelection.cs | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs index 2544b6c2a1..81ab4712ab 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs @@ -47,25 +47,25 @@ namespace osu.Game.Tests.Visual.Editing AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[] { - new HitCircle { StartTime = 100 }, - new HitCircle { StartTime = 200, Position = new Vector2(100) }, - new HitCircle { StartTime = 300, Position = new Vector2(200) }, - new HitCircle { StartTime = 400, Position = new Vector2(300) }, + new HitCircle { StartTime = 500 }, + new HitCircle { StartTime = 1000, Position = new Vector2(100) }, + new HitCircle { StartTime = 1500, Position = new Vector2(200) }, + new HitCircle { StartTime = 2000, Position = new Vector2(300) }, })); AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.AddRange(addedObjects)); AddStep("nudge forwards", () => InputManager.Key(Key.K)); - AddAssert("objects moved forwards in time", () => addedObjects[0].StartTime > 100); + AddAssert("objects moved forwards in time", () => addedObjects[0].StartTime > 500); AddStep("nudge backwards", () => InputManager.Key(Key.J)); - AddAssert("objects reverted to original position", () => addedObjects[0].StartTime == 100); + AddAssert("objects reverted to original position", () => addedObjects[0].StartTime == 500); } [Test] public void TestBasicSelect() { - var addedObject = new HitCircle { StartTime = 100 }; + var addedObject = new HitCircle { StartTime = 500 }; AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); moveMouseToObject(() => addedObject); @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.Editing var addedObject2 = new HitCircle { - StartTime = 200, + StartTime = 1000, Position = new Vector2(100), }; @@ -92,10 +92,10 @@ namespace osu.Game.Tests.Visual.Editing { var addedObjects = new[] { - new HitCircle { StartTime = 100 }, - new HitCircle { StartTime = 200, Position = new Vector2(100) }, - new HitCircle { StartTime = 300, Position = new Vector2(200) }, - new HitCircle { StartTime = 400, Position = new Vector2(300) }, + new HitCircle { StartTime = 500 }, + new HitCircle { StartTime = 1000, Position = new Vector2(100) }, + new HitCircle { StartTime = 1500, Position = new Vector2(200) }, + new HitCircle { StartTime = 2000, Position = new Vector2(300) }, }; AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects)); @@ -125,7 +125,7 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestBasicDeselect() { - var addedObject = new HitCircle { StartTime = 100 }; + var addedObject = new HitCircle { StartTime = 500 }; AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); moveMouseToObject(() => addedObject); @@ -166,11 +166,11 @@ namespace osu.Game.Tests.Visual.Editing { var addedObjects = new[] { - new HitCircle { StartTime = 100 }, - new HitCircle { StartTime = 200, Position = new Vector2(100) }, - new HitCircle { StartTime = 300, Position = new Vector2(200) }, - new HitCircle { StartTime = 400, Position = new Vector2(300) }, - new HitCircle { StartTime = 500, Position = new Vector2(400) }, + new HitCircle { StartTime = 500 }, + new HitCircle { StartTime = 1000, Position = new Vector2(100) }, + new HitCircle { StartTime = 1500, Position = new Vector2(200) }, + new HitCircle { StartTime = 2000, Position = new Vector2(300) }, + new HitCircle { StartTime = 2500, Position = new Vector2(400) }, }; AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects)); @@ -236,10 +236,10 @@ namespace osu.Game.Tests.Visual.Editing { var addedObjects = new[] { - new HitCircle { StartTime = 100 }, - new HitCircle { StartTime = 200, Position = new Vector2(100) }, - new HitCircle { StartTime = 300, Position = new Vector2(200) }, - new HitCircle { StartTime = 400, Position = new Vector2(300) }, + new HitCircle { StartTime = 500 }, + new HitCircle { StartTime = 1000, Position = new Vector2(100) }, + new HitCircle { StartTime = 1500, Position = new Vector2(200) }, + new HitCircle { StartTime = 2000, Position = new Vector2(300) }, }; AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects)); From 4169e5592ee973349fc5f39e1778b948ac722338 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jan 2022 19:36:19 +0300 Subject: [PATCH 0253/5199] Reword event handler name and update xmldoc --- .../Screens/Edit/Compose/Components/Timeline/Timeline.cs | 4 ++-- .../Compose/Components/Timeline/ZoomableScrollContainer.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 5722174490..51cca4ceff 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -216,9 +216,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return base.OnScroll(e); } - protected override void OnZoomChange() + protected override void OnZoomChanged() { - base.OnZoomChange(); + base.OnZoomChanged(); timelineZoomScale.Value = Zoom / defaultTimelineZoom; } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 6dea99f1c8..35d103ddf1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -137,16 +137,16 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline zoomTarget = Math.Clamp(newZoom, MinZoom, MaxZoom); transformZoomTo(zoomTarget, focusPoint, ZoomDuration, ZoomEasing); - OnZoomChange(); + OnZoomChanged(); } private void transformZoomTo(float newZoom, float focusPoint, double duration = 0, Easing easing = Easing.None) => this.TransformTo(this.PopulateTransform(new TransformZoom(focusPoint, zoomedContent.DrawWidth, Current), newZoom, duration, easing)); /// - /// Invoked when the zoom target has changed. + /// Invoked when has changed. /// - protected virtual void OnZoomChange() + protected virtual void OnZoomChanged() { } From 5085eb68018d1ae069cce9e754d5e6af326d3a06 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 03:37:11 +0900 Subject: [PATCH 0254/5199] Ensure gameplay starts by dismissing any notifications in `TestSceneChangeAndUseGameplayBindings` --- .../TestSceneChangeAndUseGameplayBindings.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs index 6165c9d9b1..347b4b6c54 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs @@ -57,6 +57,13 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("enter gameplay", () => InputManager.Key(Key.Enter)); + AddUntilStep("wait for player", () => + { + // dismiss any notifications that may appear (ie. muted notification). + clickMouseInCentre(); + return player != null; + }); + AddUntilStep("wait for gameplay", () => player?.IsBreakTime.Value == false); AddStep("press 'z'", () => InputManager.Key(Key.Z)); @@ -66,6 +73,12 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("key counter did increase", () => keyCounter.CountPresses == 1); } + private void clickMouseInCentre() + { + InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre); + InputManager.Click(MouseButton.Left); + } + private KeyBindingsSubsection osuBindingSubsection => keyBindingPanel .ChildrenOfType() .FirstOrDefault(s => s.Ruleset.ShortName == "osu"); From 64914c45a4d7528713b8d06d3e7d54c2335db2cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 11:53:50 +0900 Subject: [PATCH 0255/5199] Remove unnecessary realm query helper method --- osu.Game/Overlays/Music/PlaylistOverlay.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 6adcd2a6d6..ffddf168ed 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -38,10 +38,6 @@ namespace osu.Game.Overlays.Music private IDisposable beatmapSubscription; - private IQueryable availableBeatmaps => realm.Realm - .All() - .Where(s => !s.DeletePending); - private FilterControl filter; private Playlist list; @@ -105,7 +101,7 @@ namespace osu.Game.Overlays.Music // tests might bind externally, in which case we don't want to involve realm. if (beatmapSets.Count == 0) - beatmapSubscription = realm.RegisterForNotifications(realm => availableBeatmaps, beatmapsChanged); + beatmapSubscription = realm.RegisterForNotifications(r => r.All().Where(s => !s.DeletePending), beatmapsChanged); list.Items.BindTo(beatmapSets); beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo, true); From dda513dd08f4aad11b847fef0da0ef4e669f35b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 12:19:05 +0900 Subject: [PATCH 0256/5199] Change `PlaylistOverlay` to use `ILive` --- .../UserInterface/TestScenePlaylistOverlay.cs | 9 +-- osu.Game/Collections/BeatmapCollection.cs | 2 +- .../Collections/CollectionFilterDropdown.cs | 4 +- osu.Game/Overlays/Music/Playlist.cs | 17 ++--- osu.Game/Overlays/Music/PlaylistItem.cs | 66 ++++++++++--------- osu.Game/Overlays/Music/PlaylistOverlay.cs | 42 +++++++----- 6 files changed, 76 insertions(+), 64 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs index 7131c19471..1a3e38ddd7 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.Overlays.Music; using osu.Game.Tests.Resources; @@ -18,11 +19,11 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestScenePlaylistOverlay : OsuManualInputManagerTestScene { - private readonly BindableList beatmapSets = new BindableList(); + private readonly BindableList> beatmapSets = new BindableList>(); private PlaylistOverlay playlistOverlay; - private BeatmapSetInfo first; + private ILive first; [SetUp] public void Setup() => Schedule(() => @@ -45,7 +46,7 @@ namespace osu.Game.Tests.Visual.UserInterface for (int i = 0; i < 100; i++) { - beatmapSets.Add(TestResources.CreateTestBeatmapSetInfo()); + beatmapSets.Add(TestResources.CreateTestBeatmapSetInfo().ToLiveUnmanaged()); } first = beatmapSets.First(); @@ -60,7 +61,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("hold 1st item handle", () => { - var handle = this.ChildrenOfType.PlaylistItemHandle>().First(); + var handle = this.ChildrenOfType>.PlaylistItemHandle>().First(); InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre); InputManager.PressButton(MouseButton.Left); }); diff --git a/osu.Game/Collections/BeatmapCollection.cs b/osu.Game/Collections/BeatmapCollection.cs index 1a739f824f..7e4b15ecf9 100644 --- a/osu.Game/Collections/BeatmapCollection.cs +++ b/osu.Game/Collections/BeatmapCollection.cs @@ -25,7 +25,7 @@ namespace osu.Game.Collections /// /// The beatmaps contained by the collection. /// - public readonly BindableList Beatmaps = new BindableList(); + public readonly BindableList Beatmaps = new BindableList(); /// /// The date when this collection was last modified. diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index 77bda00107..c46ba8e06e 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -38,7 +38,7 @@ namespace osu.Game.Collections } private readonly IBindableList collections = new BindableList(); - private readonly IBindableList beatmaps = new BindableList(); + private readonly IBindableList beatmaps = new BindableList(); private readonly BindableList filters = new BindableList(); [Resolved(CanBeNull = true)] @@ -196,7 +196,7 @@ namespace osu.Game.Collections private IBindable beatmap { get; set; } [CanBeNull] - private readonly BindableList collectionBeatmaps; + private readonly BindableList collectionBeatmaps; [NotNull] private readonly Bindable collectionName; diff --git a/osu.Game/Overlays/Music/Playlist.cs b/osu.Game/Overlays/Music/Playlist.cs index 0b15a3a1bc..c86146ff25 100644 --- a/osu.Game/Overlays/Music/Playlist.cs +++ b/osu.Game/Overlays/Music/Playlist.cs @@ -7,16 +7,17 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics.Containers; using osuTK; namespace osu.Game.Overlays.Music { - public class Playlist : OsuRearrangeableListContainer + public class Playlist : OsuRearrangeableListContainer> { - public Action RequestSelection; + public Action> RequestSelection; - public readonly Bindable SelectedSet = new Bindable(); + public readonly Bindable> SelectedSet = new Bindable>(); public new MarginPadding Padding { @@ -26,23 +27,23 @@ namespace osu.Game.Overlays.Music public void Filter(FilterCriteria criteria) { - var items = (SearchContainer>)ListContainer; + var items = (SearchContainer>>)ListContainer; foreach (var item in items.OfType()) - item.InSelectedCollection = criteria.Collection?.Beatmaps.Any(b => item.Model.Equals(b.BeatmapSet)) ?? true; + item.InSelectedCollection = criteria.Collection?.Beatmaps.Any(b => item.Model.ID == b.BeatmapSet?.ID) ?? true; items.SearchTerm = criteria.SearchText; } - public BeatmapSetInfo FirstVisibleSet => Items.FirstOrDefault(i => ((PlaylistItem)ItemMap[i]).MatchingFilter); + public ILive FirstVisibleSet => Items.FirstOrDefault(i => ((PlaylistItem)ItemMap[i]).MatchingFilter); - protected override OsuRearrangeableListItem CreateOsuDrawable(BeatmapSetInfo item) => new PlaylistItem(item) + protected override OsuRearrangeableListItem> CreateOsuDrawable(ILive item) => new PlaylistItem(item) { SelectedSet = { BindTarget = SelectedSet }, RequestSelection = set => RequestSelection?.Invoke(set) }; - protected override FillFlowContainer> CreateListFillFlowContainer() => new SearchContainer> + protected override FillFlowContainer>> CreateListFillFlowContainer() => new SearchContainer>> { Spacing = new Vector2(0, 3), LayoutDuration = 200, diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index b31a03a64d..3f82580bfb 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -10,17 +10,18 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK.Graphics; namespace osu.Game.Overlays.Music { - public class PlaylistItem : OsuRearrangeableListItem, IFilterable + public class PlaylistItem : OsuRearrangeableListItem>, IFilterable { - public readonly Bindable SelectedSet = new Bindable(); + public readonly Bindable> SelectedSet = new Bindable>(); - public Action RequestSelection; + public Action> RequestSelection; private TextFlowContainer text; private ITextPart titlePart; @@ -28,7 +29,7 @@ namespace osu.Game.Overlays.Music [Resolved] private OsuColour colours { get; set; } - public PlaylistItem(BeatmapSetInfo item) + public PlaylistItem(ILive item) : base(item) { Padding = new MarginPadding { Left = 5 }; @@ -44,34 +45,37 @@ namespace osu.Game.Overlays.Music { base.LoadComplete(); - var metadata = Model.Metadata; - - var title = new RomanisableString(metadata.TitleUnicode, metadata.Title); - var artist = new RomanisableString(metadata.ArtistUnicode, metadata.Artist); - - titlePart = text.AddText(title, sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)); - titlePart.DrawablePartsRecreated += _ => updateSelectionState(true); - - text.AddText(@" "); // to separate the title from the artist. - text.AddText(artist, sprite => + Model.PerformRead(m => { - sprite.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); - sprite.Colour = colours.Gray9; - sprite.Padding = new MarginPadding { Top = 1 }; + var metadata = m.Metadata; + + var title = new RomanisableString(metadata.TitleUnicode, metadata.Title); + var artist = new RomanisableString(metadata.ArtistUnicode, metadata.Artist); + + titlePart = text.AddText(title, sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)); + titlePart.DrawablePartsRecreated += _ => updateSelectionState(true); + + text.AddText(@" "); // to separate the title from the artist. + text.AddText(artist, sprite => + { + sprite.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); + sprite.Colour = colours.Gray9; + sprite.Padding = new MarginPadding { Top = 1 }; + }); + + SelectedSet.BindValueChanged(set => + { + bool newSelected = set.NewValue?.Equals(Model) == true; + + if (newSelected == selected) + return; + + selected = newSelected; + updateSelectionState(false); + }); + + updateSelectionState(true); }); - - SelectedSet.BindValueChanged(set => - { - bool newSelected = set.NewValue?.Equals(Model) == true; - - if (newSelected == selected) - return; - - selected = newSelected; - updateSelectionState(false); - }); - - updateSelectionState(true); } private bool selected; @@ -109,7 +113,7 @@ namespace osu.Game.Overlays.Music } } - public IEnumerable FilterTerms => Model.Metadata.GetSearchableTerms(); + public IEnumerable FilterTerms => Model.PerformRead(m => m.Metadata.GetSearchableTerms()); private bool matchingFilter = true; diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index ffddf168ed..4b10bb779e 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -24,9 +24,9 @@ namespace osu.Game.Overlays.Music private const float transition_duration = 600; private const float playlist_height = 510; - public IBindableList BeatmapSets => beatmapSets; + public IBindableList> BeatmapSets => beatmapSets; - private readonly BindableList beatmapSets = new BindableList(); + private readonly BindableList> beatmapSets = new BindableList>(); private readonly Bindable beatmap = new Bindable(); @@ -85,13 +85,16 @@ namespace osu.Game.Overlays.Music filter.Search.OnCommit += (sender, newText) => { - BeatmapInfo toSelect = list.FirstVisibleSet?.Beatmaps.FirstOrDefault(); - - if (toSelect != null) + list.FirstVisibleSet.PerformRead(set => { - beatmap.Value = beatmaps.GetWorkingBeatmap(toSelect); - beatmap.Value.Track.Restart(); - } + BeatmapInfo toSelect = set.Beatmaps.FirstOrDefault(); + + if (toSelect != null) + { + beatmap.Value = beatmaps.GetWorkingBeatmap(toSelect); + beatmap.Value.Track.Restart(); + } + }); }; } @@ -104,7 +107,7 @@ namespace osu.Game.Overlays.Music beatmapSubscription = realm.RegisterForNotifications(r => r.All().Where(s => !s.DeletePending), beatmapsChanged); list.Items.BindTo(beatmapSets); - beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo, true); + beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo.ToLive(realm), true); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) @@ -113,12 +116,12 @@ namespace osu.Game.Overlays.Music { beatmapSets.Clear(); // must use AddRange to avoid RearrangeableList sort overhead per add op. - beatmapSets.AddRange(sender); + beatmapSets.AddRange(sender.ToLive(realm)); return; } foreach (int i in changes.InsertedIndices) - beatmapSets.Insert(i, sender[i]); + beatmapSets.Insert(i, sender[i].ToLive(realm)); foreach (int i in changes.DeletedIndices.OrderByDescending(i => i)) beatmapSets.RemoveAt(i); @@ -141,16 +144,19 @@ namespace osu.Game.Overlays.Music this.FadeOut(transition_duration); } - private void itemSelected(BeatmapSetInfo set) + private void itemSelected(ILive beatmapSet) { - if (set.Equals((beatmap.Value?.BeatmapSetInfo))) + beatmapSet.PerformRead(set => { - beatmap.Value?.Track.Seek(0); - return; - } + if (set.Equals((beatmap.Value?.BeatmapSetInfo))) + { + beatmap.Value?.Track.Seek(0); + return; + } - beatmap.Value = beatmaps.GetWorkingBeatmap(set.Beatmaps.First()); - beatmap.Value.Track.Restart(); + beatmap.Value = beatmaps.GetWorkingBeatmap(set.Beatmaps.First()); + beatmap.Value.Track.Restart(); + }); } protected override void Dispose(bool isDisposing) From d76822b6857dbad12737662352157d1bb4b6ff69 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Jan 2022 18:32:57 +0900 Subject: [PATCH 0257/5199] Avoid creating realm contexts or refetching when accessing `RealmLive` from the update thread --- osu.Game/Database/RealmLive.cs | 38 +++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index f06ba1eff9..c572d52cc6 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using osu.Framework.Development; using Realms; @@ -22,7 +23,9 @@ namespace osu.Game.Database /// /// The original live data used to create this instance. /// - private readonly T data; + private T data; + + private bool dataIsFromUpdateThread; private readonly RealmAccess realm; @@ -37,6 +40,7 @@ namespace osu.Game.Database this.realm = realm; ID = data.ID; + dataIsFromUpdateThread = ThreadSafety.IsUpdateThread; } /// @@ -51,7 +55,17 @@ namespace osu.Game.Database return; } - realm.Run(r => perform(retrieveFromID(r, ID))); + realm.Run(r => + { + if (ThreadSafety.IsUpdateThread) + { + ensureDataIsFromUpdateThread(); + perform(data); + return; + } + + perform(retrieveFromID(r, ID)); + }); } /// @@ -63,6 +77,12 @@ namespace osu.Game.Database if (!IsManaged) return perform(data); + if (ThreadSafety.IsUpdateThread) + { + ensureDataIsFromUpdateThread(); + return perform(data); + } + return realm.Run(r => { var returnData = perform(retrieveFromID(r, ID)); @@ -101,10 +121,22 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException($"Can't use {nameof(Value)} on managed objects from non-update threads"); - return realm.Realm.Find(ID); + ensureDataIsFromUpdateThread(); + return data; } } + private void ensureDataIsFromUpdateThread() + { + Debug.Assert(ThreadSafety.IsUpdateThread); + + if (dataIsFromUpdateThread) + return; + + dataIsFromUpdateThread = true; + data = retrieveFromID(realm.Realm, ID); + } + private T retrieveFromID(Realm realm, Guid id) { var found = realm.Find(ID); From 56b06f34f01818063f18cf5d7d1c952f328f7dc9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 02:10:14 +0900 Subject: [PATCH 0258/5199] Fix `RealmLive` not refetching if update thread context was closed at some point --- osu.Game/Database/RealmLive.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index c572d52cc6..d420c3b5c7 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -130,7 +130,7 @@ namespace osu.Game.Database { Debug.Assert(ThreadSafety.IsUpdateThread); - if (dataIsFromUpdateThread) + if (dataIsFromUpdateThread && !data.Realm.IsClosed) return; dataIsFromUpdateThread = true; From c7947b34890aa64297cc22139ec9f6b683c81492 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 12:42:24 +0900 Subject: [PATCH 0259/5199] Add statistics for `Live` usage --- osu.Game/Database/RealmLive.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index d420c3b5c7..76a17e860f 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using osu.Framework.Development; +using osu.Framework.Statistics; using Realms; #nullable enable @@ -14,7 +15,7 @@ namespace osu.Game.Database /// Provides a method of working with realm objects over longer application lifetimes. /// /// The underlying object type. - public class RealmLive : ILive where T : RealmObject, IHasGuidPrimaryKey + public class RealmLive : RealmLive, ILive where T : RealmObject, IHasGuidPrimaryKey { public Guid ID { get; } @@ -65,6 +66,7 @@ namespace osu.Game.Database } perform(retrieveFromID(r, ID)); + USAGE_ASYNC.Value++; }); } @@ -86,6 +88,7 @@ namespace osu.Game.Database return realm.Run(r => { var returnData = perform(retrieveFromID(r, ID)); + USAGE_ASYNC.Value++; if (returnData is RealmObjectBase realmObject && realmObject.IsManaged) throw new InvalidOperationException(@$"Managed realm objects should not exit the scope of {nameof(PerformRead)}."); @@ -108,6 +111,7 @@ namespace osu.Game.Database var transaction = t.Realm.BeginWrite(); perform(t); transaction.Commit(); + WRITES.Value++; }); } @@ -131,10 +135,14 @@ namespace osu.Game.Database Debug.Assert(ThreadSafety.IsUpdateThread); if (dataIsFromUpdateThread && !data.Realm.IsClosed) + { + USAGE_UPDATE_IMMEDIATE.Value++; return; + } dataIsFromUpdateThread = true; data = retrieveFromID(realm.Realm, ID); + USAGE_UPDATE_REFETCH.Value++; } private T retrieveFromID(Realm realm, Guid id) @@ -157,4 +165,12 @@ namespace osu.Game.Database public override string ToString() => PerformRead(i => i.ToString()); } + + public class RealmLive + { + protected static readonly GlobalStatistic WRITES = GlobalStatistics.Get(@"Realm", @"Live writes"); + protected static readonly GlobalStatistic USAGE_UPDATE_IMMEDIATE = GlobalStatistics.Get(@"Realm", @"Live update read (fast)"); + protected static readonly GlobalStatistic USAGE_UPDATE_REFETCH = GlobalStatistics.Get(@"Realm", @"Live update read (slow)"); + protected static readonly GlobalStatistic USAGE_ASYNC = GlobalStatistics.Get(@"Realm", @"Live async read"); + } } From 7ca73f7e6def6c7ae3447ba79fc0b475984c9b74 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 12:51:09 +0900 Subject: [PATCH 0260/5199] Don't auto-unblock realm when user has manually pressed unblock button --- .../Overlays/Settings/Sections/DebugSettings/MemorySettings.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index 3b94cae171..f26326a220 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -73,6 +73,9 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings void unblock() { + if (token == null) + return; + token?.Dispose(); token = null; From d37c3c463e5b82aaa7084e1e748ead6651eb2ba9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 13:29:12 +0900 Subject: [PATCH 0261/5199] Move statistics to static class --- osu.Game/Database/RealmLive.cs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index 76a17e860f..13b9bc2704 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -15,7 +15,7 @@ namespace osu.Game.Database /// Provides a method of working with realm objects over longer application lifetimes. /// /// The underlying object type. - public class RealmLive : RealmLive, ILive where T : RealmObject, IHasGuidPrimaryKey + public class RealmLive : ILive where T : RealmObject, IHasGuidPrimaryKey { public Guid ID { get; } @@ -66,7 +66,7 @@ namespace osu.Game.Database } perform(retrieveFromID(r, ID)); - USAGE_ASYNC.Value++; + RealmLiveStatistics.USAGE_ASYNC.Value++; }); } @@ -88,7 +88,7 @@ namespace osu.Game.Database return realm.Run(r => { var returnData = perform(retrieveFromID(r, ID)); - USAGE_ASYNC.Value++; + RealmLiveStatistics.USAGE_ASYNC.Value++; if (returnData is RealmObjectBase realmObject && realmObject.IsManaged) throw new InvalidOperationException(@$"Managed realm objects should not exit the scope of {nameof(PerformRead)}."); @@ -111,7 +111,7 @@ namespace osu.Game.Database var transaction = t.Realm.BeginWrite(); perform(t); transaction.Commit(); - WRITES.Value++; + RealmLiveStatistics.WRITES.Value++; }); } @@ -136,13 +136,13 @@ namespace osu.Game.Database if (dataIsFromUpdateThread && !data.Realm.IsClosed) { - USAGE_UPDATE_IMMEDIATE.Value++; + RealmLiveStatistics.USAGE_UPDATE_IMMEDIATE.Value++; return; } dataIsFromUpdateThread = true; data = retrieveFromID(realm.Realm, ID); - USAGE_UPDATE_REFETCH.Value++; + RealmLiveStatistics.USAGE_UPDATE_REFETCH.Value++; } private T retrieveFromID(Realm realm, Guid id) @@ -166,11 +166,11 @@ namespace osu.Game.Database public override string ToString() => PerformRead(i => i.ToString()); } - public class RealmLive + internal static class RealmLiveStatistics { - protected static readonly GlobalStatistic WRITES = GlobalStatistics.Get(@"Realm", @"Live writes"); - protected static readonly GlobalStatistic USAGE_UPDATE_IMMEDIATE = GlobalStatistics.Get(@"Realm", @"Live update read (fast)"); - protected static readonly GlobalStatistic USAGE_UPDATE_REFETCH = GlobalStatistics.Get(@"Realm", @"Live update read (slow)"); - protected static readonly GlobalStatistic USAGE_ASYNC = GlobalStatistics.Get(@"Realm", @"Live async read"); + public static readonly GlobalStatistic WRITES = GlobalStatistics.Get(@"Realm", @"Live writes"); + public static readonly GlobalStatistic USAGE_UPDATE_IMMEDIATE = GlobalStatistics.Get(@"Realm", @"Live update read (fast)"); + public static readonly GlobalStatistic USAGE_UPDATE_REFETCH = GlobalStatistics.Get(@"Realm", @"Live update read (slow)"); + public static readonly GlobalStatistic USAGE_ASYNC = GlobalStatistics.Get(@"Realm", @"Live async read"); } } From cd71ec0edd6576384418b45683ae3615dbdca669 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 13:37:33 +0900 Subject: [PATCH 0262/5199] Remove `ILive<>` interface (and use `abstract Live<>` instead) --- .../Database/BeatmapImporterTests.cs | 8 +++--- osu.Game.Tests/Database/RealmLiveTests.cs | 16 +++++------ ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 4 +-- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 8 +++--- .../Gameplay/TestSceneReplayDownloadButton.cs | 2 +- .../TestSceneDrawableRoomPlaylist.cs | 2 +- osu.Game/Beatmaps/BeatmapManager.cs | 14 +++++----- osu.Game/Database/IModelImporter.cs | 10 +++---- osu.Game/Database/IPostImports.cs | 4 +-- osu.Game/Database/LegacyModelImporter.cs | 2 +- osu.Game/Database/{ILive.cs => Live.cs} | 27 +++++++++++++------ osu.Game/Database/RealmLive.cs | 20 +++++--------- osu.Game/Database/RealmLiveUnmanaged.cs | 27 ++++++++----------- osu.Game/Database/RealmObjectExtensions.cs | 12 ++++----- osu.Game/OsuGame.cs | 4 +-- .../Overlays/Settings/Sections/SkinSection.cs | 14 +++++----- osu.Game/Scoring/ScoreManager.cs | 10 +++---- osu.Game/Skinning/Skin.cs | 2 +- osu.Game/Skinning/SkinManager.cs | 14 +++++----- osu.Game/Stores/RealmArchiveModelImporter.cs | 16 +++++------ 20 files changed, 108 insertions(+), 108 deletions(-) rename osu.Game/Database/{ILive.cs => Live.cs} (65%) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 69dd2d930a..2c7d0211a0 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Database using (var importer = new BeatmapModelManager(realm, storage)) using (new RulesetStore(realm, storage)) { - ILive? beatmapSet; + Live? beatmapSet; using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream())) beatmapSet = await importer.Import(reader); @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Database using (var importer = new BeatmapModelManager(realm, storage)) using (new RulesetStore(realm, storage)) { - ILive? beatmapSet; + Live? beatmapSet; using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream())) beatmapSet = await importer.Import(reader); @@ -144,7 +144,7 @@ namespace osu.Game.Tests.Database using (var importer = new BeatmapModelManager(realm, storage)) using (new RulesetStore(realm, storage)) { - ILive? imported; + Live? imported; using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream())) imported = await importer.Import(reader); @@ -219,7 +219,7 @@ namespace osu.Game.Tests.Database string? tempPath = TestResources.GetTestBeatmapForImport(); - ILive? importedSet; + Live? importedSet; using (var stream = File.OpenRead(tempPath)) { diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 2e3f708f79..3f81b36378 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -23,9 +23,9 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realm, _) => { - ILive beatmap = realm.Run(r => r.Write(_ => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realm)); + Live beatmap = realm.Run(r => r.Write(_ => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realm)); - ILive beatmap2 = realm.Run(r => r.All().First().ToLive(realm)); + Live beatmap2 = realm.Run(r => r.All().First().ToLive(realm)); Assert.AreEqual(beatmap, beatmap2); }); @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Database { var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()); - ILive? liveBeatmap = null; + Live? liveBeatmap = null; realm.Run(r => { @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realm, _) => { - ILive? liveBeatmap = null; + Live? liveBeatmap = null; Task.Factory.StartNew(() => { realm.Run(threadContext => @@ -129,7 +129,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realm, _) => { - ILive? liveBeatmap = null; + Live? liveBeatmap = null; Task.Factory.StartNew(() => { realm.Run(threadContext => @@ -170,7 +170,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realm, _) => { - ILive? liveBeatmap = null; + Live? liveBeatmap = null; Task.Factory.StartNew(() => { @@ -209,7 +209,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realm, _) => { - ILive? liveBeatmap = null; + Live? liveBeatmap = null; Task.Factory.StartNew(() => { realm.Run(threadContext => @@ -242,7 +242,7 @@ namespace osu.Game.Tests.Database realm.RegisterCustomSubscription(outerRealm => { outerRealm.All().QueryAsyncWithNotifications(gotChange); - ILive? liveBeatmap = null; + Live? liveBeatmap = null; Task.Factory.StartNew(() => { diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 95c15367aa..f9161816e7 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -164,7 +164,7 @@ namespace osu.Game.Tests.Online { public TaskCompletionSource AllowImport = new TaskCompletionSource(); - public ILive CurrentImport { get; private set; } + public Live CurrentImport { get; private set; } public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null) : base(storage, realm, rulesets, api, audioManager, resources, host, defaultBeatmap) @@ -186,7 +186,7 @@ namespace osu.Game.Tests.Online this.testBeatmapManager = testBeatmapManager; } - public override ILive Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public override Live Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { testBeatmapManager.AllowImport.Task.WaitSafely(); return (testBeatmapManager.CurrentImport = base.Import(item, archive, lowPriority, cancellationToken)); diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index 3f063264e0..9b0facd625 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -235,7 +235,7 @@ namespace osu.Game.Tests.Skins.IO #endregion - private void assertCorrectMetadata(ILive import1, string name, string creator, OsuGameBase osu) + private void assertCorrectMetadata(Live import1, string name, string creator, OsuGameBase osu) { import1.PerformRead(i => { @@ -250,7 +250,7 @@ namespace osu.Game.Tests.Skins.IO }); } - private void assertImportedBoth(ILive import1, ILive import2) + private void assertImportedBoth(Live import1, Live import2) { import1.PerformRead(i1 => import2.PerformRead(i2 => { @@ -260,7 +260,7 @@ namespace osu.Game.Tests.Skins.IO })); } - private void assertImportedOnce(ILive import1, ILive import2) + private void assertImportedOnce(Live import1, Live import2) { import1.PerformRead(i1 => import2.PerformRead(i2 => { @@ -334,7 +334,7 @@ namespace osu.Game.Tests.Skins.IO } } - private async Task> loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null) + private async Task> loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null) { var skinManager = osu.Dependencies.Get(); return await skinManager.Import(archive); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index a12171401a..8b7e1c4e58 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestScoreImportThenDelete() { - ILive imported = null; + Live imported = null; AddStep("create button without replay", () => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 92accb0cd1..5c8c90e166 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -153,7 +153,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestDownloadButtonHiddenWhenBeatmapExists() { var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; - ILive imported = null; + Live imported = null; Debug.Assert(beatmap.BeatmapSet != null); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index a1c1982f00..414b7cd12b 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -182,7 +182,7 @@ namespace osu.Game.Beatmaps /// /// The query. /// The first result for the provided query, or null if no results were found. - public ILive? QueryBeatmapSet(Expression> query) + public Live? QueryBeatmapSet(Expression> query) { return realm.Run(r => r.All().FirstOrDefault(query)?.ToLive(realm)); } @@ -279,22 +279,22 @@ namespace osu.Game.Beatmaps return beatmapModelManager.Import(tasks); } - public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) + public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) { return beatmapModelManager.Import(notification, tasks); } - public Task?> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task?> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) { return beatmapModelManager.Import(task, lowPriority, cancellationToken); } - public Task?> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task?> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) { return beatmapModelManager.Import(archive, lowPriority, cancellationToken); } - public ILive? Import(BeatmapSetInfo item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public Live? Import(BeatmapSetInfo item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { return beatmapModelManager.Import(item, archive, lowPriority, cancellationToken); } @@ -323,7 +323,7 @@ namespace osu.Game.Beatmaps return workingBeatmapCache.GetWorkingBeatmap(importedBeatmap); } - public WorkingBeatmap GetWorkingBeatmap(ILive? importedBeatmap) + public WorkingBeatmap GetWorkingBeatmap(Live? importedBeatmap) { WorkingBeatmap working = workingBeatmapCache.GetWorkingBeatmap(null); @@ -367,7 +367,7 @@ namespace osu.Game.Beatmaps #region Implementation of IPostImports - public Action>>? PostImport + public Action>>? PostImport { set => beatmapModelManager.PostImport = value; } diff --git a/osu.Game/Database/IModelImporter.cs b/osu.Game/Database/IModelImporter.cs index 3047a1d30a..90df13477e 100644 --- a/osu.Game/Database/IModelImporter.cs +++ b/osu.Game/Database/IModelImporter.cs @@ -16,9 +16,9 @@ namespace osu.Game.Database /// /// The model type. public interface IModelImporter : IPostNotifications, IPostImports, ICanAcceptFiles - where TModel : class + where TModel : class, IHasGuidPrimaryKey { - Task>> Import(ProgressNotification notification, params ImportTask[] tasks); + Task>> Import(ProgressNotification notification, params ImportTask[] tasks); /// /// Import one from the filesystem and delete the file on success. @@ -28,7 +28,7 @@ namespace osu.Game.Database /// Whether this is a low priority import. /// An optional cancellation token. /// The imported model, if successful. - Task?> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default); + Task?> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default); /// /// Silently import an item from an . @@ -36,7 +36,7 @@ namespace osu.Game.Database /// The archive to be imported. /// Whether this is a low priority import. /// An optional cancellation token. - Task?> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default); + Task?> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default); /// /// Silently import an item from a . @@ -45,7 +45,7 @@ namespace osu.Game.Database /// An optional archive to use for model population. /// Whether this is a low priority import. /// An optional cancellation token. - ILive? Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default); + Live? Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default); /// /// A user displayable name for the model type associated with this manager. diff --git a/osu.Game/Database/IPostImports.cs b/osu.Game/Database/IPostImports.cs index adb3a7108d..6f047098da 100644 --- a/osu.Game/Database/IPostImports.cs +++ b/osu.Game/Database/IPostImports.cs @@ -9,11 +9,11 @@ using System.Collections.Generic; namespace osu.Game.Database { public interface IPostImports - where TModel : class + where TModel : class, IHasGuidPrimaryKey { /// /// Fired when the user requests to view the resulting import. /// - public Action>>? PostImport { set; } + public Action>>? PostImport { set; } } } diff --git a/osu.Game/Database/LegacyModelImporter.cs b/osu.Game/Database/LegacyModelImporter.cs index dacb7327ea..d85fb5aab2 100644 --- a/osu.Game/Database/LegacyModelImporter.cs +++ b/osu.Game/Database/LegacyModelImporter.cs @@ -14,7 +14,7 @@ namespace osu.Game.Database /// A class which handles importing legacy user data of a single type from osu-stable. /// public abstract class LegacyModelImporter - where TModel : class + where TModel : class, IHasGuidPrimaryKey { /// /// The relative path from osu-stable's data directory to import items from. diff --git a/osu.Game/Database/ILive.cs b/osu.Game/Database/Live.cs similarity index 65% rename from osu.Game/Database/ILive.cs rename to osu.Game/Database/Live.cs index 3011754bc1..6256902e17 100644 --- a/osu.Game/Database/ILive.cs +++ b/osu.Game/Database/Live.cs @@ -3,39 +3,41 @@ using System; +#nullable enable + namespace osu.Game.Database { /// /// A wrapper to provide access to database backed classes in a thread-safe manner. /// /// The databased type. - public interface ILive : IEquatable> - where T : class // TODO: Add IHasGuidPrimaryKey once we don't need EF support any more. + public abstract class Live : IEquatable> + where T : class, IHasGuidPrimaryKey { - Guid ID { get; } + public Guid ID { get; } /// /// Perform a read operation on this live object. /// /// The action to perform. - void PerformRead(Action perform); + public abstract void PerformRead(Action perform); /// /// Perform a read operation on this live object. /// /// The action to perform. - TReturn PerformRead(Func perform); + public abstract TReturn PerformRead(Func perform); /// /// Perform a write operation on this live object. /// /// The action to perform. - void PerformWrite(Action perform); + public abstract void PerformWrite(Action perform); /// /// Whether this instance is tracking data which is managed by the database backing. /// - bool IsManaged { get; } + public abstract bool IsManaged { get; } /// /// Resolve the value of this instance on the update thread. @@ -43,6 +45,15 @@ namespace osu.Game.Database /// /// After resolving, the data should not be passed between threads. /// - T Value { get; } + public abstract T Value { get; } + + protected Live(Guid id) + { + ID = id; + } + + public bool Equals(Live? other) => ID == other?.ID; + + public override string ToString() => PerformRead(i => i.ToString()); } } diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index 13b9bc2704..186e801425 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -15,11 +15,9 @@ namespace osu.Game.Database /// Provides a method of working with realm objects over longer application lifetimes. /// /// The underlying object type. - public class RealmLive : ILive where T : RealmObject, IHasGuidPrimaryKey + public class RealmLive : Live where T : RealmObject, IHasGuidPrimaryKey { - public Guid ID { get; } - - public bool IsManaged => data.IsManaged; + public override bool IsManaged => data.IsManaged; /// /// The original live data used to create this instance. @@ -36,11 +34,11 @@ namespace osu.Game.Database /// The realm data. /// The realm factory the data was sourced from. May be null for an unmanaged object. public RealmLive(T data, RealmAccess realm) + : base(data.ID) { this.data = data; this.realm = realm; - ID = data.ID; dataIsFromUpdateThread = ThreadSafety.IsUpdateThread; } @@ -48,7 +46,7 @@ namespace osu.Game.Database /// Perform a read operation on this live object. /// /// The action to perform. - public void PerformRead(Action perform) + public override void PerformRead(Action perform) { if (!IsManaged) { @@ -74,7 +72,7 @@ namespace osu.Game.Database /// Perform a read operation on this live object. /// /// The action to perform. - public TReturn PerformRead(Func perform) + public override TReturn PerformRead(Func perform) { if (!IsManaged) return perform(data); @@ -101,7 +99,7 @@ namespace osu.Game.Database /// Perform a write operation on this live object. /// /// The action to perform. - public void PerformWrite(Action perform) + public override void PerformWrite(Action perform) { if (!IsManaged) throw new InvalidOperationException(@"Can't perform writes on a non-managed underlying value"); @@ -115,7 +113,7 @@ namespace osu.Game.Database }); } - public T Value + public override T Value { get { @@ -160,10 +158,6 @@ namespace osu.Game.Database return found; } - - public bool Equals(ILive? other) => ID == other?.ID; - - public override string ToString() => PerformRead(i => i.ToString()); } internal static class RealmLiveStatistics diff --git a/osu.Game/Database/RealmLiveUnmanaged.cs b/osu.Game/Database/RealmLiveUnmanaged.cs index 97f2faa656..1080f3b8c7 100644 --- a/osu.Game/Database/RealmLiveUnmanaged.cs +++ b/osu.Game/Database/RealmLiveUnmanaged.cs @@ -13,13 +13,19 @@ namespace osu.Game.Database /// Usually used for testing purposes where the instance is never required to be managed. /// /// The underlying object type. - public class RealmLiveUnmanaged : ILive where T : RealmObjectBase, IHasGuidPrimaryKey + public class RealmLiveUnmanaged : Live where T : RealmObjectBase, IHasGuidPrimaryKey { + /// + /// The original live data used to create this instance. + /// + public override T Value { get; } + /// /// Construct a new instance of live realm data. /// /// The realm data. public RealmLiveUnmanaged(T data) + : base(data.ID) { if (data.IsManaged) throw new InvalidOperationException($"Cannot use {nameof(RealmLiveUnmanaged)} with managed instances"); @@ -27,23 +33,12 @@ namespace osu.Game.Database Value = data; } - public bool Equals(ILive? other) => ID == other?.ID; + public override void PerformRead(Action perform) => perform(Value); - public override string ToString() => Value.ToString(); + public override TReturn PerformRead(Func perform) => perform(Value); - public Guid ID => Value.ID; + public override void PerformWrite(Action perform) => throw new InvalidOperationException(@"Can't perform writes on a non-managed underlying value"); - public void PerformRead(Action perform) => perform(Value); - - public TReturn PerformRead(Func perform) => perform(Value); - - public void PerformWrite(Action perform) => throw new InvalidOperationException(@"Can't perform writes on a non-managed underlying value"); - - public bool IsManaged => false; - - /// - /// The original live data used to create this instance. - /// - public T Value { get; } + public override bool IsManaged => false; } } diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index d4f8978ac5..dba8633f53 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -204,25 +204,25 @@ namespace osu.Game.Database private static void copyChangesToRealm(T source, T destination) where T : RealmObjectBase => write_mapper.Map(source, destination); - public static List> ToLiveUnmanaged(this IEnumerable realmList) + public static List> ToLiveUnmanaged(this IEnumerable realmList) where T : RealmObject, IHasGuidPrimaryKey { - return realmList.Select(l => new RealmLiveUnmanaged(l)).Cast>().ToList(); + return realmList.Select(l => new RealmLiveUnmanaged(l)).Cast>().ToList(); } - public static ILive ToLiveUnmanaged(this T realmObject) + public static Live ToLiveUnmanaged(this T realmObject) where T : RealmObject, IHasGuidPrimaryKey { return new RealmLiveUnmanaged(realmObject); } - public static List> ToLive(this IEnumerable realmList, RealmAccess realm) + public static List> ToLive(this IEnumerable realmList, RealmAccess realm) where T : RealmObject, IHasGuidPrimaryKey { - return realmList.Select(l => new RealmLive(l, realm)).Cast>().ToList(); + return realmList.Select(l => new RealmLive(l, realm)).Cast>().ToList(); } - public static ILive ToLive(this T realmObject, RealmAccess realm) + public static Live ToLive(this T realmObject, RealmAccess realm) where T : RealmObject, IHasGuidPrimaryKey { return new RealmLive(realmObject, realm); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5b3abc54d3..c2e1b25d94 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -249,7 +249,7 @@ namespace osu.Game SkinManager.CurrentSkinInfo.ValueChanged += skin => configSkin.Value = skin.NewValue.ID.ToString(); configSkin.ValueChanged += skinId => { - ILive skinInfo = null; + Live skinInfo = null; if (Guid.TryParse(skinId.NewValue, out var guid)) skinInfo = SkinManager.Query(s => s.ID == guid); @@ -439,7 +439,7 @@ namespace osu.Game /// public void PresentBeatmap(IBeatmapSetInfo beatmap, Predicate difficultyCriteria = null) { - ILive databasedSet = null; + Live databasedSet = null; if (beatmap.OnlineID > 0) databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineID == beatmap.OnlineID); diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 8ab296c0a8..0846c023c1 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -32,16 +32,16 @@ namespace osu.Game.Overlays.Settings.Sections Icon = FontAwesome.Solid.PaintBrush }; - private readonly Bindable> dropdownBindable = new Bindable> { Default = DefaultSkin.CreateInfo().ToLiveUnmanaged() }; + private readonly Bindable> dropdownBindable = new Bindable> { Default = DefaultSkin.CreateInfo().ToLiveUnmanaged() }; private readonly Bindable configBindable = new Bindable(); - private static readonly ILive random_skin_info = new SkinInfo + private static readonly Live random_skin_info = new SkinInfo { ID = SkinInfo.RANDOM_SKIN, Name = "", }.ToLiveUnmanaged(); - private List> skinItems; + private List> skinItems; [Resolved] private SkinManager skins { get; set; } @@ -118,7 +118,7 @@ namespace osu.Game.Overlays.Settings.Sections private void updateSelectedSkinFromConfig() { - ILive skin = null; + Live skin = null; if (Guid.TryParse(configBindable.Value, out var configId)) skin = skinDropdown.Items.FirstOrDefault(s => s.ID == configId); @@ -144,13 +144,13 @@ namespace osu.Game.Overlays.Settings.Sections realmSubscription?.Dispose(); } - private class SkinSettingsDropdown : SettingsDropdown> + private class SkinSettingsDropdown : SettingsDropdown> { - protected override OsuDropdown> CreateDropdown() => new SkinDropdownControl(); + protected override OsuDropdown> CreateDropdown() => new SkinDropdownControl(); private class SkinDropdownControl : DropdownControl { - protected override LocalisableString GenerateItemText(ILive item) => item.ToString(); + protected override LocalisableString GenerateItemText(Live item) => item.ToString(); } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 3a842a048a..8f665224ee 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -293,22 +293,22 @@ namespace osu.Game.Scoring public IEnumerable HandledExtensions => scoreModelManager.HandledExtensions; - public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) + public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) { return scoreModelManager.Import(notification, tasks); } - public Task> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) { return scoreModelManager.Import(task, lowPriority, cancellationToken); } - public Task> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) { return scoreModelManager.Import(archive, lowPriority, cancellationToken); } - public ILive Import(ScoreInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public Live Import(ScoreInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { return scoreModelManager.Import(item, archive, lowPriority, cancellationToken); } @@ -322,7 +322,7 @@ namespace osu.Game.Scoring #region Implementation of IPresentImports - public Action>> PostImport + public Action>> PostImport { set => scoreModelManager.PostImport = value; } diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 3685a26e26..931bdfed48 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -24,7 +24,7 @@ namespace osu.Game.Skinning { public abstract class Skin : IDisposable, ISkin { - public readonly ILive SkinInfo; + public readonly Live SkinInfo; private readonly IStorageResourceProvider resources; public SkinConfiguration Configuration { get; set; } diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 47c7bc060a..06bd0abc9f 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -47,7 +47,7 @@ namespace osu.Game.Skinning public readonly Bindable CurrentSkin = new Bindable(); - public readonly Bindable> CurrentSkinInfo = new Bindable>(Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged()) + public readonly Bindable> CurrentSkinInfo = new Bindable>(Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged()) { Default = Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged() }; @@ -176,7 +176,7 @@ namespace osu.Game.Skinning /// /// The query. /// The first result for the provided query, or null if no results were found. - public ILive Query(Expression> query) + public Live Query(Expression> query) { return realm.Run(r => r.All().FirstOrDefault(query)?.ToLive(realm)); } @@ -245,7 +245,7 @@ namespace osu.Game.Skinning set => skinModelManager.PostNotification = value; } - public Action>> PostImport + public Action>> PostImport { set => skinModelManager.PostImport = value; } @@ -262,22 +262,22 @@ namespace osu.Game.Skinning public IEnumerable HandledExtensions => skinModelManager.HandledExtensions; - public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) + public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) { return skinModelManager.Import(notification, tasks); } - public Task> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) { return skinModelManager.Import(task, lowPriority, cancellationToken); } - public Task> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) { return skinModelManager.Import(archive, lowPriority, cancellationToken); } - public ILive Import(SkinInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public Live Import(SkinInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { return skinModelManager.Import(item, archive, lowPriority, cancellationToken); } diff --git a/osu.Game/Stores/RealmArchiveModelImporter.cs b/osu.Game/Stores/RealmArchiveModelImporter.cs index 43c1c7c888..3011bc0320 100644 --- a/osu.Game/Stores/RealmArchiveModelImporter.cs +++ b/osu.Game/Stores/RealmArchiveModelImporter.cs @@ -64,7 +64,7 @@ namespace osu.Game.Stores /// /// Fired when the user requests to view the resulting import. /// - public Action>>? PostImport { get; set; } + public Action>>? PostImport { get; set; } /// /// Set an endpoint for notifications to be posted to. @@ -104,7 +104,7 @@ namespace osu.Game.Stores return Import(notification, tasks); } - public async Task>> Import(ProgressNotification notification, params ImportTask[] tasks) + public async Task>> Import(ProgressNotification notification, params ImportTask[] tasks) { if (tasks.Length == 0) { @@ -118,7 +118,7 @@ namespace osu.Game.Stores int current = 0; - var imported = new List>(); + var imported = new List>(); bool isLowPriorityImport = tasks.Length > low_priority_import_batch_size; @@ -196,11 +196,11 @@ namespace osu.Game.Stores /// Whether this is a low priority import. /// An optional cancellation token. /// The imported model, if successful. - public async Task?> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) + public async Task?> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); - ILive? import; + Live? import; using (ArchiveReader reader = task.GetReader()) import = await Import(reader, lowPriority, cancellationToken).ConfigureAwait(false); @@ -227,7 +227,7 @@ namespace osu.Game.Stores /// The archive to be imported. /// Whether this is a low priority import. /// An optional cancellation token. - public async Task?> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) + public async Task?> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -320,7 +320,7 @@ namespace osu.Game.Stores /// An optional archive to use for model population. /// Whether this is a low priority import. /// An optional cancellation token. - public virtual ILive? Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public virtual Live? Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { return Realm.Run(realm => { @@ -416,7 +416,7 @@ namespace osu.Game.Stores throw; } - return (ILive?)item.ToLive(Realm); + return (Live?)item.ToLive(Realm); }); } From 064468faada4ba6af341296f634978e774bcec03 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jan 2022 11:31:05 +0300 Subject: [PATCH 0263/5199] Refactor editor saving test scene for scalability --- .../Visual/Editing/TestSceneEditorSaving.cs | 152 ++++++++++-------- osu.Game/Beatmaps/BeatmapInfo.cs | 2 +- .../Tests/Visual/EditorSavingTestScene.cs | 67 ++++++++ 3 files changed, 154 insertions(+), 67 deletions(-) create mode 100644 osu.Game/Tests/Visual/EditorSavingTestScene.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index 96f815621c..58daab1ce2 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -4,97 +4,117 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Input; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Setup; -using osu.Game.Screens.Menu; -using osu.Game.Screens.Select; +using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK.Input; namespace osu.Game.Tests.Visual.Editing { - public class TestSceneEditorSaving : OsuGameTestScene + public class TestSceneEditorSaving : EditorSavingTestScene { - private Editor editor => Game.ChildrenOfType().FirstOrDefault(); - - private EditorBeatmap editorBeatmap => (EditorBeatmap)editor.Dependencies.Get(typeof(EditorBeatmap)); - - /// - /// Tests the general expected flow of creating a new beatmap, saving it, then loading it back from song select. - /// [Test] - public void TestNewBeatmapSaveThenLoad() + public void TestMetadata() { - AddStep("set default beatmap", () => Game.Beatmap.SetDefault()); - - PushAndConfirm(() => new EditorLoader()); - - AddUntilStep("wait for editor load", () => editor?.IsLoaded == true); - - AddUntilStep("wait for metadata screen load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); - - // We intentionally switch away from the metadata screen, else there is a feedback loop with the textbox handling which causes metadata changes below to get overwritten. - - AddStep("Enter compose mode", () => InputManager.Key(Key.F1)); - AddUntilStep("Wait for compose mode load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); - - AddStep("Set beat divisor", () => editor.Dependencies.Get().Value = 16); - AddStep("Set overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty = 7); AddStep("Set artist and title", () => { - editorBeatmap.BeatmapInfo.Metadata.Artist = "artist"; - editorBeatmap.BeatmapInfo.Metadata.Title = "title"; + EditorBeatmap.BeatmapInfo.Metadata.Artist = "artist"; + EditorBeatmap.BeatmapInfo.Metadata.Title = "title"; }); - AddStep("Set author", () => editorBeatmap.BeatmapInfo.Metadata.Author.Username = "author"); - AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName = "difficulty"); + AddStep("Set author", () => EditorBeatmap.BeatmapInfo.Metadata.Author.Username = "author"); + AddStep("Set difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = "difficulty"); - AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint())); + SaveEditor(); + AddAssert("Beatmap has correct metadata", () => EditorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && EditorBeatmap.BeatmapInfo.Metadata.Title == "title"); + AddAssert("Beatmap has correct author", () => EditorBeatmap.BeatmapInfo.Metadata.Author.Username == "author"); + AddAssert("Beatmap has correct difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName == "difficulty"); + AddAssert("Beatmap has correct .osu file path", () => EditorBeatmap.BeatmapInfo.Path == "artist - title (author) [difficulty].osu"); + + ReloadEditorToSameBeatmap(); + + AddAssert("Beatmap still has correct metadata", () => EditorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && EditorBeatmap.BeatmapInfo.Metadata.Title == "title"); + AddAssert("Beatmap still has correct author", () => EditorBeatmap.BeatmapInfo.Metadata.Author.Username == "author"); + AddAssert("Beatmap still has correct difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName == "difficulty"); + AddAssert("Beatmap still has correct .osu file path", () => EditorBeatmap.BeatmapInfo.Path == "artist - title (author) [difficulty].osu"); + } + + [Test] + public void TestConfiguration() + { + double originalTimelineZoom = 0; + double changedTimelineZoom = 0; + + AddStep("Set beat divisor", () => Editor.Dependencies.Get().Value = 16); + AddStep("Set timeline zoom", () => + { + originalTimelineZoom = EditorBeatmap.BeatmapInfo.TimelineZoom; + + var timeline = Editor.ChildrenOfType().Single(); + InputManager.MoveMouseTo(timeline); + InputManager.PressKey(Key.AltLeft); + InputManager.ScrollVerticalBy(15f); + InputManager.ReleaseKey(Key.AltLeft); + }); + + AddAssert("Ensure timeline zoom changed", () => + { + changedTimelineZoom = EditorBeatmap.BeatmapInfo.TimelineZoom; + return !Precision.AlmostEquals(changedTimelineZoom, originalTimelineZoom); + }); + + SaveEditor(); + + AddAssert("Beatmap has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor == 16); + AddAssert("Beatmap has correct timeline zoom", () => EditorBeatmap.BeatmapInfo.TimelineZoom == changedTimelineZoom); + + ReloadEditorToSameBeatmap(); + + AddAssert("Beatmap still has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor == 16); + AddAssert("Beatmap still has correct timeline zoom", () => EditorBeatmap.BeatmapInfo.TimelineZoom == changedTimelineZoom); + } + + [Test] + public void TestDifficulty() + { + AddStep("Set overall difficulty", () => EditorBeatmap.Difficulty.OverallDifficulty = 7); + + SaveEditor(); + + AddAssert("Beatmap has correct overall difficulty", () => EditorBeatmap.Difficulty.OverallDifficulty == 7); + + ReloadEditorToSameBeatmap(); + + AddAssert("Beatmap still has correct overall difficulty", () => EditorBeatmap.Difficulty.OverallDifficulty == 7); + } + + [Test] + public void TestHitObjectPlacement() + { + AddStep("Add timing point", () => EditorBeatmap.ControlPointInfo.Add(500, new TimingControlPoint())); AddStep("Change to placement mode", () => InputManager.Key(Key.Number2)); AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre)); AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left)); - checkMutations(); + SaveEditor(); + + AddAssert("Beatmap has correct timing point", () => EditorBeatmap.ControlPointInfo.TimingPoints.Single().Time == 500); // After placement these must be non-default as defaults are read-only. AddAssert("Placed object has non-default control points", () => - editorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT && - editorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT); + EditorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT && + EditorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT); - AddStep("Save", () => InputManager.Keys(PlatformAction.Save)); + ReloadEditorToSameBeatmap(); - checkMutations(); - AddAssert("Beatmap has correct .osu file path", () => editorBeatmap.BeatmapInfo.Path == "artist - title (author) [difficulty].osu"); + AddAssert("Beatmap still has correct timing point", () => EditorBeatmap.ControlPointInfo.TimingPoints.Single().Time == 500); - AddStep("Exit", () => InputManager.Key(Key.Escape)); - - AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu); - - Screens.Select.SongSelect songSelect = null; - - PushAndConfirm(() => songSelect = new PlaySongSelect()); - AddUntilStep("wait for carousel load", () => songSelect.BeatmapSetsLoaded); - - AddUntilStep("Wait for beatmap selected", () => !Game.Beatmap.IsDefault); - AddStep("Open options", () => InputManager.Key(Key.F3)); - AddStep("Enter editor", () => InputManager.Key(Key.Number5)); - - AddUntilStep("Wait for editor load", () => editor != null); - - checkMutations(); - } - - private void checkMutations() - { - AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1); - AddAssert("Beatmap has correct beat divisor", () => editorBeatmap.BeatmapInfo.BeatDivisor == 16); - AddAssert("Beatmap has correct overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty == 7); - AddAssert("Beatmap has correct metadata", () => editorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && editorBeatmap.BeatmapInfo.Metadata.Title == "title"); - AddAssert("Beatmap has correct author", () => editorBeatmap.BeatmapInfo.Metadata.Author.Username == "author"); - AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName == "difficulty"); + // After placement these must be non-default as defaults are read-only. + AddAssert("Placed object still has non-default control points", () => + EditorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT && + EditorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT); } } } diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 96254295a6..4e1a34ddbf 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -111,7 +111,7 @@ namespace osu.Game.Beatmaps public int GridSize { get; set; } - public double TimelineZoom { get; set; } + public double TimelineZoom { get; set; } = 1.0; [Ignored] public CountdownType Countdown { get; set; } = CountdownType.Normal; diff --git a/osu.Game/Tests/Visual/EditorSavingTestScene.cs b/osu.Game/Tests/Visual/EditorSavingTestScene.cs new file mode 100644 index 0000000000..72b5d076a5 --- /dev/null +++ b/osu.Game/Tests/Visual/EditorSavingTestScene.cs @@ -0,0 +1,67 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Input; +using osu.Framework.Testing; +using osu.Game.Rulesets.Edit; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Setup; +using osu.Game.Screens.Menu; +using osu.Game.Screens.Select; +using osuTK.Input; + +namespace osu.Game.Tests.Visual +{ + /// + /// Tests the general expected flow of creating a new beatmap, saving it, then loading it back from song select. + /// + public class EditorSavingTestScene : OsuGameTestScene + { + protected Editor Editor => Game.ChildrenOfType().FirstOrDefault(); + + protected EditorBeatmap EditorBeatmap => (EditorBeatmap)Editor.Dependencies.Get(typeof(EditorBeatmap)); + + [SetUpSteps] + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("set default beatmap", () => Game.Beatmap.SetDefault()); + + PushAndConfirm(() => new EditorLoader()); + + AddUntilStep("wait for editor load", () => Editor?.IsLoaded == true); + + AddUntilStep("wait for metadata screen load", () => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + + // We intentionally switch away from the metadata screen, else there is a feedback loop with the textbox handling which causes metadata changes below to get overwritten. + + AddStep("Enter compose mode", () => InputManager.Key(Key.F1)); + AddUntilStep("Wait for compose mode load", () => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + } + + protected void SaveEditor() + { + AddStep("Save", () => InputManager.Keys(PlatformAction.Save)); + } + + protected void ReloadEditorToSameBeatmap() + { + AddStep("Exit", () => InputManager.Key(Key.Escape)); + + AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu); + + SongSelect songSelect = null; + + PushAndConfirm(() => songSelect = new PlaySongSelect()); + AddUntilStep("wait for carousel load", () => songSelect.BeatmapSetsLoaded); + + AddUntilStep("Wait for beatmap selected", () => !Game.Beatmap.IsDefault); + AddStep("Open options", () => InputManager.Key(Key.F3)); + AddStep("Enter editor", () => InputManager.Key(Key.Number5)); + + AddUntilStep("Wait for editor load", () => Editor != null); + } + } +} From de0a7d8501c5c3853755c49cf289a6be666c77a9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jan 2022 12:29:41 +0300 Subject: [PATCH 0264/5199] Migrate taiko editor saving test scene to `EditorSavingTestScene` --- .../Editor/TestSceneEditorSaving.cs | 91 ------------------- .../Editor/TestSceneTaikoEditorSaving.cs | 38 ++++++++ 2 files changed, 38 insertions(+), 91 deletions(-) delete mode 100644 osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs create mode 100644 osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs deleted file mode 100644 index 42ab84714a..0000000000 --- a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs +++ /dev/null @@ -1,91 +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.Linq; -using NUnit.Framework; -using osu.Framework.Input; -using osu.Framework.Testing; -using osu.Framework.Utils; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Taiko.Beatmaps; -using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Setup; -using osu.Game.Screens.Menu; -using osu.Game.Screens.Select; -using osu.Game.Tests.Visual; -using osuTK.Input; - -namespace osu.Game.Rulesets.Taiko.Tests.Editor -{ - public class TestSceneEditorSaving : OsuGameTestScene - { - private Screens.Edit.Editor editor => Game.ChildrenOfType().FirstOrDefault(); - - private EditorBeatmap editorBeatmap => (EditorBeatmap)editor.Dependencies.Get(typeof(EditorBeatmap)); - - /// - /// Tests the general expected flow of creating a new beatmap, saving it, then loading it back from song select. - /// Emphasis is placed on , since taiko has special handling for it to keep compatibility with stable. - /// - [Test] - public void TestNewBeatmapSaveThenLoad() - { - AddStep("set default beatmap", () => Game.Beatmap.SetDefault()); - AddStep("set taiko ruleset", () => Ruleset.Value = new TaikoRuleset().RulesetInfo); - - PushAndConfirm(() => new EditorLoader()); - - AddUntilStep("wait for editor load", () => editor?.IsLoaded == true); - - AddUntilStep("wait for metadata screen load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); - - // We intentionally switch away from the metadata screen, else there is a feedback loop with the textbox handling which causes metadata changes below to get overwritten. - - AddStep("Enter compose mode", () => InputManager.Key(Key.F1)); - AddUntilStep("Wait for compose mode load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); - - AddStep("Set slider multiplier", () => editorBeatmap.Difficulty.SliderMultiplier = 2); - AddStep("Set artist and title", () => - { - editorBeatmap.BeatmapInfo.Metadata.Artist = "artist"; - editorBeatmap.BeatmapInfo.Metadata.Title = "title"; - }); - AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName = "difficulty"); - - checkMutations(); - - AddStep("Save", () => InputManager.Keys(PlatformAction.Save)); - - checkMutations(); - - AddStep("Exit", () => InputManager.Key(Key.Escape)); - - AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu); - - PushAndConfirm(() => new PlaySongSelect()); - - AddUntilStep("Wait for beatmap selected", () => !Game.Beatmap.IsDefault); - AddStep("Open options", () => InputManager.Key(Key.F3)); - AddStep("Enter editor", () => InputManager.Key(Key.Number5)); - - AddUntilStep("Wait for editor load", () => editor != null); - - checkMutations(); - } - - private void checkMutations() - { - AddAssert("Beatmap has correct slider multiplier", () => - { - // we can only assert value correctness on TaikoMultiplierAppliedDifficulty, because that is the final difficulty converted taiko beatmaps use. - // therefore, ensure that we have that difficulty type by calling .CopyFrom(), which is a no-op if the type is already correct. - var taikoDifficulty = new TaikoBeatmapConverter.TaikoMultiplierAppliedDifficulty(); - taikoDifficulty.CopyFrom(editorBeatmap.Difficulty); - return Precision.AlmostEquals(taikoDifficulty.SliderMultiplier, 2); - }); - AddAssert("Beatmap has correct metadata", () => editorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && editorBeatmap.BeatmapInfo.Metadata.Title == "title"); - AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName == "difficulty"); - } - } -} diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs new file mode 100644 index 0000000000..33c2ba532e --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs @@ -0,0 +1,38 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Utils; +using osu.Game.Rulesets.Taiko.Beatmaps; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests.Editor +{ + public class TestSceneTaikoEditorSaving : EditorSavingTestScene + { + protected override Ruleset CreateRuleset() => new TaikoRuleset(); + + [Test] + public void TestTaikoSliderMultiplier() + { + AddStep("Set slider multiplier", () => EditorBeatmap.Difficulty.SliderMultiplier = 2); + + SaveEditor(); + + AddAssert("Beatmap has correct slider multiplier", assertTaikoSliderMulitplier); + + ReloadEditorToSameBeatmap(); + + AddAssert("Beatmap still has correct slider multiplier", assertTaikoSliderMulitplier); + + bool assertTaikoSliderMulitplier() + { + // we can only assert value correctness on TaikoMultiplierAppliedDifficulty, because that is the final difficulty converted taiko beatmaps use. + // therefore, ensure that we have that difficulty type by calling .CopyFrom(), which is a no-op if the type is already correct. + var taikoDifficulty = new TaikoBeatmapConverter.TaikoMultiplierAppliedDifficulty(); + taikoDifficulty.CopyFrom(EditorBeatmap.Difficulty); + return Precision.AlmostEquals(taikoDifficulty.SliderMultiplier, 2); + } + } + } +} From 3491b77c8cc95ce752a25434e00f130227dad182 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 14:25:55 +0900 Subject: [PATCH 0265/5199] Fix `ScoreInfo.RealmUser` not getting deep cloned correctly I'm still not at all happy with the play-to-results flow (with multiple clones), but this will have to do for now. --- osu.Game.Tests/NonVisual/ScoreInfoTest.cs | 4 ++++ osu.Game/Scoring/ScoreInfo.cs | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/osu.Game.Tests/NonVisual/ScoreInfoTest.cs b/osu.Game.Tests/NonVisual/ScoreInfoTest.cs index e0acc6d8db..836c978ba2 100644 --- a/osu.Game.Tests/NonVisual/ScoreInfoTest.cs +++ b/osu.Game.Tests/NonVisual/ScoreInfoTest.cs @@ -26,12 +26,16 @@ namespace osu.Game.Tests.NonVisual score.Statistics[HitResult.Good]++; score.Rank = ScoreRank.X; + score.RealmUser.Username = "test"; Assert.That(scoreCopy.Statistics[HitResult.Good], Is.EqualTo(10)); Assert.That(score.Statistics[HitResult.Good], Is.EqualTo(11)); Assert.That(scoreCopy.Rank, Is.EqualTo(ScoreRank.B)); Assert.That(score.Rank, Is.EqualTo(ScoreRank.X)); + + Assert.That(scoreCopy.RealmUser.Username, Is.EqualTo("test")); + Assert.That(score.Rank, Is.Empty); } [Test] diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index a28e16450f..4de1d580dc 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -133,6 +133,11 @@ namespace osu.Game.Scoring var clone = (ScoreInfo)this.Detach().MemberwiseClone(); clone.Statistics = new Dictionary(clone.Statistics); + clone.RealmUser = new RealmUser + { + OnlineID = RealmUser.OnlineID, + Username = RealmUser.Username, + }; return clone; } From 9532454e2a0d691652f40b7e15c3bd3503d9c9b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 15:12:01 +0900 Subject: [PATCH 0266/5199] Remove `ILive` remnants --- .../UserInterface/TestScenePlaylistOverlay.cs | 6 +++--- osu.Game/Overlays/Music/Playlist.cs | 14 +++++++------- osu.Game/Overlays/Music/PlaylistItem.cs | 8 ++++---- osu.Game/Overlays/Music/PlaylistOverlay.cs | 6 +++--- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs index 1a3e38ddd7..09e5bc849e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs @@ -19,11 +19,11 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestScenePlaylistOverlay : OsuManualInputManagerTestScene { - private readonly BindableList> beatmapSets = new BindableList>(); + private readonly BindableList> beatmapSets = new BindableList>(); private PlaylistOverlay playlistOverlay; - private ILive first; + private Live first; [SetUp] public void Setup() => Schedule(() => @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("hold 1st item handle", () => { - var handle = this.ChildrenOfType>.PlaylistItemHandle>().First(); + var handle = this.ChildrenOfType>.PlaylistItemHandle>().First(); InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre); InputManager.PressButton(MouseButton.Left); }); diff --git a/osu.Game/Overlays/Music/Playlist.cs b/osu.Game/Overlays/Music/Playlist.cs index c86146ff25..24d867141c 100644 --- a/osu.Game/Overlays/Music/Playlist.cs +++ b/osu.Game/Overlays/Music/Playlist.cs @@ -13,11 +13,11 @@ using osuTK; namespace osu.Game.Overlays.Music { - public class Playlist : OsuRearrangeableListContainer> + public class Playlist : OsuRearrangeableListContainer> { - public Action> RequestSelection; + public Action> RequestSelection; - public readonly Bindable> SelectedSet = new Bindable>(); + public readonly Bindable> SelectedSet = new Bindable>(); public new MarginPadding Padding { @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Music public void Filter(FilterCriteria criteria) { - var items = (SearchContainer>>)ListContainer; + var items = (SearchContainer>>)ListContainer; foreach (var item in items.OfType()) item.InSelectedCollection = criteria.Collection?.Beatmaps.Any(b => item.Model.ID == b.BeatmapSet?.ID) ?? true; @@ -35,15 +35,15 @@ namespace osu.Game.Overlays.Music items.SearchTerm = criteria.SearchText; } - public ILive FirstVisibleSet => Items.FirstOrDefault(i => ((PlaylistItem)ItemMap[i]).MatchingFilter); + public Live FirstVisibleSet => Items.FirstOrDefault(i => ((PlaylistItem)ItemMap[i]).MatchingFilter); - protected override OsuRearrangeableListItem> CreateOsuDrawable(ILive item) => new PlaylistItem(item) + protected override OsuRearrangeableListItem> CreateOsuDrawable(Live item) => new PlaylistItem(item) { SelectedSet = { BindTarget = SelectedSet }, RequestSelection = set => RequestSelection?.Invoke(set) }; - protected override FillFlowContainer>> CreateListFillFlowContainer() => new SearchContainer>> + protected override FillFlowContainer>> CreateListFillFlowContainer() => new SearchContainer>> { Spacing = new Vector2(0, 3), LayoutDuration = 200, diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 3f82580bfb..f081cc0503 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -17,11 +17,11 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Music { - public class PlaylistItem : OsuRearrangeableListItem>, IFilterable + public class PlaylistItem : OsuRearrangeableListItem>, IFilterable { - public readonly Bindable> SelectedSet = new Bindable>(); + public readonly Bindable> SelectedSet = new Bindable>(); - public Action> RequestSelection; + public Action> RequestSelection; private TextFlowContainer text; private ITextPart titlePart; @@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Music [Resolved] private OsuColour colours { get; set; } - public PlaylistItem(ILive item) + public PlaylistItem(Live item) : base(item) { Padding = new MarginPadding { Left = 5 }; diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 4b10bb779e..7b705a905b 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -24,9 +24,9 @@ namespace osu.Game.Overlays.Music private const float transition_duration = 600; private const float playlist_height = 510; - public IBindableList> BeatmapSets => beatmapSets; + public IBindableList> BeatmapSets => beatmapSets; - private readonly BindableList> beatmapSets = new BindableList>(); + private readonly BindableList> beatmapSets = new BindableList>(); private readonly Bindable beatmap = new Bindable(); @@ -144,7 +144,7 @@ namespace osu.Game.Overlays.Music this.FadeOut(transition_duration); } - private void itemSelected(ILive beatmapSet) + private void itemSelected(Live beatmapSet) { beatmapSet.PerformRead(set => { From d0a2818847d85fe68e99a3cc5feb11ca4602e130 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 15:14:43 +0900 Subject: [PATCH 0267/5199] Fix incorrect testing --- osu.Game.Tests/NonVisual/ScoreInfoTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/NonVisual/ScoreInfoTest.cs b/osu.Game.Tests/NonVisual/ScoreInfoTest.cs index 836c978ba2..41b08a9e98 100644 --- a/osu.Game.Tests/NonVisual/ScoreInfoTest.cs +++ b/osu.Game.Tests/NonVisual/ScoreInfoTest.cs @@ -34,8 +34,8 @@ namespace osu.Game.Tests.NonVisual Assert.That(scoreCopy.Rank, Is.EqualTo(ScoreRank.B)); Assert.That(score.Rank, Is.EqualTo(ScoreRank.X)); - Assert.That(scoreCopy.RealmUser.Username, Is.EqualTo("test")); - Assert.That(score.Rank, Is.Empty); + Assert.That(scoreCopy.RealmUser.Username, Is.Empty); + Assert.That(score.RealmUser.Username, Is.EqualTo("test")); } [Test] From 267a7bd21fa709fc941d3620fe2c57c48e5bbe4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 15:14:49 +0900 Subject: [PATCH 0268/5199] Give `RealmUser.Username` a better default value --- osu.Game/Models/RealmUser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Models/RealmUser.cs b/osu.Game/Models/RealmUser.cs index ff35528827..5fccff597c 100644 --- a/osu.Game/Models/RealmUser.cs +++ b/osu.Game/Models/RealmUser.cs @@ -11,7 +11,7 @@ namespace osu.Game.Models { public int OnlineID { get; set; } = 1; - public string Username { get; set; } + public string Username { get; set; } = string.Empty; public bool IsBot => false; From 4fe3d83fc40a176edf7ba9a1e3785c0fb57d24f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 15:21:14 +0900 Subject: [PATCH 0269/5199] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 4e5b9fdbb1..f85a96f819 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index af5d8a5920..aa6fb93aa0 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 2bcdea61b3..fbb4688588 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - + From 5ea781faef9a4fe2de3be7a72ce84ad2306ecf23 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 15:24:53 +0900 Subject: [PATCH 0270/5199] `Send` unsubscribe actions to synchronization context for consistency and safety --- osu.Game/Database/RealmAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 3866138d40..3572446aef 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -300,7 +300,7 @@ namespace osu.Game.Database return new InvokeOnDisposal(() => { if (ThreadSafety.IsUpdateThread) - unsubscribe(); + syncContext.Send(_ => unsubscribe(), null); else syncContext.Post(_ => unsubscribe(), null); From b5e6352137d4254323d2888f308c81dc2ced3208 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 26 Jan 2022 09:31:29 +0300 Subject: [PATCH 0271/5199] Revert `SettingsItem`-related changes --- osu.Game/Overlays/Settings/ISettingsItem.cs | 14 +--- osu.Game/Overlays/Settings/SettingsItem.cs | 91 +++++---------------- 2 files changed, 21 insertions(+), 84 deletions(-) diff --git a/osu.Game/Overlays/Settings/ISettingsItem.cs b/osu.Game/Overlays/Settings/ISettingsItem.cs index 20e2f48f96..e7afa48502 100644 --- a/osu.Game/Overlays/Settings/ISettingsItem.cs +++ b/osu.Game/Overlays/Settings/ISettingsItem.cs @@ -2,22 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics; namespace osu.Game.Overlays.Settings { - /// - /// A non-generic interface for s. - /// - public interface ISettingsItem : IExpandable, IDisposable + public interface ISettingsItem : IDrawable, IDisposable { - /// - /// Invoked when the setting value has changed. - /// event Action SettingChanged; - - /// - /// Returns whether the UI control is currently in a dragged state. - /// - bool IsControlDragged { get; } } } diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 29980dc5a8..e709be1343 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -30,7 +30,7 @@ namespace osu.Game.Overlays.Settings protected readonly FillFlowContainer FlowContent; - private SpriteText label; + private SpriteText labelText; private OsuTextFlowContainer warningText; @@ -42,34 +42,21 @@ namespace osu.Game.Overlays.Settings [Resolved] private OsuColour colours { get; set; } - private LocalisableString labelText; - public virtual LocalisableString LabelText { - get => labelText; + get => labelText?.Text ?? string.Empty; set { - ensureLabelCreated(); + if (labelText == null) + { + // construct lazily for cases where the label is not needed (may be provided by the Control). + FlowContent.Insert(-1, labelText = new OsuSpriteText()); - labelText = value; - updateLabelText(); - } - } + updateDisabled(); + } - private LocalisableString? contractedLabelText; - - /// - /// Text to be displayed in place of when this is in a contracted state. - /// - public LocalisableString? ContractedLabelText - { - get => contractedLabelText; - set - { - ensureLabelCreated(); - - contractedLabelText = value; - updateLabelText(); + labelText.Text = value; + updateLayout(); } } @@ -103,12 +90,6 @@ namespace osu.Game.Overlays.Settings set => controlWithCurrent.Current = value; } - public BindableBool Expanded { get; } = new BindableBool(true); - - public bool IsControlDragged => Control.IsDragged; - - public event Action SettingChanged; - public virtual IEnumerable FilterTerms => Keywords == null ? new[] { LabelText.ToString() } : new List(Keywords) { LabelText.ToString() }.ToArray(); public IEnumerable Keywords { get; set; } @@ -120,6 +101,8 @@ namespace osu.Game.Overlays.Settings public bool FilteringActive { get; set; } + public event Action SettingChanged; + protected SettingsItem() { RelativeSizeAxes = Axes.X; @@ -168,59 +151,23 @@ namespace osu.Game.Overlays.Settings Anchor = Anchor.Centre, Origin = Anchor.Centre }); + updateLayout(); } } - [Resolved(canBeNull: true)] - private IExpandingContainer expandingContainer { get; set; } - - protected override void LoadComplete() + private void updateLayout() { - base.LoadComplete(); + bool hasLabel = labelText != null && !string.IsNullOrEmpty(labelText.Text.ToString()); - expandingContainer?.Expanded.BindValueChanged(containerExpanded => Expanded.Value = containerExpanded.NewValue, true); - - Expanded.BindValueChanged(v => - { - updateLabelText(); - - Control.FadeTo(v.NewValue ? 1 : 0, 500, Easing.OutQuint); - Control.BypassAutoSizeAxes = v.NewValue ? Axes.None : Axes.Both; - }, true); - - FinishTransforms(true); - } - - private void ensureLabelCreated() - { - if (label != null) - return; - - // construct lazily for cases where the label is not needed (may be provided by the Control). - FlowContent.Insert(-1, label = new OsuSpriteText()); - - updateDisabled(); - } - - private void updateLabelText() - { - if (label != null) - { - if (contractedLabelText is LocalisableString contractedText) - label.Text = Expanded.Value ? labelText : contractedText; - else - label.Text = labelText; - } - - // if the settings item is providing a non-empty label, the default value indicator should be centred vertically to the left of the label. + // if the settings item is providing a label, the default value indicator should be centred vertically to the left of the label. // otherwise, it should be centred vertically to the left of the main control of the settings item. - defaultValueIndicatorContainer.Height = !string.IsNullOrEmpty(label?.Text.ToString()) ? label.DrawHeight : Control.DrawHeight; + defaultValueIndicatorContainer.Height = hasLabel ? labelText.DrawHeight : Control.DrawHeight; } private void updateDisabled() { - if (label != null) - label.Alpha = controlWithCurrent.Current.Disabled ? 0.3f : 1; + if (labelText != null) + labelText.Alpha = controlWithCurrent.Current.Disabled ? 0.3f : 1; } } } From 24bcba64183a7bf404eca503a3c31a24762bfdac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 15:57:05 +0900 Subject: [PATCH 0272/5199] Move final result set firing to before the update realm is disposed Without this, if any registered callback attempts to access `RealmAccess.Realm` when handling the empty set callback, it will deadlock the game. --- osu.Game/Database/RealmAccess.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 3572446aef..1a40176c1d 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -604,20 +604,18 @@ namespace osu.Game.Database Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); + // Force a flush of any pending callbacks in the synchronization context. + // We want to ensure that the empty set callbacks are the last thing to arrive. + syncContext?.Send(_ => + { + foreach (var action in notificationsResetMap.Values) + action(); + }, null); + updateRealm?.Dispose(); updateRealm = null; } - // In order to ensure events arrive in the correct order, these *must* be fired post disposal of the update realm, - // and must be posted to the synchronization context. - // This is because realm may fire event callbacks between the `unregisterAllSubscriptions` and `updateRealm.Dispose` - // calls above. - syncContext?.Send(_ => - { - foreach (var action in notificationsResetMap.Values) - action(); - }, null); - const int sleep_length = 200; int timeout = 5000; From 699826677006b6e7a230f061c505c53b2fbd73c6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 26 Jan 2022 10:18:06 +0300 Subject: [PATCH 0273/5199] Add simplified implementation of an expandable slider --- osu.Game/Overlays/ExpandableSlider.cs | 123 ++++++++++++++++++++++++ osu.Game/Overlays/IExpandableControl.cs | 16 +++ 2 files changed, 139 insertions(+) create mode 100644 osu.Game/Overlays/ExpandableSlider.cs create mode 100644 osu.Game/Overlays/IExpandableControl.cs diff --git a/osu.Game/Overlays/ExpandableSlider.cs b/osu.Game/Overlays/ExpandableSlider.cs new file mode 100644 index 0000000000..38faf44148 --- /dev/null +++ b/osu.Game/Overlays/ExpandableSlider.cs @@ -0,0 +1,123 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Localisation; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays +{ + /// + /// An implementation for the UI slider bar control. + /// + public class ExpandableSlider : CompositeDrawable, IExpandableControl, IHasCurrentValue + where T : struct, IEquatable, IComparable, IConvertible + where TSlider : OsuSliderBar, new() + { + private readonly OsuSpriteText label; + private readonly TSlider slider; + + private LocalisableString contractedLabelText; + + /// + /// The label text to display when this slider is in a contracted state. + /// + public LocalisableString ContractedLabelText + { + get => contractedLabelText; + set + { + if (value == contractedLabelText) + return; + + contractedLabelText = value; + + if (!Expanded.Value) + label.Text = value; + } + } + + private LocalisableString expandedLabelText; + + /// + /// The label text to display when this slider is in an expanded state. + /// + public LocalisableString ExpandedLabelText + { + get => expandedLabelText; + set + { + if (value == expandedLabelText) + return; + + expandedLabelText = value; + + if (Expanded.Value) + label.Text = value; + } + } + + public Bindable Current + { + get => slider.Current; + set => slider.Current = value; + } + + public BindableBool Expanded { get; } = new BindableBool(); + + public bool IsControlDragged => slider.IsDragged; + + public override bool HandlePositionalInput => true; + + public ExpandableSlider() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + label = new OsuSpriteText(), + slider = new TSlider(), + } + }; + } + + [Resolved(canBeNull: true)] + private IExpandingContainer expandingContainer { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + expandingContainer?.Expanded.BindValueChanged(containerExpanded => + { + Expanded.Value = containerExpanded.NewValue; + }, true); + + Expanded.BindValueChanged(v => + { + label.Text = v.NewValue ? expandedLabelText : contractedLabelText; + slider.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint); + slider.BypassAutoSizeAxes = v.NewValue ? Axes.Y : Axes.None; + }, true); + } + } + + /// + /// An implementation for the UI slider bar control. + /// + public class ExpandableSlider : ExpandableSlider> + where T : struct, IEquatable, IComparable, IConvertible + { + } +} diff --git a/osu.Game/Overlays/IExpandableControl.cs b/osu.Game/Overlays/IExpandableControl.cs new file mode 100644 index 0000000000..fae07ae23b --- /dev/null +++ b/osu.Game/Overlays/IExpandableControl.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Overlays +{ + /// + /// An interface for UI controls with the ability to expand/contract. + /// + public interface IExpandableControl : IExpandable + { + /// + /// Returns whether the UI control is currently in a dragged state. + /// + bool IsControlDragged { get; } + } +} From eb83b7fe0a912130641da4e56a9808ed18ce099b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 26 Jan 2022 10:18:17 +0300 Subject: [PATCH 0274/5199] Update existing implementation with changes --- .../TestSceneExpandingControlContainer.cs | 15 +++++++-------- osu.Game/Overlays/ExpandingControlContainer.cs | 3 +-- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingControlContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingControlContainer.cs index d75089ceac..48089566cc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingControlContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingControlContainer.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; -using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings.Sections; using osuTK; using osuTK.Input; @@ -20,8 +19,8 @@ namespace osu.Game.Tests.Visual.UserInterface private TestExpandingContainer container; private SettingsToolboxGroup toolboxGroup; - private SettingsSlider slider1; - private SettingsSlider slider2; + private ExpandableSlider slider1; + private ExpandableSlider slider2; [SetUp] public void SetUp() => Schedule(() => @@ -37,7 +36,7 @@ namespace osu.Game.Tests.Visual.UserInterface Width = 1, Children = new Drawable[] { - slider1 = new SettingsSlider + slider1 = new ExpandableSlider { Current = new BindableFloat { @@ -47,7 +46,7 @@ namespace osu.Game.Tests.Visual.UserInterface Precision = 0.01f, }, }, - slider2 = new SettingsSlider + slider2 = new ExpandableSlider { Current = new BindableDouble { @@ -63,13 +62,13 @@ namespace osu.Game.Tests.Visual.UserInterface slider1.Current.BindValueChanged(v => { - slider1.LabelText = $"Slider One ({v.NewValue:0.##x})"; + slider1.ExpandedLabelText = $"Slider One ({v.NewValue:0.##x})"; slider1.ContractedLabelText = $"S. 1. ({v.NewValue:0.##x})"; }, true); slider2.Current.BindValueChanged(v => { - slider2.LabelText = $"Slider Two ({v.NewValue:N2})"; + slider2.ExpandedLabelText = $"Slider Two ({v.NewValue:N2})"; slider2.ContractedLabelText = $"S. 2. ({v.NewValue:N2})"; }, true); }); @@ -172,7 +171,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("container still expanded", () => container.Expanded.Value); } - private class TestExpandingContainer : ExpandingControlContainer + private class TestExpandingContainer : ExpandingControlContainer { public TestExpandingContainer() : base(120, 250) diff --git a/osu.Game/Overlays/ExpandingControlContainer.cs b/osu.Game/Overlays/ExpandingControlContainer.cs index 2accd63fb9..fb6a71ba97 100644 --- a/osu.Game/Overlays/ExpandingControlContainer.cs +++ b/osu.Game/Overlays/ExpandingControlContainer.cs @@ -9,7 +9,6 @@ using osu.Framework.Input.Events; using osu.Framework.Testing; using osu.Framework.Threading; using osu.Game.Graphics.Containers; -using osu.Game.Overlays.Settings; namespace osu.Game.Overlays { @@ -119,6 +118,6 @@ namespace osu.Game.Overlays /// /// Whether the given control is currently active, by checking whether it's hovered or dragged. /// - private bool isControlActive(TControl control) => control.IsHovered || control.IsDragged || (control is ISettingsItem item && item.IsControlDragged); + private bool isControlActive(TControl control) => control.IsHovered || control.IsDragged || (control is IExpandableControl expandable && expandable.IsControlDragged); } } From 11f0f3c17de6986c7aa6f6a3915b239bf2b5543e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 16:21:24 +0900 Subject: [PATCH 0275/5199] Revert "Move final result set firing to before the update realm is disposed" This reverts commit 24bcba64183a7bf404eca503a3c31a24762bfdac. --- osu.Game/Database/RealmAccess.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 1a40176c1d..3572446aef 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -604,18 +604,20 @@ namespace osu.Game.Database Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); - // Force a flush of any pending callbacks in the synchronization context. - // We want to ensure that the empty set callbacks are the last thing to arrive. - syncContext?.Send(_ => - { - foreach (var action in notificationsResetMap.Values) - action(); - }, null); - updateRealm?.Dispose(); updateRealm = null; } + // In order to ensure events arrive in the correct order, these *must* be fired post disposal of the update realm, + // and must be posted to the synchronization context. + // This is because realm may fire event callbacks between the `unregisterAllSubscriptions` and `updateRealm.Dispose` + // calls above. + syncContext?.Send(_ => + { + foreach (var action in notificationsResetMap.Values) + action(); + }, null); + const int sleep_length = 200; int timeout = 5000; From 161ff45f8cb5e93d7cf2a02c5601344d9d35064c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 26 Jan 2022 10:35:42 +0300 Subject: [PATCH 0276/5199] Resolve further UI-related issues --- osu.Game/Overlays/ExpandableSlider.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/ExpandableSlider.cs b/osu.Game/Overlays/ExpandableSlider.cs index 38faf44148..524485d806 100644 --- a/osu.Game/Overlays/ExpandableSlider.cs +++ b/osu.Game/Overlays/ExpandableSlider.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osuTK; namespace osu.Game.Overlays { @@ -84,10 +85,14 @@ namespace osu.Game.Overlays { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, 10f), Children = new Drawable[] { label = new OsuSpriteText(), - slider = new TSlider(), + slider = new TSlider + { + RelativeSizeAxes = Axes.X, + }, } }; } @@ -108,7 +113,7 @@ namespace osu.Game.Overlays { label.Text = v.NewValue ? expandedLabelText : contractedLabelText; slider.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint); - slider.BypassAutoSizeAxes = v.NewValue ? Axes.Y : Axes.None; + slider.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None; }, true); } } From 791ea0308f8026c127d26ee2118b083ffa72abf9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 17:09:28 +0900 Subject: [PATCH 0277/5199] Add flag to guard against deadlocks during blocking operations --- osu.Game/Database/RealmAccess.cs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 3572446aef..dc7c8bf668 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -89,10 +89,15 @@ namespace osu.Game.Database private Realm? updateRealm; + private bool isSendingNotificationResetEvents; + public Realm Realm => ensureUpdateRealm(); private Realm ensureUpdateRealm() { + if (isSendingNotificationResetEvents) + throw new InvalidOperationException("Cannot retrieve a realm context from a notification callback during a blocking operation."); + if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException(@$"Use {nameof(getRealmInstance)} when performing realm operations from a non-update thread"); @@ -614,8 +619,20 @@ namespace osu.Game.Database // calls above. syncContext?.Send(_ => { - foreach (var action in notificationsResetMap.Values) - action(); + // Flag ensures that we don't get in a deadlocked scenario due to a callback attempting to access `RealmAccess.Realm` or `RealmAccess.Run` + // and hitting `realmRetrievalLock` a second time. Generally such usages should not exist, and as such we throw when an attempt is made + // to use in this fashion. + isSendingNotificationResetEvents = true; + + try + { + foreach (var action in notificationsResetMap.Values) + action(); + } + finally + { + isSendingNotificationResetEvents = false; + } }, null); const int sleep_length = 200; From 885fb92aada99e4dfccb0904f544e16a093787ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 17:21:57 +0900 Subject: [PATCH 0278/5199] Move final empty result set sending to post-compact --- osu.Game/Database/RealmAccess.cs | 44 ++++++++++++++++---------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index dc7c8bf668..9bdbebfe89 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -613,28 +613,6 @@ namespace osu.Game.Database updateRealm = null; } - // In order to ensure events arrive in the correct order, these *must* be fired post disposal of the update realm, - // and must be posted to the synchronization context. - // This is because realm may fire event callbacks between the `unregisterAllSubscriptions` and `updateRealm.Dispose` - // calls above. - syncContext?.Send(_ => - { - // Flag ensures that we don't get in a deadlocked scenario due to a callback attempting to access `RealmAccess.Realm` or `RealmAccess.Run` - // and hitting `realmRetrievalLock` a second time. Generally such usages should not exist, and as such we throw when an attempt is made - // to use in this fashion. - isSendingNotificationResetEvents = true; - - try - { - foreach (var action in notificationsResetMap.Values) - action(); - } - finally - { - isSendingNotificationResetEvents = false; - } - }, null); - const int sleep_length = 200; int timeout = 5000; @@ -656,6 +634,28 @@ namespace osu.Game.Database // We still want to continue with the blocking operation, though. Logger.Log($"Realm compact failed with error {e}", LoggingTarget.Database); } + + // In order to ensure events arrive in the correct order, these *must* be fired post disposal of the update realm, + // and must be posted to the synchronization context. + // This is because realm may fire event callbacks between the `unregisterAllSubscriptions` and `updateRealm.Dispose` + // calls above. + syncContext?.Send(_ => + { + // Flag ensures that we don't get in a deadlocked scenario due to a callback attempting to access `RealmAccess.Realm` or `RealmAccess.Run` + // and hitting `realmRetrievalLock` a second time. Generally such usages should not exist, and as such we throw when an attempt is made + // to use in this fashion. + isSendingNotificationResetEvents = true; + + try + { + foreach (var action in notificationsResetMap.Values) + action(); + } + finally + { + isSendingNotificationResetEvents = false; + } + }, null); } catch { From d1a22562621a2768056d3604400108f0b20c5441 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 17:48:11 +0900 Subject: [PATCH 0279/5199] Refactor `SkinSection` to avoid unnecessary realm queries --- .../Overlays/Settings/Sections/SkinSection.cs | 83 ++++++++++--------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 0846c023c1..441a439596 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -18,6 +18,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Skinning; using osu.Game.Skinning.Editor; +using Realms; namespace osu.Game.Overlays.Settings.Sections { @@ -51,12 +52,6 @@ namespace osu.Game.Overlays.Settings.Sections private IDisposable realmSubscription; - private IQueryable queryRealmSkins() => - realm.Realm.All() - .Where(s => !s.DeletePending) - .OrderByDescending(s => s.Protected) // protected skins should be at the top. - .ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase); - [BackgroundDependencyLoader(permitNulls: true)] private void load(OsuConfigManager config, [CanBeNull] SkinEditorOverlay skinEditor) { @@ -83,37 +78,54 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown.Current = dropdownBindable; - realmSubscription = realm.RegisterForNotifications(r => queryRealmSkins(), (sender, changes, error) => - { - // The first fire of this is a bit redundant due to the call below, - // but this is safest in case the subscription is restored after a context recycle. - updateItems(); - }); - - updateItems(); + realmSubscription = realm.RegisterForNotifications(r => realm.Realm.All() + .Where(s => !s.DeletePending) + .OrderByDescending(s => s.Protected) // protected skins should be at the top. + .ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase), skinsChanged); configBindable.BindValueChanged(id => Scheduler.AddOnce(updateSelectedSkinFromConfig)); - updateSelectedSkinFromConfig(); - dropdownBindable.BindValueChanged(skin => + dropdownBindable.BindValueChanged(dropdownSelectionChanged); + } + + private void dropdownSelectionChanged(ValueChangedEvent> skin) + { + // Only handle cases where it's clear the user has intent to change skins. + if (skin.OldValue == null) return; + + if (skin.NewValue.Equals(random_skin_info)) { - if (skin.NewValue.Equals(random_skin_info)) + var skinBefore = skins.CurrentSkinInfo.Value; + + skins.SelectRandomSkin(); + + if (skinBefore == skins.CurrentSkinInfo.Value) { - var skinBefore = skins.CurrentSkinInfo.Value; - - skins.SelectRandomSkin(); - - if (skinBefore == skins.CurrentSkinInfo.Value) - { - // the random selection didn't change the skin, so we should manually update the dropdown to match. - dropdownBindable.Value = skins.CurrentSkinInfo.Value; - } - - return; + // the random selection didn't change the skin, so we should manually update the dropdown to match. + dropdownBindable.Value = skins.CurrentSkinInfo.Value; } - configBindable.Value = skin.NewValue.ID.ToString(); - }); + return; + } + + configBindable.Value = skin.NewValue.ID.ToString(); + } + + private void skinsChanged(IRealmCollection skins, ChangeSet changes, Exception error) + { + // This can only mean that realm is recycling, else we would see the protected skins. + // Because we are using `Live<>` in this class, we don't need to worry about this scenario too much. + if (!skins.Any()) + return; + + int protectedCount = skins.Count(s => s.Protected); + + skinItems = skins.ToLive(realm); + skinItems.Insert(protectedCount, random_skin_info); + + skinDropdown.Items = skinItems; + + updateSelectedSkinFromConfig(); } private void updateSelectedSkinFromConfig() @@ -126,17 +138,6 @@ namespace osu.Game.Overlays.Settings.Sections dropdownBindable.Value = skin ?? skinDropdown.Items.First(); } - private void updateItems() - { - int protectedCount = queryRealmSkins().Count(s => s.Protected); - - skinItems = queryRealmSkins().ToLive(realm); - - skinItems.Insert(protectedCount, random_skin_info); - - skinDropdown.Items = skinItems; - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From f2d48d088d81773f1d93f298b8e7d142565b7e80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 17:57:03 +0900 Subject: [PATCH 0280/5199] Fix realm migration failures with presence of databased EF rulesets that don't exist on disk --- osu.Game/Rulesets/EFRulesetInfo.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/EFRulesetInfo.cs b/osu.Game/Rulesets/EFRulesetInfo.cs index 473b7c657e..9559cfa00c 100644 --- a/osu.Game/Rulesets/EFRulesetInfo.cs +++ b/osu.Game/Rulesets/EFRulesetInfo.cs @@ -28,21 +28,15 @@ namespace osu.Game.Rulesets public Ruleset CreateInstance() { if (!Available) - throw new RulesetLoadException(@"Ruleset not available"); + return null; var type = Type.GetType(InstantiationInfo); if (type == null) - throw new RulesetLoadException(@"Type lookup failure"); + return null; var ruleset = Activator.CreateInstance(type) as Ruleset; - if (ruleset == null) - throw new RulesetLoadException(@"Instantiation failure"); - - // overwrite the pre-populated RulesetInfo with a potentially database attached copy. - // ruleset.RulesetInfo = this; - return ruleset; } From 3aa681005bd5bf676b0cdf912aa0567a8120f077 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 18:04:53 +0900 Subject: [PATCH 0281/5199] Skip importing scores which have no matching realm ruleset There's no real way to recover these unless we want to start importing rulesets into realm. And that seems counter productive. This can only happen if users don't have the dll present any more, and it was removed far before realm was tracking rulesets (else it would have an `Available=0` entry in realm to match). --- osu.Game/Database/EFToRealmMigrator.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index adf91e4a41..33f7c25b28 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -286,6 +286,7 @@ namespace osu.Game.Database var transaction = r.BeginWrite(); int written = 0; + int missing = 0; try { @@ -300,6 +301,13 @@ namespace osu.Game.Database var beatmap = r.All().First(b => b.Hash == score.BeatmapInfo.Hash); var ruleset = r.Find(score.Ruleset.ShortName); + + if (ruleset == null) + { + log($"Skipping {++missing} scores with missing ruleset"); + continue; + } + var user = new RealmUser { OnlineID = score.User.OnlineID, From 45636ce04b224ca516b798c253f2289c357aaa5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 18:25:27 +0900 Subject: [PATCH 0282/5199] Remove collection `ToLive` helper method to avoid confusion --- osu.Game/Database/RealmObjectExtensions.cs | 6 ------ .../Overlays/Settings/Sections/SkinSection.cs | 18 +++++++++++------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index dba8633f53..7a0ca2c85a 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -216,12 +216,6 @@ namespace osu.Game.Database return new RealmLiveUnmanaged(realmObject); } - public static List> ToLive(this IEnumerable realmList, RealmAccess realm) - where T : RealmObject, IHasGuidPrimaryKey - { - return realmList.Select(l => new RealmLive(l, realm)).Cast>().ToList(); - } - public static Live ToLive(this T realmObject, RealmAccess realm) where T : RealmObject, IHasGuidPrimaryKey { diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 441a439596..1dfe49945f 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Settings.Sections Name = "", }.ToLiveUnmanaged(); - private List> skinItems; + private readonly List> dropdownItems = new List>(); [Resolved] private SkinManager skins { get; set; } @@ -111,19 +111,23 @@ namespace osu.Game.Overlays.Settings.Sections configBindable.Value = skin.NewValue.ID.ToString(); } - private void skinsChanged(IRealmCollection skins, ChangeSet changes, Exception error) + private void skinsChanged(IRealmCollection sender, ChangeSet changes, Exception error) { // This can only mean that realm is recycling, else we would see the protected skins. // Because we are using `Live<>` in this class, we don't need to worry about this scenario too much. - if (!skins.Any()) + if (!sender.Any()) return; - int protectedCount = skins.Count(s => s.Protected); + int protectedCount = sender.Count(s => s.Protected); - skinItems = skins.ToLive(realm); - skinItems.Insert(protectedCount, random_skin_info); + // For simplicity repopulate the full list. + // In the future we should change this to properly handle ChangeSet events. + dropdownItems.Clear(); + foreach (var skin in sender) + dropdownItems.Add(skin.ToLive(realm)); + dropdownItems.Insert(protectedCount, random_skin_info); - skinDropdown.Items = skinItems; + skinDropdown.Items = dropdownItems; updateSelectedSkinFromConfig(); } From 473c4d00cabb5079b8c1630dd46659889cb25665 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 18:38:44 +0900 Subject: [PATCH 0283/5199] Fix grouped difficulty icons using incorrect lookup for ruleset grouping --- osu.Game/Screens/Select/Carousel/SetPanelContent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index 619806f96e..82523c9d9d 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -87,7 +87,7 @@ namespace osu.Game.Screens.Select.Carousel var beatmaps = carouselSet.Beatmaps.ToList(); return beatmaps.Count > maximum_difficulty_icons - ? (IEnumerable)beatmaps.GroupBy(b => b.BeatmapInfo.RulesetID) + ? (IEnumerable)beatmaps.GroupBy(b => b.BeatmapInfo.Ruleset.ShortName) .Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Last().BeatmapInfo.Ruleset)) : beatmaps.Select(b => new FilterableDifficultyIcon(b)); } From 57e624d8e7519427fbe05a54da4fe7bed5f7cf67 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 18:46:32 +0900 Subject: [PATCH 0284/5199] Fix custom rulesets being displayed before official ones --- osu.Game/Rulesets/RulesetStore.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 9af9ace7ad..0f518be639 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -163,7 +163,10 @@ namespace osu.Game.Rulesets } } - availableRulesets.AddRange(detachedRulesets); + // add known official rulesets first.. + availableRulesets.AddRange(detachedRulesets.Where(r => r.OnlineID >= 0).OrderBy(r => r.OnlineID)); + // .. then add any customs + availableRulesets.AddRange(detachedRulesets.Where(r => r.OnlineID < 0).OrderBy(r => r.ShortName)); }); } From abe2cccaaecd1a01b5f3855705b4195958ef11da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 19:03:26 +0900 Subject: [PATCH 0285/5199] Fix completely invalid method of testing realm migration --- osu.Game.Tests/Database/RealmLiveTests.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 3f81b36378..4bc1f5078a 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -47,16 +47,14 @@ namespace osu.Game.Tests.Database liveBeatmap = beatmap.ToLive(realm); }); - using (realm.BlockAllOperations()) - { - // recycle realm before migrating - } - using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target")) { migratedStorage.DeleteDirectory(string.Empty); - storage.Migrate(migratedStorage); + using (realm.BlockAllOperations()) + { + storage.Migrate(migratedStorage); + } Assert.IsFalse(liveBeatmap?.PerformRead(l => l.Hidden)); } From b3f2392358ac141b17f28328932d299384e3bec9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 26 Jan 2022 19:04:18 +0900 Subject: [PATCH 0286/5199] Resolve compilation error due to removed method --- osu.Game/Overlays/Music/PlaylistOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 7b705a905b..59ade0918d 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -116,7 +116,7 @@ namespace osu.Game.Overlays.Music { beatmapSets.Clear(); // must use AddRange to avoid RearrangeableList sort overhead per add op. - beatmapSets.AddRange(sender.ToLive(realm)); + beatmapSets.AddRange(sender.Select(b => b.ToLive(realm))); return; } From 378173cc66e202e97a342b51c6f0f4a6327cf278 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 22:46:29 +0900 Subject: [PATCH 0287/5199] Fix some score imports failing due to null string attempted to be parsed as json --- osu.Game/Scoring/EFScoreInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/EFScoreInfo.cs b/osu.Game/Scoring/EFScoreInfo.cs index 1dd4e3b6b3..4161336cfc 100644 --- a/osu.Game/Scoring/EFScoreInfo.cs +++ b/osu.Game/Scoring/EFScoreInfo.cs @@ -105,7 +105,7 @@ namespace osu.Game.Scoring public string ModsJson { get => JsonConvert.SerializeObject(APIMods); - set => APIMods = JsonConvert.DeserializeObject(value); + set => APIMods = !string.IsNullOrEmpty(value) ? JsonConvert.DeserializeObject(value) : Array.Empty(); } [NotMapped] From 7f34085baa52a837edef6e1e1ed431f186d08224 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 26 Jan 2022 17:04:28 +0300 Subject: [PATCH 0288/5199] Mark `EditorSavingTestScene` as abstract --- osu.Game/Tests/Visual/EditorSavingTestScene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/EditorSavingTestScene.cs b/osu.Game/Tests/Visual/EditorSavingTestScene.cs index 72b5d076a5..cc39ead1de 100644 --- a/osu.Game/Tests/Visual/EditorSavingTestScene.cs +++ b/osu.Game/Tests/Visual/EditorSavingTestScene.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual /// /// Tests the general expected flow of creating a new beatmap, saving it, then loading it back from song select. /// - public class EditorSavingTestScene : OsuGameTestScene + public abstract class EditorSavingTestScene : OsuGameTestScene { protected Editor Editor => Game.ChildrenOfType().FirstOrDefault(); From 873d3676153521b732167bfeb052445931984846 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Wed, 26 Jan 2022 15:51:39 +0100 Subject: [PATCH 0289/5199] Fix custom rulesets not being able to convert maps --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 6b198ab505..7fd312371b 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Select.Carousel bool match = criteria.Ruleset == null || BeatmapInfo.RulesetID == criteria.Ruleset.OnlineID || - (BeatmapInfo.RulesetID == 0 && criteria.Ruleset.OnlineID > 0 && criteria.AllowConvertedBeatmaps); + (BeatmapInfo.RulesetID == 0 && criteria.Ruleset.OnlineID != 0 && criteria.AllowConvertedBeatmaps); if (BeatmapInfo.BeatmapSet?.Equals(criteria.SelectedBeatmapSet) == true) { From e712fab2994557019a825c80d8658197fca51b21 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Wed, 26 Jan 2022 16:14:07 +0100 Subject: [PATCH 0290/5199] Add test for custom ruleset conversion filtering --- .../NonVisual/Filtering/FilterMatchingTest.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 74904f4585..2e7a4a65e5 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -78,6 +78,20 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.IsFalse(carouselItem.Filtered.Value); } + [Test] + public void TestCriteriaMatchingConvertedBeatmapsForCustomRulesets() + { + var exampleBeatmapInfo = getExampleBeatmap(); + var criteria = new FilterCriteria + { + Ruleset = new RulesetInfo { OnlineID = -25 }, + AllowConvertedBeatmaps = true + }; + var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); + carouselItem.Filter(criteria); + Assert.IsFalse(carouselItem.Filtered.Value); + } + [Test] [TestCase(true)] [TestCase(false)] From 6ec9c5c21a7891c0d1d7e8f9e88b44f482885da1 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Wed, 26 Jan 2022 16:23:00 +0100 Subject: [PATCH 0291/5199] Use default custom ruleset ID --- osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 2e7a4a65e5..363753cb33 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -84,7 +84,7 @@ namespace osu.Game.Tests.NonVisual.Filtering var exampleBeatmapInfo = getExampleBeatmap(); var criteria = new FilterCriteria { - Ruleset = new RulesetInfo { OnlineID = -25 }, + Ruleset = new RulesetInfo { OnlineID = -1 }, AllowConvertedBeatmaps = true }; var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); From f21e3d0d866e637870d9a9693b693b108c114611 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 00:34:51 +0900 Subject: [PATCH 0292/5199] Block collection loading until realm migration has completed --- osu.Game/Collections/CollectionManager.cs | 5 +++++ osu.Game/Database/DatabaseContextFactory.cs | 6 ++++++ osu.Game/Database/EFToRealmMigrator.cs | 1 + 3 files changed, 12 insertions(+) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index c4f991094c..5845e0d4d1 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -50,9 +50,14 @@ namespace osu.Game.Collections this.storage = storage; } + [Resolved(canBeNull: true)] + private DatabaseContextFactory efContextFactory { get; set; } = null!; + [BackgroundDependencyLoader] private void load() { + efContextFactory?.WaitForMigrationCompletion(); + Collections.CollectionChanged += collectionsChanged; if (storage.Exists(database_backup_name)) diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index c84edbfb81..123ed8019c 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -184,5 +184,11 @@ namespace osu.Game.Database } public static string CreateDatabaseConnectionString(string filename, Storage storage) => string.Concat("Data Source=", storage.GetFullPath($@"{filename}", true)); + + private readonly ManualResetEventSlim migrationComplete = new ManualResetEventSlim(); + + public void SetMigrationCompletion() => migrationComplete.Set(); + + public void WaitForMigrationCompletion() => migrationComplete.Wait(); } } diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 33f7c25b28..96baad35ec 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -128,6 +128,7 @@ namespace osu.Game.Database }, TaskCreationOptions.LongRunning).ContinueWith(t => { migrationCompleted.SetResult(true); + efContextFactory.SetMigrationCompletion(); }); } From 0c2ed2f9a77c863865844f20bc0796ef1d6e14e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 01:25:55 +0900 Subject: [PATCH 0293/5199] Add failing test coverage of incorrect filter ruleset matching --- osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 363753cb33..33204d33a7 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -16,7 +16,11 @@ namespace osu.Game.Tests.NonVisual.Filtering { private BeatmapInfo getExampleBeatmap() => new BeatmapInfo { - Ruleset = new RulesetInfo { OnlineID = 0 }, + Ruleset = new RulesetInfo + { + ShortName = "osu", + OnlineID = 0 + }, StarRating = 4.0d, Difficulty = new BeatmapDifficulty { @@ -57,7 +61,7 @@ namespace osu.Game.Tests.NonVisual.Filtering var exampleBeatmapInfo = getExampleBeatmap(); var criteria = new FilterCriteria { - Ruleset = new RulesetInfo { OnlineID = 6 } + Ruleset = new RulesetInfo { ShortName = "catch" } }; var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); carouselItem.Filter(criteria); From f70e10e8a4607dd51254a9915c78880b35a4f923 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 01:25:40 +0900 Subject: [PATCH 0294/5199] Fix ruleset filter matching using `OnlineID` instead of `ShortName` --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 7fd312371b..e6dee0233f 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Select.Carousel bool match = criteria.Ruleset == null || - BeatmapInfo.RulesetID == criteria.Ruleset.OnlineID || + BeatmapInfo.Ruleset.ShortName == criteria.Ruleset.ShortName || (BeatmapInfo.RulesetID == 0 && criteria.Ruleset.OnlineID != 0 && criteria.AllowConvertedBeatmaps); if (BeatmapInfo.BeatmapSet?.Equals(criteria.SelectedBeatmapSet) == true) From 4382adad8240fee9c85f1b5cb21efea70c011184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 25 Jan 2022 23:20:33 +0100 Subject: [PATCH 0295/5199] Add test coverage for editor changes not resetting after exit without save --- .../Visual/Editing/TestSceneEditorSaving.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index 58daab1ce2..adaa24d542 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -4,11 +4,13 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Screens; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osu.Game.Screens.Select; using osuTK.Input; namespace osu.Game.Tests.Visual.Editing @@ -116,5 +118,24 @@ namespace osu.Game.Tests.Visual.Editing EditorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT && EditorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT); } + + [Test] + public void TestExitWithoutSaveFromExistingBeatmap() + { + const string tags_to_save = "these tags will be saved"; + const string tags_to_discard = "these tags should be discarded"; + + AddStep("Set tags", () => EditorBeatmap.BeatmapInfo.Metadata.Tags = tags_to_save); + SaveEditor(); + AddAssert("Tags saved correctly", () => EditorBeatmap.BeatmapInfo.Metadata.Tags == tags_to_save); + + ReloadEditorToSameBeatmap(); + AddAssert("Tags saved correctly", () => EditorBeatmap.BeatmapInfo.Metadata.Tags == tags_to_save); + AddStep("Set tags again", () => EditorBeatmap.BeatmapInfo.Metadata.Tags = tags_to_discard); + + AddStep("Exit editor", () => Editor.Exit()); + AddUntilStep("Wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); + AddAssert("Tags reverted correctly", () => Game.Beatmap.Value.BeatmapInfo.Metadata.Tags == tags_to_save); + } } } From d760283665b6ecadcdee4989ded6e7684387dd41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 25 Jan 2022 23:31:42 +0100 Subject: [PATCH 0296/5199] Ensure edited beatmap is restored to a baseline state on exit --- osu.Game/Screens/Edit/Editor.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b42f629aad..e26453f99a 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -574,7 +574,9 @@ namespace osu.Game.Screens.Edit // To update the game-wide beatmap with any changes, perform a re-fetch on exit/suspend. // This is required as the editor makes its local changes via EditorBeatmap // (which are not propagated outwards to a potentially cached WorkingBeatmap). - var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(Beatmap.Value.BeatmapInfo); + ((IWorkingBeatmapCache)beatmapManager).Invalidate(Beatmap.Value.BeatmapInfo); + var refetchedBeatmapInfo = beatmapManager.QueryBeatmap(b => b.ID == Beatmap.Value.BeatmapInfo.ID); + var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(refetchedBeatmapInfo); if (!(refetchedBeatmap is DummyWorkingBeatmap)) { From e0616476e2a0a772c5998b0194d8a0dab0ad0eb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 26 Jan 2022 20:52:14 +0100 Subject: [PATCH 0297/5199] Fix test gameplay tests failing due to beatmap refetch on suspend --- .../Visual/Editing/TestSceneEditorTestGameplay.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index 79afc8cf27..e2406a8d27 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -70,7 +70,11 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); AddUntilStep("background has correct params", () => { - var background = this.ChildrenOfType().Single(); + // the test gameplay player's beatmap may be the "same" beatmap as the one being edited, *but* the `BeatmapInfo` references may differ + // due to the beatmap refetch logic ran on editor suspend. + // this test cares about checking the background belonging to the editor specifically, so check that using reference equality + // (as `.Equals()` cannot discern between the two, as they technically share the same database GUID). + var background = this.ChildrenOfType().Single(b => ReferenceEquals(b.Beatmap.BeatmapInfo, EditorBeatmap.BeatmapInfo)); return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0; }); AddAssert("no mods selected", () => SelectedMods.Value.Count == 0); @@ -99,7 +103,11 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); AddUntilStep("background has correct params", () => { - var background = this.ChildrenOfType().Single(); + // the test gameplay player's beatmap may be the "same" beatmap as the one being edited, *but* the `BeatmapInfo` references may differ + // due to the beatmap refetch logic ran on editor suspend. + // this test cares about checking the background belonging to the editor specifically, so check that using reference equality + // (as `.Equals()` cannot discern between the two, as they technically share the same database GUID). + var background = this.ChildrenOfType().Single(b => ReferenceEquals(b.Beatmap.BeatmapInfo, EditorBeatmap.BeatmapInfo)); return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0; }); From 35be0f24d02a5474b05b997526f662021311bb4d Mon Sep 17 00:00:00 2001 From: mk-56 Date: Thu, 27 Jan 2022 00:10:15 +0100 Subject: [PATCH 0298/5199] fixed leading "0"s not being present infront of decimal floats --- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 10806318b0..853c974a04 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -27,9 +27,9 @@ namespace osu.Game.Rulesets.Osu.Mods [SettingSource("Strength")] public BindableDouble WiggleStrength { get; } = new BindableDouble(1) { - MinValue = .1f, + MinValue = 0.1f, MaxValue = 2f, - Precision = .1f + Precision = 0.1f }; [SettingSource("Duration", "Milliseconds per wiggle")] From 587c0f965c877ee05db77ad87762ee9c810f4366 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 14:32:20 +0900 Subject: [PATCH 0299/5199] Add more attempts to delete EF database Just noticed in passing. Probably best we do this since it was known to fail on windows in some rare cases. --- osu.Game/Database/DatabaseContextFactory.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index 123ed8019c..45557aa5ec 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.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.IO; using System.Linq; using System.Threading; @@ -163,7 +164,24 @@ namespace osu.Game.Database try { - storage.Delete(DATABASE_NAME); + int attempts = 10; + + // Retry logic taken from MigratableStorage.AttemptOperation. + while (true) + { + try + { + storage.Delete(DATABASE_NAME); + return; + } + catch (Exception) + { + if (attempts-- == 0) + throw; + } + + Thread.Sleep(250); + } } catch { From deb5d75b5f21078d0877fd1ce5b75468fc896e3d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 14:33:44 +0900 Subject: [PATCH 0300/5199] Change migration process to always delete old EF database It is already backed up, so this is probably fine. --- osu.Game/Database/EFToRealmMigrator.cs | 44 +++++++++++++++++++++----- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 96baad35ec..072d1c95f1 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -15,6 +15,8 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Models; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Skinning; @@ -40,6 +42,12 @@ namespace osu.Game.Database [Resolved] private OsuConfigManager config { get; set; } = null!; + [Resolved] + private NotificationOverlay notificationOverlay { get; set; } = null!; + + [Resolved] + private OsuGame game { get; set; } = null!; + private readonly OsuSpriteText currentOperationText; public EFToRealmMigrator() @@ -96,7 +104,11 @@ namespace osu.Game.Database protected override void LoadComplete() { base.LoadComplete(); + beginMigration(); + } + private void beginMigration() + { Task.Factory.StartNew(() => { using (var ef = efContextFactory.Get()) @@ -117,21 +129,37 @@ namespace osu.Game.Database migrateBeatmaps(ef); migrateScores(ef); } - - // Delete the database permanently. - // Will cause future startups to not attempt migration. - log("Migration successful, deleting EF database"); - efContextFactory.ResetDatabase(); - - if (DebugUtils.IsDebugBuild) - Logger.Log("Your development database has been fully migrated to realm. If you switch back to a pre-realm branch and need your previous database, rename the backup file back to \"client.db\".\n\nNote that doing this can potentially leave your file store in a bad state.", level: LogLevel.Important); }, TaskCreationOptions.LongRunning).ContinueWith(t => { + if (t.Exception == null) + { + log("Migration successful!"); + + if (DebugUtils.IsDebugBuild) + Logger.Log("Your development database has been fully migrated to realm. If you switch back to a pre-realm branch and need your previous database, rename the backup file back to \"client.db\".\n\nNote that doing this can potentially leave your file store in a bad state.", level: LogLevel.Important); + } + else + { + log("Migration failed!"); + Logger.Log(t.Exception.ToString(), LoggingTarget.Database); + } + + // Regardless of success, since the game is going to continue with startup let's move the ef database out of the way. + // If we were to not do this, the migration would run another time the next time the user starts the game. + deletePreRealmData(); + migrationCompleted.SetResult(true); efContextFactory.SetMigrationCompletion(); }); } + private void deletePreRealmData() + { + // Delete the database permanently. + // Will cause future startups to not attempt migration. + efContextFactory.ResetDatabase(); + } + private void log(string message) { Logger.Log(message, LoggingTarget.Database); From b745252962b232e3bc3173491ab37495f302a56d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 14:34:18 +0900 Subject: [PATCH 0301/5199] Show notification when migration fails to give users a recovery path --- osu.Game/Database/EFToRealmMigrator.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 072d1c95f1..d110ac2f9f 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -142,6 +142,16 @@ namespace osu.Game.Database { log("Migration failed!"); Logger.Log(t.Exception.ToString(), LoggingTarget.Database); + + notificationOverlay.Post(new SimpleErrorNotification + { + Text = "IMPORTANT: During data migration, some of your data could not be successfully migrated. It has been backed up in your osu! folder.\n\nFor further assistance, please open a discussion on github and attach your backup files (click to get started).", + Activated = () => + { + game.OpenUrlExternally(@"https://github.com/ppy/osu/discussions/new?title=Realm%20migration%20issue&body=Please%20attach%20your%20database%20backups%20by%20zipping%20them%20and%20dragging%20in%20here!&category=q-a"); + return true; + } + }); } // Regardless of success, since the game is going to continue with startup let's move the ef database out of the way. From 08948f60f3108ccfaf098be3ebe99a69ffe61c93 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 14:39:07 +0900 Subject: [PATCH 0302/5199] Move backups to "backups" subfolder to make them easier to find --- osu.Game/OsuGameBase.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 09ca4e450d..8363c41437 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -202,16 +202,18 @@ namespace osu.Game // See https://github.com/ppy/osu/pull/16547 for more discussion. if (EFContextFactory != null) { + const string backup_folder = "backups"; + string migration = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; - EFContextFactory.CreateBackup($"client.{migration}.db"); - realm.CreateBackup($"client.{migration}.realm"); + EFContextFactory.CreateBackup(Path.Combine(backup_folder, $"client.{migration}.db")); + realm.CreateBackup(Path.Combine(backup_folder, $"client.{migration}.realm")); using (var source = Storage.GetStream("collection.db")) { if (source != null) { - using (var destination = Storage.GetStream($"collection.{migration}.db", FileAccess.Write, FileMode.CreateNew)) + using (var destination = Storage.GetStream(Path.Combine(backup_folder, $"collection.{migration}.db"), FileAccess.Write, FileMode.CreateNew)) source.CopyTo(destination); } } From 31abb372e5c2ac123decc43f88aa8d68b5d3c2ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 14:52:43 +0900 Subject: [PATCH 0303/5199] Automatically zip and show the backup archive to the user --- osu.Game/Database/EFToRealmMigrator.cs | 29 ++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index d110ac2f9f..c41461cc68 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.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.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; @@ -9,6 +10,7 @@ using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; +using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; @@ -22,6 +24,10 @@ using osu.Game.Scoring; using osu.Game.Skinning; using osuTK; using Realms; +using SharpCompress.Archives; +using SharpCompress.Archives.Zip; +using SharpCompress.Common; +using SharpCompress.Writers.Zip; #nullable enable @@ -48,6 +54,9 @@ namespace osu.Game.Database [Resolved] private OsuGame game { get; set; } = null!; + [Resolved] + private Storage storage { get; set; } = null!; + private readonly OsuSpriteText currentOperationText; public EFToRealmMigrator() @@ -145,10 +154,26 @@ namespace osu.Game.Database notificationOverlay.Post(new SimpleErrorNotification { - Text = "IMPORTANT: During data migration, some of your data could not be successfully migrated. It has been backed up in your osu! folder.\n\nFor further assistance, please open a discussion on github and attach your backup files (click to get started).", + Text = "IMPORTANT: During data migration, some of your data could not be successfully migrated. The previous version has been backed up.\n\nFor further assistance, please open a discussion on github and attach your backup files (click to get started).", Activated = () => { - game.OpenUrlExternally(@"https://github.com/ppy/osu/discussions/new?title=Realm%20migration%20issue&body=Please%20attach%20your%20database%20backups%20by%20zipping%20them%20and%20dragging%20in%20here!&category=q-a"); + game.OpenUrlExternally(@"https://github.com/ppy/osu/discussions/new?title=Realm%20migration%20issue&body=Please%20drag%20the%20""attach_me.zip""%20file%20here!&category=q-a"); + + const string attachment_filename = "attach_me.zip"; + const string backup_folder = "backups"; + + var backupStorage = storage.GetStorageForDirectory(backup_folder); + + backupStorage.Delete(attachment_filename); + + using (var zip = ZipArchive.Create()) + { + zip.AddAllFromDirectory(backupStorage.GetFullPath(string.Empty)); + zip.SaveTo(Path.Combine(backupStorage.GetFullPath(string.Empty), attachment_filename), new ZipWriterOptions(CompressionType.Deflate)); + } + + backupStorage.PresentFileExternally(attachment_filename); + return true; } }); From 465e7d29feb9529dbde477bcf384bf8ef94b2949 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 14:53:11 +0900 Subject: [PATCH 0304/5199] Avoid showing the external link warning --- osu.Game/Database/EFToRealmMigrator.cs | 2 +- osu.Game/Online/Chat/ExternalLinkOpener.cs | 4 ++-- osu.Game/OsuGame.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index c41461cc68..05bc86a7c0 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -157,7 +157,7 @@ namespace osu.Game.Database Text = "IMPORTANT: During data migration, some of your data could not be successfully migrated. The previous version has been backed up.\n\nFor further assistance, please open a discussion on github and attach your backup files (click to get started).", Activated = () => { - game.OpenUrlExternally(@"https://github.com/ppy/osu/discussions/new?title=Realm%20migration%20issue&body=Please%20drag%20the%20""attach_me.zip""%20file%20here!&category=q-a"); + game.OpenUrlExternally(@"https://github.com/ppy/osu/discussions/new?title=Realm%20migration%20issue&body=Please%20drag%20the%20""attach_me.zip""%20file%20here!&category=q-a", true); const string attachment_filename = "attach_me.zip"; const string backup_folder = "backups"; diff --git a/osu.Game/Online/Chat/ExternalLinkOpener.cs b/osu.Game/Online/Chat/ExternalLinkOpener.cs index 8407e2ca6a..328b43c4e8 100644 --- a/osu.Game/Online/Chat/ExternalLinkOpener.cs +++ b/osu.Game/Online/Chat/ExternalLinkOpener.cs @@ -27,9 +27,9 @@ namespace osu.Game.Online.Chat externalLinkWarning = config.GetBindable(OsuSetting.ExternalLinkWarning); } - public void OpenUrlExternally(string url) + public void OpenUrlExternally(string url, bool bypassWarning = false) { - if (externalLinkWarning.Value) + if (!bypassWarning && externalLinkWarning.Value) dialogOverlay.Push(new ExternalLinkDialog(url, () => host.OpenUrlExternally(url))); else host.OpenUrlExternally(url); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c2e1b25d94..5b58dec0c3 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -357,12 +357,12 @@ namespace osu.Game } }); - public void OpenUrlExternally(string url) => waitForReady(() => externalLinkOpener, _ => + public void OpenUrlExternally(string url, bool bypassExternalUrlWarning = false) => waitForReady(() => externalLinkOpener, _ => { if (url.StartsWith('/')) url = $"{API.APIEndpointUrl}{url}"; - externalLinkOpener.OpenUrlExternally(url); + externalLinkOpener.OpenUrlExternally(url, bypassExternalUrlWarning); }); /// From fb081384e13ca4a09bee571636021ea0ce894dc2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 14:55:52 +0900 Subject: [PATCH 0305/5199] Add safety against zip creation potentially failing (probably can't but still) --- osu.Game/Database/EFToRealmMigrator.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 05bc86a7c0..723b3eeada 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -166,11 +166,15 @@ namespace osu.Game.Database backupStorage.Delete(attachment_filename); - using (var zip = ZipArchive.Create()) + try { - zip.AddAllFromDirectory(backupStorage.GetFullPath(string.Empty)); - zip.SaveTo(Path.Combine(backupStorage.GetFullPath(string.Empty), attachment_filename), new ZipWriterOptions(CompressionType.Deflate)); + using (var zip = ZipArchive.Create()) + { + zip.AddAllFromDirectory(backupStorage.GetFullPath(string.Empty)); + zip.SaveTo(Path.Combine(backupStorage.GetFullPath(string.Empty), attachment_filename), new ZipWriterOptions(CompressionType.Deflate)); + } } + catch { } backupStorage.PresentFileExternally(attachment_filename); From 67ccb879926f309f3b2d3dbd3bc31a2a8760b05e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 14:56:04 +0900 Subject: [PATCH 0306/5199] Add exception message to discussion template url --- osu.Game/Database/EFToRealmMigrator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 723b3eeada..639bf10e19 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -157,7 +157,7 @@ namespace osu.Game.Database Text = "IMPORTANT: During data migration, some of your data could not be successfully migrated. The previous version has been backed up.\n\nFor further assistance, please open a discussion on github and attach your backup files (click to get started).", Activated = () => { - game.OpenUrlExternally(@"https://github.com/ppy/osu/discussions/new?title=Realm%20migration%20issue&body=Please%20drag%20the%20""attach_me.zip""%20file%20here!&category=q-a", true); + game.OpenUrlExternally($@"https://github.com/ppy/osu/discussions/new?title=Realm%20migration%20issue ({t.Exception.Message})&body=Please%20drag%20the%20""attach_me.zip""%20file%20here!&category=q-a", true); const string attachment_filename = "attach_me.zip"; const string backup_folder = "backups"; From f30d63107a73c1a4f059366a12483f086e26c22f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 15:19:27 +0900 Subject: [PATCH 0307/5199] Add `SortID` to `RulesetInfo` to allow stable ordering of rulesets for display --- .../NonVisual/RulesetInfoOrderingTest.cs | 38 +++++++++++++++++++ osu.Game/Rulesets/RulesetInfo.cs | 5 +++ osu.Game/Rulesets/RulesetStore.cs | 5 +-- .../Select/Carousel/CarouselBeatmap.cs | 2 +- .../Select/Carousel/CarouselBeatmapSet.cs | 2 +- 5 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs diff --git a/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs b/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs new file mode 100644 index 0000000000..e7c04d27c1 --- /dev/null +++ b/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs @@ -0,0 +1,38 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Osu; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class RulesetInfoOrderingTest + { + [Test] + public void TestOrdering() + { + var rulesets = new[] + { + new RulesetInfo("custom2", "Custom Ruleset 2", string.Empty, -1), + new OsuRuleset().RulesetInfo, + new RulesetInfo("custom3", "Custom Ruleset 3", string.Empty, -1), + new RulesetInfo("custom2", "Custom Ruleset 2", string.Empty, -1), + new CatchRuleset().RulesetInfo, + new RulesetInfo("custom3", "Custom Ruleset 3", string.Empty, -1), + }; + + var orderedRulesets = rulesets.OrderBy(r => r.SortID); + + // Ensure all customs are after official. + Assert.That(orderedRulesets.Select(r => r.OnlineID), Is.EqualTo(new[] { 0, 2, -1, -1, -1, -1 })); + + // Ensure customs are grouped next to each other (ie. stably sorted). + Assert.That(orderedRulesets.SkipWhile(r => r.ShortName != "custom2").Skip(1).First().ShortName, Is.EqualTo("custom2")); + Assert.That(orderedRulesets.SkipWhile(r => r.ShortName != "custom3").Skip(1).First().ShortName, Is.EqualTo("custom3")); + } + } +} diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 2e2ec5c024..d721926e95 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -24,6 +24,11 @@ namespace osu.Game.Rulesets public string InstantiationInfo { get; set; } = string.Empty; + /// + /// A best effort sort ID which provides stable ordering and puts online rulesets before non-online rulesets. + /// + public int SortID => OnlineID >= 0 ? OnlineID : Math.Abs(ShortName.GetHashCode()); + public RulesetInfo(string shortName, string name, string instantiationInfo, int onlineID) { ShortName = shortName; diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 0f518be639..a9fd8f430e 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -163,10 +163,7 @@ namespace osu.Game.Rulesets } } - // add known official rulesets first.. - availableRulesets.AddRange(detachedRulesets.Where(r => r.OnlineID >= 0).OrderBy(r => r.OnlineID)); - // .. then add any customs - availableRulesets.AddRange(detachedRulesets.Where(r => r.OnlineID < 0).OrderBy(r => r.ShortName)); + availableRulesets.AddRange(detachedRulesets.OrderBy(r => r.SortID)); }); } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index e6dee0233f..9d49fdaf6e 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -89,7 +89,7 @@ namespace osu.Game.Screens.Select.Carousel { default: case SortMode.Difficulty: - int ruleset = BeatmapInfo.RulesetID.CompareTo(otherBeatmap.BeatmapInfo.RulesetID); + int ruleset = BeatmapInfo.Ruleset.SortID.CompareTo(otherBeatmap.BeatmapInfo.Ruleset.SortID); if (ruleset != 0) return ruleset; return BeatmapInfo.StarRating.CompareTo(otherBeatmap.BeatmapInfo.StarRating); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index b2b3b5411c..274be7b73a 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Select.Carousel beatmapSet.Beatmaps .Where(b => !b.Hidden) - .OrderBy(b => b.RulesetID) + .OrderBy(b => b.Ruleset.SortID) .ThenBy(b => b.StarRating) .Select(b => new CarouselBeatmap(b)) .ForEach(AddChild); From e67b1fe0ec9c50b7ddb429edb2ee13d534e881ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 15:25:56 +0900 Subject: [PATCH 0308/5199] Make `Ruleset.RulesetInfo` get only --- osu.Game/Rulesets/Ruleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index d279f6d6ee..616540b59c 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets [ExcludeFromDynamicCompile] public abstract class Ruleset { - public RulesetInfo RulesetInfo { get; internal set; } + public RulesetInfo RulesetInfo { get; } private static readonly ConcurrentDictionary mod_reference_cache = new ConcurrentDictionary(); From 714177cce13a86d3665d1685831da3a1e30c7382 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 15:23:16 +0900 Subject: [PATCH 0309/5199] Remove pointless constructor in `RulesetInfo` --- osu.Game.Tests/Database/RealmTest.cs | 2 +- osu.Game/Rulesets/RulesetInfo.cs | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/osu.Game.Tests/Database/RealmTest.cs b/osu.Game.Tests/Database/RealmTest.cs index c2339dd9ad..838759c991 100644 --- a/osu.Game.Tests/Database/RealmTest.cs +++ b/osu.Game.Tests/Database/RealmTest.cs @@ -114,7 +114,7 @@ namespace osu.Game.Tests.Database } protected static RulesetInfo CreateRuleset() => - new RulesetInfo(0, "osu!", "osu", true); + new RulesetInfo("osu", "osu!", string.Empty, 0) { Available = true }; private class RealmTestGame : Framework.Game { diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 2e2ec5c024..5171f0d63c 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -37,14 +37,6 @@ namespace osu.Game.Rulesets { } - public RulesetInfo(int? onlineID, string name, string shortName, bool available) - { - OnlineID = onlineID ?? -1; - Name = name; - ShortName = shortName; - Available = available; - } - public bool Available { get; set; } public bool Equals(RulesetInfo? other) From 5288eedd311feabd82cebf1ddf19336557197016 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 15:19:48 +0900 Subject: [PATCH 0310/5199] Update all usages of `RulesetID` and `Ruleset.ID` to use `Ruleset.OnlineID` --- .../ManiaFilterCriteria.cs | 2 +- .../Beatmaps/TaikoBeatmapConverter.cs | 2 +- .../Formats/LegacyBeatmapDecoderTest.cs | 2 +- .../Formats/LegacyBeatmapEncoderTest.cs | 2 +- .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- .../Editing/TestSceneDifficultySwitching.cs | 4 +- .../Editing/TestSceneEditorTestGameplay.cs | 2 +- .../TestScenePlayerScoreSubmission.cs | 4 +- .../Visual/Gameplay/TestSceneSpectator.cs | 2 +- .../Visual/Multiplayer/QueueModeTestScene.cs | 4 +- .../TestSceneMultiSpectatorScreen.cs | 2 +- .../Multiplayer/TestSceneMultiplayer.cs | 48 +++++++++---------- .../TestSceneMultiplayerMatchSongSelect.cs | 4 +- .../TestSceneMultiplayerPlaylist.cs | 2 +- .../TestSceneMultiplayerQueueList.cs | 2 +- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 8 ++-- .../SongSelect/TestSceneBeatmapCarousel.cs | 2 +- .../SongSelect/TestScenePlaySongSelect.cs | 26 +++++----- .../Components/TournamentModIcon.cs | 2 +- osu.Game/Beatmaps/BeatmapInfo.cs | 1 - osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs | 2 +- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 8 ++-- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 18 ++++--- osu.Game/Rulesets/RulesetInfo.cs | 6 --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 4 +- .../Select/Carousel/CarouselBeatmap.cs | 2 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- osu.Game/Stores/BeatmapImporter.cs | 4 +- .../Tests/Beatmaps/BeatmapConversionTest.cs | 2 +- .../Tests/Beatmaps/HitObjectSampleTest.cs | 2 +- osu.Game/Tests/Visual/OsuTestScene.cs | 2 +- 33 files changed, 89 insertions(+), 90 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs index 0290230490..c8832dfdfb 100644 --- a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs +++ b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania public bool Matches(BeatmapInfo beatmapInfo) { - return !keys.HasFilter || (beatmapInfo.RulesetID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmapInfo))); + return !keys.HasFilter || (beatmapInfo.Ruleset.OnlineID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmapInfo))); } public bool TryParseCustomKeywordCriteria(string key, Operator op, string value) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 613874b7d6..b1d8575de4 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps Beatmap converted = base.ConvertBeatmap(original, cancellationToken); - if (original.BeatmapInfo.RulesetID == 3) + if (original.BeatmapInfo.Ruleset.OnlineID == 3) { // Post processing step to transform mania hit objects with the same start time into strong hits converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x => diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 0459753b28..1edd21b5a9 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -60,7 +60,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(0, beatmapInfo.AudioLeadIn); Assert.AreEqual(164471, metadata.PreviewTime); Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); - Assert.IsTrue(beatmapInfo.RulesetID == 0); + Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); Assert.IsFalse(beatmapInfo.LetterboxInBreaks); Assert.IsFalse(beatmapInfo.SpecialStyle); Assert.IsFalse(beatmapInfo.WidescreenStoryboard); diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index d12da1a22f..d19b3c71f1 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -195,7 +195,7 @@ namespace osu.Game.Tests.Beatmaps.Formats private IBeatmap convert(IBeatmap beatmap) { - switch (beatmap.BeatmapInfo.RulesetID) + switch (beatmap.BeatmapInfo.Ruleset.OnlineID) { case 0: beatmap.BeatmapInfo.Ruleset = new OsuRuleset().RulesetInfo; diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 06ed638e0a..2eb75259d9 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(0, beatmapInfo.AudioLeadIn); Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); Assert.AreEqual(false, beatmapInfo.SpecialStyle); - Assert.IsTrue(beatmapInfo.RulesetID == 0); + Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks); Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard); Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs index cf6488f721..81cb286058 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -82,8 +82,8 @@ namespace osu.Game.Tests.Visual.Editing 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); + ? importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.Ruleset.ShortName == Beatmap.Value.BeatmapInfo.Ruleset.ShortName) + : importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.Ruleset.ShortName != Beatmap.Value.BeatmapInfo.Ruleset.ShortName); }); switchToDifficulty(() => targetDifficulty); confirmEditingBeatmap(() => targetDifficulty); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index 79afc8cf27..2b827bee74 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Editing } protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) - => beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0)); + => beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)); protected override void LoadEditor() { diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index a4a4f351ec..a9675a2ee2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -110,7 +110,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); AddAssert("ensure passing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == true); - AddAssert("submitted score has correct ruleset ID", () => Player.SubmittedScore?.ScoreInfo.RulesetID == new TaikoRuleset().RulesetInfo.ID); + AddAssert("submitted score has correct ruleset ID", () => Player.SubmittedScore?.ScoreInfo.Ruleset.ShortName == new TaikoRuleset().RulesetInfo.ShortName); } [Test] @@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); AddAssert("ensure passing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == true); - AddAssert("submitted score has correct ruleset ID", () => Player.SubmittedScore?.ScoreInfo.RulesetID == new ManiaRuleset().RulesetInfo.ID); + AddAssert("submitted score has correct ruleset ID", () => Player.SubmittedScore?.ScoreInfo.Ruleset.ShortName == new ManiaRuleset().RulesetInfo.ShortName); } [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 8b7e1a1d85..0afd2c76dd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("import beatmap", () => { importedBeatmap = BeatmapImportHelper.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely(); - importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.RulesetID == 0).OnlineID; + importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.Ruleset.OnlineID == 0).OnlineID; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 4cd19b53a4..c79395b343 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -60,8 +60,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); importedSet = beatmaps.GetAllUsableBeatmapSets().First(); - InitialBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); - OtherBeatmap = importedSet.Beatmaps.Last(b => b.RulesetID == 0); + InitialBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0); + OtherBeatmap = importedSet.Beatmaps.Last(b => b.Ruleset.OnlineID == 0); }); AddStep("load multiplayer", () => LoadScreen(multiplayerComponents = new TestMultiplayerComponents())); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 9d67742e4d..b84f7760e4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void load() { importedSet = BeatmapImportHelper.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely(); - importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); + importedBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0); importedBeatmapId = importedBeatmap.OnlineID; } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 3f151a0ae8..3563869d8b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -234,7 +234,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -256,7 +256,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -286,7 +286,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -317,7 +317,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -339,7 +339,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -372,7 +372,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -392,7 +392,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -414,7 +414,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -453,7 +453,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -492,7 +492,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -531,7 +531,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -565,7 +565,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -605,7 +605,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -625,7 +625,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, AllowedMods = { new OsuModHidden() } } @@ -665,7 +665,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -696,7 +696,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -714,7 +714,7 @@ namespace osu.Game.Tests.Visual.Multiplayer roomManager.ServerSideRooms[0].Playlist.Add(new PlaylistItem { ID = 2, - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, }); }); @@ -742,7 +742,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -778,7 +778,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -817,7 +817,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -828,7 +828,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 })); AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem { - BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo.OnlineID + BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo.OnlineID }))); AddUntilStep("item arrived in playlist", () => client.Room?.Playlist.Count == 2); @@ -848,7 +848,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -859,7 +859,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 })); AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem { - BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo.OnlineID + BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo.OnlineID }))); AddUntilStep("item arrived in playlist", () => client.Room?.Playlist.Count == 2); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 5465061891..457b53ae61 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -107,7 +107,7 @@ namespace osu.Game.Tests.Visual.Multiplayer BeatmapInfo selectedBeatmap = null; AddStep("select beatmap", - () => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.Where(beatmap => beatmap.RulesetID == new OsuRuleset().LegacyID).ElementAt(1))); + () => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.Where(beatmap => beatmap.Ruleset.OnlineID == new OsuRuleset().LegacyID).ElementAt(1))); AddUntilStep("wait for selection", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap)); AddStep("exit song select", () => songSelect.Exit()); @@ -139,7 +139,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change ruleset", () => Ruleset.Value = new TaikoRuleset().RulesetInfo); AddStep("select beatmap", - () => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.First(beatmap => beatmap.RulesetID == new TaikoRuleset().LegacyID))); + () => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == new TaikoRuleset().LegacyID))); AddUntilStep("wait for selection", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap)); AddStep("set mods", () => SelectedMods.Value = new[] { new TaikoModDoubleTime() }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index d970ab6c34..936798e6b4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); importedSet = beatmaps.GetAllUsableBeatmapSets().First(); - importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); + importedBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0); }); AddStep("change to all players mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index d83421ee3a..ddf794b437 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); importedSet = beatmaps.GetAllUsableBeatmapSets().First(); - importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); + importedBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0); }); AddStep("change to all players mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 73c67d26d9..781f0a1824 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -158,7 +158,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 0298c3bea9..830631ebad 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -586,7 +586,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("filter to ruleset 0", () => carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) }, false)); AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testMixed.Beatmaps[1], false)); - AddAssert("unfiltered beatmap not selected", () => carousel.SelectedBeatmapInfo.RulesetID == 0); + AddAssert("unfiltered beatmap not selected", () => carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 0); AddStep("remove mixed set", () => { diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 458c6130c7..136173c8a7 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -325,10 +325,10 @@ namespace osu.Game.Tests.Visual.SongSelect changeRuleset(2); addRulesetImportStep(2); addRulesetImportStep(1); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.RulesetID == 2); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 2); changeRuleset(1); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.RulesetID == 1); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 1); changeRuleset(0); AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmapInfo == null); @@ -341,7 +341,7 @@ namespace osu.Game.Tests.Visual.SongSelect changeRuleset(2); addRulesetImportStep(2); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.RulesetID == 2); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 2); addRulesetImportStep(0); addRulesetImportStep(0); @@ -352,7 +352,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select beatmap/ruleset externally", () => { target = manager.GetAllUsableBeatmapSets() - .Last(b => b.Beatmaps.Any(bi => bi.RulesetID == 0)).Beatmaps.Last(); + .Last(b => b.Beatmaps.Any(bi => bi.Ruleset.OnlineID == 0)).Beatmaps.Last(); Ruleset.Value = rulesets.AvailableRulesets.First(r => r.OnlineID == 0); Beatmap.Value = manager.GetWorkingBeatmap(target); @@ -371,7 +371,7 @@ namespace osu.Game.Tests.Visual.SongSelect changeRuleset(2); addRulesetImportStep(2); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.RulesetID == 2); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 2); addRulesetImportStep(0); addRulesetImportStep(0); @@ -382,7 +382,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select beatmap/ruleset externally", () => { target = manager.GetAllUsableBeatmapSets() - .Last(b => b.Beatmaps.Any(bi => bi.RulesetID == 0)).Beatmaps.Last(); + .Last(b => b.Beatmaps.Any(bi => bi.Ruleset.OnlineID == 0)).Beatmaps.Last(); Beatmap.Value = manager.GetWorkingBeatmap(target); Ruleset.Value = rulesets.AvailableRulesets.First(r => r.OnlineID == 0); @@ -493,9 +493,9 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select beatmap externally", () => { target = manager.GetAllUsableBeatmapSets() - .First(b => b.Beatmaps.Any(bi => bi.RulesetID == targetRuleset)) + .First(b => b.Beatmaps.Any(bi => bi.Ruleset.OnlineID == targetRuleset)) .Beatmaps - .First(bi => bi.RulesetID == targetRuleset); + .First(bi => bi.Ruleset.OnlineID == targetRuleset); Beatmap.Value = manager.GetWorkingBeatmap(target); }); @@ -544,7 +544,7 @@ namespace osu.Game.Tests.Visual.SongSelect { target = manager .GetAllUsableBeatmapSets() - .First(b => b.Beatmaps.Any(bi => bi.RulesetID == 1)) + .First(b => b.Beatmaps.Any(bi => bi.Ruleset.OnlineID == 1)) .Beatmaps.First(); Beatmap.Value = manager.GetWorkingBeatmap(target); @@ -799,8 +799,8 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestChangeRulesetWhilePresentingScore() { - BeatmapInfo getPresentBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.RulesetID == 0); - BeatmapInfo getSwitchBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.RulesetID == 1); + BeatmapInfo getPresentBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.Ruleset.OnlineID == 0); + BeatmapInfo getSwitchBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.Ruleset.OnlineID == 1); changeRuleset(0); @@ -831,8 +831,8 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestChangeBeatmapWhilePresentingScore() { - BeatmapInfo getPresentBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.RulesetID == 0); - BeatmapInfo getSwitchBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.RulesetID == 1); + BeatmapInfo getPresentBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.Ruleset.OnlineID == 0); + BeatmapInfo getSwitchBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.Ruleset.OnlineID == 1); changeRuleset(0); diff --git a/osu.Game.Tournament/Components/TournamentModIcon.cs b/osu.Game.Tournament/Components/TournamentModIcon.cs index ed8a36c220..57a0390ac2 100644 --- a/osu.Game.Tournament/Components/TournamentModIcon.cs +++ b/osu.Game.Tournament/Components/TournamentModIcon.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tournament.Components return; } - var ruleset = rulesets.GetRuleset(ladderInfo.Ruleset.Value?.ID ?? 0); + var ruleset = rulesets.GetRuleset(ladderInfo.Ruleset.Value?.OnlineID ?? 0); var modIcon = ruleset?.CreateInstance().CreateModFromAcronym(modAcronym); if (modIcon == null) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 4e1a34ddbf..3e6e33f1d0 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -155,7 +155,6 @@ namespace osu.Game.Beatmaps [Ignored] public int RulesetID { - get => Ruleset.OnlineID; set { if (!string.IsNullOrEmpty(Ruleset.InstantiationInfo)) diff --git a/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs b/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs index dc8201a402..163da12b2e 100644 --- a/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs @@ -30,7 +30,7 @@ namespace osu.Game.Beatmaps { this.beatmap = beatmap; - beatmap.BeatmapInfo.Ruleset = rulesetProvider(beatmap.BeatmapInfo.RulesetID).RulesetInfo; + beatmap.BeatmapInfo.Ruleset = rulesetProvider(beatmap.BeatmapInfo.Ruleset.OnlineID).RulesetInfo; if (beatmapId.HasValue) beatmap.BeatmapInfo.OnlineID = beatmapId.Value; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 8f3f05aa9f..35d1cefeca 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -141,9 +141,11 @@ namespace osu.Game.Beatmaps.Formats break; case @"Mode": - beatmap.BeatmapInfo.RulesetID = Parsing.ParseInt(pair.Value); + int rulesetID = Parsing.ParseInt(pair.Value); - switch (beatmap.BeatmapInfo.RulesetID) + beatmap.BeatmapInfo.RulesetID = rulesetID; + + switch (rulesetID) { case 0: parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion); @@ -397,7 +399,7 @@ namespace osu.Game.Beatmaps.Formats OmitFirstBarLine = omitFirstBarSignature, }; - bool isOsuRuleset = beatmap.BeatmapInfo.RulesetID == 0; + bool isOsuRuleset = beatmap.BeatmapInfo.Ruleset.OnlineID == 0; // scrolling rulesets use effect points rather than difficulty points for scroll speed adjustments. if (!isOsuRuleset) effectPoint.ScrollSpeed = speedMultiplier; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 9d848fd8a4..7ddbc2f768 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -35,6 +35,8 @@ namespace osu.Game.Beatmaps.Formats [CanBeNull] private readonly ISkin skin; + private readonly int onlineRulesetID; + /// /// Creates a new . /// @@ -45,7 +47,9 @@ namespace osu.Game.Beatmaps.Formats this.beatmap = beatmap; this.skin = skin; - if (beatmap.BeatmapInfo.RulesetID < 0 || beatmap.BeatmapInfo.RulesetID > 3) + onlineRulesetID = beatmap.BeatmapInfo.Ruleset.OnlineID; + + if (onlineRulesetID < 0 || onlineRulesetID > 3) throw new ArgumentException("Only beatmaps in the osu, taiko, catch, or mania rulesets can be encoded to the legacy beatmap format.", nameof(beatmap)); } @@ -88,7 +92,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.BeatmapInfo.Countdown}")); writer.WriteLine(FormattableString.Invariant($"SampleSet: {toLegacySampleBank((beatmap.HitObjects.FirstOrDefault()?.SampleControlPoint ?? SampleControlPoint.DEFAULT).SampleBank)}")); writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.BeatmapInfo.StackLeniency}")); - writer.WriteLine(FormattableString.Invariant($"Mode: {beatmap.BeatmapInfo.RulesetID}")); + writer.WriteLine(FormattableString.Invariant($"Mode: {onlineRulesetID}")); writer.WriteLine(FormattableString.Invariant($"LetterboxInBreaks: {(beatmap.BeatmapInfo.LetterboxInBreaks ? '1' : '0')}")); // if (beatmap.BeatmapInfo.UseSkinSprites) // writer.WriteLine(@"UseSkinSprites: 1"); @@ -102,7 +106,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(@"EpilepsyWarning: 1"); if (beatmap.BeatmapInfo.CountdownOffset > 0) writer.WriteLine(FormattableString.Invariant($@"CountdownOffset: {beatmap.BeatmapInfo.CountdownOffset}")); - if (beatmap.BeatmapInfo.RulesetID == 3) + if (onlineRulesetID == 3) writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.BeatmapInfo.SpecialStyle ? '1' : '0')}")); writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.BeatmapInfo.WidescreenStoryboard ? '1' : '0')}")); if (beatmap.BeatmapInfo.SamplesMatchPlaybackRate) @@ -147,7 +151,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"ApproachRate: {beatmap.Difficulty.ApproachRate}")); // Taiko adjusts the slider multiplier (see: LEGACY_TAIKO_VELOCITY_MULTIPLIER) - writer.WriteLine(beatmap.BeatmapInfo.RulesetID == 1 + writer.WriteLine(onlineRulesetID == 1 ? FormattableString.Invariant($"SliderMultiplier: {beatmap.Difficulty.SliderMultiplier / LEGACY_TAIKO_VELOCITY_MULTIPLIER}") : FormattableString.Invariant($"SliderMultiplier: {beatmap.Difficulty.SliderMultiplier}")); @@ -179,7 +183,7 @@ namespace osu.Game.Beatmaps.Formats SampleControlPoint lastRelevantSamplePoint = null; DifficultyControlPoint lastRelevantDifficultyPoint = null; - bool isOsuRuleset = beatmap.BeatmapInfo.RulesetID == 0; + bool isOsuRuleset = onlineRulesetID == 0; // iterate over hitobjects and pull out all required sample and difficulty changes extractDifficultyControlPoints(beatmap.HitObjects); @@ -318,7 +322,7 @@ namespace osu.Game.Beatmaps.Formats { Vector2 position = new Vector2(256, 192); - switch (beatmap.BeatmapInfo.RulesetID) + switch (onlineRulesetID) { case 0: case 2: @@ -372,7 +376,7 @@ namespace osu.Game.Beatmaps.Formats break; case IHasDuration _: - if (beatmap.BeatmapInfo.RulesetID == 3) + if (onlineRulesetID == 3) type |= LegacyHitObjectType.Hold; else type |= LegacyHitObjectType.Spinner; diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 2e2ec5c024..e2007c910e 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -98,11 +98,5 @@ namespace osu.Game.Rulesets return ruleset; } - - #region Compatibility properties - - public int ID => OnlineID; - - #endregion } } diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 2902ff7848..9460ec680c 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -34,7 +34,7 @@ namespace osu.Game.Scoring.Legacy this.score = score; this.beatmap = beatmap; - if (score.ScoreInfo.BeatmapInfo.RulesetID < 0 || score.ScoreInfo.BeatmapInfo.RulesetID > 3) + if (score.ScoreInfo.BeatmapInfo.Ruleset.OnlineID < 0 || score.ScoreInfo.BeatmapInfo.Ruleset.OnlineID > 3) throw new ArgumentException("Only scores in the osu, taiko, catch, or mania rulesets can be encoded to the legacy score format.", nameof(score)); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b42f629aad..55b01bd8ab 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -338,7 +338,7 @@ namespace osu.Game.Screens.Edit public EditorState GetState([CanBeNull] BeatmapInfo nextBeatmap = null) => new EditorState { Time = clock.CurrentTimeAccurate, - ClipboardContent = nextBeatmap == null || editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? Clipboard.Content.Value : string.Empty + ClipboardContent = nextBeatmap == null || editorBeatmap.BeatmapInfo.Ruleset.ShortName == nextBeatmap.Ruleset.ShortName ? Clipboard.Content.Value : string.Empty }; /// @@ -780,7 +780,7 @@ namespace osu.Game.Screens.Edit var difficultyItems = new List(); - foreach (var rulesetBeatmaps in beatmapSet.Beatmaps.GroupBy(b => b.RulesetID).OrderBy(group => group.Key)) + foreach (var rulesetBeatmaps in beatmapSet.Beatmaps.GroupBy(b => b.Ruleset.ShortName).OrderBy(group => group.Key)) { if (difficultyItems.Count > 0) difficultyItems.Add(new EditorMenuItemSpacer()); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index e6dee0233f..3962f7b26f 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Select.Carousel bool match = criteria.Ruleset == null || BeatmapInfo.Ruleset.ShortName == criteria.Ruleset.ShortName || - (BeatmapInfo.RulesetID == 0 && criteria.Ruleset.OnlineID != 0 && criteria.AllowConvertedBeatmaps); + (BeatmapInfo.Ruleset.OnlineID == 0 && criteria.Ruleset.OnlineID != 0 && criteria.AllowConvertedBeatmaps); if (BeatmapInfo.BeatmapSet?.Equals(criteria.SelectedBeatmapSet) == true) { diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index f25997650b..2070e53257 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -155,7 +155,7 @@ namespace osu.Game.Screens.Select.Leaderboards var scores = r.All() .AsEnumerable() // TODO: update to use a realm filter directly (or at least figure out the beatmap part to reduce scope). - .Where(s => !s.DeletePending && s.BeatmapInfo.ID == fetchBeatmapInfo.ID && s.Ruleset.OnlineID == ruleset.Value.ID); + .Where(s => !s.DeletePending && s.BeatmapInfo.ID == fetchBeatmapInfo.ID && s.Ruleset.ShortName == ruleset.Value.ShortName); if (filterMods && !mods.Value.Any()) { diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 837f30eb2b..10150fcd9f 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -502,7 +502,7 @@ namespace osu.Game.Screens.Select // clear pending task immediately to track any potential nested debounce operation. selectionChangedDebounce = null; - Logger.Log($"updating selection with beatmap:{beatmap?.ID.ToString() ?? "null"} ruleset:{ruleset?.ID.ToString() ?? "null"}"); + Logger.Log($"updating selection with beatmap:{beatmap?.ID.ToString() ?? "null"} ruleset:{ruleset?.ShortName ?? "null"}"); if (transferRulesetValue()) { diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index a6f20c8d4f..709dd67087 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -219,11 +219,11 @@ namespace osu.Game.Stores var decodedInfo = decoded.BeatmapInfo; var decodedDifficulty = decodedInfo.Difficulty; - var ruleset = realm.All().FirstOrDefault(r => r.OnlineID == decodedInfo.RulesetID); + var ruleset = realm.All().FirstOrDefault(r => r.OnlineID == decodedInfo.Ruleset.OnlineID); if (ruleset?.Available != true) { - Logger.Log($"Skipping import of {file.Filename} due to missing local ruleset {decodedInfo.RulesetID}.", LoggingTarget.Database); + Logger.Log($"Skipping import of {file.Filename} due to missing local ruleset {decodedInfo.Ruleset.OnlineID}.", LoggingTarget.Database); continue; } diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 897d4363f1..8d622955b7 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -175,7 +175,7 @@ namespace osu.Game.Tests.Beatmaps var beatmap = decoder.Decode(stream); var rulesetInstance = CreateRuleset(); - beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.RulesetID == rulesetInstance.RulesetInfo.ID ? rulesetInstance.RulesetInfo : new RulesetInfo(); + beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.Ruleset.OnlineID == rulesetInstance.RulesetInfo.OnlineID ? rulesetInstance.RulesetInfo : new RulesetInfo(); return beatmap; } diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index f7e154b5e7..2a3e51b4f5 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Beatmaps currentTestBeatmap = Decoder.GetDecoder(reader).Decode(reader); // populate ruleset for beatmap converters that require it to be present. - var ruleset = rulesetStore.GetRuleset(currentTestBeatmap.BeatmapInfo.RulesetID); + var ruleset = rulesetStore.GetRuleset(currentTestBeatmap.BeatmapInfo.Ruleset.OnlineID); Debug.Assert(ruleset != null); diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 42e96f80ca..ec02655544 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -282,7 +282,7 @@ namespace osu.Game.Tests.Visual Status = beatmap.Status, Checksum = beatmap.MD5Hash, AuthorID = beatmap.Metadata.Author.OnlineID, - RulesetID = beatmap.RulesetID, + RulesetID = beatmap.Ruleset.OnlineID, StarRating = beatmap.StarRating, DifficultyName = beatmap.DifficultyName, } From 5637fd64d641dcf147d7f9e2d17532bf351264ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 15:59:20 +0900 Subject: [PATCH 0311/5199] Perform ordering using `IComparable` instead --- .../NonVisual/RulesetInfoOrderingTest.cs | 2 +- osu.Game/Rulesets/EFRulesetInfo.cs | 2 ++ osu.Game/Rulesets/IRulesetInfo.cs | 2 +- osu.Game/Rulesets/RulesetInfo.cs | 19 ++++++++++++++----- osu.Game/Rulesets/RulesetStore.cs | 2 +- .../Select/Carousel/CarouselBeatmap.cs | 2 +- .../Select/Carousel/CarouselBeatmapSet.cs | 2 +- 7 files changed, 21 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs b/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs index e7c04d27c1..ae999d08d5 100644 --- a/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs +++ b/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.NonVisual new RulesetInfo("custom3", "Custom Ruleset 3", string.Empty, -1), }; - var orderedRulesets = rulesets.OrderBy(r => r.SortID); + var orderedRulesets = rulesets.OrderBy(r => r); // Ensure all customs are after official. Assert.That(orderedRulesets.Select(r => r.OnlineID), Is.EqualTo(new[] { 0, 2, -1, -1, -1, -1 })); diff --git a/osu.Game/Rulesets/EFRulesetInfo.cs b/osu.Game/Rulesets/EFRulesetInfo.cs index 9559cfa00c..b4a64fe932 100644 --- a/osu.Game/Rulesets/EFRulesetInfo.cs +++ b/osu.Game/Rulesets/EFRulesetInfo.cs @@ -42,6 +42,8 @@ namespace osu.Game.Rulesets public bool Equals(EFRulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo; + public int CompareTo(RulesetInfo other) => ID?.CompareTo(other.ID) ?? -1; + public override bool Equals(object obj) => obj is EFRulesetInfo rulesetInfo && Equals(rulesetInfo); public bool Equals(IRulesetInfo other) => other is RulesetInfo b && Equals(b); diff --git a/osu.Game/Rulesets/IRulesetInfo.cs b/osu.Game/Rulesets/IRulesetInfo.cs index 6599e0d59d..44731a2495 100644 --- a/osu.Game/Rulesets/IRulesetInfo.cs +++ b/osu.Game/Rulesets/IRulesetInfo.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets /// /// A representation of a ruleset's metadata. /// - public interface IRulesetInfo : IHasOnlineID, IEquatable + public interface IRulesetInfo : IHasOnlineID, IEquatable, IComparable { /// /// The user-exposed name of this ruleset. diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index d721926e95..896b9ee279 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -24,11 +24,6 @@ namespace osu.Game.Rulesets public string InstantiationInfo { get; set; } = string.Empty; - /// - /// A best effort sort ID which provides stable ordering and puts online rulesets before non-online rulesets. - /// - public int SortID => OnlineID >= 0 ? OnlineID : Math.Abs(ShortName.GetHashCode()); - public RulesetInfo(string shortName, string name, string instantiationInfo, int onlineID) { ShortName = shortName; @@ -62,6 +57,20 @@ namespace osu.Game.Rulesets public bool Equals(IRulesetInfo? other) => other is RulesetInfo b && Equals(b); + public int CompareTo(RulesetInfo other) + { + // Official rulesets are always given precedence for the time being. + if (OnlineID >= 0) + { + if (other.OnlineID >= 0) + return OnlineID.CompareTo(other.OnlineID); + + return -1; + } + + return string.Compare(ShortName, other.ShortName, StringComparison.Ordinal); + } + public override int GetHashCode() { // Importantly, ignore the underlying realm hash code, as it will usually not match. diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index a9fd8f430e..d017d54ed9 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -163,7 +163,7 @@ namespace osu.Game.Rulesets } } - availableRulesets.AddRange(detachedRulesets.OrderBy(r => r.SortID)); + availableRulesets.AddRange(detachedRulesets.OrderBy(r => r)); }); } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 9d49fdaf6e..0045e7ba4f 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -89,7 +89,7 @@ namespace osu.Game.Screens.Select.Carousel { default: case SortMode.Difficulty: - int ruleset = BeatmapInfo.Ruleset.SortID.CompareTo(otherBeatmap.BeatmapInfo.Ruleset.SortID); + int ruleset = BeatmapInfo.Ruleset.CompareTo(otherBeatmap.BeatmapInfo.Ruleset); if (ruleset != 0) return ruleset; return BeatmapInfo.StarRating.CompareTo(otherBeatmap.BeatmapInfo.StarRating); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 274be7b73a..fc4b6c27f3 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Select.Carousel beatmapSet.Beatmaps .Where(b => !b.Hidden) - .OrderBy(b => b.Ruleset.SortID) + .OrderBy(b => b.Ruleset) .ThenBy(b => b.StarRating) .Select(b => new CarouselBeatmap(b)) .ForEach(AddChild); From b87d1a61a8d2d2d48674ba3bed92568cabef7c25 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 16:39:36 +0900 Subject: [PATCH 0312/5199] Fix `ButtonSystem` null reference crash due to missing null check in delayed animations ```csharp [runtime] 2022-01-27 07:36:34 [error]: System.NullReferenceException: Object reference not set to an instance of an object. [runtime] 2022-01-27 07:36:34 [error]: at osu.Game.Screens.Menu.ButtonSystem.<>c__DisplayClass56_0.b__1() in /Users/dean/Projects/osu/osu.Game/Screens/Menu/ButtonSystem.cs:line 357 [runtime] 2022-01-27 07:36:34 [error]: at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal() [runtime] 2022-01-27 07:36:34 [error]: at osu.Framework.Threading.Scheduler.Update() [runtime] 2022-01-27 07:36:34 [error]: at osu.Framework.Graphics.Drawable.UpdateSubTree() ``` --- osu.Game/Screens/Menu/ButtonSystem.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 32731407fd..b03425fef4 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -49,6 +50,7 @@ namespace osu.Game.Screens.Menu public const float BUTTON_WIDTH = 140f; public const float WEDGE_WIDTH = 20; + [CanBeNull] private OsuLogo logo; /// @@ -328,9 +330,9 @@ namespace osu.Game.Screens.Menu game?.Toolbar.Hide(); - logo.ClearTransforms(targetMember: nameof(Position)); - logo.MoveTo(new Vector2(0.5f), 800, Easing.OutExpo); - logo.ScaleTo(1, 800, Easing.OutExpo); + logo?.ClearTransforms(targetMember: nameof(Position)); + logo?.MoveTo(new Vector2(0.5f), 800, Easing.OutExpo); + logo?.ScaleTo(1, 800, Easing.OutExpo); }, buttonArea.Alpha * 150); break; @@ -354,7 +356,7 @@ namespace osu.Game.Screens.Menu logoDelayedAction = Scheduler.AddDelayed(() => { if (impact) - logo.Impact(); + logo?.Impact(); game?.Toolbar.Show(); }, 200); From 0a45aa80cb011e658dc1d7c3504d2fe4ef12f079 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 16:16:49 +0900 Subject: [PATCH 0313/5199] Remove unnecessary double-schedule in `UpdateBeatmapSet` --- osu.Game/Screens/Select/BeatmapCarousel.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index dff2c598c3..94e520ba1c 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -311,13 +311,10 @@ namespace osu.Game.Screens.Select itemsCache.Invalidate(); - Schedule(() => - { - if (!Scroll.UserScrolling) - ScrollToSelected(true); + if (!Scroll.UserScrolling) + ScrollToSelected(true); - BeatmapSetsChanged?.Invoke(); - }); + BeatmapSetsChanged?.Invoke(); }); /// From 449e9bcf5c123e1e1c92534f8f3a710412a5028d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 16:17:38 +0900 Subject: [PATCH 0314/5199] Ensure beatmap carousel scroll position is maintained during deletion operations --- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 94e520ba1c..c27915c383 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -286,6 +286,9 @@ namespace osu.Game.Screens.Select root.RemoveChild(existingSet); itemsCache.Invalidate(); + + if (!Scroll.UserScrolling) + ScrollToSelected(true); }); public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => From f2cecad83b2e1df2b2e15c5cd49e04534cf55dca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 16:38:02 +0900 Subject: [PATCH 0315/5199] Add failing test coverage showing carousel deletions don't keep scroll position --- .../SongSelect/TestSceneBeatmapCarousel.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 0298c3bea9..2f2001c4f2 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets; @@ -40,6 +41,36 @@ namespace osu.Game.Tests.Visual.SongSelect this.rulesets = rulesets; } + [Test] + public void TestScrollPositionMaintainedOnAdd() + { + loadBeatmaps(count: 1, randomDifficulties: false); + + for (int i = 0; i < 10; i++) + { + AddRepeatStep("Add some sets", () => carousel.UpdateBeatmapSet(TestResources.CreateTestBeatmapSetInfo()), 4); + + checkSelectionIsCentered(); + } + } + + [Test] + public void TestScrollPositionMaintainedOnDelete() + { + loadBeatmaps(count: 50, randomDifficulties: false); + + for (int i = 0; i < 10; i++) + { + AddRepeatStep("Remove some sets", () => + carousel.RemoveBeatmapSet(carousel.Items.Select(item => item.Item) + .OfType() + .OrderBy(item => item.GetHashCode()) + .First(item => item.State.Value != CarouselItemState.Selected && item.Visible).BeatmapSet), 4); + + checkSelectionIsCentered(); + } + } + [Test] public void TestManyPanels() { @@ -813,6 +844,18 @@ namespace osu.Game.Tests.Visual.SongSelect carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count); } + private void checkSelectionIsCentered() + { + AddAssert("Selected panel is centered", () => + { + return Precision.AlmostEquals( + carousel.ScreenSpaceDrawQuad.Centre, + carousel.Items + .First(i => i.Item.State.Value == CarouselItemState.Selected) + .ScreenSpaceDrawQuad.Centre, 100); + }); + } + private void checkNoSelection() => AddAssert("Selection is null", () => currentSelection == null); private void nextRandom() => From 3ae5973ab7de887e1908430d3601fbc18301489e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 17:08:12 +0900 Subject: [PATCH 0316/5199] Fix compilation error due to commit split --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 2 +- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 3962f7b26f..1f346d19f0 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -89,7 +89,7 @@ namespace osu.Game.Screens.Select.Carousel { default: case SortMode.Difficulty: - int ruleset = BeatmapInfo.RulesetID.CompareTo(otherBeatmap.BeatmapInfo.RulesetID); + int ruleset = BeatmapInfo.Ruleset.OnlineID.CompareTo(otherBeatmap.BeatmapInfo.Ruleset.OnlineID); if (ruleset != 0) return ruleset; return BeatmapInfo.StarRating.CompareTo(otherBeatmap.BeatmapInfo.StarRating); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index b2b3b5411c..6156fea769 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Select.Carousel beatmapSet.Beatmaps .Where(b => !b.Hidden) - .OrderBy(b => b.RulesetID) + .OrderBy(b => b.Ruleset.OnlineID) .ThenBy(b => b.StarRating) .Select(b => new CarouselBeatmap(b)) .ForEach(AddChild); From 831fa44433059078f0c96d06cf0e69b8686e5d8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 19:35:04 +0900 Subject: [PATCH 0317/5199] Fix song select tests not waiting for beatmap imports to arrive After the change to realm, notification fires could take a frame or two. We aren't accounting for this. Fixes test failures like https://github.com/ppy/osu/runs/4963255990?check_suite_focus=true --- .../SongSelect/TestScenePlaySongSelect.cs | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 458c6130c7..ca0f2b8012 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -13,6 +13,7 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Extensions; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; @@ -73,6 +74,17 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("delete all beatmaps", () => manager?.Delete()); } + public override void TearDownSteps() + { + base.TearDownSteps(); + + AddStep("remove song select", () => + { + songSelect?.Expire(); + songSelect = null; + }); + } + [Test] public void TestSingleFilterOnEnter() { @@ -870,9 +882,16 @@ namespace osu.Game.Tests.Visual.SongSelect return set.ChildrenOfType().ToList().FindIndex(i => i == icon); } - private void addRulesetImportStep(int id) => AddStep($"import test map for ruleset {id}", () => importForRuleset(id)); + private void addRulesetImportStep(int id) + { + Live imported = null; + AddStep($"import test map for ruleset {id}", () => imported = importForRuleset(id)); + // This is specifically for cases where the add is happening post song select load. + // For cases where song select is null, the assertions are provided by the load checks. + AddUntilStep("wait for imported to arrive in carousel", () => songSelect == null || songSelect.Carousel.BeatmapSets.Any(s => s.ID == imported?.ID)); + } - private void importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray())); + private Live importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray())); private void checkMusicPlaying(bool playing) => AddUntilStep($"music {(playing ? "" : "not ")}playing", () => music.IsPlaying == playing); From 1bdf16494baa48fd15715ece296b198f6f60b64f Mon Sep 17 00:00:00 2001 From: Spooghetti420 Date: Thu, 27 Jan 2022 11:35:31 +0000 Subject: [PATCH 0318/5199] Add No Long Notes mod --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 3 +- .../Mods/ManiaModNoLongNotes.cs | 80 +++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 6fc7dc018b..3471f4ca66 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -243,7 +243,8 @@ namespace osu.Game.Rulesets.Mania new ManiaModDifficultyAdjust(), new ManiaModClassic(), new ManiaModInvert(), - new ManiaModConstantSpeed() + new ManiaModConstantSpeed(), + new ManiaModNoLongNotes() }; case ModType.Automation: diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs new file mode 100644 index 0000000000..7295a9eb6e --- /dev/null +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs @@ -0,0 +1,80 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mods; +using osu.Framework.Graphics.Sprites; +using System; +using System.Collections.Generic; +using osu.Game.Audio; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Framework.Utils; +using osu.Game.Overlays.Settings; +using osu.Framework.Bindables; +using osu.Game.Configuration; +using osu.Game.Graphics; + + + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModNoLongNotes : Mod, IApplicableAfterBeatmapConversion + { + + public override string Name => "No Long Notes"; + public override string Acronym => "NL"; + public override double ScoreMultiplier => 1; + public override string Description => @"Turns all held notes into tap notes. No coordination required."; + public override IconUsage? Icon => FontAwesome.Solid.DotCircle; + public override ModType Type => ModType.Conversion; + + [SettingSource("Add end notes", "Also add a note at the end of a held note")] + public BindableBool AddEndNotes { get; } = new BindableBool + { + Default = true, + Value = true + }; + + [SettingSource("Length threshold", "Only add an end note for held notes longer than this threshold (in milliseconds)")] + public BindableNumber Threshold { get; } = new BindableDouble + { + MinValue = 1.0, + MaxValue = 1990.0, + Default = 200.0, + Value = 200.0, + Precision = 1.0, + }; + public void ApplyToBeatmap(IBeatmap beatmap) + { + var maniaBeatmap = (ManiaBeatmap)beatmap; + + var newObjects = new List(); + beatmap.HitObjects.OfType().ForEach(h => + { + // Add a note for the beginning of the hold note + newObjects.Add(new Note + { + Column = h.Column, + StartTime = h.StartTime, + Samples = h.Samples + }); + + // Don't add an end note if the duration is below the threshold, or end notes are disabled + if (AddEndNotes.Value && h.Duration > Threshold.Value) + { + newObjects.Add(new Note + { + Column = h.Column, + StartTime = h.EndTime, + Samples = h.Samples + }); + } + }); + + maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType().Concat(newObjects).OrderBy(h => h.StartTime).ToList(); + } + } +} From 33b7bdcf8260cf0d9090d9edaa186729682b8b3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 21:49:33 +0900 Subject: [PATCH 0319/5199] Update pointless `CompareTo` implementation --- osu.Game/Rulesets/EFRulesetInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/EFRulesetInfo.cs b/osu.Game/Rulesets/EFRulesetInfo.cs index b4a64fe932..ba56adac49 100644 --- a/osu.Game/Rulesets/EFRulesetInfo.cs +++ b/osu.Game/Rulesets/EFRulesetInfo.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets public bool Equals(EFRulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo; - public int CompareTo(RulesetInfo other) => ID?.CompareTo(other.ID) ?? -1; + public int CompareTo(RulesetInfo other) => OnlineID.CompareTo(other.OnlineID); public override bool Equals(object obj) => obj is EFRulesetInfo rulesetInfo && Equals(rulesetInfo); From 2cc69d6b19b2682db3a7c6ec8dd79ccc1421a0d1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 27 Jan 2022 15:57:56 +0300 Subject: [PATCH 0320/5199] Replace `IsControlDragged` with an abstract `ShouldBeExpanded` --- osu.Game/Overlays/ExpandableSlider.cs | 2 +- osu.Game/Overlays/ExpandingControlContainer.cs | 8 +++++++- osu.Game/Overlays/IExpandable.cs | 11 +++++++++++ osu.Game/Overlays/IExpandableControl.cs | 4 ---- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/ExpandableSlider.cs b/osu.Game/Overlays/ExpandableSlider.cs index 524485d806..d346b9b22c 100644 --- a/osu.Game/Overlays/ExpandableSlider.cs +++ b/osu.Game/Overlays/ExpandableSlider.cs @@ -72,7 +72,7 @@ namespace osu.Game.Overlays public BindableBool Expanded { get; } = new BindableBool(); - public bool IsControlDragged => slider.IsDragged; + bool IExpandable.ShouldBeExpanded => IsHovered || slider.IsDragged; public override bool HandlePositionalInput => true; diff --git a/osu.Game/Overlays/ExpandingControlContainer.cs b/osu.Game/Overlays/ExpandingControlContainer.cs index fb6a71ba97..859e4bcd25 100644 --- a/osu.Game/Overlays/ExpandingControlContainer.cs +++ b/osu.Game/Overlays/ExpandingControlContainer.cs @@ -118,6 +118,12 @@ namespace osu.Game.Overlays /// /// Whether the given control is currently active, by checking whether it's hovered or dragged. /// - private bool isControlActive(TControl control) => control.IsHovered || control.IsDragged || (control is IExpandableControl expandable && expandable.IsControlDragged); + private bool isControlActive(TControl control) + { + if (control is IExpandable expandable) + return expandable.ShouldBeExpanded; + + return control.IsHovered || control.IsDragged; + } } } diff --git a/osu.Game/Overlays/IExpandable.cs b/osu.Game/Overlays/IExpandable.cs index 770ac97847..f998fc7b9f 100644 --- a/osu.Game/Overlays/IExpandable.cs +++ b/osu.Game/Overlays/IExpandable.cs @@ -3,6 +3,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays { @@ -15,5 +16,15 @@ namespace osu.Game.Overlays /// Whether this drawable is in an expanded state. /// BindableBool Expanded { get; } + + /// + /// Whether this drawable should be/stay expanded by a parenting . + /// By default, this is when this drawable is in a hovered or dragged state. + /// + /// + /// This is defined for certain controls which may have a child handling dragging instead. + /// (e.g. in which dragging is handled by their underlying control). + /// + bool ShouldBeExpanded => IsHovered || IsDragged; } } diff --git a/osu.Game/Overlays/IExpandableControl.cs b/osu.Game/Overlays/IExpandableControl.cs index fae07ae23b..2cda6f467b 100644 --- a/osu.Game/Overlays/IExpandableControl.cs +++ b/osu.Game/Overlays/IExpandableControl.cs @@ -8,9 +8,5 @@ namespace osu.Game.Overlays /// public interface IExpandableControl : IExpandable { - /// - /// Returns whether the UI control is currently in a dragged state. - /// - bool IsControlDragged { get; } } } From 2d1a8a9d492258f760ed9d1475135862d76f483f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 21:58:04 +0900 Subject: [PATCH 0321/5199] Use a more correct `CompareTo` implementation for ruleset ordering --- osu.Game/Rulesets/RulesetInfo.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index dd47a19f32..0a0941d1ff 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -51,14 +51,14 @@ namespace osu.Game.Rulesets public int CompareTo(RulesetInfo other) { + if (OnlineID >= 0 && other.OnlineID >= 0) + return OnlineID.CompareTo(other.OnlineID); + // Official rulesets are always given precedence for the time being. if (OnlineID >= 0) - { - if (other.OnlineID >= 0) - return OnlineID.CompareTo(other.OnlineID); - return -1; - } + if (other.OnlineID >= 0) + return 1; return string.Compare(ShortName, other.ShortName, StringComparison.Ordinal); } From fae4f8bd8e17f7a5df716b8a19ca731a229c714f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 21:59:44 +0900 Subject: [PATCH 0322/5199] Move nulling of previous `songSelect` to `SetUpSteps` instead --- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index ca0f2b8012..0c452f857f 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -69,22 +69,13 @@ namespace osu.Game.Tests.Visual.SongSelect { Ruleset.Value = new OsuRuleset().RulesetInfo; Beatmap.SetDefault(); + + songSelect = null; }); AddStep("delete all beatmaps", () => manager?.Delete()); } - public override void TearDownSteps() - { - base.TearDownSteps(); - - AddStep("remove song select", () => - { - songSelect?.Expire(); - songSelect = null; - }); - } - [Test] public void TestSingleFilterOnEnter() { From 81461be49fede927581065f46f03331d69d93e8d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 00:14:18 +0900 Subject: [PATCH 0323/5199] Skip beatmap imports where ruleset is not present in realm Closes #16651. When a ruleset is not available, the `Find` call would return null. When a null is passed to the constructor, `BeatmapInfo` would create an "osu" ruleset, which tries to get stored to realm and fails on duplicate primary key. Probably need to add better safeties against this (or change that constructor...) but this will fix the migration process. Probably not serious enough to pull the build. This only affects rulesets like karaoke which have custom beatmaps. --- osu.Game/Database/EFToRealmMigrator.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 639bf10e19..9fb0130619 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -232,6 +232,7 @@ namespace osu.Game.Database var transaction = r.BeginWrite(); int written = 0; + int missing = 0; try { @@ -261,6 +262,12 @@ namespace osu.Game.Database var ruleset = r.Find(beatmap.RulesetInfo.ShortName); var metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata); + if (ruleset == null) + { + log($"Skipping {++missing} beatmaps with missing ruleset"); + continue; + } + var realmBeatmap = new BeatmapInfo(ruleset, new BeatmapDifficulty(beatmap.BaseDifficulty), metadata) { DifficultyName = beatmap.DifficultyName, From 942ea896f1dfab080c624e57b8c90b30844e56ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 01:20:32 +0900 Subject: [PATCH 0324/5199] Skip scores missing beatmaps during realm migration --- osu.Game/Database/EFToRealmMigrator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 9fb0130619..a53704df9d 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -374,12 +374,12 @@ namespace osu.Game.Database log($"Migrated {written}/{count} scores..."); } - var beatmap = r.All().First(b => b.Hash == score.BeatmapInfo.Hash); + var beatmap = r.All().FirstOrDefault(b => b.Hash == score.BeatmapInfo.Hash); var ruleset = r.Find(score.Ruleset.ShortName); - if (ruleset == null) + if (beatmap == null || ruleset == null) { - log($"Skipping {++missing} scores with missing ruleset"); + log($"Skipping {++missing} scores with missing ruleset or beatmap"); continue; } From c0b2f8bd01b62c2bbd0366c8d97eb883e95b9349 Mon Sep 17 00:00:00 2001 From: Spooghetti420 Date: Thu, 27 Jan 2022 16:21:38 +0000 Subject: [PATCH 0325/5199] Fix newline style in mod --- osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs index 7295a9eb6e..56522a4587 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs @@ -25,10 +25,15 @@ namespace osu.Game.Rulesets.Mania.Mods { public override string Name => "No Long Notes"; + public override string Acronym => "NL"; + public override double ScoreMultiplier => 1; + public override string Description => @"Turns all held notes into tap notes. No coordination required."; + public override IconUsage? Icon => FontAwesome.Solid.DotCircle; + public override ModType Type => ModType.Conversion; [SettingSource("Add end notes", "Also add a note at the end of a held note")] From 400633bd99a8cb86f647437d18f25ee4c702232b Mon Sep 17 00:00:00 2001 From: Spooghetti420 Date: Thu, 27 Jan 2022 16:23:09 +0000 Subject: [PATCH 0326/5199] Add another newline --- osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs index 56522a4587..d10003b783 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs @@ -52,6 +52,7 @@ namespace osu.Game.Rulesets.Mania.Mods Value = 200.0, Precision = 1.0, }; + public void ApplyToBeatmap(IBeatmap beatmap) { var maniaBeatmap = (ManiaBeatmap)beatmap; From 9deeaee40481e7702c4b0e143ec177e755928dd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 27 Jan 2022 16:57:38 +0100 Subject: [PATCH 0327/5199] Fix tournament client not loading Caused by a `LoadComponentsAsync()` call being fired from a worker thread, which will throw exceptions since the recent addition of safety checks around that method. --- osu.Game.Tournament/TournamentGame.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index 5d613894d4..7967f54b49 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -61,18 +61,15 @@ namespace osu.Game.Tournament loadingSpinner.Show(); - BracketLoadTask.ContinueWith(t => + BracketLoadTask.ContinueWith(t => Schedule(() => { if (t.IsFaulted) { - Schedule(() => - { - loadingSpinner.Hide(); - loadingSpinner.Expire(); + loadingSpinner.Hide(); + loadingSpinner.Expire(); - Logger.Error(t.Exception, "Couldn't load bracket with error"); - Add(new WarningBox($"Your {BRACKET_FILENAME} file could not be parsed. Please check runtime.log for more details.")); - }); + Logger.Error(t.Exception, "Couldn't load bracket with error"); + Add(new WarningBox($"Your {BRACKET_FILENAME} file could not be parsed. Please check runtime.log for more details.")); return; } @@ -143,7 +140,7 @@ namespace osu.Game.Tournament windowMode.Value = WindowMode.Windowed; }), true); }); - }); + })); } } } From 7dc3940dee206b6ebec790078c4eefa6ee78b464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 27 Jan 2022 21:41:09 +0100 Subject: [PATCH 0328/5199] Add test coverage for preserving legacy beatmap info defaults --- .../Formats/LegacyBeatmapDecoderTest.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 1edd21b5a9..3a6051ed43 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -794,5 +794,32 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(path.Distance, Is.EqualTo(1)); } } + + [Test] + public void TestLegacyDefaultsPreserved() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var memoryStream = new MemoryStream()) + using (var stream = new LineBufferedReader(memoryStream)) + { + var decoded = decoder.Decode(stream); + + Assert.Multiple(() => + { + Assert.That(decoded.BeatmapInfo.AudioLeadIn, Is.EqualTo(0)); + Assert.That(decoded.BeatmapInfo.StackLeniency, Is.EqualTo(0.7f)); + Assert.That(decoded.BeatmapInfo.SpecialStyle, Is.False); + Assert.That(decoded.BeatmapInfo.LetterboxInBreaks, Is.False); + Assert.That(decoded.BeatmapInfo.WidescreenStoryboard, Is.False); + Assert.That(decoded.BeatmapInfo.EpilepsyWarning, Is.False); + Assert.That(decoded.BeatmapInfo.SamplesMatchPlaybackRate, Is.False); + Assert.That(decoded.BeatmapInfo.Countdown, Is.EqualTo(CountdownType.Normal)); + Assert.That(decoded.BeatmapInfo.CountdownOffset, Is.EqualTo(0)); + Assert.That(decoded.BeatmapInfo.Metadata.PreviewTime, Is.EqualTo(-1)); + Assert.That(decoded.BeatmapInfo.Ruleset.OnlineID, Is.EqualTo(0)); + }); + } + } } } From 1b8136e3e0fa023a613dd0b66602b9b943db83d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 27 Jan 2022 21:41:30 +0100 Subject: [PATCH 0329/5199] Change some `BeatmapInfo` defaults in a backwards compatible manner --- osu.Game/Beatmaps/BeatmapInfo.cs | 4 ++-- osu.Game/Beatmaps/BeatmapManager.cs | 9 +-------- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 15 +++++++++++++++ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 3e6e33f1d0..e4bfd768b7 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -99,11 +99,11 @@ namespace osu.Game.Beatmaps public bool LetterboxInBreaks { get; set; } - public bool WidescreenStoryboard { get; set; } + public bool WidescreenStoryboard { get; set; } = true; public bool EpilepsyWarning { get; set; } - public bool SamplesMatchPlaybackRate { get; set; } + public bool SamplesMatchPlaybackRate { get; set; } = true; public double DistanceSpacing { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 414b7cd12b..e4fdb3d471 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -90,14 +90,7 @@ namespace osu.Game.Beatmaps { Beatmaps = { - new BeatmapInfo - { - Difficulty = new BeatmapDifficulty(), - Ruleset = ruleset, - Metadata = metadata, - WidescreenStoryboard = true, - SamplesMatchPlaybackRate = true, - } + new BeatmapInfo(ruleset, new BeatmapDifficulty(), metadata) } }; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 35d1cefeca..01ba0fcc9f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -56,6 +56,8 @@ namespace osu.Game.Beatmaps.Formats this.beatmap = beatmap; this.beatmap.BeatmapInfo.BeatmapVersion = FormatVersion; + applyLegacyDefaults(this.beatmap.BeatmapInfo); + base.ParseStreamInto(stream, beatmap); flushPendingPoints(); @@ -70,6 +72,19 @@ namespace osu.Game.Beatmaps.Formats hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.Difficulty); } + /// + /// Some `BeatmapInfo` members have default values that differ from the default values used by stable. + /// In addition, legacy beatmaps will sometimes not contain some configuration keys, in which case + /// the legacy default values should be used. + /// This method's intention is to restore those legacy defaults. + /// See also: https://osu.ppy.sh/wiki/en/Client/File_formats/Osu_%28file_format%29 + /// + private void applyLegacyDefaults(BeatmapInfo beatmapInfo) + { + beatmapInfo.WidescreenStoryboard = false; + beatmapInfo.SamplesMatchPlaybackRate = false; + } + protected override bool ShouldSkipLine(string line) => base.ShouldSkipLine(line) || line.StartsWith(' ') || line.StartsWith('_'); protected override void ParseLine(Beatmap beatmap, Section section, string line) From 6674567af1d678f33eb7c54920e3f11cba7301a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 27 Jan 2022 21:51:51 +0100 Subject: [PATCH 0330/5199] Use -1 as the default preview time globally in metadata --- osu.Game/Beatmaps/BeatmapMetadata.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index cb38373bd3..1514d3af7a 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -39,7 +39,7 @@ namespace osu.Game.Beatmaps /// The time in milliseconds to begin playing the track for preview purposes. /// If -1, the track should begin playing at 40% of its length. /// - public int PreviewTime { get; set; } + public int PreviewTime { get; set; } = -1; public string AudioFile { get; set; } = string.Empty; public string BackgroundFile { get; set; } = string.Empty; From 3e068e564d79d6b446b93a69904268abd19ba194 Mon Sep 17 00:00:00 2001 From: Spooghetti420 Date: Thu, 27 Jan 2022 20:56:51 +0000 Subject: [PATCH 0331/5199] Update mod per discussion + create test --- .../Mods/TestSceneManiaModNoHolds.cs | 52 +++++++++++++ osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- .../Mods/ManiaModNoHolds.cs | 75 +++++++++++++++++++ 3 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs create mode 100644 osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs new file mode 100644 index 0000000000..411f891f43 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Tests.Visual; +using osu.Game.Rulesets.Mania.Tests; +using System.Collections.Generic; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Mania.Beatmaps; + +namespace osu.Game.Rulesets.Mania.Tests.Mods +{ + public class TestSceneManiaModNoHolds : ModTestScene + { + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + + [Test] + public void TestMapHasNoHeldNotes() + { + var testBeatmap = createBeatmap(); + Assert.That(!testBeatmap.HitObjects.OfType().Any()); + } + + + private static IBeatmap createBeatmap() + { + var beatmap = createRawBeatmap(); + var noHoldsMod = new ManiaModNoHolds(); + + foreach (var hitObject in beatmap.HitObjects) + hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + noHoldsMod.ApplyToBeatmap(beatmap); + + return beatmap; + } + private static IBeatmap createRawBeatmap() + { + var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); + beatmap.HitObjects.Add(new Note { StartTime = 1000 }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, EndTime = 3000 }); + return beatmap; + } + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 3471f4ca66..55ca1a465e 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -244,7 +244,7 @@ namespace osu.Game.Rulesets.Mania new ManiaModClassic(), new ManiaModInvert(), new ManiaModConstantSpeed(), - new ManiaModNoLongNotes() + new ManiaModNoHolds() }; case ModType.Automation: diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs new file mode 100644 index 0000000000..0d5ca44898 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs @@ -0,0 +1,75 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mods; +using osu.Framework.Graphics.Sprites; +using System; +using System.Collections.Generic; +using osu.Game.Audio; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Framework.Utils; +using osu.Game.Overlays.Settings; +using osu.Framework.Bindables; +using osu.Game.Configuration; +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModNoHolds : Mod, IApplicableAfterBeatmapConversion + { + public override string Name => "No Holds"; + + public override string Acronym => "NH"; + + public override double ScoreMultiplier => 1; + + public override string Description => @"Turns all hold notes into normal notes. No coordination required."; + + public override IconUsage? Icon => FontAwesome.Solid.DotCircle; + + public override ModType Type => ModType.Conversion; + + [SettingSource("Add end notes", "Also add a note at the end of a hold note")] + public BindableBool AddEndNotes { get; } = new BindableBool + { + Default = true, + Value = true + }; + + public void ApplyToBeatmap(IBeatmap beatmap) + { + var maniaBeatmap = (ManiaBeatmap)beatmap; + + var newObjects = new List(); + foreach (var h in beatmap.HitObjects.OfType()) + { + // Add a note for the beginning of the hold note + newObjects.Add(new Note + { + Column = h.Column, + StartTime = h.StartTime, + Samples = h.GetNodeSamples(0) + }); + + // Don't add an end note if the duration is shorter than some threshold, or end notes are disabled + if (AddEndNotes.Value && h.Duration > 200) + { + newObjects.Add(new Note + { + Column = h.Column, + StartTime = h.EndTime, + Samples = h.GetNodeSamples((h.NodeSamples?.Count - 1) ?? 1) + }); + } + } + + maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType().Concat(newObjects).OrderBy(h => h.StartTime).ToList(); + } + + } + +} From 146c54a2c14608b95363647a0e965aeeb47aa03e Mon Sep 17 00:00:00 2001 From: Spooghetti420 Date: Thu, 27 Jan 2022 21:02:59 +0000 Subject: [PATCH 0332/5199] Fix code formatting --- .../Mods/TestSceneManiaModNoHolds.cs | 1 - osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs index 411f891f43..f22b724c99 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs @@ -28,7 +28,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Assert.That(!testBeatmap.HitObjects.OfType().Any()); } - private static IBeatmap createBeatmap() { var beatmap = createRawBeatmap(); diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs index 0d5ca44898..a18dbad4a1 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs @@ -69,7 +69,5 @@ namespace osu.Game.Rulesets.Mania.Mods maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType().Concat(newObjects).OrderBy(h => h.StartTime).ToList(); } - } - -} +} \ No newline at end of file From f59828e2d9dc745e352ecbbf01fe811389e2ed47 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 28 Jan 2022 13:27:12 +0900 Subject: [PATCH 0333/5199] Add audio feedback to song select 'random' --- osu.Game/Screens/Select/BeatmapCarousel.cs | 29 +++++++++++++++++++++- osu.Game/Screens/Select/SongSelect.cs | 21 +++++++++++++--- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c27915c383..ef19eb187a 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -6,6 +6,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Graphics; @@ -151,6 +153,10 @@ namespace osu.Game.Screens.Select private readonly DrawablePool setPool = new DrawablePool(100); + private Sample spinSample; + + private int visibleSetsCount; + public BeatmapCarousel() { root = new CarouselRoot(this); @@ -169,8 +175,10 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, AudioManager audio) { + spinSample = audio.Samples.Get("SongSelect/random-spin"); + config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm); config.BindWith(OsuSetting.SongSelectRightMouseScroll, RightClickScrollingEnabled); @@ -419,6 +427,9 @@ namespace osu.Game.Screens.Select return false; var visibleSets = beatmapSets.Where(s => !s.Filtered.Value).ToList(); + + visibleSetsCount = visibleSets.Count; + if (!visibleSets.Any()) return false; @@ -450,6 +461,9 @@ namespace osu.Game.Screens.Select else set = visibleSets.ElementAt(RNG.Next(visibleSets.Count)); + if (selectedBeatmapSet != null) + playSpinSample(distanceBetween(set, selectedBeatmapSet)); + select(set); return true; } @@ -464,12 +478,25 @@ namespace osu.Game.Screens.Select { if (RandomAlgorithm.Value == RandomSelectAlgorithm.RandomPermutation) previouslyVisitedRandomSets.Remove(selectedBeatmapSet); + + if (selectedBeatmapSet != null) + playSpinSample(distanceBetween(beatmap, selectedBeatmapSet)); + select(beatmap); break; } } } + private double distanceBetween(CarouselItem item1, CarouselItem item2) => Math.Ceiling(Math.Abs(item1.CarouselYPosition - item2.CarouselYPosition) / DrawableCarouselItem.MAX_HEIGHT); + + private void playSpinSample(double distance) + { + var chan = spinSample.GetChannel(); + chan.Frequency.Value = 1f + Math.Min(1f, distance / visibleSetsCount); + chan.Play(); + } + private void select(CarouselItem item) { if (!AllowSelection) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 10150fcd9f..6fbadfe6cf 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -100,6 +100,7 @@ namespace osu.Game.Screens.Select private Sample sampleChangeDifficulty; private Sample sampleChangeBeatmap; + private Sample sampleRandomBeatmap; private Container carouselContainer; @@ -109,6 +110,8 @@ namespace osu.Game.Screens.Select private double audioFeedbackLastPlaybackTime; + private bool randomClicked; + [Resolved] private MusicController music { get; set; } @@ -288,6 +291,7 @@ namespace osu.Game.Screens.Select sampleChangeDifficulty = audio.Samples.Get(@"SongSelect/select-difficulty"); sampleChangeBeatmap = audio.Samples.Get(@"SongSelect/select-expand"); + sampleRandomBeatmap = audio.Samples.Get(@"SongSelect/select-random"); SampleConfirm = audio.Samples.Get(@"SongSelect/confirm-selection"); if (dialogOverlay != null) @@ -315,8 +319,16 @@ namespace osu.Game.Screens.Select (new FooterButtonMods { Current = Mods }, ModSelect), (new FooterButtonRandom { - NextRandom = () => Carousel.SelectNextRandom(), - PreviousRandom = Carousel.SelectPreviousRandom + NextRandom = () => + { + randomClicked = true; + Carousel.SelectNextRandom(); + }, + PreviousRandom = () => + { + randomClicked = true; + Carousel.SelectPreviousRandom(); + } }, null), (new FooterButtonOptions(), BeatmapOptions) }; @@ -486,7 +498,9 @@ namespace osu.Game.Screens.Select { if (beatmap != null && beatmapInfoPrevious != null && Time.Current - audioFeedbackLastPlaybackTime >= 50) { - if (beatmap.BeatmapSet?.ID == beatmapInfoPrevious.BeatmapSet?.ID) + if (randomClicked) + sampleRandomBeatmap.Play(); + else if (beatmap.BeatmapSet?.ID == beatmapInfoPrevious.BeatmapSet?.ID) sampleChangeDifficulty.Play(); else sampleChangeBeatmap.Play(); @@ -494,6 +508,7 @@ namespace osu.Game.Screens.Select audioFeedbackLastPlaybackTime = Time.Current; } + randomClicked = false; beatmapInfoPrevious = beatmap; } From c44af4853d6aabb82006d7944e146d82c06401fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 13:43:53 +0900 Subject: [PATCH 0334/5199] Add thread safety to `PollingComponent.Poll` implementations --- .../Components/ListingPollingComponent.cs | 14 ++++++++------ .../Components/SelectionPollingComponent.cs | 15 +++++++++------ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs index fcf7767958..666d425f62 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.OnlinePlay.Components }); } - private GetRoomsRequest pollReq; + private GetRoomsRequest lastPollRequest; protected override Task Poll() { @@ -45,10 +45,11 @@ namespace osu.Game.Screens.OnlinePlay.Components var tcs = new TaskCompletionSource(); - pollReq?.Cancel(); - pollReq = new GetRoomsRequest(Filter.Value.Status, Filter.Value.Category); + lastPollRequest?.Cancel(); - pollReq.Success += result => + var req = new GetRoomsRequest(Filter.Value.Status, Filter.Value.Category); + + req.Success += result => { foreach (var existing in RoomManager.Rooms.ToArray()) { @@ -66,10 +67,11 @@ namespace osu.Game.Screens.OnlinePlay.Components tcs.SetResult(true); }; - pollReq.Failure += _ => tcs.SetResult(false); + req.Failure += _ => tcs.SetResult(false); - API.Queue(pollReq); + API.Queue(req); + lastPollRequest = req; return tcs.Task; } } diff --git a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs index 22842fbb9e..e05bdf8c8e 100644 --- a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.OnlinePlay.Components this.room = room; } - private GetRoomRequest pollReq; + private GetRoomRequest lastPollRequest; protected override Task Poll() { @@ -30,19 +30,22 @@ namespace osu.Game.Screens.OnlinePlay.Components var tcs = new TaskCompletionSource(); - pollReq?.Cancel(); - pollReq = new GetRoomRequest(room.RoomID.Value.Value); + lastPollRequest?.Cancel(); - pollReq.Success += result => + var req = new GetRoomRequest(room.RoomID.Value.Value); + + req.Success += result => { result.RemoveExpiredPlaylistItems(); RoomManager.AddOrUpdateRoom(result); tcs.SetResult(true); }; - pollReq.Failure += _ => tcs.SetResult(false); + req.Failure += _ => tcs.SetResult(false); - API.Queue(pollReq); + API.Queue(req); + + lastPollRequest = req; return tcs.Task; } From c953a5d5034416bb32ee2caada7c0a74f0b4551a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 13:44:09 +0900 Subject: [PATCH 0335/5199] Ensure `PollingComponent.Poll` is always called from the update thread Not strictly required since all `Poll` implementations are now threadsafe, but extra safety is never a bad thing? --- osu.Game/Online/PollingComponent.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/PollingComponent.cs b/osu.Game/Online/PollingComponent.cs index 243be8da44..5eddb3b49d 100644 --- a/osu.Game/Online/PollingComponent.cs +++ b/osu.Game/Online/PollingComponent.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.Threading.Tasks; using osu.Framework.Bindables; +using osu.Framework.Development; using osu.Framework.Graphics.Containers; using osu.Framework.Threading; @@ -66,6 +68,8 @@ namespace osu.Game.Online private void doPoll() { + Debug.Assert(ThreadSafety.IsUpdateThread); + scheduledPoll = null; pollingActive = true; Poll().ContinueWith(_ => pollComplete()); @@ -96,13 +100,13 @@ namespace osu.Game.Online if (!lastTimePolled.HasValue) { - doPoll(); + Scheduler.AddOnce(doPoll); return; } if (Time.Current - lastTimePolled.Value > TimeBetweenPolls.Value) { - doPoll(); + Scheduler.AddOnce(doPoll); return; } From 91be77ad3d61f844c997681cfdf5b2643115bd43 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 14:01:10 +0900 Subject: [PATCH 0336/5199] Fix null ref in `ComposeScreen` when ruleset doesn't provide a composer --- osu.Game/Screens/Edit/Compose/ComposeScreen.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 2bdf59b21c..2cde962b12 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -82,6 +82,11 @@ namespace osu.Game.Screens.Edit.Compose protected override void LoadComplete() { base.LoadComplete(); + + // May be null in the case of a ruleset that doesn't have editor support, see CreateMainContent(). + if (composer == null) + return; + EditorBeatmap.SelectedHitObjects.BindCollectionChanged((_, __) => updateClipboardActionAvailability()); clipboard.BindValueChanged(_ => updateClipboardActionAvailability()); composer.OnLoadComplete += _ => updateClipboardActionAvailability(); From b3856c900541e1af68aa8e4a5c55b9a03f62356f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 14:01:31 +0900 Subject: [PATCH 0337/5199] Fix editor crashing on custom rulesets due to `ChangeHandler` not being supported As per https://github.com/ppy/osu/discussions/16668, even without proper saving support some ruleset developers do want to work on the editor. This brings things back into a workable state. --- osu.Game/Screens/Edit/Editor.cs | 46 +++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 4812da98f4..2fead84deb 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -28,6 +28,8 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Online.API; using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; +using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; @@ -61,7 +63,16 @@ namespace osu.Game.Screens.Edit public override bool? AllowTrackAdjustments => false; - protected bool HasUnsavedChanges => lastSavedHash != changeHandler.CurrentStateHash; + protected bool HasUnsavedChanges + { + get + { + if (!canSave) + return false; + + return lastSavedHash != changeHandler?.CurrentStateHash; + } + } [Resolved] private BeatmapManager beatmapManager { get; set; } @@ -72,10 +83,15 @@ namespace osu.Game.Screens.Edit [Resolved(canBeNull: true)] private DialogOverlay dialogOverlay { get; set; } + [Resolved(canBeNull: true)] + private NotificationOverlay notifications { get; set; } + public IBindable SamplePlaybackDisabled => samplePlaybackDisabled; private readonly Bindable samplePlaybackDisabled = new Bindable(); + private bool canSave; + private bool exitConfirmed; private string lastSavedHash; @@ -92,6 +108,8 @@ namespace osu.Game.Screens.Edit private IBeatmap playableBeatmap; private EditorBeatmap editorBeatmap; + + [CanBeNull] // Should be non-null once it can support custom rulesets. private EditorChangeHandler changeHandler; private EditorMenuBar menuBar; @@ -172,8 +190,14 @@ namespace osu.Game.Screens.Edit AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap, loadableBeatmap.GetSkin(), loadableBeatmap.BeatmapInfo)); dependencies.CacheAs(editorBeatmap); - changeHandler = new EditorChangeHandler(editorBeatmap); - dependencies.CacheAs(changeHandler); + + canSave = editorBeatmap.BeatmapInfo.Ruleset.CreateInstance() is ILegacyRuleset; + + if (canSave) + { + changeHandler = new EditorChangeHandler(editorBeatmap); + dependencies.CacheAs(changeHandler); + } beatDivisor.Value = editorBeatmap.BeatmapInfo.BeatDivisor; beatDivisor.BindValueChanged(divisor => editorBeatmap.BeatmapInfo.BeatDivisor = divisor.NewValue); @@ -311,8 +335,8 @@ namespace osu.Game.Screens.Edit } }); - changeHandler.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true); - changeHandler.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true); + changeHandler?.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true); + changeHandler?.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true); menuBar.Mode.ValueChanged += onModeChanged; } @@ -353,6 +377,12 @@ namespace osu.Game.Screens.Edit protected void Save() { + if (!canSave) + { + notifications?.Post(new SimpleErrorNotification { Text = "Saving is not supported for this ruleset yet, sorry!" }); + return; + } + // no longer new after first user-triggered save. isNewBeatmap = false; @@ -648,9 +678,9 @@ namespace osu.Game.Screens.Edit #endregion - protected void Undo() => changeHandler.RestoreState(-1); + protected void Undo() => changeHandler?.RestoreState(-1); - protected void Redo() => changeHandler.RestoreState(1); + protected void Redo() => changeHandler?.RestoreState(1); private void resetTrack(bool seekToStart = false) { @@ -761,7 +791,7 @@ namespace osu.Game.Screens.Edit private void updateLastSavedHash() { - lastSavedHash = changeHandler.CurrentStateHash; + lastSavedHash = changeHandler?.CurrentStateHash; } private List createFileMenuItems() From b7d8c9bf067f64021a4648ecfd413648258f373f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 14:18:10 +0900 Subject: [PATCH 0338/5199] Fix a couple of cases of incorrect equality checks in the case both values are null --- .../BeatmapSet/Scores/TopScoreStatisticsSection.cs | 3 +++ .../Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 1f3f73a60a..ec795cf6b2 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -107,6 +107,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { set { + if (score == null && value == null) + return; + if (score?.Equals(value) == true) return; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 2070e53257..31cbe91f5c 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -38,6 +38,14 @@ namespace osu.Game.Screens.Select.Leaderboards get => beatmapInfo; set { + if (beatmapInfo == null && value == null) + { + // always null scores to ensure a correct initial display. + // see weird `scoresLoadedOnce` logic in base implementation. + Scores = null; + return; + } + if (beatmapInfo?.Equals(value) == true) return; From f32d56e213fa26566b38ca12bb44cf9d8f0f0100 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 14:48:17 +0900 Subject: [PATCH 0339/5199] Bring `HoldForMenuButton` tests up-to-date in code quality --- .../Gameplay/TestSceneHoldForMenuButton.cs | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs index 235842acc9..e0765ab5fb 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs @@ -3,9 +3,10 @@ using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; +using osu.Game.Graphics.Containers; using osu.Game.Screens.Play.HUD; using osuTK; using osuTK.Input; @@ -19,28 +20,35 @@ namespace osu.Game.Tests.Visual.Gameplay protected override double TimePerAction => 100; // required for the early exit test, since hold-to-confirm delay is 200ms - [BackgroundDependencyLoader] - private void load() + private HoldForMenuButton holdForMenuButton; + + [SetUpSteps] + public void SetUpSteps() { - HoldForMenuButton holdForMenuButton; - - Add(holdForMenuButton = new HoldForMenuButton + AddStep("create button", () => { - Origin = Anchor.BottomRight, - Anchor = Anchor.BottomRight, - Action = () => exitAction = true + exitAction = false; + + Child = holdForMenuButton = new HoldForMenuButton + { + Scale = new Vector2(2), + Origin = Anchor.CentreRight, + Anchor = Anchor.CentreRight, + Action = () => exitAction = true + }; }); + } - var text = holdForMenuButton.Children.OfType().First(); - + [Test] + public void TestMovementAndTrigger() + { AddStep("Trigger text fade in", () => InputManager.MoveMouseTo(holdForMenuButton)); - AddUntilStep("Text visible", () => text.IsPresent && !exitAction); + AddUntilStep("Text visible", () => getSpriteText().IsPresent && !exitAction); AddStep("Trigger text fade out", () => InputManager.MoveMouseTo(Vector2.One)); - AddUntilStep("Text is not visible", () => !text.IsPresent && !exitAction); + AddUntilStep("Text is not visible", () => !getSpriteText().IsPresent && !exitAction); AddStep("Trigger exit action", () => { - exitAction = false; InputManager.MoveMouseTo(holdForMenuButton); InputManager.PressButton(MouseButton.Left); }); @@ -50,6 +58,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Trigger exit action", () => InputManager.PressButton(MouseButton.Left)); AddUntilStep($"{nameof(holdForMenuButton.Action)} was triggered", () => exitAction); + AddStep("Release", () => InputManager.ReleaseButton(MouseButton.Left)); } } } From 28c8e07e3f223aaaa15fe840fc8ee375c31dc48d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 14:48:34 +0900 Subject: [PATCH 0340/5199] Ensure hold for menu button fades out if the cursor is never moved Closes https://github.com/ppy/osu/discussions/16669. --- .../Visual/Gameplay/TestSceneHoldForMenuButton.cs | 10 ++++++++++ osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs index e0765ab5fb..feec06f372 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs @@ -60,5 +60,15 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep($"{nameof(holdForMenuButton.Action)} was triggered", () => exitAction); AddStep("Release", () => InputManager.ReleaseButton(MouseButton.Left)); } + + [Test] + public void TestFadeOnNoInput() + { + AddStep("move mouse away", () => InputManager.MoveMouseTo(Vector2.One)); + AddUntilStep("wait for text fade out", () => !getSpriteText().IsPresent); + AddUntilStep("wait for button fade out", () => holdForMenuButton.Alpha < 0.1f); + } + + private SpriteText getSpriteText() => holdForMenuButton.Children.OfType().First(); } } diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 5a7ef786d3..430f001427 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -80,7 +80,7 @@ namespace osu.Game.Screens.Play.HUD base.LoadComplete(); } - private float positionalAdjust; + private float positionalAdjust = 1; // Start at 1 to handle the case where a user never send positional input. protected override bool OnMouseMove(MouseMoveEvent e) { From 32f9299fe082f7347aebfceef7aa6f8dd017f2f6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 28 Jan 2022 15:26:29 +0900 Subject: [PATCH 0341/5199] Remove unused using --- osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs index feec06f372..ddb0872541 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs @@ -6,7 +6,6 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; -using osu.Game.Graphics.Containers; using osu.Game.Screens.Play.HUD; using osuTK; using osuTK.Input; From cb7ae413feea2e53fd4b5aa3b59167ae0a398c13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 15:50:13 +0900 Subject: [PATCH 0342/5199] Ensure test game is always active --- osu.Game/Tests/Visual/OsuGameTestScene.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index ebbd9bb5dd..dabf49c15e 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -158,6 +158,14 @@ namespace osu.Game.Tests.Visual Dependencies.Get().SetValue(Static.MutedAudioNotificationShownOnce, true); } + + protected override void Update() + { + base.Update(); + + // when running in visual tests and the window loses focus, we generally don't want the game to pause. + ((Bindable)IsActive).Value = true; + } } public class TestLoader : Loader From 778eebc94df6b2ff8a068b014aaee05de8bd594b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 15:50:22 +0900 Subject: [PATCH 0343/5199] Add test coverage of local score import and deletion --- .../Navigation/TestSceneScreenNavigation.cs | 90 +++++++++++++++---- osu.Game/Tests/Visual/OsuGameTestScene.cs | 3 + 2 files changed, 74 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 89dca77af4..6d89feef52 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.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.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -10,16 +11,19 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Leaderboards; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Overlays.Toolbar; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Scoring; using osu.Game.Screens.Menu; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Screens.Select; +using osu.Game.Screens.Select.Leaderboards; using osu.Game.Screens.Select.Options; using osu.Game.Tests.Beatmaps.IO; using osuTK; @@ -96,35 +100,52 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestRetryFromResults() { - Player player = null; - ResultsScreen results = null; + var getOriginalPlayer = playToResults(); - IWorkingBeatmap beatmap() => Game.Beatmap.Value; + AddStep("attempt to retry", () => ((ResultsScreen)Game.ScreenStack.CurrentScreen).ChildrenOfType().First().Action()); + AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != getOriginalPlayer() && Game.ScreenStack.CurrentScreen is Player); + } - Screens.Select.SongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); - AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + [Test] + public void TestDeleteScoreAfterPlaying() + { + playToResults(); - AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + ScoreInfo score = null; + LeaderboardScore scorePanel = null; - AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + AddStep("get score", () => score = ((ResultsScreen)Game.ScreenStack.CurrentScreen).Score); - AddStep("set mods", () => Game.SelectedMods.Value = new Mod[] { new OsuModNoFail(), new OsuModDoubleTime { SpeedChange = { Value = 2 } } }); + AddAssert("ensure score is databased", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == false)); - AddStep("press enter", () => InputManager.Key(Key.Enter)); + AddStep("press back button", () => Game.ChildrenOfType().First().Action()); - AddUntilStep("wait for player", () => + AddStep("show local scores", () => Game.ChildrenOfType().First().Current.Value = new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Local)); + + AddUntilStep("wait for score displayed", () => (scorePanel = Game.ChildrenOfType().FirstOrDefault(s => s.Score.Equals(score))) != null); + + AddStep("right click panel", () => { - // dismiss any notifications that may appear (ie. muted notification). - clickMouseInCentre(); - return (player = Game.ScreenStack.CurrentScreen as Player) != null; + InputManager.MoveMouseTo(scorePanel); + InputManager.Click(MouseButton.Right); }); - 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); - AddStep("attempt to retry", () => results.ChildrenOfType().First().Action()); - AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != player && Game.ScreenStack.CurrentScreen is Player); + AddStep("click delete", () => + { + var dropdownItem = Game + .ChildrenOfType().First() + .ChildrenOfType().First() + .ChildrenOfType().First(i => i.Item.Text.ToString() == "Delete"); + + InputManager.MoveMouseTo(dropdownItem); + InputManager.Click(MouseButton.Left); + }); + + AddStep("confirm deletion", () => InputManager.Key(Key.Number1)); + + AddAssert("ensure score is pending deletion", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == true)); + + AddUntilStep("wait for score panel removal", () => scorePanel.Parent == null); } [TestCase(true)] @@ -432,6 +453,37 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("test dispose doesn't crash", () => Game.Dispose()); } + private Func playToResults() + { + Player player = null; + + IWorkingBeatmap beatmap() => Game.Beatmap.Value; + + Screens.Select.SongSelect songSelect = null; + PushAndConfirm(() => songSelect = new TestPlaySongSelect()); + AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + + 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", () => + { + // 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", () => (Game.ScreenStack.CurrentScreen as ResultsScreen)?.IsLoaded == true); + return () => player; + } + private void clickMouseInCentre() { InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre); diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index dabf49c15e..3b8d9a4cd1 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -14,6 +14,7 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Overlays; @@ -111,6 +112,8 @@ namespace osu.Game.Tests.Visual public new ScreenStack ScreenStack => base.ScreenStack; + public RealmAccess Realm => Dependencies.Get(); + public new BackButton BackButton => base.BackButton; public new BeatmapManager BeatmapManager => base.BeatmapManager; From 2453bf5ed0b8b3660c2a109ad2720ba125b1fc2c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 15:54:51 +0900 Subject: [PATCH 0344/5199] Add test coverage of the same thing but via "clear all scores" button --- .../Navigation/TestSceneScreenNavigation.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 6d89feef52..3be7cf7c5c 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -106,6 +106,35 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != getOriginalPlayer() && Game.ScreenStack.CurrentScreen is Player); } + [Test] + public void TestDeleteAllScoresAfterPlaying() + { + playToResults(); + + ScoreInfo score = null; + LeaderboardScore scorePanel = null; + + AddStep("get score", () => score = ((ResultsScreen)Game.ScreenStack.CurrentScreen).Score); + + AddAssert("ensure score is databased", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == false)); + + AddStep("press back button", () => Game.ChildrenOfType().First().Action()); + + AddStep("show local scores", () => Game.ChildrenOfType().First().Current.Value = new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Local)); + + AddUntilStep("wait for score displayed", () => (scorePanel = Game.ChildrenOfType().FirstOrDefault(s => s.Score.Equals(score))) != null); + + AddStep("open options", () => InputManager.Key(Key.F3)); + + AddStep("choose clear all scores", () => InputManager.Key(Key.Number4)); + + AddStep("confirm deletion", () => InputManager.Key(Key.Number1)); + + AddAssert("ensure score is pending deletion", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == true)); + + AddUntilStep("wait for score panel removal", () => scorePanel.Parent == null); + } + [Test] public void TestDeleteScoreAfterPlaying() { From 0d3ac4fd9c72db8eb2049d07652375c0de419f0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 15:08:39 +0900 Subject: [PATCH 0345/5199] Fix delete local scores crashing the game --- osu.Game/Scoring/ScoreManager.cs | 9 +++++++++ osu.Game/Screens/Select/BeatmapClearScoresDialog.cs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 8f665224ee..6f9cce2d3c 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -266,6 +266,15 @@ namespace osu.Game.Scoring }); } + public void Delete(BeatmapInfo beatmap, bool silent = false) + { + realm.Run(r => + { + var beatmapScores = r.Find(beatmap.ID).Scores.ToList(); + scoreModelManager.Delete(beatmapScores, silent); + }); + } + public void Delete(List items, bool silent = false) { scoreModelManager.Delete(items, silent); diff --git a/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs b/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs index 774d3b4b28..4a16be4a3a 100644 --- a/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs +++ b/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Select Text = @"Yes. Please.", Action = () => { - Task.Run(() => scoreManager.Delete(s => !s.DeletePending && s.BeatmapInfo.ID == beatmapInfo.ID)) + Task.Run(() => scoreManager.Delete(beatmapInfo)) .ContinueWith(_ => onCompletion); } }, From 4d9b61212b22d154c0632c0d50978e90ecc59f9d Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 28 Jan 2022 18:13:51 +0900 Subject: [PATCH 0346/5199] Add 'cursor tap' audio feedback --- osu.Game/Graphics/Cursor/MenuCursor.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 8e272f637f..a89d8dac71 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -10,6 +10,8 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using System; using JetBrains.Annotations; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; @@ -30,13 +32,17 @@ namespace osu.Game.Graphics.Cursor private DragRotationState dragRotationState; private Vector2 positionMouseDown; + private Sample tapSample; + [BackgroundDependencyLoader(true)] - private void load([NotNull] OsuConfigManager config, [CanBeNull] ScreenshotManager screenshotManager) + private void load([NotNull] OsuConfigManager config, [CanBeNull] ScreenshotManager screenshotManager, AudioManager audio) { cursorRotate = config.GetBindable(OsuSetting.CursorRotation); if (screenshotManager != null) screenshotCursorVisibility.BindTo(screenshotManager.CursorVisibility); + + tapSample = audio.Samples.Get(@"UI/cursor-tap"); } protected override bool OnMouseMove(MouseMoveEvent e) @@ -70,6 +76,18 @@ namespace osu.Game.Graphics.Cursor return base.OnMouseMove(e); } + protected override bool OnClick(ClickEvent e) + { + var channel = tapSample.GetChannel(); + + // scale to [-0.75, 0.75] so that the sample isn't fully panned left or right (sounds weird) + channel.Balance.Value = ((activeCursor.X / DrawWidth) * 2 - 1) * 0.75; + channel.Frequency.Value = 0.99 + RNG.NextDouble(0.02); + channel.Play(); + + return base.OnClick(e); + } + protected override bool OnMouseDown(MouseDownEvent e) { if (State.Value == Visibility.Visible) From 17f0d7897b8a361a7a1fc5e6faa2379efc0a9a8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 18:35:59 +0900 Subject: [PATCH 0347/5199] Increase lenience of alpha check in `TestSceneOsuModNoScope` I believe the [test failures](https://github.com/ppy/osu/runs/4977283066?check_suite_focus=true) we're seeing here are due to the implementation of interpolation of the alpha being frame dependent (in a way that doesn't interact well with tests). The reason for never hitting the expected value is that the beatmap ends, causing the cursor to become fully visible again. It's probably already good-enough for most cases, so let's attempt to silence these test failures by not checking so precisely for the alpha value. We're checking for either 1 or 0 so it's not too important how close it is to either. --- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs index 8e226c7ded..44404ca245 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs @@ -145,6 +145,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods private bool isBreak() => Player.IsBreakTime.Value; - private bool cursorAlphaAlmostEquals(float alpha) => Precision.AlmostEquals(Player.DrawableRuleset.Cursor.Alpha, alpha); + private bool cursorAlphaAlmostEquals(float alpha) => Precision.AlmostEquals(Player.DrawableRuleset.Cursor.Alpha, alpha, 0.1f); } } From 142a67e1634fb379e8725d3ad51e5cf4dd9825a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 18:53:28 +0900 Subject: [PATCH 0348/5199] Fix approach rate not being transferred from OD on older beatmaps --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 01ba0fcc9f..07ada8ecc4 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -311,10 +311,13 @@ namespace osu.Game.Beatmaps.Formats case @"OverallDifficulty": difficulty.OverallDifficulty = Parsing.ParseFloat(pair.Value); + if (!hasApproachRate) + difficulty.ApproachRate = difficulty.OverallDifficulty; break; case @"ApproachRate": difficulty.ApproachRate = Parsing.ParseFloat(pair.Value); + hasApproachRate = true; break; case @"SliderMultiplier": @@ -432,6 +435,7 @@ namespace osu.Game.Beatmaps.Formats private readonly List pendingControlPoints = new List(); private readonly HashSet pendingControlPointTypes = new HashSet(); private double pendingControlPointsTime; + private bool hasApproachRate; private void addControlPoint(double time, ControlPoint point, bool timingChange) { From 53ca597e2b2c85431c722f1801f00805179b150b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 19:12:32 +0900 Subject: [PATCH 0349/5199] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index f85a96f819..2902c74f0a 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index aa6fb93aa0..b74d51e8f1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index fbb4688588..90ffb9605f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - + From bda3cdc9a773ac76c36470174e13db398e33fd7e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 28 Jan 2022 19:28:42 +0900 Subject: [PATCH 0350/5199] Add tests --- .../Formats/LegacyBeatmapDecoderTest.cs | 42 +++++++++++++++++++ ...approach-rate-after-overall-difficulty.osu | 3 ++ ...pproach-rate-before-overall-difficulty.osu | 3 ++ .../Resources/undefined-approach-rate.osu | 2 + 4 files changed, 50 insertions(+) create mode 100644 osu.Game.Tests/Resources/approach-rate-after-overall-difficulty.osu create mode 100644 osu.Game.Tests/Resources/approach-rate-before-overall-difficulty.osu create mode 100644 osu.Game.Tests/Resources/undefined-approach-rate.osu diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 3a6051ed43..468cb7683c 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -821,5 +821,47 @@ namespace osu.Game.Tests.Beatmaps.Formats }); } } + + [Test] + public void TestUndefinedApproachRateInheritsOverallDifficulty() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("undefined-approach-rate.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var decoded = decoder.Decode(stream); + Assert.That(decoded.Difficulty.ApproachRate, Is.EqualTo(1)); + Assert.That(decoded.Difficulty.OverallDifficulty, Is.EqualTo(1)); + } + } + + [Test] + public void TestApproachRateDefinedBeforeOverallDifficulty() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("approach-rate-before-overall-difficulty.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var decoded = decoder.Decode(stream); + Assert.That(decoded.Difficulty.ApproachRate, Is.EqualTo(9)); + Assert.That(decoded.Difficulty.OverallDifficulty, Is.EqualTo(1)); + } + } + + [Test] + public void TestApproachRateDefinedAfterOverallDifficulty() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("approach-rate-after-overall-difficulty.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var decoded = decoder.Decode(stream); + Assert.That(decoded.Difficulty.ApproachRate, Is.EqualTo(9)); + Assert.That(decoded.Difficulty.OverallDifficulty, Is.EqualTo(1)); + } + } } } diff --git a/osu.Game.Tests/Resources/approach-rate-after-overall-difficulty.osu b/osu.Game.Tests/Resources/approach-rate-after-overall-difficulty.osu new file mode 100644 index 0000000000..23732aef8c --- /dev/null +++ b/osu.Game.Tests/Resources/approach-rate-after-overall-difficulty.osu @@ -0,0 +1,3 @@ +[Difficulty] +OverallDifficulty:1 +ApproachRate:9 \ No newline at end of file diff --git a/osu.Game.Tests/Resources/approach-rate-before-overall-difficulty.osu b/osu.Game.Tests/Resources/approach-rate-before-overall-difficulty.osu new file mode 100644 index 0000000000..18885c6624 --- /dev/null +++ b/osu.Game.Tests/Resources/approach-rate-before-overall-difficulty.osu @@ -0,0 +1,3 @@ +[Difficulty] +ApproachRate:9 +OverallDifficulty:1 \ No newline at end of file diff --git a/osu.Game.Tests/Resources/undefined-approach-rate.osu b/osu.Game.Tests/Resources/undefined-approach-rate.osu new file mode 100644 index 0000000000..0de24238bf --- /dev/null +++ b/osu.Game.Tests/Resources/undefined-approach-rate.osu @@ -0,0 +1,2 @@ +[Difficulty] +OverallDifficulty:1 \ No newline at end of file From 397971c6313eff22d645c07d782547a4e7683a2e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 28 Jan 2022 22:06:34 +0900 Subject: [PATCH 0351/5199] Change FrameDataBundle.Frames into an IList --- osu.Game/Online/Spectator/FrameDataBundle.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Spectator/FrameDataBundle.cs b/osu.Game/Online/Spectator/FrameDataBundle.cs index 0e59cdf4ce..a4c4972989 100644 --- a/osu.Game/Online/Spectator/FrameDataBundle.cs +++ b/osu.Game/Online/Spectator/FrameDataBundle.cs @@ -20,16 +20,16 @@ namespace osu.Game.Online.Spectator public FrameHeader Header { get; set; } [Key(1)] - public IEnumerable Frames { get; set; } + public IList Frames { get; set; } - public FrameDataBundle(ScoreInfo score, IEnumerable frames) + public FrameDataBundle(ScoreInfo score, IList frames) { Frames = frames; Header = new FrameHeader(score); } [JsonConstructor] - public FrameDataBundle(FrameHeader header, IEnumerable frames) + public FrameDataBundle(FrameHeader header, IList frames) { Header = header; Frames = frames; From 3037a3a7696b7f00cef3820c5c5994949de78622 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 28 Jan 2022 22:26:05 +0900 Subject: [PATCH 0352/5199] Purge final spectator frames before ending play --- .../Visual/Gameplay/TestSceneSpectator.cs | 18 ++++++++++++++++++ osu.Game/Online/Spectator/SpectatorClient.cs | 10 ++++++++-- .../Visual/Spectator/TestSpectatorClient.cs | 11 +++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 0afd2c76dd..d1a25b07ec 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -15,11 +15,14 @@ using osu.Game.Online.Spectator; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Visual.Multiplayer; using osu.Game.Tests.Visual.Spectator; +using osuTK; namespace osu.Game.Tests.Visual.Gameplay { @@ -200,6 +203,21 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("screen didn't change", () => Stack.CurrentScreen is SoloSpectator); } + [Test] + public void TestFinalFramesPurgedBeforeEndingPlay() + { + AddStep("begin playing", () => spectatorClient.BeginPlaying(new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()), new Score())); + + AddStep("send frames and finish play", () => + { + spectatorClient.HandleFrame(new OsuReplayFrame(1000, Vector2.Zero)); + spectatorClient.EndPlaying(); + }); + + // We can't access API because we're an "online" test. + AddAssert("last received frame has time = 1000", () => spectatorClient.LastReceivedFrame.First().Value.Time == 1000); + } + private OsuFramedReplayInputHandler replayHandler => (OsuFramedReplayInputHandler)Stack.ChildrenOfType().First().ReplayInputHandler; diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 4da9bace70..fddb94fad7 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -167,6 +167,9 @@ namespace osu.Game.Online.Spectator if (!IsPlaying) return; + if (pendingFrames.Count > 0) + purgePendingFrames(true); + IsPlaying = false; currentBeatmap = null; @@ -238,9 +241,12 @@ namespace osu.Game.Online.Spectator purgePendingFrames(); } - private void purgePendingFrames() + private void purgePendingFrames(bool force = false) { - if (lastSend?.IsCompleted == false) + if (lastSend?.IsCompleted == false && !force) + return; + + if (pendingFrames.Count == 0) return; var frames = pendingFrames.ToArray(); diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index f206d4f8b0..3f3d0f5b01 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -14,6 +14,7 @@ using osu.Framework.Utils; using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Replays.Legacy; +using osu.Game.Rulesets.Replays; using osu.Game.Scoring; namespace osu.Game.Tests.Visual.Spectator @@ -27,12 +28,22 @@ namespace osu.Game.Tests.Visual.Spectator public override IBindable IsConnected { get; } = new Bindable(true); + public readonly Dictionary LastReceivedFrame = new Dictionary(); private readonly Dictionary userBeatmapDictionary = new Dictionary(); private readonly Dictionary userNextFrameDictionary = new Dictionary(); [Resolved] private IAPIProvider api { get; set; } = null!; + public TestSpectatorClient() + { + OnNewFrames += (i, bundle) => + { + if (PlayingUsers.Contains(i)) + LastReceivedFrame[i] = bundle.Frames[^1]; + }; + } + /// /// Starts play for an arbitrary user. /// From 1253e1ecc1f7ec68f78f7b30878b673b2c37345d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 28 Jan 2022 20:25:12 +0100 Subject: [PATCH 0353/5199] Replace LINQ `Count()` invocation with count property access --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 4790bd44db..90abdf2ba3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -184,7 +184,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void onNewFrames(int userId, FrameDataBundle frames) { - Logger.Log($"Received {frames.Frames.Count()} new frames ({string.Join(',', frames.Frames.Select(f => ((int)f.Time).ToString()))})"); + Logger.Log($"Received {frames.Frames.Count} new frames ({string.Join(',', frames.Frames.Select(f => ((int)f.Time).ToString()))})"); foreach (var legacyFrame in frames.Frames) { From a4aa501bb540cd83f988febd90d128f1c890b0b9 Mon Sep 17 00:00:00 2001 From: Spooghetti420 Date: Fri, 28 Jan 2022 21:59:53 +0000 Subject: [PATCH 0354/5199] Change threshold from ms to beat-based, add tests --- .../Mods/TestSceneManiaModHoldOff.cs | 130 ++++++++++++++++++ .../Mods/TestSceneManiaModNoHolds.cs | 51 ------- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- ...{ManiaModNoHolds.cs => ManiaModHoldOff.cs} | 34 +++-- .../Mods/ManiaModNoLongNotes.cs | 86 ------------ 5 files changed, 155 insertions(+), 148 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs delete mode 100644 osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs rename osu.Game.Rulesets.Mania/Mods/{ManiaModNoHolds.cs => ManiaModHoldOff.cs} (66%) delete mode 100644 osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs new file mode 100644 index 0000000000..e157cb50b1 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs @@ -0,0 +1,130 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Tests.Visual; +using System.Collections.Generic; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Difficulty; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Mania.Tests.Mods +{ + public class TestSceneManiaModHoldOff : ModTestScene + { + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + + [Test] + public void TestMapHasNoHoldNotes() + { + var testBeatmap = createModdedBeatmap(); + Assert.False(testBeatmap.HitObjects.OfType().Any()); + } + + [Test] + public void TestCorrectNoteValues() + { + var testBeatmap = createRawBeatmap(); + var noteValues = new List(testBeatmap.HitObjects.OfType().Count()); + foreach (HoldNote h in testBeatmap.HitObjects.OfType()) { + noteValues.Add(ManiaModHoldOff.getNoteValue(h, (ManiaBeatmap)testBeatmap)); + } + noteValues.Sort(); + Assert.AreEqual(noteValues, new List { 0.125, 0.250, 0.500, 1.000, 2.000 }); + } + + [TestCase(ManiaModHoldOff.BeatDivisors.Whole)] + [TestCase(ManiaModHoldOff.BeatDivisors.Half)] + [TestCase(ManiaModHoldOff.BeatDivisors.Quarter)] + [TestCase(ManiaModHoldOff.BeatDivisors.Eighth)] + public void TestCorrectObjectCount(ManiaModHoldOff.BeatDivisors minBeatSnap) { + /* + This test is to ensure that, given that end notes are enabled, + the mod produces the expected number of objects when the mod is applied. + */ + + // Mod settings will be set to include the correct beat snap value + var rawBeatmap = createRawBeatmap(); + var testBeatmap = createModdedBeatmap(minBeatSnap); + + // Calculate expected number of objects + int expectedObjectCount = 0; + double beatSnapValue = 1/(Math.Pow(2, (int)minBeatSnap)); + + foreach (ManiaHitObject h in rawBeatmap.HitObjects) { + // Both notes and hold notes account for at least one object + expectedObjectCount++; + if (h.GetType() == typeof(HoldNote)) { + var noteValue = ManiaModHoldOff.getNoteValue((HoldNote)h, (ManiaBeatmap)rawBeatmap); + if (noteValue >= beatSnapValue) { + // Should generate an end note if it's longer than the minimum note value + expectedObjectCount++; + } + } + } + + Assert.That(testBeatmap.HitObjects.Count() == expectedObjectCount); + } + + [Test] + public void TestDifficultyIncrease() { + // A lower minimum beat snap divisor should only make the map harder, never easier + // (as more notes can be spawned) + var beatmaps = new ManiaBeatmap[] { + createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Whole), + createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Half), + createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Quarter), + createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Eighth), + createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Sixteenth) + }; + + var mapDifficulties = new double[beatmaps.Length]; + for (int i = 0; i < mapDifficulties.Length; i++) { + var workingBeatmap = new TestWorkingBeatmap(beatmaps[i]); + var difficultyCalculator = new ManiaDifficultyCalculator(new ManiaRuleset().RulesetInfo, workingBeatmap); + mapDifficulties[i] = difficultyCalculator.Calculate().StarRating; + if (i > 0) { + Assert.LessOrEqual(mapDifficulties[i-1], mapDifficulties[i]); + Assert.LessOrEqual(beatmaps[i-1].HitObjects.Count, beatmaps[i].HitObjects.Count); + } + } + } + + private static ManiaBeatmap createModdedBeatmap(ManiaModHoldOff.BeatDivisors minBeatSnap=ManiaModHoldOff.BeatDivisors.Whole) + { + var beatmap = createRawBeatmap(); + var holdOffMod = new ManiaModHoldOff(); + holdOffMod.MinBeatSnap.Value = minBeatSnap; // Set the specified beat snap setting + Assert.AreEqual(holdOffMod.MinBeatSnap.Value, minBeatSnap); + + foreach (var hitObject in beatmap.HitObjects) + hitObject.ApplyDefaults(beatmap.ControlPointInfo, new BeatmapDifficulty()); + + holdOffMod.ApplyToBeatmap(beatmap); + + return (ManiaBeatmap)beatmap; + } + private static ManiaBeatmap createRawBeatmap() + { + var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); + beatmap.ControlPointInfo.Add(0.0, new TimingControlPoint { BeatLength = 1000 } ); // Set BPM to 60 + + // Add test hit objects + beatmap.HitObjects.Add(new Note { StartTime = 4000 }); + beatmap.HitObjects.Add(new Note { StartTime = 4500 }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 125 }); // 1/8 note + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 250 }); // 1/4 note + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 500 }); // 1/2 note + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 1000 }); // 1/1 note + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 2000 }); // 2/1 note + + return beatmap; + } + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs deleted file mode 100644 index f22b724c99..0000000000 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs +++ /dev/null @@ -1,51 +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.Linq; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Testing; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mania.Mods; -using osu.Game.Tests.Visual; -using osu.Game.Rulesets.Mania.Tests; -using System.Collections.Generic; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Objects; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Mania.Beatmaps; - -namespace osu.Game.Rulesets.Mania.Tests.Mods -{ - public class TestSceneManiaModNoHolds : ModTestScene - { - protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); - - [Test] - public void TestMapHasNoHeldNotes() - { - var testBeatmap = createBeatmap(); - Assert.That(!testBeatmap.HitObjects.OfType().Any()); - } - - private static IBeatmap createBeatmap() - { - var beatmap = createRawBeatmap(); - var noHoldsMod = new ManiaModNoHolds(); - - foreach (var hitObject in beatmap.HitObjects) - hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - - noHoldsMod.ApplyToBeatmap(beatmap); - - return beatmap; - } - private static IBeatmap createRawBeatmap() - { - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); - beatmap.HitObjects.Add(new Note { StartTime = 1000 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, EndTime = 3000 }); - return beatmap; - } - } -} diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 55ca1a465e..2098c7f5d8 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -244,7 +244,7 @@ namespace osu.Game.Rulesets.Mania new ManiaModClassic(), new ManiaModInvert(), new ManiaModConstantSpeed(), - new ManiaModNoHolds() + new ManiaModHoldOff() }; case ModType.Automation: diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs similarity index 66% rename from osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs rename to osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index a18dbad4a1..cfdb58ee67 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -2,28 +2,23 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; using osu.Framework.Graphics.Sprites; using System; using System.Collections.Generic; -using osu.Game.Audio; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Framework.Utils; -using osu.Game.Overlays.Settings; using osu.Framework.Bindables; using osu.Game.Configuration; -using osu.Game.Graphics; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModNoHolds : Mod, IApplicableAfterBeatmapConversion + public class ManiaModHoldOff : Mod, IApplicableAfterBeatmapConversion { - public override string Name => "No Holds"; + public override string Name => "Hold Off"; - public override string Acronym => "NH"; + public override string Acronym => "HO"; public override double ScoreMultiplier => 1; @@ -40,11 +35,15 @@ namespace osu.Game.Rulesets.Mania.Mods Value = true }; + [SettingSource("Minimum end note beat snap", "Don't add end notes for hold notes shorter than this beat division")] + public Bindable MinBeatSnap { get; } = new Bindable(defaultValue: BeatDivisors.Half); + public void ApplyToBeatmap(IBeatmap beatmap) { var maniaBeatmap = (ManiaBeatmap)beatmap; var newObjects = new List(); + var beatSnap = 1/(Math.Pow(2, (double)MinBeatSnap.Value)); foreach (var h in beatmap.HitObjects.OfType()) { // Add a note for the beginning of the hold note @@ -56,7 +55,8 @@ namespace osu.Game.Rulesets.Mania.Mods }); // Don't add an end note if the duration is shorter than some threshold, or end notes are disabled - if (AddEndNotes.Value && h.Duration > 200) + var noteValue = getNoteValue(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc. + if (AddEndNotes.Value && noteValue >= beatSnap) { newObjects.Add(new Note { @@ -66,8 +66,22 @@ namespace osu.Game.Rulesets.Mania.Mods }); } } - maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType().Concat(newObjects).OrderBy(h => h.StartTime).ToList(); } + + public static double getNoteValue(HoldNote holdNote, ManiaBeatmap beatmap) { + var bpmAtNoteTime = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BPM; + var noteValue = (60*holdNote.Duration)/(1000*bpmAtNoteTime); + return noteValue; + } + + public enum BeatDivisors + { + Whole, + Half, + Quarter, + Eighth, + Sixteenth + } } } \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs deleted file mode 100644 index d10003b783..0000000000 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs +++ /dev/null @@ -1,86 +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.Linq; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mods; -using osu.Framework.Graphics.Sprites; -using System; -using System.Collections.Generic; -using osu.Game.Audio; -using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Framework.Utils; -using osu.Game.Overlays.Settings; -using osu.Framework.Bindables; -using osu.Game.Configuration; -using osu.Game.Graphics; - - - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModNoLongNotes : Mod, IApplicableAfterBeatmapConversion - { - - public override string Name => "No Long Notes"; - - public override string Acronym => "NL"; - - public override double ScoreMultiplier => 1; - - public override string Description => @"Turns all held notes into tap notes. No coordination required."; - - public override IconUsage? Icon => FontAwesome.Solid.DotCircle; - - public override ModType Type => ModType.Conversion; - - [SettingSource("Add end notes", "Also add a note at the end of a held note")] - public BindableBool AddEndNotes { get; } = new BindableBool - { - Default = true, - Value = true - }; - - [SettingSource("Length threshold", "Only add an end note for held notes longer than this threshold (in milliseconds)")] - public BindableNumber Threshold { get; } = new BindableDouble - { - MinValue = 1.0, - MaxValue = 1990.0, - Default = 200.0, - Value = 200.0, - Precision = 1.0, - }; - - public void ApplyToBeatmap(IBeatmap beatmap) - { - var maniaBeatmap = (ManiaBeatmap)beatmap; - - var newObjects = new List(); - beatmap.HitObjects.OfType().ForEach(h => - { - // Add a note for the beginning of the hold note - newObjects.Add(new Note - { - Column = h.Column, - StartTime = h.StartTime, - Samples = h.Samples - }); - - // Don't add an end note if the duration is below the threshold, or end notes are disabled - if (AddEndNotes.Value && h.Duration > Threshold.Value) - { - newObjects.Add(new Note - { - Column = h.Column, - StartTime = h.EndTime, - Samples = h.Samples - }); - } - }); - - maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType().Concat(newObjects).OrderBy(h => h.StartTime).ToList(); - } - } -} From b2ebcabdd5c56fa734a6eaf8eacc8b8c5c3db9a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 13:27:19 +0900 Subject: [PATCH 0355/5199] Fix potential crash during stable install migration due to multiple configuration files Apparently this can be a thing on windows. Closes https://github.com/ppy/osu/discussions/16689. --- osu.Game/IO/StableStorage.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/IO/StableStorage.cs b/osu.Game/IO/StableStorage.cs index f5a8c4dc9e..84b7da91fc 100644 --- a/osu.Game/IO/StableStorage.cs +++ b/osu.Game/IO/StableStorage.cs @@ -34,11 +34,17 @@ namespace osu.Game.IO private string locateSongsDirectory() { - string configFile = GetFiles(".", $"osu!.{Environment.UserName}.cfg").SingleOrDefault(); + var configurationFiles = GetFiles(".", $"osu!.{Environment.UserName}.cfg"); - if (configFile != null) + // GetFiles returns case insensitive results, so multiple files could exist. + // Prefer a case-correct match, but fallback to any available. + string usableConfigFile = + configurationFiles.FirstOrDefault(f => f.Contains(Environment.UserName, StringComparison.Ordinal)) + ?? configurationFiles.FirstOrDefault(); + + if (usableConfigFile != null) { - using (var stream = GetStream(configFile)) + using (var stream = GetStream(usableConfigFile)) using (var textReader = new StreamReader(stream)) { string line; From c75ffe9b0764effd0c5270ac6b0a0bcd02c6762a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 13:47:04 +0900 Subject: [PATCH 0356/5199] Apply code style changes --- osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index cfdb58ee67..aa447356ff 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -43,7 +43,8 @@ namespace osu.Game.Rulesets.Mania.Mods var maniaBeatmap = (ManiaBeatmap)beatmap; var newObjects = new List(); - var beatSnap = 1/(Math.Pow(2, (double)MinBeatSnap.Value)); + double beatSnap = 1 / (Math.Pow(2, (double)MinBeatSnap.Value)); + foreach (var h in beatmap.HitObjects.OfType()) { // Add a note for the beginning of the hold note @@ -55,7 +56,8 @@ namespace osu.Game.Rulesets.Mania.Mods }); // Don't add an end note if the duration is shorter than some threshold, or end notes are disabled - var noteValue = getNoteValue(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc. + double noteValue = getNoteValue(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc. + if (AddEndNotes.Value && noteValue >= beatSnap) { newObjects.Add(new Note @@ -66,13 +68,14 @@ namespace osu.Game.Rulesets.Mania.Mods }); } } + maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType().Concat(newObjects).OrderBy(h => h.StartTime).ToList(); } - public static double getNoteValue(HoldNote holdNote, ManiaBeatmap beatmap) { - var bpmAtNoteTime = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BPM; - var noteValue = (60*holdNote.Duration)/(1000*bpmAtNoteTime); - return noteValue; + private static double getNoteValue(HoldNote holdNote, ManiaBeatmap beatmap) + { + double bpmAtNoteTime = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BPM; + return (60 * holdNote.Duration) / (1000 * bpmAtNoteTime); } public enum BeatDivisors @@ -84,4 +87,4 @@ namespace osu.Game.Rulesets.Mania.Mods Sixteenth } } -} \ No newline at end of file +} From 035a84e75c4b84f655ad8fadd27b24677bd66ae2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 14:05:23 +0900 Subject: [PATCH 0357/5199] Rename function and make `public` again for test usage --- .../Mods/TestSceneManiaModHoldOff.cs | 54 ++++++++++++------- .../Mods/ManiaModHoldOff.cs | 4 +- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs index e157cb50b1..ddf09aab33 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs @@ -32,9 +32,12 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods { var testBeatmap = createRawBeatmap(); var noteValues = new List(testBeatmap.HitObjects.OfType().Count()); - foreach (HoldNote h in testBeatmap.HitObjects.OfType()) { - noteValues.Add(ManiaModHoldOff.getNoteValue(h, (ManiaBeatmap)testBeatmap)); + + foreach (HoldNote h in testBeatmap.HitObjects.OfType()) + { + noteValues.Add(ManiaModHoldOff.GetNoteDurationInBeatLength(h, (ManiaBeatmap)testBeatmap)); } + noteValues.Sort(); Assert.AreEqual(noteValues, new List { 0.125, 0.250, 0.500, 1.000, 2.000 }); } @@ -43,7 +46,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods [TestCase(ManiaModHoldOff.BeatDivisors.Half)] [TestCase(ManiaModHoldOff.BeatDivisors.Quarter)] [TestCase(ManiaModHoldOff.BeatDivisors.Eighth)] - public void TestCorrectObjectCount(ManiaModHoldOff.BeatDivisors minBeatSnap) { + public void TestCorrectObjectCount(ManiaModHoldOff.BeatDivisors minBeatSnap) + { /* This test is to ensure that, given that end notes are enabled, the mod produces the expected number of objects when the mod is applied. @@ -55,14 +59,19 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods // Calculate expected number of objects int expectedObjectCount = 0; - double beatSnapValue = 1/(Math.Pow(2, (int)minBeatSnap)); + double beatSnapValue = 1 / (Math.Pow(2, (int)minBeatSnap)); - foreach (ManiaHitObject h in rawBeatmap.HitObjects) { + foreach (ManiaHitObject h in rawBeatmap.HitObjects) + { // Both notes and hold notes account for at least one object expectedObjectCount++; - if (h.GetType() == typeof(HoldNote)) { - var noteValue = ManiaModHoldOff.getNoteValue((HoldNote)h, (ManiaBeatmap)rawBeatmap); - if (noteValue >= beatSnapValue) { + + if (h.GetType() == typeof(HoldNote)) + { + var noteValue = ManiaModHoldOff.GetNoteDurationInBeatLength((HoldNote)h, (ManiaBeatmap)rawBeatmap); + + if (noteValue >= beatSnapValue) + { // Should generate an end note if it's longer than the minimum note value expectedObjectCount++; } @@ -73,10 +82,12 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods } [Test] - public void TestDifficultyIncrease() { + public void TestDifficultyIncrease() + { // A lower minimum beat snap divisor should only make the map harder, never easier // (as more notes can be spawned) - var beatmaps = new ManiaBeatmap[] { + var beatmaps = new ManiaBeatmap[] + { createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Whole), createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Half), createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Quarter), @@ -85,18 +96,22 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods }; var mapDifficulties = new double[beatmaps.Length]; - for (int i = 0; i < mapDifficulties.Length; i++) { + + for (int i = 0; i < mapDifficulties.Length; i++) + { var workingBeatmap = new TestWorkingBeatmap(beatmaps[i]); var difficultyCalculator = new ManiaDifficultyCalculator(new ManiaRuleset().RulesetInfo, workingBeatmap); mapDifficulties[i] = difficultyCalculator.Calculate().StarRating; - if (i > 0) { - Assert.LessOrEqual(mapDifficulties[i-1], mapDifficulties[i]); - Assert.LessOrEqual(beatmaps[i-1].HitObjects.Count, beatmaps[i].HitObjects.Count); + + if (i > 0) + { + Assert.LessOrEqual(mapDifficulties[i - 1], mapDifficulties[i]); + Assert.LessOrEqual(beatmaps[i - 1].HitObjects.Count, beatmaps[i].HitObjects.Count); } } } - private static ManiaBeatmap createModdedBeatmap(ManiaModHoldOff.BeatDivisors minBeatSnap=ManiaModHoldOff.BeatDivisors.Whole) + private static ManiaBeatmap createModdedBeatmap(ManiaModHoldOff.BeatDivisors minBeatSnap = ManiaModHoldOff.BeatDivisors.Whole) { var beatmap = createRawBeatmap(); var holdOffMod = new ManiaModHoldOff(); @@ -110,17 +125,18 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods return (ManiaBeatmap)beatmap; } + private static ManiaBeatmap createRawBeatmap() { var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); - beatmap.ControlPointInfo.Add(0.0, new TimingControlPoint { BeatLength = 1000 } ); // Set BPM to 60 + beatmap.ControlPointInfo.Add(0.0, new TimingControlPoint { BeatLength = 1000 }); // Set BPM to 60 // Add test hit objects beatmap.HitObjects.Add(new Note { StartTime = 4000 }); beatmap.HitObjects.Add(new Note { StartTime = 4500 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 125 }); // 1/8 note - beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 250 }); // 1/4 note - beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 500 }); // 1/2 note + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 125 }); // 1/8 note + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 250 }); // 1/4 note + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 500 }); // 1/2 note beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 1000 }); // 1/1 note beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 2000 }); // 2/1 note diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index aa447356ff..637142aed6 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.Mods }); // Don't add an end note if the duration is shorter than some threshold, or end notes are disabled - double noteValue = getNoteValue(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc. + double noteValue = GetNoteDurationInBeatLength(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc. if (AddEndNotes.Value && noteValue >= beatSnap) { @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Mania.Mods maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType().Concat(newObjects).OrderBy(h => h.StartTime).ToList(); } - private static double getNoteValue(HoldNote holdNote, ManiaBeatmap beatmap) + public static double GetNoteDurationInBeatLength(HoldNote holdNote, ManiaBeatmap beatmap) { double bpmAtNoteTime = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BPM; return (60 * holdNote.Duration) / (1000 * bpmAtNoteTime); From c7580a5177eb24aad1a667bee76e098dcb60f559 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 14:06:43 +0900 Subject: [PATCH 0358/5199] Fix inspections in test scene --- .../Mods/TestSceneManiaModHoldOff.cs | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs index ddf09aab33..e74f63abb3 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods foreach (HoldNote h in testBeatmap.HitObjects.OfType()) { - noteValues.Add(ManiaModHoldOff.GetNoteDurationInBeatLength(h, (ManiaBeatmap)testBeatmap)); + noteValues.Add(ManiaModHoldOff.GetNoteDurationInBeatLength(h, testBeatmap)); } noteValues.Sort(); @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods if (h.GetType() == typeof(HoldNote)) { - var noteValue = ManiaModHoldOff.GetNoteDurationInBeatLength((HoldNote)h, (ManiaBeatmap)rawBeatmap); + double noteValue = ManiaModHoldOff.GetNoteDurationInBeatLength((HoldNote)h, rawBeatmap); if (noteValue >= beatSnapValue) { @@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods } } - Assert.That(testBeatmap.HitObjects.Count() == expectedObjectCount); + Assert.That(testBeatmap.HitObjects.Count == expectedObjectCount); } [Test] @@ -86,16 +86,16 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods { // A lower minimum beat snap divisor should only make the map harder, never easier // (as more notes can be spawned) - var beatmaps = new ManiaBeatmap[] + var beatmaps = new[] { - createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Whole), + createModdedBeatmap(), createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Half), createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Quarter), createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Eighth), createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Sixteenth) }; - var mapDifficulties = new double[beatmaps.Length]; + double[] mapDifficulties = new double[beatmaps.Length]; for (int i = 0; i < mapDifficulties.Length; i++) { @@ -114,8 +114,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods private static ManiaBeatmap createModdedBeatmap(ManiaModHoldOff.BeatDivisors minBeatSnap = ManiaModHoldOff.BeatDivisors.Whole) { var beatmap = createRawBeatmap(); - var holdOffMod = new ManiaModHoldOff(); - holdOffMod.MinBeatSnap.Value = minBeatSnap; // Set the specified beat snap setting + var holdOffMod = new ManiaModHoldOff + { + MinBeatSnap = { Value = minBeatSnap } + }; Assert.AreEqual(holdOffMod.MinBeatSnap.Value, minBeatSnap); foreach (var hitObject in beatmap.HitObjects) @@ -123,7 +125,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods holdOffMod.ApplyToBeatmap(beatmap); - return (ManiaBeatmap)beatmap; + return beatmap; } private static ManiaBeatmap createRawBeatmap() From 4c97ed676fc335a8640bcf1e39085e656b4e997f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 14:46:24 +0900 Subject: [PATCH 0359/5199] Fix score presentation tests not correctly entering song select before running --- osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index 7656bf79dc..6c32171b29 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -16,6 +16,7 @@ using osu.Game.Screens; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; +using osu.Game.Screens.Select; namespace osu.Game.Tests.Visual.Navigation { @@ -92,6 +93,9 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestFromSongSelect([Values] ScorePresentType type) { + AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo.Invoke()); + AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded); + var firstImport = importScore(1); presentAndConfirm(firstImport, type); @@ -102,6 +106,9 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestFromSongSelectDifferentRuleset([Values] ScorePresentType type) { + AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo.Invoke()); + AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded); + var firstImport = importScore(1); presentAndConfirm(firstImport, type); From e7823982d827b3371f80b4f74a5c567933c62551 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 18:38:45 +0900 Subject: [PATCH 0360/5199] Fix ruleset value not being transferred when `FinaliseSelection` is not called --- osu.Game/Screens/Select/SongSelect.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 10150fcd9f..f5b11448f8 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -619,6 +619,10 @@ namespace osu.Game.Screens.Select public override void OnSuspending(IScreen next) { + // Handle the case where FinaliseSelection is never called (ie. when a screen is pushed externally). + // Without this, it's possible for a transfer to happen while we are not the current screen. + transferRulesetValue(); + ModSelect.SelectedMods.UnbindFrom(selectedMods); ModSelect.Hide(); From aa582fb0e1a15de97b48203a7bcaed3c84a4d6c7 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 20:38:12 +0800 Subject: [PATCH 0361/5199] add Alternate Mod --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 45 +++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 1 + osu.Game/Rulesets/Mods/ModAlternate.cs | 77 +++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs create mode 100644 osu.Game/Rulesets/Mods/ModAlternate.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs new file mode 100644 index 0000000000..9a9074ee52 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -0,0 +1,45 @@ +// 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.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModAlternate : ModAlternate + { + private const double flash_duration = 1000; + private OsuAction? lastActionPressed; + private DrawableRuleset ruleset; + + public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + ruleset = drawableRuleset; + base.ApplyToDrawableRuleset(drawableRuleset); + } + + protected override void Reset() + { + lastActionPressed = null; + } + + protected override bool OnPressed(OsuAction key) + { + if (lastActionPressed == key) + { + ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint); + return true; + } + + lastActionPressed = key; + + return false; + } + + protected override void OnReleased(OsuAction key) + { + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 18e4bb259c..7e8974b5ed 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -159,6 +159,7 @@ namespace osu.Game.Rulesets.Osu new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()), new OsuModHidden(), new MultiMod(new OsuModFlashlight(), new OsuModBlinds()), + new OsuModAlternate(), }; case ModType.Conversion: diff --git a/osu.Game/Rulesets/Mods/ModAlternate.cs b/osu.Game/Rulesets/Mods/ModAlternate.cs new file mode 100644 index 0000000000..683654f605 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModAlternate.cs @@ -0,0 +1,77 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModAlternate : Mod + { + public override string Name => @"Alternate"; + public override string Acronym => @"AL"; + public override string Description => @"Never hit the same key twice!"; + public override double ScoreMultiplier => 1.0; + public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) }; + public override ModType Type => ModType.DifficultyIncrease; + public override IconUsage? Icon => FontAwesome.Solid.Keyboard; + } + + public abstract class ModAlternate : ModAlternate, IApplicableToDrawableRuleset, IApplicableToPlayer + where THitObject : HitObject + where TAction : struct + { + public bool CanIntercept => !isBreakTime.Value; + + private IBindable isBreakTime; + + public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this)); + } + + public void ApplyToPlayer(Player player) + { + isBreakTime = player.IsBreakTime.GetBoundCopy(); + isBreakTime.ValueChanged += e => + { + if (e.NewValue) + Reset(); + }; + } + + protected abstract void Reset(); + + protected abstract bool OnPressed(TAction key); + + protected abstract void OnReleased(TAction key); + + private class InputInterceptor : Component, IKeyBindingHandler + { + private readonly ModAlternate mod; + + public InputInterceptor(ModAlternate mod) + { + this.mod = mod; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + return mod.CanIntercept && mod.OnPressed(e.Action); + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + if (mod.CanIntercept) + mod.OnReleased(e.Action); + } + } + } +} From 2326c36836aa317635c869610d7daff18ecbbee3 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 21:09:36 +0800 Subject: [PATCH 0362/5199] remove unused method and fix description --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 4 ---- osu.Game/Rulesets/Mods/ModAlternate.cs | 9 ++++----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index 9a9074ee52..34802bab43 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -37,9 +37,5 @@ namespace osu.Game.Rulesets.Osu.Mods return false; } - - protected override void OnReleased(OsuAction key) - { - } } } diff --git a/osu.Game/Rulesets/Mods/ModAlternate.cs b/osu.Game/Rulesets/Mods/ModAlternate.cs index 683654f605..17d8b92469 100644 --- a/osu.Game/Rulesets/Mods/ModAlternate.cs +++ b/osu.Game/Rulesets/Mods/ModAlternate.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => @"Alternate"; public override string Acronym => @"AL"; - public override string Description => @"Never hit the same key twice!"; + public override string Description => @"Don't use the same key twice in a row!"; public override double ScoreMultiplier => 1.0; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) }; public override ModType Type => ModType.DifficultyIncrease; @@ -28,6 +28,9 @@ namespace osu.Game.Rulesets.Mods where THitObject : HitObject where TAction : struct { + /// + /// Whether incoming input must be checked by . + /// public bool CanIntercept => !isBreakTime.Value; private IBindable isBreakTime; @@ -51,8 +54,6 @@ namespace osu.Game.Rulesets.Mods protected abstract bool OnPressed(TAction key); - protected abstract void OnReleased(TAction key); - private class InputInterceptor : Component, IKeyBindingHandler { private readonly ModAlternate mod; @@ -69,8 +70,6 @@ namespace osu.Game.Rulesets.Mods public void OnReleased(KeyBindingReleaseEvent e) { - if (mod.CanIntercept) - mod.OnReleased(e.Action); } } } From 98d8b26a9c24dee403ba9d7199d0652910c72c0b Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 21:49:40 +0800 Subject: [PATCH 0363/5199] move `ModAlternate` to `OsuModAlternate` and check if intro has ended --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 67 ++++++++++++++-- osu.Game/Rulesets/Mods/ModAlternate.cs | 76 ------------------- 2 files changed, 61 insertions(+), 82 deletions(-) delete mode 100644 osu.Game/Rulesets/Mods/ModAlternate.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index 34802bab43..f366481c7d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -1,31 +1,63 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModAlternate : ModAlternate + public class OsuModAlternate : Mod, IApplicableToDrawableRuleset, IApplicableToPlayer, IUpdatableByPlayfield { + public override string Name => @"Alternate"; + public override string Acronym => @"AL"; + public override string Description => @"Don't use the same key twice in a row!"; + public override double ScoreMultiplier => 1.0; + public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) }; + public override ModType Type => ModType.DifficultyIncrease; + public override IconUsage? Icon => FontAwesome.Solid.Keyboard; + + /// + /// Whether incoming input must be checked by . + /// + public bool CanIntercept => !isBreakTime.Value && introEnded; + + private bool introEnded; + private double earliestStartTime; + private IBindable isBreakTime; private const double flash_duration = 1000; private OsuAction? lastActionPressed; private DrawableRuleset ruleset; - public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { ruleset = drawableRuleset; - base.ApplyToDrawableRuleset(drawableRuleset); + drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this)); + + var firstHitObject = ruleset.Objects.FirstOrDefault(); + earliestStartTime = (firstHitObject?.StartTime ?? 0) - (firstHitObject?.HitWindows.WindowFor(HitResult.Meh) ?? 0); } - protected override void Reset() + public void ApplyToPlayer(Player player) { - lastActionPressed = null; + isBreakTime = player.IsBreakTime.GetBoundCopy(); + isBreakTime.ValueChanged += e => + { + if (e.NewValue) + lastActionPressed = null; + }; } - protected override bool OnPressed(OsuAction key) + private bool onPressed(OsuAction key) { if (lastActionPressed == key) { @@ -37,5 +69,28 @@ namespace osu.Game.Rulesets.Osu.Mods return false; } + + public void Update(Playfield playfield) + { + if (!introEnded) + introEnded = playfield.Clock.CurrentTime > earliestStartTime; + } + + private class InputInterceptor : Component, IKeyBindingHandler + { + private readonly OsuModAlternate mod; + + public InputInterceptor(OsuModAlternate mod) + { + this.mod = mod; + } + + public bool OnPressed(KeyBindingPressEvent e) + => mod.CanIntercept && mod.onPressed(e.Action); + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + } } } diff --git a/osu.Game/Rulesets/Mods/ModAlternate.cs b/osu.Game/Rulesets/Mods/ModAlternate.cs deleted file mode 100644 index 17d8b92469..0000000000 --- a/osu.Game/Rulesets/Mods/ModAlternate.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; - -namespace osu.Game.Rulesets.Mods -{ - public abstract class ModAlternate : Mod - { - public override string Name => @"Alternate"; - public override string Acronym => @"AL"; - public override string Description => @"Don't use the same key twice in a row!"; - public override double ScoreMultiplier => 1.0; - public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) }; - public override ModType Type => ModType.DifficultyIncrease; - public override IconUsage? Icon => FontAwesome.Solid.Keyboard; - } - - public abstract class ModAlternate : ModAlternate, IApplicableToDrawableRuleset, IApplicableToPlayer - where THitObject : HitObject - where TAction : struct - { - /// - /// Whether incoming input must be checked by . - /// - public bool CanIntercept => !isBreakTime.Value; - - private IBindable isBreakTime; - - public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) - { - drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this)); - } - - public void ApplyToPlayer(Player player) - { - isBreakTime = player.IsBreakTime.GetBoundCopy(); - isBreakTime.ValueChanged += e => - { - if (e.NewValue) - Reset(); - }; - } - - protected abstract void Reset(); - - protected abstract bool OnPressed(TAction key); - - private class InputInterceptor : Component, IKeyBindingHandler - { - private readonly ModAlternate mod; - - public InputInterceptor(ModAlternate mod) - { - this.mod = mod; - } - - public bool OnPressed(KeyBindingPressEvent e) - { - return mod.CanIntercept && mod.OnPressed(e.Action); - } - - public void OnReleased(KeyBindingReleaseEvent e) - { - } - } - } -} From d48fae11004b7142a58b8d701c0cbfd073e43992 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 23:13:19 +0900 Subject: [PATCH 0364/5199] Revert "Remove all EF migrations" This reverts commit bb5b9458e8130a2a5f213e968f5590d00ede3d87. --- .../20171019041408_InitialCreate.Designer.cs | 293 ++++++++++ .../20171019041408_InitialCreate.cs | 311 +++++++++++ ...025071459_AddMissingIndexRules.Designer.cs | 299 ++++++++++ .../20171025071459_AddMissingIndexRules.cs | 80 +++ ...eatmapOnlineIDUniqueConstraint.Designer.cs | 302 ++++++++++ ...5731_AddBeatmapOnlineIDUniqueConstraint.cs | 23 + ...034410_AddRulesetInfoShortName.Designer.cs | 307 +++++++++++ .../20171209034410_AddRulesetInfoShortName.cs | 33 ++ .../20180125143340_Settings.Designer.cs | 329 +++++++++++ .../Migrations/20180125143340_Settings.cs | 55 ++ .../20180131154205_AddMuteBinding.cs | 23 + .../20180219060912_AddSkins.Designer.cs | 379 +++++++++++++ .../Migrations/20180219060912_AddSkins.cs | 71 +++ ...54_RemoveUniqueHashConstraints.Designer.cs | 377 +++++++++++++ ...80529055154_RemoveUniqueHashConstraints.cs | 51 ++ ...111_UpdateTaikoDefaultBindings.Designer.cs | 376 +++++++++++++ ...180621044111_UpdateTaikoDefaultBindings.cs | 17 + ...628011956_RemoveNegativeSetIDs.Designer.cs | 376 +++++++++++++ .../20180628011956_RemoveNegativeSetIDs.cs | 18 + .../20180913080842_AddRankStatus.Designer.cs | 380 +++++++++++++ .../20180913080842_AddRankStatus.cs | 33 ++ ...0181007180454_StandardizePaths.Designer.cs | 380 +++++++++++++ .../20181007180454_StandardizePaths.cs | 26 + ...20181128100659_AddSkinInfoHash.Designer.cs | 387 +++++++++++++ .../20181128100659_AddSkinInfoHash.cs | 41 ++ ...81130113755_AddScoreInfoTables.Designer.cs | 484 ++++++++++++++++ .../20181130113755_AddScoreInfoTables.cs | 112 ++++ ...20190225062029_AddUserIDColumn.Designer.cs | 487 +++++++++++++++++ .../20190225062029_AddUserIDColumn.cs | 22 + .../20190525060824_SkinSettings.Designer.cs | 498 +++++++++++++++++ .../Migrations/20190525060824_SkinSettings.cs | 54 ++ ...AddDateAddedColumnToBeatmapSet.Designer.cs | 489 +++++++++++++++++ ...05091246_AddDateAddedColumnToBeatmapSet.cs | 24 + ...8070844_AddBPMAndLengthColumns.Designer.cs | 504 +++++++++++++++++ .../20190708070844_AddBPMAndLengthColumns.cs | 33 ++ ...20190913104727_AddBeatmapVideo.Designer.cs | 506 +++++++++++++++++ .../20190913104727_AddBeatmapVideo.cs | 22 + ...02094919_RefreshVolumeBindings.Designer.cs | 506 +++++++++++++++++ .../20200302094919_RefreshVolumeBindings.cs | 16 + ...01019224408_AddEpilepsyWarning.Designer.cs | 508 +++++++++++++++++ .../20201019224408_AddEpilepsyWarning.cs | 23 + ...700_RefreshVolumeBindingsAgain.Designer.cs | 506 +++++++++++++++++ ...210412045700_RefreshVolumeBindingsAgain.cs | 16 + ...60743_AddSkinInstantiationInfo.Designer.cs | 508 +++++++++++++++++ ...20210511060743_AddSkinInstantiationInfo.cs | 22 + ...9_AddAuthorIdToBeatmapMetadata.Designer.cs | 511 +++++++++++++++++ ...0514062639_AddAuthorIdToBeatmapMetadata.cs | 23 + ...824185035_AddCountdownSettings.Designer.cs | 513 +++++++++++++++++ .../20210824185035_AddCountdownSettings.cs | 23 + ...11_AddSamplesMatchPlaybackRate.Designer.cs | 515 ++++++++++++++++++ ...10912144011_AddSamplesMatchPlaybackRate.cs | 23 + .../20211020081609_ResetSkinHashes.cs | 23 + .../Migrations/OsuDbContextModelSnapshot.cs | 513 +++++++++++++++++ 53 files changed, 12451 insertions(+) create mode 100644 osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs create mode 100644 osu.Game/Migrations/20171019041408_InitialCreate.cs create mode 100644 osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs create mode 100644 osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs create mode 100644 osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs create mode 100644 osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs create mode 100644 osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs create mode 100644 osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs create mode 100644 osu.Game/Migrations/20180125143340_Settings.Designer.cs create mode 100644 osu.Game/Migrations/20180125143340_Settings.cs create mode 100644 osu.Game/Migrations/20180131154205_AddMuteBinding.cs create mode 100644 osu.Game/Migrations/20180219060912_AddSkins.Designer.cs create mode 100644 osu.Game/Migrations/20180219060912_AddSkins.cs create mode 100644 osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs create mode 100644 osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs create mode 100644 osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs create mode 100644 osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs create mode 100644 osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs create mode 100644 osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs create mode 100644 osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs create mode 100644 osu.Game/Migrations/20180913080842_AddRankStatus.cs create mode 100644 osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs create mode 100644 osu.Game/Migrations/20181007180454_StandardizePaths.cs create mode 100644 osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs create mode 100644 osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs create mode 100644 osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs create mode 100644 osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs create mode 100644 osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs create mode 100644 osu.Game/Migrations/20190225062029_AddUserIDColumn.cs create mode 100644 osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs create mode 100644 osu.Game/Migrations/20190525060824_SkinSettings.cs create mode 100644 osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs create mode 100644 osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs create mode 100644 osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs create mode 100644 osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs create mode 100644 osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs create mode 100644 osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs create mode 100644 osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs create mode 100644 osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs create mode 100644 osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs create mode 100644 osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs create mode 100644 osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs create mode 100644 osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs create mode 100644 osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs create mode 100644 osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs create mode 100644 osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs create mode 100644 osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs create mode 100644 osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs create mode 100644 osu.Game/Migrations/20210824185035_AddCountdownSettings.cs create mode 100644 osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs create mode 100644 osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs create mode 100644 osu.Game/Migrations/20211020081609_ResetSkinHashes.cs create mode 100644 osu.Game/Migrations/OsuDbContextModelSnapshot.cs diff --git a/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs b/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs new file mode 100644 index 0000000000..c751530bf4 --- /dev/null +++ b/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs @@ -0,0 +1,293 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20171019041408_InitialCreate")] + partial class InitialCreate + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + 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("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + 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("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("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("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash"); + + b.HasIndex("MetadataID"); + + b.ToTable("BeatmapSetInfo"); + }); + + 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("Variant"); + + b.ToTable("KeyBinding"); + }); + + 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.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.ToTable("RulesetInfo"); + }); + + 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"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20171019041408_InitialCreate.cs b/osu.Game/Migrations/20171019041408_InitialCreate.cs new file mode 100644 index 0000000000..9b6881f98c --- /dev/null +++ b/osu.Game/Migrations/20171019041408_InitialCreate.cs @@ -0,0 +1,311 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class InitialCreate : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "BeatmapDifficulty", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ApproachRate = table.Column(type: "REAL", nullable: false), + CircleSize = table.Column(type: "REAL", nullable: false), + DrainRate = table.Column(type: "REAL", nullable: false), + OverallDifficulty = table.Column(type: "REAL", nullable: false), + SliderMultiplier = table.Column(type: "REAL", nullable: false), + SliderTickRate = table.Column(type: "REAL", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapDifficulty", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "BeatmapMetadata", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Artist = table.Column(type: "TEXT", nullable: true), + ArtistUnicode = table.Column(type: "TEXT", nullable: true), + AudioFile = table.Column(type: "TEXT", nullable: true), + Author = table.Column(type: "TEXT", nullable: true), + BackgroundFile = table.Column(type: "TEXT", nullable: true), + PreviewTime = table.Column(type: "INTEGER", nullable: false), + Source = table.Column(type: "TEXT", nullable: true), + Tags = table.Column(type: "TEXT", nullable: true), + Title = table.Column(type: "TEXT", nullable: true), + TitleUnicode = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapMetadata", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "FileInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Hash = table.Column(type: "TEXT", nullable: true), + ReferenceCount = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_FileInfo", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "KeyBinding", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Action = table.Column(type: "INTEGER", nullable: false), + Keys = table.Column(type: "TEXT", nullable: true), + RulesetID = table.Column(type: "INTEGER", nullable: true), + Variant = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_KeyBinding", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "RulesetInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Available = table.Column(type: "INTEGER", nullable: false), + InstantiationInfo = table.Column(type: "TEXT", nullable: true), + Name = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_RulesetInfo", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "BeatmapSetInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + DeletePending = table.Column(type: "INTEGER", nullable: false), + Hash = table.Column(type: "TEXT", nullable: true), + MetadataID = table.Column(type: "INTEGER", nullable: true), + OnlineBeatmapSetID = table.Column(type: "INTEGER", nullable: true), + Protected = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapSetInfo", x => x.ID); + table.ForeignKey( + name: "FK_BeatmapSetInfo_BeatmapMetadata_MetadataID", + column: x => x.MetadataID, + principalTable: "BeatmapMetadata", + principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "BeatmapInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + AudioLeadIn = table.Column(type: "INTEGER", nullable: false), + BaseDifficultyID = table.Column(type: "INTEGER", nullable: false), + BeatDivisor = table.Column(type: "INTEGER", nullable: false), + BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false), + Countdown = table.Column(type: "INTEGER", nullable: false), + DistanceSpacing = table.Column(type: "REAL", nullable: false), + GridSize = table.Column(type: "INTEGER", nullable: false), + Hash = table.Column(type: "TEXT", nullable: true), + Hidden = table.Column(type: "INTEGER", nullable: false), + LetterboxInBreaks = table.Column(type: "INTEGER", nullable: false), + MD5Hash = table.Column(type: "TEXT", nullable: true), + MetadataID = table.Column(type: "INTEGER", nullable: true), + OnlineBeatmapID = table.Column(type: "INTEGER", nullable: true), + Path = table.Column(type: "TEXT", nullable: true), + RulesetID = table.Column(type: "INTEGER", nullable: false), + SpecialStyle = table.Column(type: "INTEGER", nullable: false), + StackLeniency = table.Column(type: "REAL", nullable: false), + StarDifficulty = table.Column(type: "REAL", nullable: false), + StoredBookmarks = table.Column(type: "TEXT", nullable: true), + TimelineZoom = table.Column(type: "REAL", nullable: false), + Version = table.Column(type: "TEXT", nullable: true), + WidescreenStoryboard = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapInfo", x => x.ID); + table.ForeignKey( + name: "FK_BeatmapInfo_BeatmapDifficulty_BaseDifficultyID", + column: x => x.BaseDifficultyID, + principalTable: "BeatmapDifficulty", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_BeatmapInfo_BeatmapSetInfo_BeatmapSetInfoID", + column: x => x.BeatmapSetInfoID, + principalTable: "BeatmapSetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_BeatmapInfo_BeatmapMetadata_MetadataID", + column: x => x.MetadataID, + principalTable: "BeatmapMetadata", + principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_BeatmapInfo_RulesetInfo_RulesetID", + column: x => x.RulesetID, + principalTable: "RulesetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "BeatmapSetFileInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false), + FileInfoID = table.Column(type: "INTEGER", nullable: false), + Filename = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapSetFileInfo", x => x.ID); + table.ForeignKey( + name: "FK_BeatmapSetFileInfo_BeatmapSetInfo_BeatmapSetInfoID", + column: x => x.BeatmapSetInfoID, + principalTable: "BeatmapSetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_BeatmapSetFileInfo_FileInfo_FileInfoID", + column: x => x.FileInfoID, + principalTable: "FileInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_BaseDifficultyID", + table: "BeatmapInfo", + column: "BaseDifficultyID"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_BeatmapSetInfoID", + table: "BeatmapInfo", + column: "BeatmapSetInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo", + column: "Hash"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo", + column: "MD5Hash"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_MetadataID", + table: "BeatmapInfo", + column: "MetadataID"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_RulesetID", + table: "BeatmapInfo", + column: "RulesetID"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetFileInfo_BeatmapSetInfoID", + table: "BeatmapSetFileInfo", + column: "BeatmapSetInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetFileInfo_FileInfoID", + table: "BeatmapSetFileInfo", + column: "FileInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_DeletePending", + table: "BeatmapSetInfo", + column: "DeletePending"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_Hash", + table: "BeatmapSetInfo", + column: "Hash"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_MetadataID", + table: "BeatmapSetInfo", + column: "MetadataID"); + + migrationBuilder.CreateIndex( + name: "IX_FileInfo_Hash", + table: "FileInfo", + column: "Hash", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_FileInfo_ReferenceCount", + table: "FileInfo", + column: "ReferenceCount"); + + migrationBuilder.CreateIndex( + name: "IX_KeyBinding_Action", + table: "KeyBinding", + column: "Action"); + + migrationBuilder.CreateIndex( + name: "IX_KeyBinding_Variant", + table: "KeyBinding", + column: "Variant"); + + migrationBuilder.CreateIndex( + name: "IX_RulesetInfo_Available", + table: "RulesetInfo", + column: "Available"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BeatmapInfo"); + + migrationBuilder.DropTable( + name: "BeatmapSetFileInfo"); + + migrationBuilder.DropTable( + name: "KeyBinding"); + + migrationBuilder.DropTable( + name: "BeatmapDifficulty"); + + migrationBuilder.DropTable( + name: "RulesetInfo"); + + migrationBuilder.DropTable( + name: "BeatmapSetInfo"); + + migrationBuilder.DropTable( + name: "FileInfo"); + + migrationBuilder.DropTable( + name: "BeatmapMetadata"); + } + } +} diff --git a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs new file mode 100644 index 0000000000..4cd234f2ef --- /dev/null +++ b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs @@ -0,0 +1,299 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20171025071459_AddMissingIndexRules")] + partial class AddMissingIndexRules + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + 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("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MD5Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + 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("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("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + 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("Variant"); + + b.ToTable("KeyBinding"); + }); + + 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.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.ToTable("RulesetInfo"); + }); + + 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"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs new file mode 100644 index 0000000000..c9fc59c5a2 --- /dev/null +++ b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs @@ -0,0 +1,80 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddMissingIndexRules : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_BeatmapSetInfo_Hash", + table: "BeatmapSetInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_Hash", + table: "BeatmapSetInfo", + column: "Hash", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_OnlineBeatmapSetID", + table: "BeatmapSetInfo", + column: "OnlineBeatmapSetID", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo", + column: "Hash", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo", + column: "MD5Hash", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_BeatmapSetInfo_Hash", + table: "BeatmapSetInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapSetInfo_OnlineBeatmapSetID", + table: "BeatmapSetInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_Hash", + table: "BeatmapSetInfo", + column: "Hash"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo", + column: "Hash"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo", + column: "MD5Hash"); + } + } +} diff --git a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs new file mode 100644 index 0000000000..006acf12cd --- /dev/null +++ b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs @@ -0,0 +1,302 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20171119065731_AddBeatmapOnlineIDUniqueConstraint")] + partial class AddBeatmapOnlineIDUniqueConstraint + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + 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("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MD5Hash") + .IsUnique(); + + 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("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("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + 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("Variant"); + + b.ToTable("KeyBinding"); + }); + + 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.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.ToTable("RulesetInfo"); + }); + + 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"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs new file mode 100644 index 0000000000..084ae67940 --- /dev/null +++ b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddBeatmapOnlineIDUniqueConstraint : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_OnlineBeatmapID", + table: "BeatmapInfo", + column: "OnlineBeatmapID", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_OnlineBeatmapID", + table: "BeatmapInfo"); + } + } +} diff --git a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs new file mode 100644 index 0000000000..fc2496bc24 --- /dev/null +++ b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs @@ -0,0 +1,307 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20171209034410_AddRulesetInfoShortName")] + partial class AddRulesetInfoShortName + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + 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("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MD5Hash") + .IsUnique(); + + 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("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("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + 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("Variant"); + + b.ToTable("KeyBinding"); + }); + + 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.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.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"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs new file mode 100644 index 0000000000..09cf0af89c --- /dev/null +++ b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddRulesetInfoShortName : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ShortName", + table: "RulesetInfo", + type: "TEXT", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_RulesetInfo_ShortName", + table: "RulesetInfo", + column: "ShortName", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_RulesetInfo_ShortName", + table: "RulesetInfo"); + + migrationBuilder.DropColumn( + name: "ShortName", + table: "RulesetInfo"); + } + } +} diff --git a/osu.Game/Migrations/20180125143340_Settings.Designer.cs b/osu.Game/Migrations/20180125143340_Settings.Designer.cs new file mode 100644 index 0000000000..4bb599eec1 --- /dev/null +++ b/osu.Game/Migrations/20180125143340_Settings.Designer.cs @@ -0,0 +1,329 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180125143340_Settings")] + partial class Settings + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + 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("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MD5Hash") + .IsUnique(); + + 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("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("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + 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("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + 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.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.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.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"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20180125143340_Settings.cs b/osu.Game/Migrations/20180125143340_Settings.cs new file mode 100644 index 0000000000..166d3c086d --- /dev/null +++ b/osu.Game/Migrations/20180125143340_Settings.cs @@ -0,0 +1,55 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class Settings : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_KeyBinding_Variant", + table: "KeyBinding"); + + migrationBuilder.CreateTable( + name: "Settings", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Key = table.Column(type: "TEXT", nullable: false), + RulesetID = table.Column(type: "INTEGER", nullable: true), + Value = table.Column(type: "TEXT", nullable: true), + Variant = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Settings", x => x.ID); + }); + + migrationBuilder.CreateIndex( + name: "IX_KeyBinding_RulesetID_Variant", + table: "KeyBinding", + columns: new[] { "RulesetID", "Variant" }); + + migrationBuilder.CreateIndex( + name: "IX_Settings_RulesetID_Variant", + table: "Settings", + columns: new[] { "RulesetID", "Variant" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Settings"); + + migrationBuilder.DropIndex( + name: "IX_KeyBinding_RulesetID_Variant", + table: "KeyBinding"); + + migrationBuilder.CreateIndex( + name: "IX_KeyBinding_Variant", + table: "KeyBinding", + column: "Variant"); + } + } +} diff --git a/osu.Game/Migrations/20180131154205_AddMuteBinding.cs b/osu.Game/Migrations/20180131154205_AddMuteBinding.cs new file mode 100644 index 0000000000..5564a30bbf --- /dev/null +++ b/osu.Game/Migrations/20180131154205_AddMuteBinding.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Infrastructure; +using osu.Game.Database; +using osu.Game.Input.Bindings; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180131154205_AddMuteBinding")] + public partial class AddMuteBinding : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql($"UPDATE KeyBinding SET Action = Action + 1 WHERE RulesetID IS NULL AND Variant IS NULL AND Action >= {(int)GlobalAction.ToggleMute}"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql($"DELETE FROM KeyBinding WHERE RulesetID IS NULL AND Variant IS NULL AND Action = {(int)GlobalAction.ToggleMute}"); + migrationBuilder.Sql($"UPDATE KeyBinding SET Action = Action - 1 WHERE RulesetID IS NULL AND Variant IS NULL AND Action > {(int)GlobalAction.ToggleMute}"); + } + } +} diff --git a/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs b/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs new file mode 100644 index 0000000000..cdc4ef2e66 --- /dev/null +++ b/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs @@ -0,0 +1,379 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180219060912_AddSkins")] + partial class AddSkins + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + 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("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MD5Hash") + .IsUnique(); + + 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("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("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + 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("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + 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.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.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.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("Name"); + + b.HasKey("ID"); + + 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.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/20180219060912_AddSkins.cs b/osu.Game/Migrations/20180219060912_AddSkins.cs new file mode 100644 index 0000000000..a0270ab0fd --- /dev/null +++ b/osu.Game/Migrations/20180219060912_AddSkins.cs @@ -0,0 +1,71 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddSkins : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "SkinInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Creator = table.Column(type: "TEXT", nullable: true), + DeletePending = table.Column(type: "INTEGER", nullable: false), + Name = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_SkinInfo", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "SkinFileInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + FileInfoID = table.Column(type: "INTEGER", nullable: false), + Filename = table.Column(type: "TEXT", nullable: false), + SkinInfoID = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SkinFileInfo", x => x.ID); + table.ForeignKey( + name: "FK_SkinFileInfo_FileInfo_FileInfoID", + column: x => x.FileInfoID, + principalTable: "FileInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_SkinFileInfo_SkinInfo_SkinInfoID", + column: x => x.SkinInfoID, + principalTable: "SkinInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_SkinFileInfo_FileInfoID", + table: "SkinFileInfo", + column: "FileInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_SkinFileInfo_SkinInfoID", + table: "SkinFileInfo", + column: "SkinInfoID"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "SkinFileInfo"); + + migrationBuilder.DropTable( + name: "SkinInfo"); + } + } +} diff --git a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs new file mode 100644 index 0000000000..f28408bfb3 --- /dev/null +++ b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs @@ -0,0 +1,377 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180529055154_RemoveUniqueHashConstraints")] + partial class RemoveUniqueHashConstraints + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.3-rtm-10026"); + + 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("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + 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("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("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + 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("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + 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.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.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.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("Name"); + + b.HasKey("ID"); + + 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.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/20180529055154_RemoveUniqueHashConstraints.cs b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs new file mode 100644 index 0000000000..27269cc5fc --- /dev/null +++ b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs @@ -0,0 +1,51 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class RemoveUniqueHashConstraints : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo", + column: "Hash"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo", + column: "MD5Hash"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo", + column: "Hash", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo", + column: "MD5Hash", + unique: true); + } + } +} diff --git a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs new file mode 100644 index 0000000000..aaa11e88b6 --- /dev/null +++ b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs @@ -0,0 +1,376 @@ +// +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("20180621044111_UpdateTaikoDefaultBindings")] + partial class UpdateTaikoDefaultBindings + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); + + 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("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + 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("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("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + 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("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + 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.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.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.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("Name"); + + b.HasKey("ID"); + + 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.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/20180621044111_UpdateTaikoDefaultBindings.cs b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs new file mode 100644 index 0000000000..71304ea979 --- /dev/null +++ b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class UpdateTaikoDefaultBindings : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("DELETE FROM KeyBinding WHERE RulesetID = 1"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + // we can't really tell if these should be restored or not, so let's just not do so. + } + } +} diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs new file mode 100644 index 0000000000..7eeacd56d7 --- /dev/null +++ b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs @@ -0,0 +1,376 @@ +// +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("20180628011956_RemoveNegativeSetIDs")] + partial class RemoveNegativeSetIDs + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); + + 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("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + 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("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("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + 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("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + 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.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.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.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("Name"); + + b.HasKey("ID"); + + 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.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/20180628011956_RemoveNegativeSetIDs.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs new file mode 100644 index 0000000000..506d65f761 --- /dev/null +++ b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs @@ -0,0 +1,18 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class RemoveNegativeSetIDs : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + // There was a change that beatmaps were being loaded with "-1" online IDs, which is completely incorrect. + // This ensures there will not be unique key conflicts as a result of these incorrectly imported beatmaps. + migrationBuilder.Sql("UPDATE BeatmapSetInfo SET OnlineBeatmapSetID = null WHERE OnlineBeatmapSetID <= 0"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} diff --git a/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs b/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs new file mode 100644 index 0000000000..5ab43da046 --- /dev/null +++ b/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs @@ -0,0 +1,380 @@ +// +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("20180913080842_AddRankStatus")] + partial class AddRankStatus + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.2-rtm-30932"); + + 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("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + 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("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("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("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + 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.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.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.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("Name"); + + b.HasKey("ID"); + + 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.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/20180913080842_AddRankStatus.cs b/osu.Game/Migrations/20180913080842_AddRankStatus.cs new file mode 100644 index 0000000000..bba4944bb7 --- /dev/null +++ b/osu.Game/Migrations/20180913080842_AddRankStatus.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddRankStatus : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Status", + table: "BeatmapSetInfo", + nullable: false, + defaultValue: -3); // NONE + + migrationBuilder.AddColumn( + name: "Status", + table: "BeatmapInfo", + nullable: false, + defaultValue: -3); // NONE + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Status", + table: "BeatmapSetInfo"); + + migrationBuilder.DropColumn( + name: "Status", + table: "BeatmapInfo"); + } + } +} diff --git a/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs b/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs new file mode 100644 index 0000000000..b387a45ecf --- /dev/null +++ b/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs @@ -0,0 +1,380 @@ +// +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("20181007180454_StandardizePaths")] + partial class StandardizePaths + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065"); + + 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("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + 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("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("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("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + 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.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.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.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("Name"); + + b.HasKey("ID"); + + 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.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/20181007180454_StandardizePaths.cs b/osu.Game/Migrations/20181007180454_StandardizePaths.cs new file mode 100644 index 0000000000..274b8030a9 --- /dev/null +++ b/osu.Game/Migrations/20181007180454_StandardizePaths.cs @@ -0,0 +1,26 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using System.IO; + +namespace osu.Game.Migrations +{ + public partial class StandardizePaths : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + string windowsStyle = @"\"; + string standardized = "/"; + + // Escaping \ does not seem to be needed. + migrationBuilder.Sql($"UPDATE `BeatmapInfo` SET `Path` = REPLACE(`Path`, '{windowsStyle}', '{standardized}')"); + migrationBuilder.Sql($"UPDATE `BeatmapMetadata` SET `AudioFile` = REPLACE(`AudioFile`, '{windowsStyle}', '{standardized}')"); + migrationBuilder.Sql($"UPDATE `BeatmapMetadata` SET `BackgroundFile` = REPLACE(`BackgroundFile`, '{windowsStyle}', '{standardized}')"); + migrationBuilder.Sql($"UPDATE `BeatmapSetFileInfo` SET `Filename` = REPLACE(`Filename`, '{windowsStyle}', '{standardized}')"); + migrationBuilder.Sql($"UPDATE `SkinFileInfo` SET `Filename` = REPLACE(`Filename`, '{windowsStyle}', '{standardized}')"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} diff --git a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs new file mode 100644 index 0000000000..120674671a --- /dev/null +++ b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs @@ -0,0 +1,387 @@ +// +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("20181128100659_AddSkinInfoHash")] + partial class AddSkinInfoHash + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.4-rtm-31024"); + + 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("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + 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("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("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("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + 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.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.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.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("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.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/20181128100659_AddSkinInfoHash.cs b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs new file mode 100644 index 0000000000..860264a7dd --- /dev/null +++ b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs @@ -0,0 +1,41 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddSkinInfoHash : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Hash", + table: "SkinInfo", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_SkinInfo_DeletePending", + table: "SkinInfo", + column: "DeletePending"); + + migrationBuilder.CreateIndex( + name: "IX_SkinInfo_Hash", + table: "SkinInfo", + column: "Hash", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_SkinInfo_DeletePending", + table: "SkinInfo"); + + migrationBuilder.DropIndex( + name: "IX_SkinInfo_Hash", + table: "SkinInfo"); + + migrationBuilder.DropColumn( + name: "Hash", + table: "SkinInfo"); + } + } +} diff --git a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs new file mode 100644 index 0000000000..eee53182ce --- /dev/null +++ b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs @@ -0,0 +1,484 @@ +// +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("20181130113755_AddScoreInfoTables")] + partial class AddScoreInfoTables + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.4-rtm-31024"); + + 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("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + 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("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("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("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + 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.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.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("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("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.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() + .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/20181130113755_AddScoreInfoTables.cs b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs new file mode 100644 index 0000000000..2b6f94c5a4 --- /dev/null +++ b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs @@ -0,0 +1,112 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddScoreInfoTables : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ScoreInfo", + columns: table => new + { + ID = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Rank = table.Column(nullable: false), + TotalScore = table.Column(nullable: false), + Accuracy = table.Column(type: "DECIMAL(1,4)", nullable: false), + PP = table.Column(nullable: true), + MaxCombo = table.Column(nullable: false), + Combo = table.Column(nullable: false), + RulesetID = table.Column(nullable: false), + Mods = table.Column(nullable: true), + User = table.Column(nullable: true), + BeatmapInfoID = table.Column(nullable: false), + OnlineScoreID = table.Column(nullable: true), + Date = table.Column(nullable: false), + Statistics = table.Column(nullable: true), + Hash = table.Column(nullable: true), + DeletePending = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ScoreInfo", x => x.ID); + table.ForeignKey( + name: "FK_ScoreInfo_BeatmapInfo_BeatmapInfoID", + column: x => x.BeatmapInfoID, + principalTable: "BeatmapInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ScoreInfo_RulesetInfo_RulesetID", + column: x => x.RulesetID, + principalTable: "RulesetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ScoreFileInfo", + columns: table => new + { + ID = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + FileInfoID = table.Column(nullable: false), + Filename = table.Column(nullable: false), + ScoreInfoID = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ScoreFileInfo", x => x.ID); + table.ForeignKey( + name: "FK_ScoreFileInfo_FileInfo_FileInfoID", + column: x => x.FileInfoID, + principalTable: "FileInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ScoreFileInfo_ScoreInfo_ScoreInfoID", + column: x => x.ScoreInfoID, + principalTable: "ScoreInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_ScoreFileInfo_FileInfoID", + table: "ScoreFileInfo", + column: "FileInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_ScoreFileInfo_ScoreInfoID", + table: "ScoreFileInfo", + column: "ScoreInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_ScoreInfo_BeatmapInfoID", + table: "ScoreInfo", + column: "BeatmapInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_ScoreInfo_OnlineScoreID", + table: "ScoreInfo", + column: "OnlineScoreID", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ScoreInfo_RulesetID", + table: "ScoreInfo", + column: "RulesetID"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ScoreFileInfo"); + + migrationBuilder.DropTable( + name: "ScoreInfo"); + } + } +} diff --git a/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs b/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs new file mode 100644 index 0000000000..8e1e3a59f3 --- /dev/null +++ b/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs @@ -0,0 +1,487 @@ +// +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("20190225062029_AddUserIDColumn")] + partial class AddUserIDColumn + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.1-servicing-10028"); + + 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("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + 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("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("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("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + 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("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.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/20190225062029_AddUserIDColumn.cs b/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs new file mode 100644 index 0000000000..0720e0eac7 --- /dev/null +++ b/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddUserIDColumn : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UserID", + table: "ScoreInfo", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "UserID", + table: "ScoreInfo"); + } + } +} diff --git a/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs b/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs new file mode 100644 index 0000000000..348c42adb9 --- /dev/null +++ b/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs @@ -0,0 +1,498 @@ +// +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("20190525060824_SkinSettings")] + partial class SkinSettings + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); + + 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("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + 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("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("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("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/20190525060824_SkinSettings.cs b/osu.Game/Migrations/20190525060824_SkinSettings.cs new file mode 100644 index 0000000000..99237419b7 --- /dev/null +++ b/osu.Game/Migrations/20190525060824_SkinSettings.cs @@ -0,0 +1,54 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class SkinSettings : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql(@"create table Settings_dg_tmp + ( + ID INTEGER not null + constraint PK_Settings + primary key autoincrement, + Key TEXT not null, + RulesetID INTEGER, + Value TEXT, + Variant INTEGER, + SkinInfoID int + constraint Settings_SkinInfo_ID_fk + references SkinInfo + on delete restrict + ); + + insert into Settings_dg_tmp(ID, Key, RulesetID, Value, Variant) select ID, Key, RulesetID, Value, Variant from Settings; + + drop table Settings; + + alter table Settings_dg_tmp rename to Settings; + + create index IX_Settings_RulesetID_Variant + on Settings (RulesetID, Variant); + + create index Settings_SkinInfoID_index + on Settings (SkinInfoID); + + "); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Settings_SkinInfo_SkinInfoID", + table: "Settings"); + + migrationBuilder.DropIndex( + name: "IX_Settings_SkinInfoID", + table: "Settings"); + + migrationBuilder.DropColumn( + name: "SkinInfoID", + table: "Settings"); + } + } +} diff --git a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs new file mode 100644 index 0000000000..9477369aa0 --- /dev/null +++ b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs @@ -0,0 +1,489 @@ +// +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("20190605091246_AddDateAddedColumnToBeatmapSet")] + partial class AddDateAddedColumnToBeatmapSet + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); + + 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("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + 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("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("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + 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("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.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/20190605091246_AddDateAddedColumnToBeatmapSet.cs b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs new file mode 100644 index 0000000000..55dc18b6a3 --- /dev/null +++ b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs @@ -0,0 +1,24 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddDateAddedColumnToBeatmapSet : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "DateAdded", + table: "BeatmapSetInfo", + nullable: false, + defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "DateAdded", + table: "BeatmapSetInfo"); + } + } +} diff --git a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs new file mode 100644 index 0000000000..c5fcc16f84 --- /dev/null +++ b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs @@ -0,0 +1,504 @@ +// +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("20190708070844_AddBPMAndLengthColumns")] + partial class AddBPMAndLengthColumns + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); + + 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("DistanceSpacing"); + + 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("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("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("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/20190708070844_AddBPMAndLengthColumns.cs b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs new file mode 100644 index 0000000000..f5963ebf5e --- /dev/null +++ b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddBPMAndLengthColumns : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "BPM", + table: "BeatmapInfo", + nullable: false, + defaultValue: 0.0); + + migrationBuilder.AddColumn( + name: "Length", + table: "BeatmapInfo", + nullable: false, + defaultValue: 0.0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "BPM", + table: "BeatmapInfo"); + + migrationBuilder.DropColumn( + name: "Length", + table: "BeatmapInfo"); + } + } +} diff --git a/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs new file mode 100644 index 0000000000..826233a2b0 --- /dev/null +++ b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs @@ -0,0 +1,506 @@ +// +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("20190913104727_AddBeatmapVideo")] + partial class AddBeatmapVideo + { + 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("DistanceSpacing"); + + 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("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("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.Property("VideoFile"); + + 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("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/20190913104727_AddBeatmapVideo.cs b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs new file mode 100644 index 0000000000..9ed0943acd --- /dev/null +++ b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddBeatmapVideo : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "VideoFile", + table: "BeatmapMetadata", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "VideoFile", + table: "BeatmapMetadata"); + } + } +} diff --git a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs new file mode 100644 index 0000000000..22316b0380 --- /dev/null +++ b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs @@ -0,0 +1,506 @@ +// +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("20200302094919_RefreshVolumeBindings")] + partial class RefreshVolumeBindings + { + 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("DistanceSpacing"); + + 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("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("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.Property("VideoFile"); + + 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("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/20200302094919_RefreshVolumeBindings.cs b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs new file mode 100644 index 0000000000..ec4475971c --- /dev/null +++ b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class RefreshVolumeBindings : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("DELETE FROM KeyBinding WHERE action in (6,7)"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} diff --git a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs new file mode 100644 index 0000000000..1c05de832e --- /dev/null +++ b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs @@ -0,0 +1,508 @@ +// +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("20201019224408_AddEpilepsyWarning")] + partial class AddEpilepsyWarning + { + 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("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("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("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.Property("VideoFile"); + + 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("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/20201019224408_AddEpilepsyWarning.cs b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs new file mode 100644 index 0000000000..be6968aa5d --- /dev/null +++ b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddEpilepsyWarning : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "EpilepsyWarning", + table: "BeatmapInfo", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "EpilepsyWarning", + table: "BeatmapInfo"); + } + } +} diff --git a/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs new file mode 100644 index 0000000000..2c100d39b9 --- /dev/null +++ b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs @@ -0,0 +1,506 @@ +// +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("20210412045700_RefreshVolumeBindingsAgain")] + partial class RefreshVolumeBindingsAgain + { + 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("DistanceSpacing"); + + 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("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("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.Property("VideoFile"); + + 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("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/20210412045700_RefreshVolumeBindingsAgain.cs b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs new file mode 100644 index 0000000000..155d6670a8 --- /dev/null +++ b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class RefreshVolumeBindingsAgain : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("DELETE FROM KeyBinding WHERE action in (6,7)"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} diff --git a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs new file mode 100644 index 0000000000..b808c648da --- /dev/null +++ b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs @@ -0,0 +1,508 @@ +// +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("20210511060743_AddSkinInstantiationInfo")] + partial class AddSkinInstantiationInfo + { + 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("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("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("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/20210511060743_AddSkinInstantiationInfo.cs b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs new file mode 100644 index 0000000000..1d5b0769a4 --- /dev/null +++ b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddSkinInstantiationInfo : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "InstantiationInfo", + table: "SkinInfo", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "InstantiationInfo", + table: "SkinInfo"); + } + } +} diff --git a/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs new file mode 100644 index 0000000000..89bab3a0fa --- /dev/null +++ b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs @@ -0,0 +1,511 @@ +// +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("20210514062639_AddAuthorIdToBeatmapMetadata")] + partial class AddAuthorIdToBeatmapMetadata + { + 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("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("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/20210514062639_AddAuthorIdToBeatmapMetadata.cs b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs new file mode 100644 index 0000000000..98fe9b5e13 --- /dev/null +++ b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddAuthorIdToBeatmapMetadata : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AuthorID", + table: "BeatmapMetadata", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "AuthorID", + table: "BeatmapMetadata"); + } + } +} diff --git a/osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs b/osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs new file mode 100644 index 0000000000..afeb42130d --- /dev/null +++ b/osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs @@ -0,0 +1,513 @@ +// +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("20210824185035_AddCountdownSettings")] + partial class AddCountdownSettings + { + 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("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/20210824185035_AddCountdownSettings.cs b/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs new file mode 100644 index 0000000000..564f5f4520 --- /dev/null +++ b/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddCountdownSettings : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CountdownOffset", + table: "BeatmapInfo", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "CountdownOffset", + table: "BeatmapInfo"); + } + } +} 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/20211020081609_ResetSkinHashes.cs b/osu.Game/Migrations/20211020081609_ResetSkinHashes.cs new file mode 100644 index 0000000000..6d53c019ec --- /dev/null +++ b/osu.Game/Migrations/20211020081609_ResetSkinHashes.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. + +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20211020081609_ResetSkinHashes")] + public partial class ResetSkinHashes : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql($"UPDATE SkinInfo SET Hash = null"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs new file mode 100644 index 0000000000..036c26cb0a --- /dev/null +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -0,0 +1,513 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + partial class OsuDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(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 + } + } +} From 1a14ccc7ee37f2daa4b3500483668621b7c7abef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 22:42:34 +0900 Subject: [PATCH 0365/5199] Run EF migrations before migrating to realm Turns out that there are more than zero users that are upgrading from old databases. I think we probably want to support this for now. Tested against database in https://github.com/ppy/osu/discussions/16700 and one other I had locally, both work correctly. --- osu.Game/Database/EFToRealmMigrator.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index a53704df9d..0bb5388d55 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -133,6 +133,8 @@ namespace osu.Game.Database r.RemoveAll(); }); + ef.Migrate(); + migrateSettings(ef); migrateSkins(ef); migrateBeatmaps(ef); From 24f9ef4005827338702e3e8766a835b755c7a5d0 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 22:31:04 +0800 Subject: [PATCH 0366/5199] make xmldoc more verbose --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index f366481c7d..9eeb060135 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override IconUsage? Icon => FontAwesome.Solid.Keyboard; /// - /// Whether incoming input must be checked by . + /// Whether incoming input must be checked by before it is passed to gameplay. /// public bool CanIntercept => !isBreakTime.Value && introEnded; From b4e516c535032f91c70f67e0ae8aebbe156d8d3e Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 22:31:24 +0800 Subject: [PATCH 0367/5199] allow test scenes to specify replays manually --- osu.Game/Tests/Visual/ModTestScene.cs | 33 +++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index a71d008eb9..524482237a 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -5,8 +5,12 @@ using System; using System.Collections.Generic; using JetBrains.Annotations; using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Replays; +using osu.Game.Scoring; namespace osu.Game.Tests.Visual { @@ -50,18 +54,37 @@ namespace osu.Game.Tests.Visual return CreateModPlayer(ruleset); } - protected virtual TestPlayer CreateModPlayer(Ruleset ruleset) => new ModTestPlayer(AllowFail); + protected virtual TestPlayer CreateModPlayer(Ruleset ruleset) => new ModTestPlayer(currentTestData, AllowFail); protected class ModTestPlayer : TestPlayer { private readonly bool allowFail; + private ModTestData currentTestData; protected override bool CheckModsAllowFailure() => allowFail; - public ModTestPlayer(bool allowFail) + public ModTestPlayer(ModTestData data, bool allowFail) : base(false, false) { this.allowFail = allowFail; + currentTestData = data; + } + + protected override void PrepareReplay() + { + if (currentTestData.Autoplay && currentTestData.Frames?.Count > 0) + throw new InvalidOperationException(@$"{nameof(ModTestData.Autoplay)} must be false when {nameof(ModTestData.Frames)} is specified."); + + if (currentTestData.Frames != null) + { + DrawableRuleset?.SetReplayScore(new Score + { + Replay = new Replay { Frames = currentTestData.Frames }, + ScoreInfo = new ScoreInfo { User = new APIUser { Username = @"Test" } }, + }); + } + + base.PrepareReplay(); } } @@ -72,6 +95,12 @@ namespace osu.Game.Tests.Visual /// public bool Autoplay = true; + /// + /// The frames to use for replay. must be set to false. + /// + [CanBeNull] + public List Frames; + /// /// The beatmap for this test case. /// From 1087d8b1cedfaad85e048151e20638c6cf18ff18 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 22:31:31 +0800 Subject: [PATCH 0368/5199] add tests --- .../Mods/TestSceneOsuModAlternate.cs | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs new file mode 100644 index 0000000000..7b3eaa7ffd --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs @@ -0,0 +1,154 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Replays; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public class TestSceneOsuModAlternate : OsuModTestScene + { + [Test] + public void TestInputAtIntro() => CreateModTest(new ModTestData + { + Mod = new OsuModAlternate(), + PassCondition = () => Player.ScoreProcessor.Combo.Value == 1, + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new HitCircle + { + StartTime = 1000, + Position = new Vector2(100), + }, + }, + }, + Frames = new List + { + new OsuReplayFrame(500, new Vector2(200), OsuAction.LeftButton), + new OsuReplayFrame(501, new Vector2(200)), + new OsuReplayFrame(1000, new Vector2(100), OsuAction.LeftButton), + } + }); + + [Test] + public void TestInputAlternating() => CreateModTest(new ModTestData + { + Mod = new OsuModAlternate(), + PassCondition = () => Player.ScoreProcessor.Combo.Value == 4, + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new HitCircle + { + StartTime = 500, + Position = new Vector2(100), + }, + new HitCircle + { + StartTime = 1000, + Position = new Vector2(200, 100), + }, + new HitCircle + { + StartTime = 1500, + Position = new Vector2(300, 100), + }, + new HitCircle + { + StartTime = 2000, + Position = new Vector2(400, 100), + }, + }, + }, + Frames = new List + { + new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), + new OsuReplayFrame(501, new Vector2(100)), + new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.RightButton), + new OsuReplayFrame(1001, new Vector2(200, 100)), + new OsuReplayFrame(1500, new Vector2(300, 100), OsuAction.LeftButton), + new OsuReplayFrame(1501, new Vector2(300, 100)), + new OsuReplayFrame(2000, new Vector2(400, 100), OsuAction.RightButton), + new OsuReplayFrame(2001, new Vector2(400, 100)), + } + }); + + [Test] + public void TestInputSingular() => CreateModTest(new ModTestData + { + Mod = new OsuModAlternate(), + PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 1, + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new HitCircle + { + StartTime = 500, + Position = new Vector2(100), + }, + new HitCircle + { + StartTime = 1000, + Position = new Vector2(200, 100), + }, + }, + }, + Frames = new List + { + new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), + new OsuReplayFrame(501, new Vector2(100)), + new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.LeftButton), + } + }); + + [Test] + public void TestInputSingularWithBreak() => CreateModTest(new ModTestData + { + Mod = new OsuModAlternate(), + PassCondition = () => Player.ScoreProcessor.Combo.Value == 2, + Autoplay = false, + Beatmap = new Beatmap + { + Breaks = new List + { + new BreakPeriod(500, 2250), + }, + HitObjects = new List + { + new HitCircle + { + StartTime = 500, + Position = new Vector2(100), + }, + new HitCircle + { + StartTime = 2500, + Position = new Vector2(100), + } + } + }, + Frames = new List + { + new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), + new OsuReplayFrame(501, new Vector2(100)), + new OsuReplayFrame(2500, new Vector2(100), OsuAction.LeftButton), + new OsuReplayFrame(2501, new Vector2(100)), + } + }); + } +} From a8eb3f95df2501659941c8d4d242fd8d08b897c8 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 22:54:17 +0800 Subject: [PATCH 0369/5199] add readonly modifier --- osu.Game/Tests/Visual/ModTestScene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index 524482237a..96fafa8727 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual protected class ModTestPlayer : TestPlayer { private readonly bool allowFail; - private ModTestData currentTestData; + private readonly ModTestData currentTestData; protected override bool CheckModsAllowFailure() => allowFail; From c6d303a5b44ba2f0d0c221178e9abab50dc2fbaa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 21:16:29 +0900 Subject: [PATCH 0370/5199] Add xmldoc to `Leaderboard` class --- osu.Game/Online/Leaderboards/Leaderboard.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 515cc6fd73..e2a52b5db9 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -23,6 +23,12 @@ using osuTK.Graphics; namespace osu.Game.Online.Leaderboards { + /// + /// A leaderboard which displays a scrolling list of top scores, along with a single "user best" + /// for the local user. + /// + /// The scope of the leaderboard (ie. global or local). + /// The score model class. public abstract class Leaderboard : Container { private const double fade_duration = 300; From aee93934d5c42792b41ef3b13c7baa748b7c3ee0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 21:22:09 +0900 Subject: [PATCH 0371/5199] Rename methods to make more sense (and always run through `AddOnce`) --- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 2 +- osu.Game/Online/Leaderboards/Leaderboard.cs | 10 +++++----- .../OnlinePlay/Match/Components/MatchLeaderboard.cs | 2 +- .../OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs | 2 +- .../Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 10 +++++----- osu.Game/Screens/Select/PlayBeatmapDetailArea.cs | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 4826d2fb33..4938fd1271 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.UserInterface leaderboard.FinishTransforms(true); // After setting scores, we may be waiting for transforms to expire drawables leaderboard.BeatmapInfo = beatmapInfo; - leaderboard.RefreshScores(); // Required in the case that the beatmap hasn't changed + leaderboard.RefetchScores(); // Required in the case that the beatmap hasn't changed }); [SetUpSteps] diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index e2a52b5db9..cd67fc7301 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -132,7 +132,7 @@ namespace osu.Game.Online.Leaderboards return; scope = value; - RefreshScores(); + RefetchScores(); } } @@ -160,7 +160,7 @@ namespace osu.Game.Online.Leaderboards case PlaceholderState.NetworkFailure: replacePlaceholder(new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync) { - Action = RefreshScores + Action = RefetchScores }); break; @@ -272,15 +272,15 @@ namespace osu.Game.Online.Leaderboards case APIState.Online: case APIState.Offline: if (IsOnlineScope) - RefreshScores(); + RefetchScores(); break; } }); - public void RefreshScores() => Scheduler.AddOnce(UpdateScores); + public void RefetchScores() => Scheduler.AddOnce(refetchScores); - protected void UpdateScores() + private void refetchScores() { // don't display any scores or placeholder until the first Scores_Set has been called. // this avoids scope changes flickering a "no scores" placeholder before initialisation of song select is finished. diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs index 134e083c42..e4ec3ac4a5 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components return; Scores = null; - UpdateScores(); + RefetchScores(); }, true); } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 4114a5e9a0..542851cb0f 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -220,7 +220,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists protected override Screen CreateGameplayScreen() => new PlayerLoader(() => new PlaylistsPlayer(Room, SelectedItem.Value) { - Exited = () => leaderboard.RefreshScores() + Exited = () => leaderboard.RefetchScores() }); } } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 31cbe91f5c..02e4e162dd 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Select.Leaderboards Scores = null; if (IsOnlineScope) - UpdateScores(); + RefetchScores(); else { if (IsLoaded) @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Select.Leaderboards filterMods = value; - UpdateScores(); + RefetchScores(); } } @@ -96,11 +96,11 @@ namespace osu.Game.Screens.Select.Leaderboards [BackgroundDependencyLoader] private void load() { - ruleset.ValueChanged += _ => UpdateScores(); + ruleset.ValueChanged += _ => RefetchScores(); mods.ValueChanged += _ => { if (filterMods) - UpdateScores(); + RefetchScores(); }; } @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Select.Leaderboards (_, changes, ___) => { if (!IsOnlineScope) - RefreshScores(); + RefetchScores(); }); } diff --git a/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs b/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs index b8b8e3e4bc..09f75b7658 100644 --- a/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Select { base.Refresh(); - Leaderboard.RefreshScores(); + Leaderboard.RefetchScores(); } protected override void OnTabChanged(BeatmapDetailAreaTabItem tab, bool selectedMods) From b9dac6c3b2d312bc79fefff4d5abb1c24d78c00c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 21:33:22 +0900 Subject: [PATCH 0372/5199] Reorder and tidy up bindable flows --- osu.Game/Online/Leaderboards/Leaderboard.cs | 81 ++++++++++++--------- 1 file changed, 45 insertions(+), 36 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index cd67fc7301..af448b7d7b 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -46,6 +46,18 @@ namespace osu.Game.Online.Leaderboards private bool scoresLoadedOnce; + private APIRequest getScoresRequest; + private ScheduledDelegate getScoresRequestCallback; + + protected abstract bool IsOnlineScope { get; } + + [Resolved(CanBeNull = true)] + private IAPIProvider api { get; set; } + + private ScheduledDelegate pendingUpdateScores; + + private readonly IBindable apiState = new Bindable(); + private readonly Container content; protected override Container Content => content; @@ -239,44 +251,34 @@ namespace osu.Game.Online.Leaderboards protected virtual void Reset() { - getScoresRequest?.Cancel(); - getScoresRequest = null; + cancelPendingWork(); + Scores = null; } - [Resolved(CanBeNull = true)] - private IAPIProvider api { get; set; } - - private ScheduledDelegate pendingUpdateScores; - - private readonly IBindable apiState = new Bindable(); - - [BackgroundDependencyLoader] - private void load() + protected override void LoadComplete() { + base.LoadComplete(); + if (api != null) - apiState.BindTo(api.State); - - apiState.BindValueChanged(onlineStateChanged, true); - } - - private APIRequest getScoresRequest; - private ScheduledDelegate getScoresRequestCallback; - - protected abstract bool IsOnlineScope { get; } - - private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => - { - switch (state.NewValue) { - case APIState.Online: - case APIState.Offline: - if (IsOnlineScope) - RefetchScores(); + apiState.BindTo(api.State); + apiState.BindValueChanged(state => + { + switch (state.NewValue) + { + case APIState.Online: + case APIState.Offline: + if (IsOnlineScope) + RefetchScores(); - break; + break; + } + }); } - }); + + RefetchScores(); + } public void RefetchScores() => Scheduler.AddOnce(refetchScores); @@ -286,13 +288,8 @@ namespace osu.Game.Online.Leaderboards // this avoids scope changes flickering a "no scores" placeholder before initialisation of song select is finished. if (!scoresLoadedOnce) return; - getScoresRequest?.Cancel(); - getScoresRequest = null; + cancelPendingWork(); - getScoresRequestCallback?.Cancel(); - getScoresRequestCallback = null; - - pendingUpdateScores?.Cancel(); pendingUpdateScores = Schedule(() => { PlaceholderState = PlaceholderState.Retrieving; @@ -319,6 +316,18 @@ namespace osu.Game.Online.Leaderboards }); } + private void cancelPendingWork() + { + getScoresRequest?.Cancel(); + getScoresRequest = null; + + getScoresRequestCallback?.Cancel(); + getScoresRequestCallback = null; + + pendingUpdateScores?.Cancel(); + pendingUpdateScores = null; + } + /// /// Performs a fetch/refresh of scores to be displayed. /// From 64925b3feaafa353bbfb87af39e13ee9e762a8bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 21:38:23 +0900 Subject: [PATCH 0373/5199] Remove unused `Content` override --- osu.Game/Online/Leaderboards/Leaderboard.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index af448b7d7b..785c2e2ecf 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -29,7 +29,7 @@ namespace osu.Game.Online.Leaderboards /// /// The scope of the leaderboard (ie. global or local). /// The score model class. - public abstract class Leaderboard : Container + public abstract class Leaderboard : CompositeDrawable { private const double fade_duration = 300; @@ -58,10 +58,6 @@ namespace osu.Game.Online.Leaderboards private readonly IBindable apiState = new Bindable(); - private readonly Container content; - - protected override Container Content => content; - private ICollection scores; public ICollection Scores @@ -231,7 +227,7 @@ namespace osu.Game.Online.Leaderboards }, new Drawable[] { - content = new Container + new Container { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, From 17aa9f304000505d5aff1d04239f5fa828873871 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 21:47:28 +0900 Subject: [PATCH 0374/5199] Remove pointless level of schedule/cancel logic --- osu.Game/Online/Leaderboards/Leaderboard.cs | 39 ++++++++------------- 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 785c2e2ecf..7ab3d0343a 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -54,8 +54,6 @@ namespace osu.Game.Online.Leaderboards [Resolved(CanBeNull = true)] private IAPIProvider api { get; set; } - private ScheduledDelegate pendingUpdateScores; - private readonly IBindable apiState = new Bindable(); private ICollection scores; @@ -248,7 +246,6 @@ namespace osu.Game.Online.Leaderboards protected virtual void Reset() { cancelPendingWork(); - Scores = null; } @@ -286,30 +283,27 @@ namespace osu.Game.Online.Leaderboards cancelPendingWork(); - pendingUpdateScores = Schedule(() => + PlaceholderState = PlaceholderState.Retrieving; + loading.Show(); + + getScoresRequest = FetchScores(scores => getScoresRequestCallback = Schedule(() => { - PlaceholderState = PlaceholderState.Retrieving; - loading.Show(); + Scores = scores.ToArray(); + PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; + })); - getScoresRequest = FetchScores(scores => getScoresRequestCallback = Schedule(() => - { - Scores = scores.ToArray(); - PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; - })); + if (getScoresRequest == null) + return; - if (getScoresRequest == null) + getScoresRequest.Failure += e => getScoresRequestCallback = Schedule(() => + { + if (e is OperationCanceledException) return; - getScoresRequest.Failure += e => getScoresRequestCallback = Schedule(() => - { - if (e is OperationCanceledException) - return; - - PlaceholderState = PlaceholderState.NetworkFailure; - }); - - api?.Queue(getScoresRequest); + PlaceholderState = PlaceholderState.NetworkFailure; }); + + api?.Queue(getScoresRequest); } private void cancelPendingWork() @@ -319,9 +313,6 @@ namespace osu.Game.Online.Leaderboards getScoresRequestCallback?.Cancel(); getScoresRequestCallback = null; - - pendingUpdateScores?.Cancel(); - pendingUpdateScores = null; } /// From c5486586623acd425be534fced770e456493c85c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 21:49:52 +0900 Subject: [PATCH 0375/5199] Remove move unused pieces --- osu.Game/Online/Leaderboards/Leaderboard.cs | 29 ++++++++------------- 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 7ab3d0343a..a426d2b448 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -82,7 +82,13 @@ namespace osu.Game.Online.Leaderboards // ensure placeholder is hidden when displaying scores PlaceholderState = PlaceholderState.Successful; - var scoreFlow = CreateScoreFlow(); + var scoreFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, 5f), + Padding = new MarginPadding { Top = 10, Bottom = 5 }, + }; scoreFlow.ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)); // schedule because we may not be loaded yet (LoadComponentAsync complains). @@ -118,15 +124,6 @@ namespace osu.Game.Online.Leaderboards } } - protected virtual FillFlowContainer CreateScoreFlow() - => new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0f, 5f), - Padding = new MarginPadding { Top = 10, Bottom = 5 }, - }; - private TScope scope; public TScope Scope @@ -345,9 +342,6 @@ namespace osu.Game.Online.Leaderboards currentPlaceholder = placeholder; } - protected virtual bool FadeBottom => true; - protected virtual bool FadeTop => false; - protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); @@ -366,22 +360,21 @@ namespace osu.Game.Online.Leaderboards float topY = c.ToSpaceOfOtherDrawable(Vector2.Zero, scrollFlow).Y; float bottomY = topY + LeaderboardScore.HEIGHT; - bool requireTopFade = FadeTop && topY <= fadeTop; - bool requireBottomFade = FadeBottom && bottomY >= fadeBottom; + bool requireBottomFade = bottomY >= fadeBottom; - if (!requireTopFade && !requireBottomFade) + if (!requireBottomFade) c.Colour = Color4.White; else if (topY > fadeBottom + LeaderboardScore.HEIGHT || bottomY < fadeTop - LeaderboardScore.HEIGHT) c.Colour = Color4.Transparent; else { - if (bottomY - fadeBottom > 0 && FadeBottom) + if (bottomY - fadeBottom > 0) { c.Colour = ColourInfo.GradientVertical( Color4.White.Opacity(Math.Min(1 - (topY - fadeBottom) / LeaderboardScore.HEIGHT, 1)), Color4.White.Opacity(Math.Min(1 - (bottomY - fadeBottom) / LeaderboardScore.HEIGHT, 1))); } - else if (FadeTop) + else { c.Colour = ColourInfo.GradientVertical( Color4.White.Opacity(Math.Min(1 - (fadeTop - topY) / LeaderboardScore.HEIGHT, 1)), From b85b2c01fb248db8a3deb773ba1ed5da930d07c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 21:53:12 +0900 Subject: [PATCH 0376/5199] Reorder based on accessibility and add regions --- osu.Game/Online/Leaderboards/Leaderboard.cs | 268 ++++++++++---------- 1 file changed, 138 insertions(+), 130 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index a426d2b448..d843dcb2bb 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -139,6 +139,139 @@ namespace osu.Game.Online.Leaderboards } } + protected Leaderboard() + { + InternalChildren = new Drawable[] + { + new OsuContextMenuContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] + { + scrollContainer = new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, + } + }, + new Drawable[] + { + new Container + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Child = topScoreContainer = new UserTopScoreContainer(CreateDrawableTopScore) + }, + }, + }, + }, + }, + loading = new LoadingSpinner(), + placeholderContainer = new Container + { + RelativeSizeAxes = Axes.Both + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (api != null) + { + apiState.BindTo(api.State); + apiState.BindValueChanged(state => + { + switch (state.NewValue) + { + case APIState.Online: + case APIState.Offline: + if (IsOnlineScope) + RefetchScores(); + + break; + } + }); + } + + RefetchScores(); + } + + public void RefetchScores() => Scheduler.AddOnce(refetchScores); + + protected virtual void Reset() + { + cancelPendingWork(); + Scores = null; + } + + /// + /// Performs a fetch/refresh of scores to be displayed. + /// + /// A callback which should be called when fetching is completed. Scheduling is not required. + /// An responsible for the fetch operation. This will be queued and performed automatically. + protected abstract APIRequest FetchScores(Action> scoresCallback); + + protected abstract LeaderboardScore CreateDrawableScore(TScoreInfo model, int index); + + protected abstract LeaderboardScore CreateDrawableTopScore(TScoreInfo model); + + private void refetchScores() + { + // don't display any scores or placeholder until the first Scores_Set has been called. + // this avoids scope changes flickering a "no scores" placeholder before initialisation of song select is finished. + if (!scoresLoadedOnce) return; + + cancelPendingWork(); + + PlaceholderState = PlaceholderState.Retrieving; + loading.Show(); + + getScoresRequest = FetchScores(scores => getScoresRequestCallback = Schedule(() => + { + Scores = scores.ToArray(); + PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; + })); + + if (getScoresRequest == null) + return; + + getScoresRequest.Failure += e => getScoresRequestCallback = Schedule(() => + { + if (e is OperationCanceledException) + return; + + PlaceholderState = PlaceholderState.NetworkFailure; + }); + + api?.Queue(getScoresRequest); + } + + private void cancelPendingWork() + { + getScoresRequest?.Cancel(); + getScoresRequest = null; + + getScoresRequestCallback?.Cancel(); + getScoresRequestCallback = null; + } + + #region Placeholder handling + + private Placeholder currentPlaceholder; + private PlaceholderState placeholderState; /// @@ -194,133 +327,6 @@ namespace osu.Game.Online.Leaderboards } } - protected Leaderboard() - { - InternalChildren = new Drawable[] - { - new OsuContextMenuContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Child = new GridContainer - { - RelativeSizeAxes = Axes.Both, - RowDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] - { - new Drawable[] - { - scrollContainer = new OsuScrollContainer - { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - } - }, - new Drawable[] - { - new Container - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Child = topScoreContainer = new UserTopScoreContainer(CreateDrawableTopScore) - }, - }, - }, - }, - }, - loading = new LoadingSpinner(), - placeholderContainer = new Container - { - RelativeSizeAxes = Axes.Both - }, - }; - } - - protected virtual void Reset() - { - cancelPendingWork(); - Scores = null; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - if (api != null) - { - apiState.BindTo(api.State); - apiState.BindValueChanged(state => - { - switch (state.NewValue) - { - case APIState.Online: - case APIState.Offline: - if (IsOnlineScope) - RefetchScores(); - - break; - } - }); - } - - RefetchScores(); - } - - public void RefetchScores() => Scheduler.AddOnce(refetchScores); - - private void refetchScores() - { - // don't display any scores or placeholder until the first Scores_Set has been called. - // this avoids scope changes flickering a "no scores" placeholder before initialisation of song select is finished. - if (!scoresLoadedOnce) return; - - cancelPendingWork(); - - PlaceholderState = PlaceholderState.Retrieving; - loading.Show(); - - getScoresRequest = FetchScores(scores => getScoresRequestCallback = Schedule(() => - { - Scores = scores.ToArray(); - PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; - })); - - if (getScoresRequest == null) - return; - - getScoresRequest.Failure += e => getScoresRequestCallback = Schedule(() => - { - if (e is OperationCanceledException) - return; - - PlaceholderState = PlaceholderState.NetworkFailure; - }); - - api?.Queue(getScoresRequest); - } - - private void cancelPendingWork() - { - getScoresRequest?.Cancel(); - getScoresRequest = null; - - getScoresRequestCallback?.Cancel(); - getScoresRequestCallback = null; - } - - /// - /// Performs a fetch/refresh of scores to be displayed. - /// - /// A callback which should be called when fetching is completed. Scheduling is not required. - /// An responsible for the fetch operation. This will be queued and performed automatically. - protected abstract APIRequest FetchScores(Action> scoresCallback); - - private Placeholder currentPlaceholder; - private void replacePlaceholder(Placeholder placeholder) { if (placeholder != null && placeholder.Equals(currentPlaceholder)) @@ -342,6 +348,10 @@ namespace osu.Game.Online.Leaderboards currentPlaceholder = placeholder; } + #endregion + + #region Fade handling + protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); @@ -384,8 +394,6 @@ namespace osu.Game.Online.Leaderboards } } - protected abstract LeaderboardScore CreateDrawableScore(TScoreInfo model, int index); - - protected abstract LeaderboardScore CreateDrawableTopScore(TScoreInfo model); + #endregion } } From 661fec7c8abd1fba873a5003092d9f5f45da475c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 21:59:29 +0900 Subject: [PATCH 0377/5199] Make score setter private --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 5 ++++- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 3 --- osu.Game/Online/Leaderboards/Leaderboard.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index e31be1d51a..7ffab2eebc 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -100,7 +101,7 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestGlobalScoresDisplay() { AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Global); - AddStep(@"New Scores", () => leaderboard.Scores = generateSampleScores(null)); + AddStep(@"New Scores", () => leaderboard.SetScores(generateSampleScores(null))); } [Test] @@ -422,6 +423,8 @@ namespace osu.Game.Tests.Visual.SongSelect { PlaceholderState = state; } + + public void SetScores(ICollection scores) => Scores = scores; } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 4938fd1271..985ff6f251 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -128,9 +128,6 @@ namespace osu.Game.Tests.Visual.UserInterface scoreManager.Undelete(r.All().Where(s => s.DeletePending).ToList()); }); - leaderboard.Scores = null; - leaderboard.FinishTransforms(true); // After setting scores, we may be waiting for transforms to expire drawables - leaderboard.BeatmapInfo = beatmapInfo; leaderboard.RefetchScores(); // Required in the case that the beatmap hasn't changed }); diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index d843dcb2bb..0c58faa178 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -61,7 +61,7 @@ namespace osu.Game.Online.Leaderboards public ICollection Scores { get => scores; - set + protected set { scores = value; From a700ad38499ed2fbaed093e5de5570b27f5cd0c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 22:04:34 +0900 Subject: [PATCH 0378/5199] Remove `scoresLoadedOnce` weirdness --- osu.Game/Online/Leaderboards/Leaderboard.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 0c58faa178..238e883d24 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -44,8 +44,6 @@ namespace osu.Game.Online.Leaderboards private ScheduledDelegate showScoresDelegate; private CancellationTokenSource showScoresCancellationSource; - private bool scoresLoadedOnce; - private APIRequest getScoresRequest; private ScheduledDelegate getScoresRequestCallback; @@ -65,8 +63,6 @@ namespace osu.Game.Online.Leaderboards { scores = value; - scoresLoadedOnce = true; - scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire(); scrollFlow = null; @@ -230,10 +226,6 @@ namespace osu.Game.Online.Leaderboards private void refetchScores() { - // don't display any scores or placeholder until the first Scores_Set has been called. - // this avoids scope changes flickering a "no scores" placeholder before initialisation of song select is finished. - if (!scoresLoadedOnce) return; - cancelPendingWork(); PlaceholderState = PlaceholderState.Retrieving; From c48e9f2bbdc2dba7360e5d0b7ef9e4dad12256e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 22:13:52 +0900 Subject: [PATCH 0379/5199] Remove more unnecessary schedule/cancel logic --- osu.Game/Online/Leaderboards/Leaderboard.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 238e883d24..e8fc2d126e 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -3,10 +3,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Development; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -41,7 +43,6 @@ namespace osu.Game.Online.Leaderboards private readonly LoadingSpinner loading; - private ScheduledDelegate showScoresDelegate; private CancellationTokenSource showScoresCancellationSource; private APIRequest getScoresRequest; @@ -61,15 +62,16 @@ namespace osu.Game.Online.Leaderboards get => scores; protected set { + Debug.Assert(ThreadSafety.IsUpdateThread); + scores = value; scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire(); scrollFlow = null; - showScoresDelegate?.Cancel(); showScoresCancellationSource?.Cancel(); - if (scores == null || !scores.Any()) + if (scores?.Any() != true) { loading.Hide(); return; @@ -84,11 +86,11 @@ namespace osu.Game.Online.Leaderboards AutoSizeAxes = Axes.Y, Spacing = new Vector2(0f, 5f), Padding = new MarginPadding { Top = 10, Bottom = 5 }, + ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)) }; - scoreFlow.ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)); // schedule because we may not be loaded yet (LoadComponentAsync complains). - showScoresDelegate = Schedule(() => LoadComponentAsync(scoreFlow, _ => + LoadComponentAsync(scoreFlow, _ => { scrollContainer.Add(scrollFlow = scoreFlow); @@ -100,9 +102,9 @@ namespace osu.Game.Online.Leaderboards s.Show(); } - scrollContainer.ScrollTo(0f, false); + scrollContainer.ScrollToStart(false); loading.Hide(); - }, (showScoresCancellationSource = new CancellationTokenSource()).Token)); + }, (showScoresCancellationSource = new CancellationTokenSource()).Token); } } @@ -253,6 +255,9 @@ namespace osu.Game.Online.Leaderboards private void cancelPendingWork() { + showScoresCancellationSource?.Cancel(); + showScoresCancellationSource = null; + getScoresRequest?.Cancel(); getScoresRequest = null; From 13f445ddd5c49b2be0ccf2efeeff72c4703b1b8b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 22:28:13 +0900 Subject: [PATCH 0380/5199] Move score update code into own method --- osu.Game/Online/Leaderboards/Leaderboard.cs | 91 +++++++++++---------- 1 file changed, 48 insertions(+), 43 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index e8fc2d126e..0302568b34 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -33,6 +33,8 @@ namespace osu.Game.Online.Leaderboards /// The score model class. public abstract class Leaderboard : CompositeDrawable { + protected abstract bool IsOnlineScope { get; } + private const double fade_duration = 300; private readonly OsuScrollContainer scrollContainer; @@ -48,8 +50,6 @@ namespace osu.Game.Online.Leaderboards private APIRequest getScoresRequest; private ScheduledDelegate getScoresRequestCallback; - protected abstract bool IsOnlineScope { get; } - [Resolved(CanBeNull = true)] private IAPIProvider api { get; set; } @@ -62,49 +62,9 @@ namespace osu.Game.Online.Leaderboards get => scores; protected set { - Debug.Assert(ThreadSafety.IsUpdateThread); - scores = value; - scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire(); - scrollFlow = null; - - showScoresCancellationSource?.Cancel(); - - if (scores?.Any() != true) - { - loading.Hide(); - return; - } - - // ensure placeholder is hidden when displaying scores - PlaceholderState = PlaceholderState.Successful; - - var scoreFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0f, 5f), - Padding = new MarginPadding { Top = 10, Bottom = 5 }, - ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)) - }; - - // schedule because we may not be loaded yet (LoadComponentAsync complains). - LoadComponentAsync(scoreFlow, _ => - { - scrollContainer.Add(scrollFlow = scoreFlow); - - int i = 0; - - foreach (var s in scrollFlow.Children) - { - using (s.BeginDelayedSequence(i++ * 50)) - s.Show(); - } - - scrollContainer.ScrollToStart(false); - loading.Hide(); - }, (showScoresCancellationSource = new CancellationTokenSource()).Token); + updateScoresDrawables(); } } @@ -324,6 +284,51 @@ namespace osu.Game.Online.Leaderboards } } + private void updateScoresDrawables() + { + Debug.Assert(ThreadSafety.IsUpdateThread); + + scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire(); + scrollFlow = null; + + showScoresCancellationSource?.Cancel(); + + if (scores?.Any() != true) + { + loading.Hide(); + return; + } + + // ensure placeholder is hidden when displaying scores + PlaceholderState = PlaceholderState.Successful; + + var scoreFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, 5f), + Padding = new MarginPadding { Top = 10, Bottom = 5 }, + ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)) + }; + + // schedule because we may not be loaded yet (LoadComponentAsync complains). + LoadComponentAsync(scoreFlow, _ => + { + scrollContainer.Add(scrollFlow = scoreFlow); + + int i = 0; + + foreach (var s in scrollFlow.Children) + { + using (s.BeginDelayedSequence(i++ * 50)) + s.Show(); + } + + scrollContainer.ScrollToStart(false); + loading.Hide(); + }, (showScoresCancellationSource = new CancellationTokenSource()).Token); + } + private void replacePlaceholder(Placeholder placeholder) { if (placeholder != null && placeholder.Equals(currentPlaceholder)) From 3d59bab7c61cea8d71b362bd21f9bc183c3baf7d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 22:47:45 +0900 Subject: [PATCH 0381/5199] Remove fetch callback logic completely --- osu.Game/Online/Leaderboards/Leaderboard.cs | 25 +--- .../Match/Components/MatchLeaderboard.cs | 6 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 130 ++++++++---------- 3 files changed, 67 insertions(+), 94 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 0302568b34..eeb814b4cb 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -3,12 +3,11 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Development; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -178,9 +177,9 @@ namespace osu.Game.Online.Leaderboards /// /// Performs a fetch/refresh of scores to be displayed. /// - /// A callback which should be called when fetching is completed. Scheduling is not required. /// An responsible for the fetch operation. This will be queued and performed automatically. - protected abstract APIRequest FetchScores(Action> scoresCallback); + [CanBeNull] + protected abstract APIRequest FetchScores(); protected abstract LeaderboardScore CreateDrawableScore(TScoreInfo model, int index); @@ -193,11 +192,7 @@ namespace osu.Game.Online.Leaderboards PlaceholderState = PlaceholderState.Retrieving; loading.Show(); - getScoresRequest = FetchScores(scores => getScoresRequestCallback = Schedule(() => - { - Scores = scores.ToArray(); - PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; - })); + getScoresRequest = FetchScores(); if (getScoresRequest == null) return; @@ -240,11 +235,6 @@ namespace osu.Game.Online.Leaderboards get => placeholderState; set { - if (value != PlaceholderState.Successful) - { - Reset(); - } - if (value == placeholderState) return; @@ -284,10 +274,8 @@ namespace osu.Game.Online.Leaderboards } } - private void updateScoresDrawables() + private void updateScoresDrawables() => Scheduler.Add(() => { - Debug.Assert(ThreadSafety.IsUpdateThread); - scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire(); scrollFlow = null; @@ -296,6 +284,7 @@ namespace osu.Game.Online.Leaderboards if (scores?.Any() != true) { loading.Hide(); + PlaceholderState = PlaceholderState.NoScores; return; } @@ -327,7 +316,7 @@ namespace osu.Game.Online.Leaderboards scrollContainer.ScrollToStart(false); loading.Hide(); }, (showScoresCancellationSource = new CancellationTokenSource()).Token); - } + }, false); private void replacePlaceholder(Placeholder placeholder) { diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs index e4ec3ac4a5..5b60f64160 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.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; -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Online.API; @@ -32,7 +30,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components protected override bool IsOnlineScope => true; - protected override APIRequest FetchScores(Action> scoresCallback) + protected override APIRequest FetchScores() { if (roomId.Value == null) return null; @@ -41,7 +39,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components req.Success += r => { - scoresCallback?.Invoke(r.Leaderboard); + Scores = r.Leaderboard; TopScore = r.UserScore; }; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 02e4e162dd..898b894541 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -25,12 +25,6 @@ namespace osu.Game.Screens.Select.Leaderboards { public Action ScoreSelected; - [Resolved] - private RulesetStore rulesets { get; set; } - - [Resolved] - private RealmAccess realm { get; set; } - private BeatmapInfo beatmapInfo; public BeatmapInfo BeatmapInfo @@ -52,13 +46,7 @@ namespace osu.Game.Screens.Select.Leaderboards beatmapInfo = value; Scores = null; - if (IsOnlineScope) - RefetchScores(); - else - { - if (IsLoaded) - refreshRealmSubscription(); - } + RefetchScores(); } } @@ -93,6 +81,14 @@ namespace osu.Game.Screens.Select.Leaderboards [Resolved] private IAPIProvider api { get; set; } + [Resolved] + private RulesetStore rulesets { get; set; } + + [Resolved] + private RealmAccess realm { get; set; } + + private IDisposable scoreSubscription; + [BackgroundDependencyLoader] private void load() { @@ -104,33 +100,6 @@ namespace osu.Game.Screens.Select.Leaderboards }; } - protected override void LoadComplete() - { - base.LoadComplete(); - - refreshRealmSubscription(); - } - - private IDisposable scoreSubscription; - - private void refreshRealmSubscription() - { - scoreSubscription?.Dispose(); - scoreSubscription = null; - - if (beatmapInfo == null) - return; - - scoreSubscription = realm.RegisterForNotifications(r => - r.All() - .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID), - (_, changes, ___) => - { - if (!IsOnlineScope) - RefetchScores(); - }); - } - protected override void Reset() { base.Reset(); @@ -141,13 +110,11 @@ namespace osu.Game.Screens.Select.Leaderboards private CancellationTokenSource loadCancellationSource; - protected override APIRequest FetchScores(Action> scoresCallback) + protected override APIRequest FetchScores() { loadCancellationSource?.Cancel(); loadCancellationSource = new CancellationTokenSource(); - var cancellationToken = loadCancellationSource.Token; - var fetchBeatmapInfo = BeatmapInfo; if (fetchBeatmapInfo == null) @@ -158,31 +125,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (Scope == BeatmapLeaderboardScope.Local) { - realm.Run(r => - { - var scores = r.All() - .AsEnumerable() - // TODO: update to use a realm filter directly (or at least figure out the beatmap part to reduce scope). - .Where(s => !s.DeletePending && s.BeatmapInfo.ID == fetchBeatmapInfo.ID && s.Ruleset.ShortName == ruleset.Value.ShortName); - - if (filterMods && !mods.Value.Any()) - { - // we need to filter out all scores that have any mods to get all local nomod scores - scores = scores.Where(s => !s.Mods.Any()); - } - else if (filterMods) - { - // otherwise find all the scores that have *any* of the currently selected mods (similar to how web applies mod filters) - // we're creating and using a string list representation of selected mods so that it can be translated into the DB query itself - var selectedMods = mods.Value.Select(m => m.Acronym); - scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym))); - } - - scores = scores.Detach(); - - scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken) - .ContinueWith(ordered => scoresCallback?.Invoke(ordered.GetResultSafely()), TaskContinuationOptions.OnlyOnRanToCompletion); - }); + subscribeToLocalScores(); return null; } @@ -217,13 +160,13 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { - scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets, fetchBeatmapInfo)).ToArray(), cancellationToken) + scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets, fetchBeatmapInfo)).ToArray(), loadCancellationSource.Token) .ContinueWith(task => Schedule(() => { - if (cancellationToken.IsCancellationRequested) + if (loadCancellationSource.IsCancellationRequested) return; - scoresCallback?.Invoke(task.GetResultSafely()); + Scores = task.GetResultSafely(); TopScore = r.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo); }), TaskContinuationOptions.OnlyOnRanToCompletion); }; @@ -241,10 +184,53 @@ namespace osu.Game.Screens.Select.Leaderboards Action = () => ScoreSelected?.Invoke(model) }; + private void subscribeToLocalScores() + { + scoreSubscription?.Dispose(); + scoreSubscription = null; + + if (beatmapInfo == null) + return; + + scoreSubscription = realm.RegisterForNotifications(r => + r.All().Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $0" + + $" AND {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $1" + + $" AND {nameof(ScoreInfo.DeletePending)} == false" + , beatmapInfo.ID, ruleset.Value.ShortName), localScoresChanged); + } + + private void localScoresChanged(IRealmCollection sender, ChangeSet changes, Exception exception) + { + if (IsOnlineScope) + return; + + var scores = sender.AsEnumerable(); + + if (filterMods && !mods.Value.Any()) + { + // we need to filter out all scores that have any mods to get all local nomod scores + scores = scores.Where(s => !s.Mods.Any()); + } + else if (filterMods) + { + // otherwise find all the scores that have *any* of the currently selected mods (similar to how web applies mod filters) + // we're creating and using a string list representation of selected mods so that it can be translated into the DB query itself + var selectedMods = mods.Value.Select(m => m.Acronym); + scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym))); + } + + scores = scores.Detach(); + + scoreManager.OrderByTotalScoreAsync(scores.ToArray(), loadCancellationSource.Token) + .ContinueWith(ordered => + { + Scores = ordered.GetResultSafely(); + }, TaskContinuationOptions.OnlyOnRanToCompletion); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - scoreSubscription?.Dispose(); } } From daea13f49158b5c464f20c4b395a85bd46206991 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 23:14:26 +0900 Subject: [PATCH 0382/5199] Simplify flow of cancellation token --- osu.Game/Online/Leaderboards/Leaderboard.cs | 42 ++++++------ .../Match/Components/MatchLeaderboard.cs | 6 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 65 +++++++++---------- 3 files changed, 55 insertions(+), 58 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index eeb814b4cb..0d430f4903 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using JetBrains.Annotations; @@ -13,7 +14,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Threading; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; @@ -44,10 +44,9 @@ namespace osu.Game.Online.Leaderboards private readonly LoadingSpinner loading; - private CancellationTokenSource showScoresCancellationSource; + private CancellationTokenSource currentFetchCancellationSource; - private APIRequest getScoresRequest; - private ScheduledDelegate getScoresRequestCallback; + private APIRequest fetchScoresRequest; [Resolved(CanBeNull = true)] private IAPIProvider api { get; set; } @@ -62,7 +61,6 @@ namespace osu.Game.Online.Leaderboards protected set { scores = value; - updateScoresDrawables(); } } @@ -177,9 +175,10 @@ namespace osu.Game.Online.Leaderboards /// /// Performs a fetch/refresh of scores to be displayed. /// + /// /// An responsible for the fetch operation. This will be queued and performed automatically. [CanBeNull] - protected abstract APIRequest FetchScores(); + protected abstract APIRequest FetchScores(CancellationToken cancellationToken); protected abstract LeaderboardScore CreateDrawableScore(TScoreInfo model, int index); @@ -187,37 +186,36 @@ namespace osu.Game.Online.Leaderboards private void refetchScores() { - cancelPendingWork(); + Reset(); PlaceholderState = PlaceholderState.Retrieving; loading.Show(); - getScoresRequest = FetchScores(); + currentFetchCancellationSource = new CancellationTokenSource(); - if (getScoresRequest == null) + fetchScoresRequest = FetchScores(currentFetchCancellationSource.Token); + + if (fetchScoresRequest == null) return; - getScoresRequest.Failure += e => getScoresRequestCallback = Schedule(() => + fetchScoresRequest.Failure += e => Schedule(() => { - if (e is OperationCanceledException) + if (e is OperationCanceledException || currentFetchCancellationSource.IsCancellationRequested) return; PlaceholderState = PlaceholderState.NetworkFailure; }); - api?.Queue(getScoresRequest); + api?.Queue(fetchScoresRequest); } private void cancelPendingWork() { - showScoresCancellationSource?.Cancel(); - showScoresCancellationSource = null; + currentFetchCancellationSource?.Cancel(); + currentFetchCancellationSource = null; - getScoresRequest?.Cancel(); - getScoresRequest = null; - - getScoresRequestCallback?.Cancel(); - getScoresRequestCallback = null; + fetchScoresRequest?.Cancel(); + fetchScoresRequest = null; } #region Placeholder handling @@ -279,8 +277,6 @@ namespace osu.Game.Online.Leaderboards scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire(); scrollFlow = null; - showScoresCancellationSource?.Cancel(); - if (scores?.Any() != true) { loading.Hide(); @@ -288,6 +284,8 @@ namespace osu.Game.Online.Leaderboards return; } + Debug.Assert(!currentFetchCancellationSource.IsCancellationRequested); + // ensure placeholder is hidden when displaying scores PlaceholderState = PlaceholderState.Successful; @@ -315,7 +313,7 @@ namespace osu.Game.Online.Leaderboards scrollContainer.ScrollToStart(false); loading.Hide(); - }, (showScoresCancellationSource = new CancellationTokenSource()).Token); + }, currentFetchCancellationSource.Token); }, false); private void replacePlaceholder(Placeholder placeholder) diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs index 5b60f64160..1945899a11 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.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.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Online.API; @@ -30,7 +31,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components protected override bool IsOnlineScope => true; - protected override APIRequest FetchScores() + protected override APIRequest FetchScores(CancellationToken cancellationToken) { if (roomId.Value == null) return null; @@ -39,6 +40,9 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components req.Success += r => { + if (cancellationToken.IsCancellationRequested) + return; + Scores = r.Leaderboard; TopScore = r.UserScore; }; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 898b894541..388395c9f9 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -108,13 +108,8 @@ namespace osu.Game.Screens.Select.Leaderboards protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local; - private CancellationTokenSource loadCancellationSource; - - protected override APIRequest FetchScores() + protected override APIRequest FetchScores(CancellationToken cancellationToken) { - loadCancellationSource?.Cancel(); - loadCancellationSource = new CancellationTokenSource(); - var fetchBeatmapInfo = BeatmapInfo; if (fetchBeatmapInfo == null) @@ -125,7 +120,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (Scope == BeatmapLeaderboardScope.Local) { - subscribeToLocalScores(); + subscribeToLocalScores(cancellationToken); return null; } @@ -160,10 +155,10 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { - scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets, fetchBeatmapInfo)).ToArray(), loadCancellationSource.Token) + scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets, fetchBeatmapInfo)).ToArray(), cancellationToken) .ContinueWith(task => Schedule(() => { - if (loadCancellationSource.IsCancellationRequested) + if (cancellationToken.IsCancellationRequested) return; Scores = task.GetResultSafely(); @@ -184,7 +179,7 @@ namespace osu.Game.Screens.Select.Leaderboards Action = () => ScoreSelected?.Invoke(model) }; - private void subscribeToLocalScores() + private void subscribeToLocalScores(CancellationToken cancellationToken) { scoreSubscription?.Dispose(); scoreSubscription = null; @@ -197,35 +192,35 @@ namespace osu.Game.Screens.Select.Leaderboards + $" AND {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $1" + $" AND {nameof(ScoreInfo.DeletePending)} == false" , beatmapInfo.ID, ruleset.Value.ShortName), localScoresChanged); - } - private void localScoresChanged(IRealmCollection sender, ChangeSet changes, Exception exception) - { - if (IsOnlineScope) - return; - - var scores = sender.AsEnumerable(); - - if (filterMods && !mods.Value.Any()) + void localScoresChanged(IRealmCollection sender, ChangeSet changes, Exception exception) { - // we need to filter out all scores that have any mods to get all local nomod scores - scores = scores.Where(s => !s.Mods.Any()); - } - else if (filterMods) - { - // otherwise find all the scores that have *any* of the currently selected mods (similar to how web applies mod filters) - // we're creating and using a string list representation of selected mods so that it can be translated into the DB query itself - var selectedMods = mods.Value.Select(m => m.Acronym); - scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym))); - } + if (IsOnlineScope || cancellationToken.IsCancellationRequested) + return; - scores = scores.Detach(); + var scores = sender.AsEnumerable(); - scoreManager.OrderByTotalScoreAsync(scores.ToArray(), loadCancellationSource.Token) - .ContinueWith(ordered => - { - Scores = ordered.GetResultSafely(); - }, TaskContinuationOptions.OnlyOnRanToCompletion); + if (filterMods && !mods.Value.Any()) + { + // we need to filter out all scores that have any mods to get all local nomod scores + scores = scores.Where(s => !s.Mods.Any()); + } + else if (filterMods) + { + // otherwise find all the scores that have *any* of the currently selected mods (similar to how web applies mod filters) + // we're creating and using a string list representation of selected mods so that it can be translated into the DB query itself + var selectedMods = mods.Value.Select(m => m.Acronym); + scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym))); + } + + scores = scores.Detach(); + + scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken) + .ContinueWith(ordered => + { + Scores = ordered.GetResultSafely(); + }, TaskContinuationOptions.OnlyOnRanToCompletion); + } } protected override void Dispose(bool isDisposing) From 0293d95f829f7b3596e472244e42ed040ee62717 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 23:17:06 +0900 Subject: [PATCH 0383/5199] Simplify `IsOnlineScope` usage --- osu.Game/Online/Leaderboards/Leaderboard.cs | 3 +++ osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 0d430f4903..6c2f0d9425 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -32,6 +32,9 @@ namespace osu.Game.Online.Leaderboards /// The score model class. public abstract class Leaderboard : CompositeDrawable { + /// + /// Whether the current scope should refetch in response to changes in API connectivity state. + /// protected abstract bool IsOnlineScope { get; } private const double fade_duration = 300; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 388395c9f9..e55bd56e62 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -195,7 +195,7 @@ namespace osu.Game.Screens.Select.Leaderboards void localScoresChanged(IRealmCollection sender, ChangeSet changes, Exception exception) { - if (IsOnlineScope || cancellationToken.IsCancellationRequested) + if (cancellationToken.IsCancellationRequested) return; var scores = sender.AsEnumerable(); From d0b74a91fba148d56fbd0ac586e5f6e1159e2a92 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 02:12:36 +0900 Subject: [PATCH 0384/5199] Fix edge cases with score drawable loading --- osu.Game/Online/Leaderboards/Leaderboard.cs | 45 ++++++++++--------- .../Select/Leaderboards/BeatmapLeaderboard.cs | 1 - 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 6c2f0d9425..a1f07c469a 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading; using JetBrains.Annotations; @@ -43,11 +42,12 @@ namespace osu.Game.Online.Leaderboards private readonly Container placeholderContainer; private readonly UserTopScoreContainer topScoreContainer; - private FillFlowContainer scrollFlow; + private FillFlowContainer scoreFlowContainer; private readonly LoadingSpinner loading; private CancellationTokenSource currentFetchCancellationSource; + private CancellationTokenSource currentScoresAsyncLoadCancellationSource; private APIRequest fetchScoresRequest; @@ -64,7 +64,7 @@ namespace osu.Game.Online.Leaderboards protected set { scores = value; - updateScoresDrawables(); + Scheduler.AddOnce(updateScoresDrawables); } } @@ -239,6 +239,8 @@ namespace osu.Game.Online.Leaderboards if (value == placeholderState) return; + loading.Hide(); + switch (placeholderState = value) { case PlaceholderState.NetworkFailure: @@ -275,40 +277,41 @@ namespace osu.Game.Online.Leaderboards } } - private void updateScoresDrawables() => Scheduler.Add(() => + private void updateScoresDrawables() { - scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire(); - scrollFlow = null; + currentScoresAsyncLoadCancellationSource?.Cancel(); + currentScoresAsyncLoadCancellationSource = new CancellationTokenSource(); + + scoreFlowContainer? + .FadeOut(fade_duration, Easing.OutQuint) + .Expire(); + scoreFlowContainer = null; + + loading.Hide(); if (scores?.Any() != true) { - loading.Hide(); PlaceholderState = PlaceholderState.NoScores; return; } - Debug.Assert(!currentFetchCancellationSource.IsCancellationRequested); - // ensure placeholder is hidden when displaying scores PlaceholderState = PlaceholderState.Successful; - var scoreFlow = new FillFlowContainer + LoadComponentAsync(new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Spacing = new Vector2(0f, 5f), Padding = new MarginPadding { Top = 10, Bottom = 5 }, ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)) - }; - - // schedule because we may not be loaded yet (LoadComponentAsync complains). - LoadComponentAsync(scoreFlow, _ => + }, newFlow => { - scrollContainer.Add(scrollFlow = scoreFlow); + scrollContainer.Add(scoreFlowContainer = newFlow); int i = 0; - foreach (var s in scrollFlow.Children) + foreach (var s in scoreFlowContainer.Children) { using (s.BeginDelayedSequence(i++ * 50)) s.Show(); @@ -316,8 +319,8 @@ namespace osu.Game.Online.Leaderboards scrollContainer.ScrollToStart(false); loading.Hide(); - }, currentFetchCancellationSource.Token); - }, false); + }, currentScoresAsyncLoadCancellationSource.Token); + } private void replacePlaceholder(Placeholder placeholder) { @@ -354,12 +357,12 @@ namespace osu.Game.Online.Leaderboards if (!scrollContainer.IsScrolledToEnd()) fadeBottom -= LeaderboardScore.HEIGHT; - if (scrollFlow == null) + if (scoreFlowContainer == null) return; - foreach (var c in scrollFlow.Children) + foreach (var c in scoreFlowContainer.Children) { - float topY = c.ToSpaceOfOtherDrawable(Vector2.Zero, scrollFlow).Y; + float topY = c.ToSpaceOfOtherDrawable(Vector2.Zero, scoreFlowContainer).Y; float bottomY = topY + LeaderboardScore.HEIGHT; bool requireBottomFade = bottomY >= fadeBottom; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index e55bd56e62..31868624bb 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -121,7 +121,6 @@ namespace osu.Game.Screens.Select.Leaderboards if (Scope == BeatmapLeaderboardScope.Local) { subscribeToLocalScores(cancellationToken); - return null; } From 6f54f8ad7897cc9bfa31e3ad00660eff3efeef59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 12:18:34 +0900 Subject: [PATCH 0385/5199] Add more safety around `CancellationToken` usage --- osu.Game/Online/Leaderboards/Leaderboard.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index a1f07c469a..cc6d7a7b62 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -3,11 +3,13 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Development; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -189,6 +191,8 @@ namespace osu.Game.Online.Leaderboards private void refetchScores() { + Debug.Assert(ThreadSafety.IsUpdateThread); + Reset(); PlaceholderState = PlaceholderState.Retrieving; @@ -215,10 +219,8 @@ namespace osu.Game.Online.Leaderboards private void cancelPendingWork() { currentFetchCancellationSource?.Cancel(); - currentFetchCancellationSource = null; - + currentScoresAsyncLoadCancellationSource?.Cancel(); fetchScoresRequest?.Cancel(); - fetchScoresRequest = null; } #region Placeholder handling @@ -280,7 +282,6 @@ namespace osu.Game.Online.Leaderboards private void updateScoresDrawables() { currentScoresAsyncLoadCancellationSource?.Cancel(); - currentScoresAsyncLoadCancellationSource = new CancellationTokenSource(); scoreFlowContainer? .FadeOut(fade_duration, Easing.OutQuint) @@ -319,7 +320,7 @@ namespace osu.Game.Online.Leaderboards scrollContainer.ScrollToStart(false); loading.Hide(); - }, currentScoresAsyncLoadCancellationSource.Token); + }, (currentScoresAsyncLoadCancellationSource = new CancellationTokenSource()).Token); } private void replacePlaceholder(Placeholder placeholder) From a915b9cd3093bd2ede73c3d6ef9470ff12967dc1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 23:12:57 +0900 Subject: [PATCH 0386/5199] Fix occasional failures in `TestSceneDeleteLocalScore` --- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 985ff6f251..da4cf9c6e3 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -135,11 +135,9 @@ namespace osu.Game.Tests.Visual.UserInterface [SetUpSteps] public void SetupSteps() { - // Ensure the leaderboard has finished async-loading drawables - AddUntilStep("wait for drawables", () => leaderboard.ChildrenOfType().Any()); - // Ensure the leaderboard items have finished showing up AddStep("finish transforms", () => leaderboard.FinishTransforms(true)); + AddUntilStep("wait for drawables", () => leaderboard.ChildrenOfType().Any()); } [Test] From 9861c50b33f8fa036e8bdf62ff1f646cab55c8ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 00:03:22 +0900 Subject: [PATCH 0387/5199] Remove pointless tests that no longer show anything valid --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 7ffab2eebc..b4b66e8afa 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -122,13 +122,6 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep(@"None selected", () => leaderboard.SetRetrievalState(PlaceholderState.NoneSelected)); } - [Test] - public void TestBeatmapStates() - { - foreach (BeatmapOnlineStatus status in Enum.GetValues(typeof(BeatmapOnlineStatus))) - AddStep($"{status} beatmap", () => showBeatmapWithStatus(status)); - } - private void showPersonalBestWithNullPosition() { leaderboard.TopScore = new ScoreInfo From e408d8ef0e1ced02cf3842c95318814e987c3303 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 23:19:51 +0800 Subject: [PATCH 0388/5199] rename `Frames` to `ReplayFrames` --- .../Mods/TestSceneOsuModAlternate.cs | 8 ++++---- osu.Game/Tests/Visual/ModTestScene.cs | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs index 7b3eaa7ffd..de1f61a0bd 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }, }, }, - Frames = new List + ReplayFrames = new List { new OsuReplayFrame(500, new Vector2(200), OsuAction.LeftButton), new OsuReplayFrame(501, new Vector2(200)), @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }, }, }, - Frames = new List + ReplayFrames = new List { new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), new OsuReplayFrame(501, new Vector2(100)), @@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }, }, }, - Frames = new List + ReplayFrames = new List { new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), new OsuReplayFrame(501, new Vector2(100)), @@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods } } }, - Frames = new List + ReplayFrames = new List { new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), new OsuReplayFrame(501, new Vector2(100)), diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index 96fafa8727..2505864d59 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -72,14 +72,14 @@ namespace osu.Game.Tests.Visual protected override void PrepareReplay() { - if (currentTestData.Autoplay && currentTestData.Frames?.Count > 0) - throw new InvalidOperationException(@$"{nameof(ModTestData.Autoplay)} must be false when {nameof(ModTestData.Frames)} is specified."); + if (currentTestData.Autoplay && currentTestData.ReplayFrames?.Count > 0) + throw new InvalidOperationException(@$"{nameof(ModTestData.Autoplay)} must be false when {nameof(ModTestData.ReplayFrames)} is specified."); - if (currentTestData.Frames != null) + if (currentTestData.ReplayFrames != null) { DrawableRuleset?.SetReplayScore(new Score { - Replay = new Replay { Frames = currentTestData.Frames }, + Replay = new Replay { Frames = currentTestData.ReplayFrames }, ScoreInfo = new ScoreInfo { User = new APIUser { Username = @"Test" } }, }); } @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual /// The frames to use for replay. must be set to false. /// [CanBeNull] - public List Frames; + public List ReplayFrames; /// /// The beatmap for this test case. From 535216a0d3f5c063d5e5519ff4cd0533c3e72df5 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 23:20:31 +0800 Subject: [PATCH 0389/5199] rename `CanIntercept` to `ShouldAlternate` --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index 9eeb060135..876742caba 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; @@ -25,11 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) }; public override ModType Type => ModType.DifficultyIncrease; public override IconUsage? Icon => FontAwesome.Solid.Keyboard; - - /// - /// Whether incoming input must be checked by before it is passed to gameplay. - /// - public bool CanIntercept => !isBreakTime.Value && introEnded; + public bool ShouldAlternate => !isBreakTime.Value && introEnded; private bool introEnded; private double earliestStartTime; @@ -86,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Mods } public bool OnPressed(KeyBindingPressEvent e) - => mod.CanIntercept && mod.onPressed(e.Action); + => mod.ShouldAlternate && mod.onPressed(e.Action); public void OnReleased(KeyBindingReleaseEvent e) { From 40f43344f16056d66d6713078ecc3b5ccf47372b Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 23:31:26 +0800 Subject: [PATCH 0390/5199] remove unused using --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index 876742caba..db8bd217f6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; From 51acf79935724e5786ecd1b515f07657071b65fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 02:29:51 +0900 Subject: [PATCH 0391/5199] Change test exposure to property instead of method --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index b4b66e8afa..91ec1de3ad 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestGlobalScoresDisplay() { AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Global); - AddStep(@"New Scores", () => leaderboard.SetScores(generateSampleScores(null))); + AddStep(@"New Scores", () => leaderboard.Scores = generateSampleScores(null)); } [Test] @@ -417,7 +417,11 @@ namespace osu.Game.Tests.Visual.SongSelect PlaceholderState = state; } - public void SetScores(ICollection scores) => Scores = scores; + public new ICollection Scores + { + get => base.Scores; + set => base.Scores = value; + } } } } From 3d771c0fc7ed0f292ec28c0257ff80c122bcd058 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 02:34:31 +0900 Subject: [PATCH 0392/5199] Remove unnecessary `loading` hide call from `PlaceholderState_Set` and add more assertiveness --- osu.Game/Online/Leaderboards/Leaderboard.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index cc6d7a7b62..e20c8b5007 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -241,9 +241,11 @@ namespace osu.Game.Online.Leaderboards if (value == placeholderState) return; - loading.Hide(); + placeholderState = value; - switch (placeholderState = value) + Debug.Assert(placeholderState != PlaceholderState.Successful || scores?.Any() == true); + + switch (placeholderState) { case PlaceholderState.NetworkFailure: replacePlaceholder(new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync) From d3cb910cf82cb208bf3ae6c3059e8b851107770e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 02:36:50 +0900 Subject: [PATCH 0393/5199] Convert inline math to not so inline to make operation more explicit --- osu.Game/Online/Leaderboards/Leaderboard.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index e20c8b5007..460869fa54 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -312,12 +312,14 @@ namespace osu.Game.Online.Leaderboards { scrollContainer.Add(scoreFlowContainer = newFlow); - int i = 0; + double delay = 0; foreach (var s in scoreFlowContainer.Children) { - using (s.BeginDelayedSequence(i++ * 50)) + using (s.BeginDelayedSequence(delay)) s.Show(); + + delay += 50; } scrollContainer.ScrollToStart(false); From d21464ea61b8efcf53eb867970b8bf4a5013a109 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 02:54:51 +0900 Subject: [PATCH 0394/5199] Fix assertions to work in both directions --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 1 + osu.Game/Online/Leaderboards/Leaderboard.cs | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 91ec1de3ad..31b7b9fa8d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -414,6 +414,7 @@ namespace osu.Game.Tests.Visual.SongSelect { public void SetRetrievalState(PlaceholderState state) { + Scores = null; PlaceholderState = state; } diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 460869fa54..7b596d8381 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -243,11 +243,10 @@ namespace osu.Game.Online.Leaderboards placeholderState = value; - Debug.Assert(placeholderState != PlaceholderState.Successful || scores?.Any() == true); - switch (placeholderState) { case PlaceholderState.NetworkFailure: + Debug.Assert(scores?.Any() != true); replacePlaceholder(new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync) { Action = RefetchScores @@ -255,26 +254,32 @@ namespace osu.Game.Online.Leaderboards break; case PlaceholderState.NoneSelected: + Debug.Assert(scores?.Any() != true); replacePlaceholder(new MessagePlaceholder(@"Please select a beatmap!")); break; case PlaceholderState.Unavailable: + Debug.Assert(scores?.Any() != true); replacePlaceholder(new MessagePlaceholder(@"Leaderboards are not available for this beatmap!")); break; case PlaceholderState.NoScores: + Debug.Assert(scores?.Any() != true); replacePlaceholder(new MessagePlaceholder(@"No records yet!")); break; case PlaceholderState.NotLoggedIn: + Debug.Assert(scores?.Any() != true); replacePlaceholder(new LoginPlaceholder(@"Please sign in to view online leaderboards!")); break; case PlaceholderState.NotSupporter: + Debug.Assert(scores?.Any() != true); replacePlaceholder(new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!")); break; default: + Debug.Assert(scores?.Any() == true); replacePlaceholder(null); break; } From 9b573fbc2bdc044d0702179fa9b94a82a0fe0648 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 02:58:53 +0900 Subject: [PATCH 0395/5199] Add missing entries to `switch` statement and guard against out of range --- osu.Game/Online/Leaderboards/Leaderboard.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 7b596d8381..4134046320 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -278,10 +278,18 @@ namespace osu.Game.Online.Leaderboards replacePlaceholder(new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!")); break; - default: + case PlaceholderState.Retrieving: + Debug.Assert(scores?.Any() != true); + replacePlaceholder(null); + break; + + case PlaceholderState.Successful: Debug.Assert(scores?.Any() == true); replacePlaceholder(null); break; + + default: + throw new ArgumentOutOfRangeException(); } } } From 06660ff96019000f7b8302d24fdd0cf28176f460 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 03:02:56 +0900 Subject: [PATCH 0396/5199] Fix null beatmap in test scene --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 31b7b9fa8d..77fe3be933 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestGlobalScoresDisplay() { AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Global); - AddStep(@"New Scores", () => leaderboard.Scores = generateSampleScores(null)); + AddStep(@"New Scores", () => leaderboard.Scores = generateSampleScores(new BeatmapInfo())); } [Test] From dad9cc931524f6627c507d5831c9cbd3a79ba6fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 03:06:29 +0900 Subject: [PATCH 0397/5199] Ensure `Reset`/`Scores_Set` run inline where possible --- osu.Game/Online/Leaderboards/Leaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 4134046320..0ad9f46130 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -66,7 +66,7 @@ namespace osu.Game.Online.Leaderboards protected set { scores = value; - Scheduler.AddOnce(updateScoresDrawables); + Scheduler.Add(updateScoresDrawables, false); } } From b434e29a7c4f4883cb55498678933dc44b4ec185 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 03:10:01 +0900 Subject: [PATCH 0398/5199] Move loading hide operation inside early return to ensure not hidden too early It should only be hidden after the async load completes. --- osu.Game/Online/Leaderboards/Leaderboard.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 0ad9f46130..8cf20ea8d1 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -303,10 +303,9 @@ namespace osu.Game.Online.Leaderboards .Expire(); scoreFlowContainer = null; - loading.Hide(); - if (scores?.Any() != true) { + loading.Hide(); PlaceholderState = PlaceholderState.NoScores; return; } From 4f4f60248faad019bc3c0501e42de854fea5dbdc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 11 Aug 2021 12:16:25 +0300 Subject: [PATCH 0399/5199] Add failing test case --- .../TestSceneMultiSpectatorScreen.cs | 51 +++++++++++++++++-- .../Spectate/MultiSpectatorScreen.cs | 5 +- .../Multiplayer/Spectate/PlayerArea.cs | 13 ++++- osu.Game/Screens/Play/Player.cs | 7 +++ 4 files changed, 71 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index b84f7760e4..56cb6036c7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -347,19 +347,44 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert($"{PLAYER_1_ID} score quit still set", () => getLeaderboardScore(PLAYER_1_ID).HasQuit.Value); } - private void loadSpectateScreen(bool waitForPlayerLoad = true) + /// + /// Tests spectating with a gameplay start time set to a negative value. + /// Simulating beatmaps with high or negative time storyboard elements. + /// + [Test] + public void TestNegativeGameplayStartTime() { - AddStep("load screen", () => + start(PLAYER_1_ID); + + loadSpectateScreen(false, -500); + + // to ensure negative gameplay start time does not affect spectator, send frames exactly after StartGameplay(). + // (similar to real spectating sessions in which the first frames get sent between StartGameplay() and player load complete) + AddStep("send frames at gameplay start", () => getInstance(PLAYER_1_ID).OnGameplayStarted += () => SpectatorClient.SendFrames(PLAYER_1_ID, 100)); + + AddUntilStep("wait for player load", () => spectatorScreen.AllPlayersLoaded); + + AddWaitStep("wait for progression", 3); + + assertNotCatchingUp(PLAYER_1_ID); + assertRunning(PLAYER_1_ID); + } + + private void loadSpectateScreen(bool waitForPlayerLoad = true, double? gameplayStartTime = null) + { + AddStep(!gameplayStartTime.HasValue ? "load screen" : $"load screen (start = {gameplayStartTime}ms)", () => { Beatmap.Value = beatmapManager.GetWorkingBeatmap(importedBeatmap); Ruleset.Value = importedBeatmap.Ruleset; - LoadScreen(spectatorScreen = new MultiSpectatorScreen(playingUsers.ToArray())); + LoadScreen(spectatorScreen = new TestMultiSpectatorScreen(playingUsers.ToArray(), gameplayStartTime)); }); AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded)); } + private void start(int userId, int? beatmapId = null) => start(new[] { userId }, beatmapId); + private void start(int[] userIds, int? beatmapId = null) { AddStep("start play", () => @@ -419,6 +444,12 @@ namespace osu.Game.Tests.Visual.Multiplayer private void assertMuted(int userId, bool muted) => AddAssert($"{userId} {(muted ? "is" : "is not")} muted", () => getInstance(userId).Mute == muted); + private void assertRunning(int userId) + => AddAssert($"{userId} clock running", () => getInstance(userId).GameplayClock.IsRunning); + + private void assertNotCatchingUp(int userId) + => AddAssert($"{userId} in sync", () => !getInstance(userId).GameplayClock.IsCatchingUp); + private void waitForCatchup(int userId) => AddUntilStep($"{userId} not catching up", () => !getInstance(userId).GameplayClock.IsCatchingUp); @@ -429,5 +460,19 @@ namespace osu.Game.Tests.Visual.Multiplayer private GameplayLeaderboardScore getLeaderboardScore(int userId) => spectatorScreen.ChildrenOfType().Single(s => s.User?.Id == userId); private int[] getPlayerIds(int count) => Enumerable.Range(PLAYER_1_ID, count).ToArray(); + + private class TestMultiSpectatorScreen : MultiSpectatorScreen + { + private readonly double? gameplayStartTime; + + public TestMultiSpectatorScreen(MultiplayerRoomUser[] users, double? gameplayStartTime = null) + : base(users) + { + this.gameplayStartTime = gameplayStartTime; + } + + protected override MasterGameplayClockContainer CreateMasterGameplayClockContainer(WorkingBeatmap beatmap) + => new MasterGameplayClockContainer(beatmap, gameplayStartTime ?? 0, gameplayStartTime.HasValue); + } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 7350408eba..4646f42d63 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Online.Multiplayer; using osu.Game.Online.Spectator; @@ -68,7 +69,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate Container leaderboardContainer; Container scoreDisplayContainer; - masterClockContainer = new MasterGameplayClockContainer(Beatmap.Value, 0); + masterClockContainer = CreateMasterGameplayClockContainer(Beatmap.Value); InternalChildren = new[] { @@ -235,5 +236,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate return base.OnBackButton(); } + + protected virtual MasterGameplayClockContainer CreateMasterGameplayClockContainer(WorkingBeatmap beatmap) => new MasterGameplayClockContainer(beatmap, 0); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 48f153ecbe..f5a6777a62 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -24,6 +24,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public class PlayerArea : CompositeDrawable { + /// + /// Raised after is called on . + /// + public event Action OnGameplayStarted; + /// /// Whether a is loaded in the area. /// @@ -93,7 +98,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } }; - stack.Push(new MultiSpectatorPlayerLoader(Score, () => new MultiSpectatorPlayer(Score, GameplayClock))); + stack.Push(new MultiSpectatorPlayerLoader(Score, () => + { + var player = new MultiSpectatorPlayer(Score, GameplayClock); + player.OnGameplayStarted += OnGameplayStarted; + return player; + })); + loadingLayer.Hide(); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index cfca2d0a3d..0312789b12 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -45,6 +45,11 @@ namespace osu.Game.Screens.Play /// public const double RESULTS_DISPLAY_DELAY = 1000.0; + /// + /// Raised after is called. + /// + public event Action OnGameplayStarted; + public override bool AllowBackButton => false; // handled by HoldForMenuButton protected override UserActivity InitialActivity => new UserActivity.InSoloGame(Beatmap.Value.BeatmapInfo, Ruleset.Value); @@ -958,7 +963,9 @@ namespace osu.Game.Screens.Play updateGameplayState(); GameplayClockContainer.FadeInFromZero(750, Easing.OutQuint); + StartGameplay(); + OnGameplayStarted?.Invoke(); } /// From 3ec193d47e9655de747d6de21b042e36ed5f8211 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Jan 2022 20:17:57 +0300 Subject: [PATCH 0400/5199] Fix spectator clock container incorrectly starting catch-up clock --- .../OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs | 2 ++ osu.Game/Screens/Play/GameplayClockContainer.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs index ececa1e497..615bd41f3f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs @@ -55,6 +55,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public SpectatorGameplayClockContainer([NotNull] IClock sourceClock) : base(sourceClock) { + // the container should initially be in a stopped state until the catch-up clock is started by the sync manager. + Stop(); } protected override void Update() diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 0c9b827a41..0fd524f976 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Play /// /// Stops gameplay. /// - public virtual void Stop() => IsPaused.Value = true; + public void Stop() => IsPaused.Value = true; /// /// Resets this and the source to an initial state ready for gameplay. From c401629dd84e8b91a9389fc85c16cc84f55aa013 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 03:37:57 +0900 Subject: [PATCH 0401/5199] Also refactor `placeholder` logic to make more sense --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 27 +-- osu.Game/Online/Leaderboards/Leaderboard.cs | 155 +++++++++--------- ...olderState.cs => LeaderboardErrorState.cs} | 5 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 15 +- 4 files changed, 86 insertions(+), 116 deletions(-) rename osu.Game/Online/Leaderboards/{PlaceholderState.cs => LeaderboardErrorState.cs} (82%) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 77fe3be933..969bafa98b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -114,12 +114,12 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestPlaceholderStates() { - AddStep(@"Empty Scores", () => leaderboard.SetRetrievalState(PlaceholderState.NoScores)); - AddStep(@"Network failure", () => leaderboard.SetRetrievalState(PlaceholderState.NetworkFailure)); - AddStep(@"No supporter", () => leaderboard.SetRetrievalState(PlaceholderState.NotSupporter)); - AddStep(@"Not logged in", () => leaderboard.SetRetrievalState(PlaceholderState.NotLoggedIn)); - AddStep(@"Unavailable", () => leaderboard.SetRetrievalState(PlaceholderState.Unavailable)); - AddStep(@"None selected", () => leaderboard.SetRetrievalState(PlaceholderState.NoneSelected)); + AddStep(@"Empty Scores", () => leaderboard.SetErrorState(LeaderboardErrorState.NoScores)); + AddStep(@"Network failure", () => leaderboard.SetErrorState(LeaderboardErrorState.NetworkFailure)); + AddStep(@"No supporter", () => leaderboard.SetErrorState(LeaderboardErrorState.NotSupporter)); + AddStep(@"Not logged in", () => leaderboard.SetErrorState(LeaderboardErrorState.NotLoggedIn)); + AddStep(@"Unavailable", () => leaderboard.SetErrorState(LeaderboardErrorState.Unavailable)); + AddStep(@"None selected", () => leaderboard.SetErrorState(LeaderboardErrorState.NoneSelected)); } private void showPersonalBestWithNullPosition() @@ -401,22 +401,9 @@ namespace osu.Game.Tests.Visual.SongSelect }; } - private void showBeatmapWithStatus(BeatmapOnlineStatus status) - { - leaderboard.BeatmapInfo = new BeatmapInfo - { - OnlineID = 1113057, - Status = status, - }; - } - private class FailableLeaderboard : BeatmapLeaderboard { - public void SetRetrievalState(PlaceholderState state) - { - Scores = null; - PlaceholderState = state; - } + public new void SetErrorState(LeaderboardErrorState errorState) => base.SetErrorState(errorState); public new ICollection Scores { diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 8cf20ea8d1..2e552f9774 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -53,6 +53,8 @@ namespace osu.Game.Online.Leaderboards private APIRequest fetchScoresRequest; + private LeaderboardErrorState errorState; + [Resolved(CanBeNull = true)] private IAPIProvider api { get; set; } @@ -169,14 +171,37 @@ namespace osu.Game.Online.Leaderboards RefetchScores(); } + /// + /// Perform a full refetch of scores using current criteria. + /// public void RefetchScores() => Scheduler.AddOnce(refetchScores); + /// + /// Reset the leaderboard into an empty state. + /// protected virtual void Reset() { cancelPendingWork(); Scores = null; } + /// + /// Call when a retrieval or display failure happened to show a relevant message to the user. + /// + /// The state to display. + protected void SetErrorState(LeaderboardErrorState errorState) + { + switch (errorState) + { + case LeaderboardErrorState.NoError: + throw new InvalidOperationException($"State {errorState} cannot be set by a leaderboard implementation."); + } + + Debug.Assert(scores?.Any() != true); + + setErrorState(errorState); + } + /// /// Performs a fetch/refresh of scores to be displayed. /// @@ -195,7 +220,7 @@ namespace osu.Game.Online.Leaderboards Reset(); - PlaceholderState = PlaceholderState.Retrieving; + setErrorState(LeaderboardErrorState.NoError); loading.Show(); currentFetchCancellationSource = new CancellationTokenSource(); @@ -210,7 +235,7 @@ namespace osu.Game.Online.Leaderboards if (e is OperationCanceledException || currentFetchCancellationSource.IsCancellationRequested) return; - PlaceholderState = PlaceholderState.NetworkFailure; + SetErrorState(LeaderboardErrorState.NetworkFailure); }); api?.Queue(fetchScoresRequest); @@ -223,77 +248,6 @@ namespace osu.Game.Online.Leaderboards fetchScoresRequest?.Cancel(); } - #region Placeholder handling - - private Placeholder currentPlaceholder; - - private PlaceholderState placeholderState; - - /// - /// Update the placeholder visibility. - /// Setting this to anything other than PlaceholderState.Successful will cancel all existing retrieval requests and hide scores. - /// - protected PlaceholderState PlaceholderState - { - get => placeholderState; - set - { - if (value == placeholderState) - return; - - placeholderState = value; - - switch (placeholderState) - { - case PlaceholderState.NetworkFailure: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync) - { - Action = RefetchScores - }); - break; - - case PlaceholderState.NoneSelected: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(new MessagePlaceholder(@"Please select a beatmap!")); - break; - - case PlaceholderState.Unavailable: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(new MessagePlaceholder(@"Leaderboards are not available for this beatmap!")); - break; - - case PlaceholderState.NoScores: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(new MessagePlaceholder(@"No records yet!")); - break; - - case PlaceholderState.NotLoggedIn: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(new LoginPlaceholder(@"Please sign in to view online leaderboards!")); - break; - - case PlaceholderState.NotSupporter: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!")); - break; - - case PlaceholderState.Retrieving: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(null); - break; - - case PlaceholderState.Successful: - Debug.Assert(scores?.Any() == true); - replacePlaceholder(null); - break; - - default: - throw new ArgumentOutOfRangeException(); - } - } - } - private void updateScoresDrawables() { currentScoresAsyncLoadCancellationSource?.Cancel(); @@ -305,13 +259,14 @@ namespace osu.Game.Online.Leaderboards if (scores?.Any() != true) { + SetErrorState(LeaderboardErrorState.NoScores); loading.Hide(); - PlaceholderState = PlaceholderState.NoScores; return; } // ensure placeholder is hidden when displaying scores - PlaceholderState = PlaceholderState.Successful; + setErrorState(LeaderboardErrorState.NoError); + loading.Show(); LoadComponentAsync(new FillFlowContainer { @@ -339,25 +294,61 @@ namespace osu.Game.Online.Leaderboards }, (currentScoresAsyncLoadCancellationSource = new CancellationTokenSource()).Token); } - private void replacePlaceholder(Placeholder placeholder) + #region Placeholder handling + + private Placeholder placeholder; + + private void setErrorState(LeaderboardErrorState errorState) { - if (placeholder != null && placeholder.Equals(currentPlaceholder)) + if (errorState == this.errorState) return; - currentPlaceholder?.FadeOut(150, Easing.OutQuint).Expire(); + this.errorState = errorState; + + placeholder?.FadeOut(150, Easing.OutQuint).Expire(); + + placeholder = getPlaceholderFor(errorState); if (placeholder == null) - { - currentPlaceholder = null; return; - } placeholderContainer.Child = placeholder; placeholder.ScaleTo(0.8f).Then().ScaleTo(1, fade_duration * 3, Easing.OutQuint); placeholder.FadeInFromZero(fade_duration, Easing.OutQuint); + } - currentPlaceholder = placeholder; + private Placeholder getPlaceholderFor(LeaderboardErrorState errorState) + { + switch (errorState) + { + case LeaderboardErrorState.NetworkFailure: + return new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync) + { + Action = RefetchScores + }; + + case LeaderboardErrorState.NoneSelected: + return new MessagePlaceholder(@"Please select a beatmap!"); + + case LeaderboardErrorState.Unavailable: + return new MessagePlaceholder(@"Leaderboards are not available for this beatmap!"); + + case LeaderboardErrorState.NoScores: + return new MessagePlaceholder(@"No records yet!"); + + case LeaderboardErrorState.NotLoggedIn: + return new LoginPlaceholder(@"Please sign in to view online leaderboards!"); + + case LeaderboardErrorState.NotSupporter: + return new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!"); + + case LeaderboardErrorState.NoError: + return null; + + default: + throw new ArgumentOutOfRangeException(); + } } #endregion diff --git a/osu.Game/Online/Leaderboards/PlaceholderState.cs b/osu.Game/Online/Leaderboards/LeaderboardErrorState.cs similarity index 82% rename from osu.Game/Online/Leaderboards/PlaceholderState.cs rename to osu.Game/Online/Leaderboards/LeaderboardErrorState.cs index 297241fa73..17f47bb557 100644 --- a/osu.Game/Online/Leaderboards/PlaceholderState.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardErrorState.cs @@ -3,10 +3,9 @@ namespace osu.Game.Online.Leaderboards { - public enum PlaceholderState + public enum LeaderboardErrorState { - Successful, - Retrieving, + NoError, NetworkFailure, Unavailable, NoneSelected, diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 31868624bb..b0ba830076 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -33,19 +33,12 @@ namespace osu.Game.Screens.Select.Leaderboards set { if (beatmapInfo == null && value == null) - { - // always null scores to ensure a correct initial display. - // see weird `scoresLoadedOnce` logic in base implementation. - Scores = null; return; - } if (beatmapInfo?.Equals(value) == true) return; beatmapInfo = value; - Scores = null; - RefetchScores(); } } @@ -114,7 +107,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (fetchBeatmapInfo == null) { - PlaceholderState = PlaceholderState.NoneSelected; + SetErrorState(LeaderboardErrorState.NoneSelected); return null; } @@ -126,19 +119,19 @@ namespace osu.Game.Screens.Select.Leaderboards if (api?.IsLoggedIn != true) { - PlaceholderState = PlaceholderState.NotLoggedIn; + SetErrorState(LeaderboardErrorState.NotLoggedIn); return null; } if (fetchBeatmapInfo.OnlineID <= 0 || fetchBeatmapInfo.Status <= BeatmapOnlineStatus.Pending) { - PlaceholderState = PlaceholderState.Unavailable; + SetErrorState(LeaderboardErrorState.Unavailable); return null; } if (!api.LocalUser.Value.IsSupporter && (Scope != BeatmapLeaderboardScope.Global || filterMods)) { - PlaceholderState = PlaceholderState.NotSupporter; + SetErrorState(LeaderboardErrorState.NotSupporter); return null; } From acc1199addc044c2673516c1738586e16a058a87 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 16:16:00 +0900 Subject: [PATCH 0402/5199] Consolidate flows of `Set` operations, either result or error --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 17 ++--- osu.Game/Online/Leaderboards/Leaderboard.cs | 63 ++++++++----------- .../Match/Components/MatchLeaderboard.cs | 5 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 11 +--- 4 files changed, 37 insertions(+), 59 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 969bafa98b..e9a518b2fd 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestGlobalScoresDisplay() { AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Global); - AddStep(@"New Scores", () => leaderboard.Scores = generateSampleScores(new BeatmapInfo())); + AddStep(@"New Scores", () => leaderboard.SetScores(generateSampleScores(new BeatmapInfo()))); } [Test] @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Visual.SongSelect private void showPersonalBestWithNullPosition() { - leaderboard.TopScore = new ScoreInfo + leaderboard.SetScores(leaderboard.Scores, new ScoreInfo { Rank = ScoreRank.XH, Accuracy = 1, @@ -142,12 +142,12 @@ namespace osu.Game.Tests.Visual.SongSelect FlagName = @"ES", }, }, - }; + }); } private void showPersonalBest() { - leaderboard.TopScore = new ScoreInfo + leaderboard.SetScores(leaderboard.Scores, new ScoreInfo { Position = 999, Rank = ScoreRank.XH, @@ -166,7 +166,7 @@ namespace osu.Game.Tests.Visual.SongSelect FlagName = @"ES", }, }, - }; + }); } private void loadMoreScores(Func beatmapInfo) @@ -404,12 +404,7 @@ namespace osu.Game.Tests.Visual.SongSelect private class FailableLeaderboard : BeatmapLeaderboard { public new void SetErrorState(LeaderboardErrorState errorState) => base.SetErrorState(errorState); - - public new ICollection Scores - { - get => base.Scores; - set => base.Scores = value; - } + public new void SetScores(IEnumerable scores, ScoreInfo userScore = default) => base.SetScores(scores, userScore); } } } diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 2e552f9774..9c46739443 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -33,6 +33,11 @@ namespace osu.Game.Online.Leaderboards /// The score model class. public abstract class Leaderboard : CompositeDrawable { + /// + /// The currently displayed scores. + /// + public IEnumerable Scores => scores; + /// /// Whether the current scope should refetch in response to changes in API connectivity state. /// @@ -42,7 +47,7 @@ namespace osu.Game.Online.Leaderboards private readonly OsuScrollContainer scrollContainer; private readonly Container placeholderContainer; - private readonly UserTopScoreContainer topScoreContainer; + private readonly UserTopScoreContainer userScoreContainer; private FillFlowContainer scoreFlowContainer; @@ -62,30 +67,6 @@ namespace osu.Game.Online.Leaderboards private ICollection scores; - public ICollection Scores - { - get => scores; - protected set - { - scores = value; - Scheduler.Add(updateScoresDrawables, false); - } - } - - public TScoreInfo TopScore - { - get => topScoreContainer.Score.Value; - set - { - topScoreContainer.Score.Value = value; - - if (value == null) - topScoreContainer.Hide(); - else - topScoreContainer.Show(); - } - } - private TScope scope; public TScope Scope @@ -133,7 +114,7 @@ namespace osu.Game.Online.Leaderboards { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, - Child = topScoreContainer = new UserTopScoreContainer(CreateDrawableTopScore) + Child = userScoreContainer = new UserTopScoreContainer(CreateDrawableTopScore) }, }, }, @@ -176,15 +157,6 @@ namespace osu.Game.Online.Leaderboards /// public void RefetchScores() => Scheduler.AddOnce(refetchScores); - /// - /// Reset the leaderboard into an empty state. - /// - protected virtual void Reset() - { - cancelPendingWork(); - Scores = null; - } - /// /// Call when a retrieval or display failure happened to show a relevant message to the user. /// @@ -202,6 +174,24 @@ namespace osu.Game.Online.Leaderboards setErrorState(errorState); } + /// + /// Call when score retrieval is ready to be displayed. + /// + /// The scores to display. + /// The user top score, if any. + protected void SetScores(IEnumerable scores, TScoreInfo userScore = default) + { + this.scores = scores?.ToList(); + userScoreContainer.Score.Value = userScore; + + if (userScore == null) + userScoreContainer.Hide(); + else + userScoreContainer.Show(); + + Scheduler.Add(updateScoresDrawables, false); + } + /// /// Performs a fetch/refresh of scores to be displayed. /// @@ -218,7 +208,8 @@ namespace osu.Game.Online.Leaderboards { Debug.Assert(ThreadSafety.IsUpdateThread); - Reset(); + cancelPendingWork(); + SetScores(null); setErrorState(LeaderboardErrorState.NoError); loading.Show(); diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs index 1945899a11..ea7de917e2 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components if (id.NewValue == null) return; - Scores = null; + SetScores(null); RefetchScores(); }, true); } @@ -43,8 +43,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components if (cancellationToken.IsCancellationRequested) return; - Scores = r.Leaderboard; - TopScore = r.UserScore; + SetScores(r.Leaderboard, r.UserScore); }; return req; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index b0ba830076..3521a2ef78 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -93,12 +93,6 @@ namespace osu.Game.Screens.Select.Leaderboards }; } - protected override void Reset() - { - base.Reset(); - TopScore = null; - } - protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local; protected override APIRequest FetchScores(CancellationToken cancellationToken) @@ -153,8 +147,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (cancellationToken.IsCancellationRequested) return; - Scores = task.GetResultSafely(); - TopScore = r.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo); + SetScores(task.GetResultSafely(), r.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo)); }), TaskContinuationOptions.OnlyOnRanToCompletion); }; @@ -210,7 +203,7 @@ namespace osu.Game.Screens.Select.Leaderboards scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken) .ContinueWith(ordered => { - Scores = ordered.GetResultSafely(); + SetScores(ordered.GetResultSafely()); }, TaskContinuationOptions.OnlyOnRanToCompletion); } } From 04dbb5d3c6cc9c8285fa5d95ac23621761e4a57c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 16:16:33 +0900 Subject: [PATCH 0403/5199] Disallow setting "NoScores" externally as it is handled internally --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 3 ++- osu.Game/Online/Leaderboards/Leaderboard.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index e9a518b2fd..f91339e060 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -114,7 +114,8 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestPlaceholderStates() { - AddStep(@"Empty Scores", () => leaderboard.SetErrorState(LeaderboardErrorState.NoScores)); + AddStep("ensure no scores displayed", () => leaderboard.SetScores(null)); + AddStep(@"Network failure", () => leaderboard.SetErrorState(LeaderboardErrorState.NetworkFailure)); AddStep(@"No supporter", () => leaderboard.SetErrorState(LeaderboardErrorState.NotSupporter)); AddStep(@"Not logged in", () => leaderboard.SetErrorState(LeaderboardErrorState.NotLoggedIn)); diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 9c46739443..9fb0fe0e3b 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -165,6 +165,7 @@ namespace osu.Game.Online.Leaderboards { switch (errorState) { + case LeaderboardErrorState.NoScores: case LeaderboardErrorState.NoError: throw new InvalidOperationException($"State {errorState} cannot be set by a leaderboard implementation."); } @@ -250,7 +251,7 @@ namespace osu.Game.Online.Leaderboards if (scores?.Any() != true) { - SetErrorState(LeaderboardErrorState.NoScores); + setErrorState(LeaderboardErrorState.NoScores); loading.Hide(); return; } From 1dbcb5ab63a7d5656c40f370213148011d3c8fbb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 18:51:15 +0900 Subject: [PATCH 0404/5199] Add test coverage of intro fail scenario --- osu.Game.Tests/Visual/Menus/IntroTestScene.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs index bfea97410a..40b072c171 100644 --- a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs +++ b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs @@ -5,6 +5,8 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Threading; +using osu.Game.Overlays; using osu.Game.Screens; using osu.Game.Screens.Menu; using osuTK; @@ -22,6 +24,11 @@ namespace osu.Game.Tests.Visual.Menus private IntroScreen intro; + [Cached] + private NotificationOverlay notifications; + + private ScheduledDelegate trackResetDelegate; + protected IntroTestScene() { Children = new Drawable[] @@ -38,6 +45,11 @@ namespace osu.Game.Tests.Visual.Menus RelativePositionAxes = Axes.Both, Depth = float.MinValue, Position = new Vector2(0.5f), + }, + notifications = new NotificationOverlay + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, } }; } @@ -63,6 +75,39 @@ namespace osu.Game.Tests.Visual.Menus AddUntilStep("wait for menu", () => intro.DidLoadMenu); } + [Test] + public virtual void TestPlayIntroWithFailingAudioDevice() + { + AddStep("hide notifications", () => notifications.Hide()); + AddStep("restart sequence", () => + { + logo.FinishTransforms(); + logo.IsTracking = false; + + IntroStack?.Expire(); + + Add(IntroStack = new OsuScreenStack + { + RelativeSizeAxes = Axes.Both, + }); + + IntroStack.Push(intro = CreateScreen()); + }); + + AddStep("trigger failure", () => + { + trackResetDelegate = Scheduler.AddDelayed(() => + { + intro.Beatmap.Value.Track.Seek(0); + }, 0, true); + }); + + AddUntilStep("wait for menu", () => intro.DidLoadMenu); + AddUntilStep("wait for notification", () => notifications.UnreadCount.Value == 1); + + AddStep("uninstall delegate", () => trackResetDelegate?.Cancel()); + } + protected abstract IntroScreen CreateScreen(); } } From 52f1c2bfdbc349259bc817ac3e0a4925f9c78a8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 18:37:00 +0900 Subject: [PATCH 0405/5199] Add failsafe to `IntroScreen` to stop users with incorrect audio configuration getting stuck The most common case of this seems to be linux users with incorrect or unsupported audio driver configurations. It continues to be brought up in discussions as people are unsure of why their game freezes on startup, and unable to easily recover. --- osu.Game/Screens/Menu/IntroScreen.cs | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index fceb083916..98c4b15f7f 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -18,6 +18,7 @@ using osu.Game.Configuration; using osu.Game.Database; using osu.Game.IO.Archives; using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; using osu.Game.Screens.Backgrounds; using osuTK; using osuTK.Graphics; @@ -147,6 +148,36 @@ namespace osu.Game.Screens.Menu } } + public override void OnEntering(IScreen last) + { + base.OnEntering(last); + ensureEventuallyArrivingAtMenu(); + } + + [Resolved] + private NotificationOverlay notifications { get; set; } + + private void ensureEventuallyArrivingAtMenu() + { + // This intends to handle the case where an intro may get stuck. + // Historically, this could happen if the host system's audio device is in a state it can't + // play audio, causing a clock to never elapse time and the intro to never end. + // + // This safety measure gives the user a chance to fix the problem from the settings menu. + Scheduler.AddDelayed(() => + { + if (DidLoadMenu) + return; + + PrepareMenuLoad(); + LoadMenu(); + notifications.Post(new SimpleErrorNotification + { + Text = "osu! doesn't seem to be able to play audio correctly.\n\nPlease try changing your audio device to a working setting." + }); + }, 5000); + } + public override void OnResuming(IScreen last) { this.FadeIn(300); @@ -241,6 +272,9 @@ namespace osu.Game.Screens.Menu protected void PrepareMenuLoad() { + if (nextScreen != null) + return; + nextScreen = createNextScreen?.Invoke(); if (nextScreen != null) @@ -249,6 +283,9 @@ namespace osu.Game.Screens.Menu protected void LoadMenu() { + if (DidLoadMenu) + return; + beatmap.Return(); DidLoadMenu = true; From 6a21d583259b43c0d7865371f34c73d5cba04e04 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 18:59:18 +0900 Subject: [PATCH 0406/5199] Avoid test failures on non-triangle intro tests --- osu.Game.Tests/Visual/Menus/IntroTestScene.cs | 6 +++++- osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs | 1 + osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs | 1 + osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs | 1 + 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs index 40b072c171..82accceb23 100644 --- a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs +++ b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs @@ -20,6 +20,8 @@ namespace osu.Game.Tests.Visual.Menus [Cached] private OsuLogo logo; + protected abstract bool IntroReliesOnTrack { get; } + protected OsuScreenStack IntroStack; private IntroScreen intro; @@ -103,7 +105,9 @@ namespace osu.Game.Tests.Visual.Menus }); AddUntilStep("wait for menu", () => intro.DidLoadMenu); - AddUntilStep("wait for notification", () => notifications.UnreadCount.Value == 1); + + if (IntroReliesOnTrack) + AddUntilStep("wait for notification", () => notifications.UnreadCount.Value == 1); AddStep("uninstall delegate", () => trackResetDelegate?.Cancel()); } diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs index ffc99185fb..7ad49b5dcd 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs @@ -9,6 +9,7 @@ namespace osu.Game.Tests.Visual.Menus [TestFixture] public class TestSceneIntroCircles : IntroTestScene { + protected override bool IntroReliesOnTrack => false; protected override IntroScreen CreateScreen() => new IntroCircles(); } } diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs index 8f01e0321b..abe8936330 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs @@ -9,6 +9,7 @@ namespace osu.Game.Tests.Visual.Menus [TestFixture] public class TestSceneIntroTriangles : IntroTestScene { + protected override bool IntroReliesOnTrack => true; protected override IntroScreen CreateScreen() => new IntroTriangles(); } } diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs index 9081be3dd6..11cea25865 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs @@ -10,6 +10,7 @@ namespace osu.Game.Tests.Visual.Menus [TestFixture] public class TestSceneIntroWelcome : IntroTestScene { + protected override bool IntroReliesOnTrack => false; protected override IntroScreen CreateScreen() => new IntroWelcome(); public override void TestPlayIntro() From 82806d7aebc5a0a2273575f4efdbbd6d1c7c866f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 21:32:39 +0900 Subject: [PATCH 0407/5199] Ensure the background is eventually displayed when `IntroTriangles` suspends --- osu.Game/Screens/Menu/IntroTriangles.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 10f940e9de..b6b6bf2ad7 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -93,6 +93,9 @@ namespace osu.Game.Screens.Menu { base.OnSuspending(next); + // ensure the background is shown, even if the TriangleIntroSequence failed to do so. + background.ApplyToBackground(b => b.Show()); + // important as there is a clock attached to a track which will likely be disposed before returning to this screen. intro.Expire(); } From 1cec76df74df950db682ecde9de14ceeb9c1de8a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 23:18:40 +0900 Subject: [PATCH 0408/5199] Fix weird reading xmldoc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Online/Leaderboards/Leaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 9fb0fe0e3b..e96b12bc5d 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -176,7 +176,7 @@ namespace osu.Game.Online.Leaderboards } /// - /// Call when score retrieval is ready to be displayed. + /// Call when retrieved scores are ready to be displayed. /// /// The scores to display. /// The user top score, if any. From f8939af5e6d165d97ad733d0b39634ef6c5b1723 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 01:12:03 +0900 Subject: [PATCH 0409/5199] Track loading via state as well --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 12 ++-- osu.Game/Online/Leaderboards/Leaderboard.cs | 66 ++++++++++--------- ...boardErrorState.cs => LeaderboardState.cs} | 5 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 8 +-- 4 files changed, 48 insertions(+), 43 deletions(-) rename osu.Game/Online/Leaderboards/{LeaderboardErrorState.cs => LeaderboardState.cs} (82%) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index f91339e060..48230ff9e9 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -116,11 +116,11 @@ namespace osu.Game.Tests.Visual.SongSelect { AddStep("ensure no scores displayed", () => leaderboard.SetScores(null)); - AddStep(@"Network failure", () => leaderboard.SetErrorState(LeaderboardErrorState.NetworkFailure)); - AddStep(@"No supporter", () => leaderboard.SetErrorState(LeaderboardErrorState.NotSupporter)); - AddStep(@"Not logged in", () => leaderboard.SetErrorState(LeaderboardErrorState.NotLoggedIn)); - AddStep(@"Unavailable", () => leaderboard.SetErrorState(LeaderboardErrorState.Unavailable)); - AddStep(@"None selected", () => leaderboard.SetErrorState(LeaderboardErrorState.NoneSelected)); + AddStep(@"Network failure", () => leaderboard.SetErrorState(LeaderboardState.NetworkFailure)); + AddStep(@"No supporter", () => leaderboard.SetErrorState(LeaderboardState.NotSupporter)); + AddStep(@"Not logged in", () => leaderboard.SetErrorState(LeaderboardState.NotLoggedIn)); + AddStep(@"Unavailable", () => leaderboard.SetErrorState(LeaderboardState.Unavailable)); + AddStep(@"None selected", () => leaderboard.SetErrorState(LeaderboardState.NoneSelected)); } private void showPersonalBestWithNullPosition() @@ -404,7 +404,7 @@ namespace osu.Game.Tests.Visual.SongSelect private class FailableLeaderboard : BeatmapLeaderboard { - public new void SetErrorState(LeaderboardErrorState errorState) => base.SetErrorState(errorState); + public new void SetErrorState(LeaderboardState state) => base.SetErrorState(state); public new void SetScores(IEnumerable scores, ScoreInfo userScore = default) => base.SetScores(scores, userScore); } } diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index e96b12bc5d..7ac05fb0c0 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -58,7 +58,7 @@ namespace osu.Game.Online.Leaderboards private APIRequest fetchScoresRequest; - private LeaderboardErrorState errorState; + private LeaderboardState state; [Resolved(CanBeNull = true)] private IAPIProvider api { get; set; } @@ -160,19 +160,20 @@ namespace osu.Game.Online.Leaderboards /// /// Call when a retrieval or display failure happened to show a relevant message to the user. /// - /// The state to display. - protected void SetErrorState(LeaderboardErrorState errorState) + /// The state to display. + protected void SetErrorState(LeaderboardState state) { - switch (errorState) + switch (state) { - case LeaderboardErrorState.NoScores: - case LeaderboardErrorState.NoError: - throw new InvalidOperationException($"State {errorState} cannot be set by a leaderboard implementation."); + case LeaderboardState.NoScores: + case LeaderboardState.Retrieving: + case LeaderboardState.Success: + throw new InvalidOperationException($"State {state} cannot be set by a leaderboard implementation."); } Debug.Assert(scores?.Any() != true); - setErrorState(errorState); + setState(state); } /// @@ -212,8 +213,7 @@ namespace osu.Game.Online.Leaderboards cancelPendingWork(); SetScores(null); - setErrorState(LeaderboardErrorState.NoError); - loading.Show(); + setState(LeaderboardState.Retrieving); currentFetchCancellationSource = new CancellationTokenSource(); @@ -227,7 +227,7 @@ namespace osu.Game.Online.Leaderboards if (e is OperationCanceledException || currentFetchCancellationSource.IsCancellationRequested) return; - SetErrorState(LeaderboardErrorState.NetworkFailure); + SetErrorState(LeaderboardState.NetworkFailure); }); api?.Queue(fetchScoresRequest); @@ -251,15 +251,10 @@ namespace osu.Game.Online.Leaderboards if (scores?.Any() != true) { - setErrorState(LeaderboardErrorState.NoScores); - loading.Hide(); + setState(LeaderboardState.NoScores); return; } - // ensure placeholder is hidden when displaying scores - setErrorState(LeaderboardErrorState.NoError); - loading.Show(); - LoadComponentAsync(new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -269,6 +264,8 @@ namespace osu.Game.Online.Leaderboards ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)) }, newFlow => { + setState(LeaderboardState.Success); + scrollContainer.Add(scoreFlowContainer = newFlow); double delay = 0; @@ -282,7 +279,6 @@ namespace osu.Game.Online.Leaderboards } scrollContainer.ScrollToStart(false); - loading.Hide(); }, (currentScoresAsyncLoadCancellationSource = new CancellationTokenSource()).Token); } @@ -290,16 +286,21 @@ namespace osu.Game.Online.Leaderboards private Placeholder placeholder; - private void setErrorState(LeaderboardErrorState errorState) + private void setState(LeaderboardState state) { - if (errorState == this.errorState) + if (state == this.state) return; - this.errorState = errorState; + if (state == LeaderboardState.Retrieving) + loading.Show(); + else + loading.Hide(); + + this.state = state; placeholder?.FadeOut(150, Easing.OutQuint).Expire(); - placeholder = getPlaceholderFor(errorState); + placeholder = getPlaceholderFor(state); if (placeholder == null) return; @@ -310,32 +311,35 @@ namespace osu.Game.Online.Leaderboards placeholder.FadeInFromZero(fade_duration, Easing.OutQuint); } - private Placeholder getPlaceholderFor(LeaderboardErrorState errorState) + private Placeholder getPlaceholderFor(LeaderboardState state) { - switch (errorState) + switch (state) { - case LeaderboardErrorState.NetworkFailure: + case LeaderboardState.NetworkFailure: return new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync) { Action = RefetchScores }; - case LeaderboardErrorState.NoneSelected: + case LeaderboardState.NoneSelected: return new MessagePlaceholder(@"Please select a beatmap!"); - case LeaderboardErrorState.Unavailable: + case LeaderboardState.Unavailable: return new MessagePlaceholder(@"Leaderboards are not available for this beatmap!"); - case LeaderboardErrorState.NoScores: + case LeaderboardState.NoScores: return new MessagePlaceholder(@"No records yet!"); - case LeaderboardErrorState.NotLoggedIn: + case LeaderboardState.NotLoggedIn: return new LoginPlaceholder(@"Please sign in to view online leaderboards!"); - case LeaderboardErrorState.NotSupporter: + case LeaderboardState.NotSupporter: return new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!"); - case LeaderboardErrorState.NoError: + case LeaderboardState.Retrieving: + return null; + + case LeaderboardState.Success: return null; default: diff --git a/osu.Game/Online/Leaderboards/LeaderboardErrorState.cs b/osu.Game/Online/Leaderboards/LeaderboardState.cs similarity index 82% rename from osu.Game/Online/Leaderboards/LeaderboardErrorState.cs rename to osu.Game/Online/Leaderboards/LeaderboardState.cs index 17f47bb557..75e2c6e6db 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardErrorState.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardState.cs @@ -3,9 +3,10 @@ namespace osu.Game.Online.Leaderboards { - public enum LeaderboardErrorState + public enum LeaderboardState { - NoError, + Success, + Retrieving, NetworkFailure, Unavailable, NoneSelected, diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 3521a2ef78..48eb33cc05 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (fetchBeatmapInfo == null) { - SetErrorState(LeaderboardErrorState.NoneSelected); + SetErrorState(LeaderboardState.NoneSelected); return null; } @@ -113,19 +113,19 @@ namespace osu.Game.Screens.Select.Leaderboards if (api?.IsLoggedIn != true) { - SetErrorState(LeaderboardErrorState.NotLoggedIn); + SetErrorState(LeaderboardState.NotLoggedIn); return null; } if (fetchBeatmapInfo.OnlineID <= 0 || fetchBeatmapInfo.Status <= BeatmapOnlineStatus.Pending) { - SetErrorState(LeaderboardErrorState.Unavailable); + SetErrorState(LeaderboardState.Unavailable); return null; } if (!api.LocalUser.Value.IsSupporter && (Scope != BeatmapLeaderboardScope.Global || filterMods)) { - SetErrorState(LeaderboardErrorState.NotSupporter); + SetErrorState(LeaderboardState.NotSupporter); return null; } From b52153e73d354f137b221d5c14cd505d37a8ffdd Mon Sep 17 00:00:00 2001 From: Spooghetti420 Date: Sun, 30 Jan 2022 17:40:15 +0000 Subject: [PATCH 0410/5199] Remove settings --- .../Mods/TestSceneManiaModHoldOff.cs | 58 +++---------------- .../Mods/ManiaModHoldOff.cs | 28 +-------- 2 files changed, 10 insertions(+), 76 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs index e74f63abb3..a113b7ac80 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.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.Linq; using NUnit.Framework; using osu.Game.Beatmaps; @@ -11,8 +10,6 @@ using System.Collections.Generic; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.Difficulty; -using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Mania.Tests.Mods { @@ -42,24 +39,17 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Assert.AreEqual(noteValues, new List { 0.125, 0.250, 0.500, 1.000, 2.000 }); } - [TestCase(ManiaModHoldOff.BeatDivisors.Whole)] - [TestCase(ManiaModHoldOff.BeatDivisors.Half)] - [TestCase(ManiaModHoldOff.BeatDivisors.Quarter)] - [TestCase(ManiaModHoldOff.BeatDivisors.Eighth)] - public void TestCorrectObjectCount(ManiaModHoldOff.BeatDivisors minBeatSnap) + [Test] + public void TestCorrectObjectCount() { - /* - This test is to ensure that, given that end notes are enabled, - the mod produces the expected number of objects when the mod is applied. - */ + // Ensure that the mod produces the expected number of objects when applied. - // Mod settings will be set to include the correct beat snap value var rawBeatmap = createRawBeatmap(); - var testBeatmap = createModdedBeatmap(minBeatSnap); + var testBeatmap = createModdedBeatmap(); // Calculate expected number of objects int expectedObjectCount = 0; - double beatSnapValue = 1 / (Math.Pow(2, (int)minBeatSnap)); + double beatSnapValue = ManiaModHoldOff.Threshold; foreach (ManiaHitObject h in rawBeatmap.HitObjects) { @@ -81,44 +71,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Assert.That(testBeatmap.HitObjects.Count == expectedObjectCount); } - [Test] - public void TestDifficultyIncrease() - { - // A lower minimum beat snap divisor should only make the map harder, never easier - // (as more notes can be spawned) - var beatmaps = new[] - { - createModdedBeatmap(), - createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Half), - createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Quarter), - createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Eighth), - createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Sixteenth) - }; - - double[] mapDifficulties = new double[beatmaps.Length]; - - for (int i = 0; i < mapDifficulties.Length; i++) - { - var workingBeatmap = new TestWorkingBeatmap(beatmaps[i]); - var difficultyCalculator = new ManiaDifficultyCalculator(new ManiaRuleset().RulesetInfo, workingBeatmap); - mapDifficulties[i] = difficultyCalculator.Calculate().StarRating; - - if (i > 0) - { - Assert.LessOrEqual(mapDifficulties[i - 1], mapDifficulties[i]); - Assert.LessOrEqual(beatmaps[i - 1].HitObjects.Count, beatmaps[i].HitObjects.Count); - } - } - } - - private static ManiaBeatmap createModdedBeatmap(ManiaModHoldOff.BeatDivisors minBeatSnap = ManiaModHoldOff.BeatDivisors.Whole) + private static ManiaBeatmap createModdedBeatmap() { var beatmap = createRawBeatmap(); - var holdOffMod = new ManiaModHoldOff - { - MinBeatSnap = { Value = minBeatSnap } - }; - Assert.AreEqual(holdOffMod.MinBeatSnap.Value, minBeatSnap); + var holdOffMod = new ManiaModHoldOff(); foreach (var hitObject in beatmap.HitObjects) hitObject.ApplyDefaults(beatmap.ControlPointInfo, new BeatmapDifficulty()); diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index 637142aed6..5a47e1ba89 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -6,11 +6,8 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; using osu.Framework.Graphics.Sprites; -using System; using System.Collections.Generic; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Framework.Bindables; -using osu.Game.Configuration; namespace osu.Game.Rulesets.Mania.Mods { @@ -27,23 +24,13 @@ namespace osu.Game.Rulesets.Mania.Mods public override IconUsage? Icon => FontAwesome.Solid.DotCircle; public override ModType Type => ModType.Conversion; - - [SettingSource("Add end notes", "Also add a note at the end of a hold note")] - public BindableBool AddEndNotes { get; } = new BindableBool - { - Default = true, - Value = true - }; - - [SettingSource("Minimum end note beat snap", "Don't add end notes for hold notes shorter than this beat division")] - public Bindable MinBeatSnap { get; } = new Bindable(defaultValue: BeatDivisors.Half); + public const double Threshold = 1/2; public void ApplyToBeatmap(IBeatmap beatmap) { var maniaBeatmap = (ManiaBeatmap)beatmap; var newObjects = new List(); - double beatSnap = 1 / (Math.Pow(2, (double)MinBeatSnap.Value)); foreach (var h in beatmap.HitObjects.OfType()) { @@ -55,10 +42,10 @@ namespace osu.Game.Rulesets.Mania.Mods Samples = h.GetNodeSamples(0) }); - // Don't add an end note if the duration is shorter than some threshold, or end notes are disabled + // Don't add an end note if the duration is shorter than the threshold double noteValue = GetNoteDurationInBeatLength(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc. - if (AddEndNotes.Value && noteValue >= beatSnap) + if (noteValue >= Threshold) { newObjects.Add(new Note { @@ -77,14 +64,5 @@ namespace osu.Game.Rulesets.Mania.Mods double bpmAtNoteTime = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BPM; return (60 * holdNote.Duration) / (1000 * bpmAtNoteTime); } - - public enum BeatDivisors - { - Whole, - Half, - Quarter, - Eighth, - Sixteenth - } } } From 2b999f9780f4f9e96c92fbe42e4a71a86605b0ec Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Jan 2022 23:39:45 +0300 Subject: [PATCH 0411/5199] Add failing test case --- .../TestSceneMasterGameplayClockContainer.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs index 935bc07733..172531605a 100644 --- a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs @@ -2,7 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Framework.Utils; +using osu.Game.Configuration; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; using osu.Game.Tests.Visual; @@ -12,6 +18,14 @@ namespace osu.Game.Tests.Gameplay [HeadlessTest] public class TestSceneMasterGameplayClockContainer : OsuTestScene { + private OsuConfigManager localConfig; + + [BackgroundDependencyLoader] + private void load() + { + Dependencies.Cache(localConfig = new OsuConfigManager(LocalStorage)); + } + [Test] public void TestStartThenElapsedTime() { @@ -54,5 +68,43 @@ namespace osu.Game.Tests.Gameplay AddAssert("current time < time at reset", () => gcc.GameplayClock.CurrentTime < timeAtReset); } + + [Test] + public void TestSeekPerformsInGameplayTime( + [Values(1.0, 0.5, 2.0)] double clockRate, + [Values(0.0, 200.0, -200.0)] double userOffset, + [Values(false, true)] bool whileStopped) + { + ClockBackedTestWorkingBeatmap working = null; + GameplayClockContainer gcc = null; + + AddStep("create container", () => + { + working = new ClockBackedTestWorkingBeatmap(new OsuRuleset().RulesetInfo, new FramedClock(new ManualClock()), Audio); + working.LoadTrack(); + + Add(gcc = new MasterGameplayClockContainer(working, 0)); + + if (whileStopped) + gcc.Stop(); + + gcc.Reset(); + }); + + AddStep($"set clock rate to {clockRate}", () => working.Track.AddAdjustment(AdjustableProperty.Frequency, new BindableDouble(clockRate))); + AddStep($"set audio offset to {userOffset}", () => localConfig.SetValue(OsuSetting.AudioOffset, userOffset)); + + AddStep("seek to 2500", () => gcc.Seek(2500)); + AddAssert("gameplay clock time = 2500", () => Precision.AlmostEquals(gcc.CurrentTime, 2500, 10f)); + + AddStep("seek to 10000", () => gcc.Seek(10000)); + AddAssert("gameplay clock time = 10000", () => Precision.AlmostEquals(gcc.CurrentTime, 10000, 10f)); + } + + protected override void Dispose(bool isDisposing) + { + localConfig?.Dispose(); + base.Dispose(isDisposing); + } } } From 6556a7e3c3533b46a1f6b97989d2d6bf4f908a4a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Jan 2022 23:06:31 +0300 Subject: [PATCH 0412/5199] Handle different gameplay rates when seeking on master clock --- .../Play/MasterGameplayClockContainer.cs | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index aa46522dec..200921680e 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Play Precision = 0.1, }; - private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset; + private double totalAppliedOffset => userOffsetClock.RateAdjustedOffset + platformOffsetClock.RateAdjustedOffset; private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1); @@ -52,8 +52,8 @@ namespace osu.Game.Screens.Play private readonly bool startAtGameplayStart; private readonly double firstHitObjectTime; - private FramedOffsetClock userOffsetClock; - private FramedOffsetClock platformOffsetClock; + private HardwareCorrectionOffsetClock userOffsetClock; + private HardwareCorrectionOffsetClock platformOffsetClock; private MasterGameplayClock masterGameplayClock; private Bindable userAudioOffset; private double startOffset; @@ -128,7 +128,7 @@ namespace osu.Game.Screens.Play { // remove the offset component here because most of the time we want the seek to be aligned to gameplay, not the audio track. // we may want to consider reversing the application of offsets in the future as it may feel more correct. - base.Seek(time - totalOffset); + base.Seek(time - totalAppliedOffset); } /// @@ -214,13 +214,25 @@ namespace osu.Game.Screens.Play private class HardwareCorrectionOffsetClock : FramedOffsetClock { - // we always want to apply the same real-time offset, so it should be adjusted by the difference in playback rate (from realtime) to achieve this. - // base implementation already adds offset at 1.0 rate, so we only add the difference from that here. - public override double CurrentTime => base.CurrentTime + offsetAdjust; - private readonly BindableDouble pauseRateAdjust; - private double offsetAdjust; + private double offset; + + public new double Offset + { + get => offset; + set + { + if (value == offset) + return; + + offset = value; + + updateOffset(); + } + } + + public double RateAdjustedOffset => base.Offset; public HardwareCorrectionOffsetClock(IClock source, BindableDouble pauseRateAdjust) : base(source) @@ -231,10 +243,17 @@ namespace osu.Game.Screens.Play public override void ProcessFrame() { base.ProcessFrame(); + updateOffset(); + } + private void updateOffset() + { // changing this during the pause transform effect will cause a potentially large offset to be suddenly applied as we approach zero rate. if (pauseRateAdjust.Value == 1) - offsetAdjust = Offset * (Rate - 1); + { + // we always want to apply the same real-time offset, so it should be adjusted by the difference in playback rate (from realtime) to achieve this. + base.Offset = Offset * Rate; + } } } From bb8dc74e88fb3fcc3ce6958d740199a9249bc11b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 12:20:51 +0900 Subject: [PATCH 0413/5199] Fix constant formatting --- .../Mods/TestSceneManiaModHoldOff.cs | 3 +-- osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs index a113b7ac80..7970d5b594 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs @@ -49,7 +49,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods // Calculate expected number of objects int expectedObjectCount = 0; - double beatSnapValue = ManiaModHoldOff.Threshold; foreach (ManiaHitObject h in rawBeatmap.HitObjects) { @@ -60,7 +59,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods { double noteValue = ManiaModHoldOff.GetNoteDurationInBeatLength((HoldNote)h, rawBeatmap); - if (noteValue >= beatSnapValue) + if (noteValue >= ManiaModHoldOff.END_NOTE_ALLOW_THRESHOLD) { // Should generate an end note if it's longer than the minimum note value expectedObjectCount++; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index 5a47e1ba89..42dc7fdc1f 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -24,7 +24,8 @@ namespace osu.Game.Rulesets.Mania.Mods public override IconUsage? Icon => FontAwesome.Solid.DotCircle; public override ModType Type => ModType.Conversion; - public const double Threshold = 1/2; + + public const double END_NOTE_ALLOW_THRESHOLD = 0.5; public void ApplyToBeatmap(IBeatmap beatmap) { @@ -45,7 +46,7 @@ namespace osu.Game.Rulesets.Mania.Mods // Don't add an end note if the duration is shorter than the threshold double noteValue = GetNoteDurationInBeatLength(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc. - if (noteValue >= Threshold) + if (noteValue >= END_NOTE_ALLOW_THRESHOLD) { newObjects.Add(new Note { From 610eb9f6a45e8f3f762bac4f25a814c7169d8010 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 13:45:49 +0900 Subject: [PATCH 0414/5199] Remove unnecessary container level --- osu.Game/Online/Leaderboards/Leaderboard.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 7ac05fb0c0..5dd3e46b4a 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -110,12 +110,7 @@ namespace osu.Game.Online.Leaderboards }, new Drawable[] { - new Container - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Child = userScoreContainer = new UserTopScoreContainer(CreateDrawableTopScore) - }, + userScoreContainer = new UserTopScoreContainer(CreateDrawableTopScore) }, }, }, From 9c9fda84f3cf54073c7d15b62946251a3a27d328 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 13:50:53 +0900 Subject: [PATCH 0415/5199] Add schedule and cancellation check to score ordering step --- osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 48eb33cc05..907a2c9bda 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -201,10 +201,13 @@ namespace osu.Game.Screens.Select.Leaderboards scores = scores.Detach(); scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken) - .ContinueWith(ordered => + .ContinueWith(ordered => Schedule(() => { + if (cancellationToken.IsCancellationRequested) + return; + SetScores(ordered.GetResultSafely()); - }, TaskContinuationOptions.OnlyOnRanToCompletion); + }), TaskContinuationOptions.OnlyOnRanToCompletion); } } From a06287e76aac73bbf141d07a71fa417c7dbb31d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 12:34:08 +0900 Subject: [PATCH 0416/5199] Remove `DrawableCarouselItem.Update` updating of height Marginal from a performance aspect, but reads better. --- .../Select/Carousel/DrawableCarouselBeatmap.cs | 7 +++---- .../Select/Carousel/DrawableCarouselItem.cs | 14 ++++++-------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 3576b77ae8..43ddfc79b1 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -36,9 +36,9 @@ namespace osu.Game.Screens.Select.Carousel /// /// The height of a carousel beatmap, including vertical spacing. /// - public const float HEIGHT = height + CAROUSEL_BEATMAP_SPACING; + public const float HEIGHT = header_height + CAROUSEL_BEATMAP_SPACING; - private const float height = MAX_HEIGHT * 0.6f; + private const float header_height = MAX_HEIGHT * 0.6f; private readonly BeatmapInfo beatmapInfo; @@ -67,6 +67,7 @@ namespace osu.Game.Screens.Select.Carousel private CancellationTokenSource starDifficultyCancellationSource; public DrawableCarouselBeatmap(CarouselBeatmap panel) + : base(header_height) { beatmapInfo = panel.BeatmapInfo; Item = panel; @@ -75,8 +76,6 @@ namespace osu.Game.Screens.Select.Carousel [BackgroundDependencyLoader(true)] private void load(BeatmapManager manager, SongSelect songSelect) { - Header.Height = height; - if (songSelect != null) { startRequested = b => songSelect.FinaliseSelection(b); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index cde3edad39..8b38bb9a96 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -60,7 +60,7 @@ namespace osu.Game.Screens.Select.Carousel } } - protected DrawableCarouselItem() + protected DrawableCarouselItem(float headerHeight = MAX_HEIGHT) { RelativeSizeAxes = Axes.X; @@ -73,10 +73,14 @@ namespace osu.Game.Screens.Select.Carousel RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - Header = new CarouselHeader(), + Header = new CarouselHeader + { + Height = headerHeight, + }, Content = new Container { RelativeSizeAxes = Axes.Both, + Y = headerHeight, } } }, @@ -92,12 +96,6 @@ namespace osu.Game.Screens.Select.Carousel UpdateItem(); } - protected override void Update() - { - base.Update(); - Content.Y = Header.Height; - } - protected virtual void UpdateItem() { if (item == null) From c3e3b2019dcf169d74c435bde269ec53bf43a8fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 13:01:41 +0900 Subject: [PATCH 0417/5199] Reduce overhead of `ApplyState` by tracking previous values Even with pooling applied, there are overheads involved with transforms when quickly cycling through the carousel. The main goal here is to reduce the transforms in cases the reuse is still in the same state. Avoiding firing `FadeIn` and `FadeOut` are the main areas of saving. --- .../Carousel/DrawableCarouselBeatmap.cs | 3 + .../Select/Carousel/DrawableCarouselItem.cs | 55 ++++++++++++++----- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 43ddfc79b1..a3483aa60a 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -71,6 +71,9 @@ namespace osu.Game.Screens.Select.Carousel { beatmapInfo = panel.BeatmapInfo; Item = panel; + + // Difficulty panels should start hidden for a better initial effect. + Hide(); } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 8b38bb9a96..4a23faa650 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -64,8 +64,6 @@ namespace osu.Game.Screens.Select.Carousel { RelativeSizeAxes = Axes.X; - Alpha = 0; - InternalChildren = new Drawable[] { MovementContainer = new Container @@ -119,29 +117,56 @@ namespace osu.Game.Screens.Select.Carousel private void onStateChange(ValueChangedEvent _) => Scheduler.AddOnce(ApplyState); + private CarouselItemState? lastAppliedState; + protected virtual void ApplyState() { - // Use the fact that we know the precise height of the item from the model to avoid the need for AutoSize overhead. - // Additionally, AutoSize doesn't work well due to content starting off-screen and being masked away. - Height = Item.TotalHeight; - Debug.Assert(Item != null); - switch (Item.State.Value) + if (lastAppliedState != Item.State.Value) { - case CarouselItemState.NotSelected: - Deselected(); - break; + lastAppliedState = Item.State.Value; - case CarouselItemState.Selected: - Selected(); - break; + // Use the fact that we know the precise height of the item from the model to avoid the need for AutoSize overhead. + // Additionally, AutoSize doesn't work well due to content starting off-screen and being masked away. + Height = Item.TotalHeight; + + switch (lastAppliedState) + { + case CarouselItemState.NotSelected: + Deselected(); + break; + + case CarouselItemState.Selected: + Selected(); + break; + } } if (!Item.Visible) - this.FadeOut(300, Easing.OutQuint); + Hide(); else - this.FadeIn(250); + Show(); + } + + private bool isVisible = true; + + public override void Show() + { + if (isVisible) + return; + + isVisible = true; + this.FadeIn(250); + } + + public override void Hide() + { + if (!isVisible) + return; + + isVisible = false; + this.FadeOut(300, Easing.OutQuint); } protected virtual void Selected() From 2ee0db0ebf68bc6651171c78e077418e952d71dd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 14:07:29 +0900 Subject: [PATCH 0418/5199] Move fade in function local --- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 618c5cf5ec..4f19e011b2 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -124,9 +124,9 @@ namespace osu.Game.Screens.Select.Carousel background.DelayedLoadComplete += fadeContentIn; mainFlow.DelayedLoadComplete += fadeContentIn; - } - private void fadeContentIn(Drawable d) => d.FadeInFromZero(750, Easing.OutQuint); + static void fadeContentIn(Drawable d) => d.FadeInFromZero(750, Easing.OutQuint); + } protected override void Deselected() { From 8917ab78f427c1dfb2e5eef47738fafec8fa746a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 14:14:50 +0900 Subject: [PATCH 0419/5199] Reduce unnecessary container nesting and adjust empty state opacity slightly --- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 ++-- .../Screens/Select/Carousel/CarouselHeader.cs | 29 ++++++++----------- .../Select/Carousel/DrawableCarouselItem.cs | 2 -- 3 files changed, 14 insertions(+), 23 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c27915c383..fbb12a86d3 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -894,10 +894,8 @@ namespace osu.Game.Screens.Select // child items (difficulties) are still visible. item.Header.X = offsetX(dist, visibleHalfHeight) - (parent?.X ?? 0); - // We are applying a multiplicative alpha (which is internally done by nesting an - // additional container and setting that container's alpha) such that we can - // layer alpha transformations on top. - item.SetMultiplicativeAlpha(Math.Clamp(1.75f - 1.5f * dist, 0, 1)); + // We are applying alpha to the header here such that we can layer alpha transformations on top. + item.Header.Alpha = Math.Clamp(1.75f - 1.5f * dist, 0, 1); } private enum PendingScrollOperation diff --git a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs index ed3aea3445..533694b265 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs @@ -21,8 +21,6 @@ namespace osu.Game.Screens.Select.Carousel { public class CarouselHeader : Container { - public Container BorderContainer; - public readonly Bindable State = new Bindable(CarouselItemState.NotSelected); private readonly HoverLayer hoverLayer; @@ -37,17 +35,14 @@ namespace osu.Game.Screens.Select.Carousel RelativeSizeAxes = Axes.X; Height = DrawableCarouselItem.MAX_HEIGHT; - InternalChild = BorderContainer = new Container + Masking = true; + CornerRadius = corner_radius; + BorderColour = new Color4(221, 255, 255, 255); + + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = corner_radius, - BorderColour = new Color4(221, 255, 255, 255), - Children = new Drawable[] - { - Content, - hoverLayer = new HoverLayer() - } + Content, + hoverLayer = new HoverLayer() }; } @@ -66,21 +61,21 @@ namespace osu.Game.Screens.Select.Carousel case CarouselItemState.NotSelected: hoverLayer.InsetForBorder = false; - BorderContainer.BorderThickness = 0; - BorderContainer.EdgeEffect = new EdgeEffectParameters + BorderThickness = 0; + EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, Offset = new Vector2(1), Radius = 10, - Colour = Color4.Black.Opacity(100), + Colour = Color4.Black.Opacity(0.5f), }; break; case CarouselItemState.Selected: hoverLayer.InsetForBorder = true; - BorderContainer.BorderThickness = border_thickness; - BorderContainer.EdgeEffect = new EdgeEffectParameters + BorderThickness = border_thickness; + EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, Colour = new Color4(130, 204, 255, 150), diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 4a23faa650..5e7ca0825a 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -85,8 +85,6 @@ namespace osu.Game.Screens.Select.Carousel }; } - public void SetMultiplicativeAlpha(float alpha) => Header.BorderContainer.Alpha = alpha; - protected override void LoadComplete() { base.LoadComplete(); From 6bc6675fa1bff93de775e10b6cf6781af9bdb3f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 14:46:16 +0900 Subject: [PATCH 0420/5199] Adjust fade in times slightly --- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 4f19e011b2..63c004f4bc 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -122,10 +122,8 @@ namespace osu.Game.Screens.Select.Carousel }, }; - background.DelayedLoadComplete += fadeContentIn; - mainFlow.DelayedLoadComplete += fadeContentIn; - - static void fadeContentIn(Drawable d) => d.FadeInFromZero(750, Easing.OutQuint); + background.DelayedLoadComplete += d => d.FadeInFromZero(750, Easing.OutQuint); + mainFlow.DelayedLoadComplete += d => d.FadeInFromZero(500, Easing.OutQuint); } protected override void Deselected() From 57f793aff0e6f562198b732dea85f9bd8c154dee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 15:12:08 +0900 Subject: [PATCH 0421/5199] Rename dictionary and make `private` for added safety --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 2 +- osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index d1a25b07ec..917b3c89a8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -215,7 +215,7 @@ namespace osu.Game.Tests.Visual.Gameplay }); // We can't access API because we're an "online" test. - AddAssert("last received frame has time = 1000", () => spectatorClient.LastReceivedFrame.First().Value.Time == 1000); + AddAssert("last received frame has time = 1000", () => spectatorClient.LastReceivedUserFrames.First().Value.Time == 1000); } private OsuFramedReplayInputHandler replayHandler => diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index 3f3d0f5b01..1a1d493249 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -28,7 +28,10 @@ namespace osu.Game.Tests.Visual.Spectator public override IBindable IsConnected { get; } = new Bindable(true); - public readonly Dictionary LastReceivedFrame = new Dictionary(); + public IReadOnlyDictionary LastReceivedUserFrames => lastReceivedUserFrames; + + private readonly Dictionary lastReceivedUserFrames = new Dictionary(); + private readonly Dictionary userBeatmapDictionary = new Dictionary(); private readonly Dictionary userNextFrameDictionary = new Dictionary(); @@ -40,7 +43,7 @@ namespace osu.Game.Tests.Visual.Spectator OnNewFrames += (i, bundle) => { if (PlayingUsers.Contains(i)) - LastReceivedFrame[i] = bundle.Frames[^1]; + lastReceivedUserFrames[i] = bundle.Frames[^1]; }; } From 9001c3a396a7f876bc52d772e1567d62161770f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 15:17:06 +0900 Subject: [PATCH 0422/5199] Fix test failures if `DialogOverlay` is not loaded in time As seen at https://github.com/ppy/osu/runs/4999391205?check_suite_focus=true, where `DialogOverlay` hasn't loaded in single file yet. --- osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 3be7cf7c5c..ed484e03f6 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -128,7 +128,9 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("choose clear all scores", () => InputManager.Key(Key.Number4)); + AddUntilStep("wait for dialog", () => Game.Dependencies.Get().CurrentDialog != null); AddStep("confirm deletion", () => InputManager.Key(Key.Number1)); + AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get().CurrentDialog == null); AddAssert("ensure score is pending deletion", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == true)); @@ -170,7 +172,9 @@ namespace osu.Game.Tests.Visual.Navigation InputManager.Click(MouseButton.Left); }); + AddUntilStep("wait for dialog", () => Game.Dependencies.Get().CurrentDialog != null); AddStep("confirm deletion", () => InputManager.Key(Key.Number1)); + AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get().CurrentDialog == null); AddAssert("ensure score is pending deletion", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == true)); From 25cbe5a0dee2ef50bdf32bdc8a2b09bc9c98446d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 15:23:31 +0900 Subject: [PATCH 0423/5199] Remove acronym shortening of `GameplayClockContainer` --- .../TestSceneMasterGameplayClockContainer.cs | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs index 172531605a..77b402ad3c 100644 --- a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs @@ -29,44 +29,44 @@ namespace osu.Game.Tests.Gameplay [Test] public void TestStartThenElapsedTime() { - GameplayClockContainer gcc = null; + GameplayClockContainer gameplayClockContainer = null; AddStep("create container", () => { var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); working.LoadTrack(); - Add(gcc = new MasterGameplayClockContainer(working, 0)); + Add(gameplayClockContainer = new MasterGameplayClockContainer(working, 0)); }); - AddStep("start clock", () => gcc.Start()); - AddUntilStep("elapsed greater than zero", () => gcc.GameplayClock.ElapsedFrameTime > 0); + AddStep("start clock", () => gameplayClockContainer.Start()); + AddUntilStep("elapsed greater than zero", () => gameplayClockContainer.GameplayClock.ElapsedFrameTime > 0); } [Test] public void TestElapseThenReset() { - GameplayClockContainer gcc = null; + GameplayClockContainer gameplayClockContainer = null; AddStep("create container", () => { var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); working.LoadTrack(); - Add(gcc = new MasterGameplayClockContainer(working, 0)); + Add(gameplayClockContainer = new MasterGameplayClockContainer(working, 0)); }); - AddStep("start clock", () => gcc.Start()); - AddUntilStep("current time greater 2000", () => gcc.GameplayClock.CurrentTime > 2000); + AddStep("start clock", () => gameplayClockContainer.Start()); + AddUntilStep("current time greater 2000", () => gameplayClockContainer.GameplayClock.CurrentTime > 2000); double timeAtReset = 0; AddStep("reset clock", () => { - timeAtReset = gcc.GameplayClock.CurrentTime; - gcc.Reset(); + timeAtReset = gameplayClockContainer.GameplayClock.CurrentTime; + gameplayClockContainer.Reset(); }); - AddAssert("current time < time at reset", () => gcc.GameplayClock.CurrentTime < timeAtReset); + AddAssert("current time < time at reset", () => gameplayClockContainer.GameplayClock.CurrentTime < timeAtReset); } [Test] @@ -76,29 +76,29 @@ namespace osu.Game.Tests.Gameplay [Values(false, true)] bool whileStopped) { ClockBackedTestWorkingBeatmap working = null; - GameplayClockContainer gcc = null; + GameplayClockContainer gameplayClockContainer = null; AddStep("create container", () => { working = new ClockBackedTestWorkingBeatmap(new OsuRuleset().RulesetInfo, new FramedClock(new ManualClock()), Audio); working.LoadTrack(); - Add(gcc = new MasterGameplayClockContainer(working, 0)); + Add(gameplayClockContainer = new MasterGameplayClockContainer(working, 0)); if (whileStopped) - gcc.Stop(); + gameplayClockContainer.Stop(); - gcc.Reset(); + gameplayClockContainer.Reset(); }); AddStep($"set clock rate to {clockRate}", () => working.Track.AddAdjustment(AdjustableProperty.Frequency, new BindableDouble(clockRate))); AddStep($"set audio offset to {userOffset}", () => localConfig.SetValue(OsuSetting.AudioOffset, userOffset)); - AddStep("seek to 2500", () => gcc.Seek(2500)); - AddAssert("gameplay clock time = 2500", () => Precision.AlmostEquals(gcc.CurrentTime, 2500, 10f)); + AddStep("seek to 2500", () => gameplayClockContainer.Seek(2500)); + AddAssert("gameplay clock time = 2500", () => Precision.AlmostEquals(gameplayClockContainer.CurrentTime, 2500, 10f)); - AddStep("seek to 10000", () => gcc.Seek(10000)); - AddAssert("gameplay clock time = 10000", () => Precision.AlmostEquals(gcc.CurrentTime, 10000, 10f)); + AddStep("seek to 10000", () => gameplayClockContainer.Seek(10000)); + AddAssert("gameplay clock time = 10000", () => Precision.AlmostEquals(gameplayClockContainer.CurrentTime, 10000, 10f)); } protected override void Dispose(bool isDisposing) From cc7fb0e559b096566b2874607f2ff5f57ca8e0e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 16:36:56 +0900 Subject: [PATCH 0424/5199] Add mouse click support and increase area to full column height --- osu.Game.Rulesets.Mania/UI/Column.cs | 72 +++++++++++++++------------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index cd5f3d2170..9bcd9ad853 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -28,7 +28,6 @@ namespace osu.Game.Rulesets.Mania.UI { public const float COLUMN_WIDTH = 80; public const float SPECIAL_COLUMN_WIDTH = 70; - public const float TOUCH_AREA_HEIGHT = 100; /// /// The index of this column as part of the whole playfield. @@ -45,37 +44,6 @@ namespace osu.Game.Rulesets.Mania.UI private readonly GameplaySampleTriggerSource sampleTriggerSource; - public class ColumnTouchInputArea : Drawable - { - public ColumnTouchInputArea() - { - RelativeSizeAxes = Axes.X; - Anchor = Anchor.BottomCentre; - Origin = Anchor.BottomCentre; - Height = TOUCH_AREA_HEIGHT; - } - - private Column column => (Column)Parent; - - protected override void LoadComplete() - { - keyBindingContainer = (ManiaInputManager.RulesetKeyBindingContainer)((ManiaInputManager)GetContainingInputManager()).KeyBindingContainer; - } - - private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } - - protected override bool OnTouchDown(TouchDownEvent e) - { - keyBindingContainer.TriggerPressed(column.Action.Value); - return false; - } - - protected override void OnTouchUp(TouchUpEvent e) - { - keyBindingContainer.TriggerReleased(column.Action.Value); - } - } - public Column(int index) { Index = index; @@ -172,5 +140,45 @@ namespace osu.Game.Rulesets.Mania.UI public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) // This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border => DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos)); + + public class ColumnTouchInputArea : Drawable + { + public ColumnTouchInputArea() + { + RelativeSizeAxes = Axes.Both; + } + + private Column column => (Column)Parent; + + protected override void LoadComplete() + { + keyBindingContainer = (ManiaInputManager.RulesetKeyBindingContainer)((ManiaInputManager)GetContainingInputManager()).KeyBindingContainer; + } + + private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } + + protected override bool OnMouseDown(MouseDownEvent e) + { + keyBindingContainer.TriggerPressed(column.Action.Value); + return base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseUpEvent e) + { + keyBindingContainer.TriggerReleased(column.Action.Value); + base.OnMouseUp(e); + } + + protected override bool OnTouchDown(TouchDownEvent e) + { + keyBindingContainer.TriggerPressed(column.Action.Value); + return true; + } + + protected override void OnTouchUp(TouchUpEvent e) + { + keyBindingContainer.TriggerReleased(column.Action.Value); + } + } } } From 9005bce0fa574ec9be7661bbeb5029e1ad8797fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 16:37:47 +0900 Subject: [PATCH 0425/5199] Add "counter" keyword for key overlay setting --- osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs index e1b452e322..ba9779d650 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs @@ -37,7 +37,8 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay new SettingsCheckbox { LabelText = GameplaySettingsStrings.AlwaysShowKeyOverlay, - Current = config.GetBindable(OsuSetting.KeyOverlay) + Current = config.GetBindable(OsuSetting.KeyOverlay), + Keywords = new[] { "counter" }, }, }; } From 0e764538e0ea81293ef99994b25e3ab7ceed609e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 16:47:20 +0900 Subject: [PATCH 0426/5199] Retrieve `KeyBindingContainer` via DI rather than traversal lookup --- osu.Game.Rulesets.Mania/ManiaInputManager.cs | 2 ++ osu.Game.Rulesets.Mania/UI/Column.cs | 17 ++++++++++------- osu.Game/Rulesets/UI/RulesetInputManager.cs | 4 ++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaInputManager.cs b/osu.Game.Rulesets.Mania/ManiaInputManager.cs index 186fc4b15d..14ca27a11a 100644 --- a/osu.Game.Rulesets.Mania/ManiaInputManager.cs +++ b/osu.Game.Rulesets.Mania/ManiaInputManager.cs @@ -2,11 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.ComponentModel; +using osu.Framework.Allocation; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania { + [Cached] // Used for touch input, see ColumnTouchInputArea. public class ManiaInputManager : RulesetInputManager { public ManiaInputManager(RulesetInfo ruleset, int variant) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 9bcd9ad853..a6c64e9b2d 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -143,6 +143,11 @@ namespace osu.Game.Rulesets.Mania.UI public class ColumnTouchInputArea : Drawable { + [Resolved(canBeNull: true)] + private ManiaInputManager maniaInputManager { get; set; } + + private KeyBindingContainer keyBindingContainer; + public ColumnTouchInputArea() { RelativeSizeAxes = Axes.Both; @@ -152,32 +157,30 @@ namespace osu.Game.Rulesets.Mania.UI protected override void LoadComplete() { - keyBindingContainer = (ManiaInputManager.RulesetKeyBindingContainer)((ManiaInputManager)GetContainingInputManager()).KeyBindingContainer; + keyBindingContainer = maniaInputManager?.KeyBindingContainer; } - private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } - protected override bool OnMouseDown(MouseDownEvent e) { - keyBindingContainer.TriggerPressed(column.Action.Value); + keyBindingContainer?.TriggerPressed(column.Action.Value); return base.OnMouseDown(e); } protected override void OnMouseUp(MouseUpEvent e) { - keyBindingContainer.TriggerReleased(column.Action.Value); + keyBindingContainer?.TriggerReleased(column.Action.Value); base.OnMouseUp(e); } protected override bool OnTouchDown(TouchDownEvent e) { - keyBindingContainer.TriggerPressed(column.Action.Value); + keyBindingContainer?.TriggerPressed(column.Action.Value); return true; } protected override void OnTouchUp(TouchUpEvent e) { - keyBindingContainer.TriggerReleased(column.Action.Value); + keyBindingContainer?.TriggerReleased(column.Action.Value); } } } diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 160c0a2606..84e42818be 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -24,6 +24,8 @@ namespace osu.Game.Rulesets.UI public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler, IHasRecordingHandler where T : struct { + public readonly KeyBindingContainer KeyBindingContainer; + private ReplayRecorder recorder; public ReplayRecorder Recorder @@ -43,8 +45,6 @@ namespace osu.Game.Rulesets.UI protected override InputState CreateInitialState() => new RulesetInputManagerInputState(base.CreateInitialState()); - public readonly KeyBindingContainer KeyBindingContainer; - protected override Container Content => content; private readonly Container content; From a49a9ed0a05d778b5af4c6328071bb0f8926d7b5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 31 Jan 2022 17:19:04 +0900 Subject: [PATCH 0427/5199] Fix incorrect invoke --- osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index f5a6777a62..4979bd906b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -101,7 +101,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate stack.Push(new MultiSpectatorPlayerLoader(Score, () => { var player = new MultiSpectatorPlayer(Score, GameplayClock); - player.OnGameplayStarted += OnGameplayStarted; + player.OnGameplayStarted += () => OnGameplayStarted?.Invoke(); return player; })); From 4727aeda012f6c5f00f500d0eaf4c05ba8bbc58c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 31 Jan 2022 18:32:17 +0900 Subject: [PATCH 0428/5199] Give last bundled replay frame the frame header --- .../Visual/Gameplay/TestSceneSpectator.cs | 16 ++++++++++++++++ osu.Game/Online/Spectator/SpectatorClient.cs | 3 +++ osu.Game/Rulesets/Replays/ReplayFrame.cs | 13 +++++++++++++ osu.Game/Screens/Play/SpectatorPlayer.cs | 1 + 4 files changed, 33 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 917b3c89a8..a7b9d45f7a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -218,6 +218,22 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("last received frame has time = 1000", () => spectatorClient.LastReceivedUserFrames.First().Value.Time == 1000); } + [Test] + public void TestFinalFrameInBundleHasHeader() + { + FrameDataBundle lastBundle = null; + + AddStep("bind to client", () => spectatorClient.OnNewFrames += (_, bundle) => lastBundle = bundle); + + start(-1234); + sendFrames(); + finish(); + + AddUntilStep("bundle received", () => lastBundle != null); + AddAssert("first frame does not have header", () => lastBundle.Frames[0].Header == null); + AddAssert("last frame has header", () => lastBundle.Frames[^1].Header != null); + } + private OsuFramedReplayInputHandler replayHandler => (OsuFramedReplayInputHandler)Stack.ChildrenOfType().First().ReplayInputHandler; diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index fddb94fad7..67aa75727d 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -129,6 +129,9 @@ namespace osu.Game.Online.Spectator Task ISpectatorClient.UserSentFrames(int userId, FrameDataBundle data) { + if (data.Frames.Count > 0) + data.Frames[^1].Header = data.Header; + Schedule(() => OnNewFrames?.Invoke(userId, data)); return Task.CompletedTask; diff --git a/osu.Game/Rulesets/Replays/ReplayFrame.cs b/osu.Game/Rulesets/Replays/ReplayFrame.cs index 7de53211a2..2b67b60d8f 100644 --- a/osu.Game/Rulesets/Replays/ReplayFrame.cs +++ b/osu.Game/Rulesets/Replays/ReplayFrame.cs @@ -1,16 +1,29 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using MessagePack; +using osu.Game.Online.Spectator; namespace osu.Game.Rulesets.Replays { [MessagePackObject] public class ReplayFrame { + /// + /// The time at which this takes place. + /// [Key(0)] public double Time; + /// + /// A containing the state of a play after this takes place. + /// May be omitted where exact per-frame accuracy is not required. + /// + [IgnoreMember] + public FrameHeader? Header; + public ReplayFrame() { } diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index d42643c416..c415041081 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -72,6 +72,7 @@ namespace osu.Game.Screens.Play var convertedFrame = (ReplayFrame)convertibleFrame; convertedFrame.Time = frame.Time; + convertedFrame.Header = frame.Header; score.Replay.Frames.Add(convertedFrame); } From 0458d408bb36b15c00c47634926dea59b116205c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 31 Jan 2022 18:37:51 +0900 Subject: [PATCH 0429/5199] Add replay statistics frames to FramedReplayInputHandler --- .../EmptyFreeformFramedReplayInputHandler.cs | 2 +- .../PippidonFramedReplayInputHandler.cs | 2 +- .../EmptyScrollingFramedReplayInputHandler.cs | 2 +- .../PippidonFramedReplayInputHandler.cs | 2 +- .../Replays/CatchFramedReplayInputHandler.cs | 2 +- .../Replays/ManiaFramedReplayInputHandler.cs | 2 +- .../Replays/OsuFramedReplayInputHandler.cs | 2 +- .../Replays/TaikoFramedReplayInputHandler.cs | 2 +- .../Gameplay/TestSceneReplayRecorder.cs | 2 +- .../Gameplay/TestSceneSpectatorPlayback.cs | 2 +- osu.Game/Input/Handlers/ReplayInputHandler.cs | 34 +++++++++++++++++++ .../Replays/FramedReplayInputHandler.cs | 15 ++++++++ 12 files changed, 59 insertions(+), 10 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs index cc4483de31..a9bc8dc10e 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Replays protected override bool IsImportant(EmptyFreeformReplayFrame frame) => frame.Actions.Any(); - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time); diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs index e005346e1e..dbfaf8a01d 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Pippidon.Replays protected override bool IsImportant(PippidonReplayFrame frame) => true; - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time); diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs index 4b998cfca3..1d33ab8a54 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Replays protected override bool IsImportant(EmptyScrollingReplayFrame frame) => frame.Actions.Any(); - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { inputs.Add(new ReplayState { diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs index 7652357b4d..702f6fdb04 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Pippidon.Replays protected override bool IsImportant(PippidonReplayFrame frame) => frame.Actions.Any(); - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { inputs.Add(new ReplayState { diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs index bd742ce6a6..b6af88a771 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Replays protected override bool IsImportant(CatchReplayFrame frame) => frame.Actions.Any(); - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { float position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time); diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs index aa0c148caf..aa164f95da 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Replays protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any(); - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs index 7d696dfb79..ea36ecc399 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Replays protected override bool IsImportant(OsuReplayFrame frame) => frame.Actions.Any(); - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time); diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs index 138e8f9785..2f9b6c7f60 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Replays protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any(); - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index 4eab1a21da..8df32c500e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -210,7 +210,7 @@ namespace osu.Game.Tests.Visual.Gameplay { } - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) }); inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 90abdf2ba3..4af254866a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -259,7 +259,7 @@ namespace osu.Game.Tests.Visual.Gameplay { } - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) }); inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); diff --git a/osu.Game/Input/Handlers/ReplayInputHandler.cs b/osu.Game/Input/Handlers/ReplayInputHandler.cs index e4aec4edac..205a1ea1ac 100644 --- a/osu.Game/Input/Handlers/ReplayInputHandler.cs +++ b/osu.Game/Input/Handlers/ReplayInputHandler.cs @@ -9,6 +9,7 @@ using osu.Framework.Input.StateChanges; using osu.Framework.Input.StateChanges.Events; using osu.Framework.Input.States; using osu.Framework.Platform; +using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.UI; using osuTK; @@ -79,5 +80,38 @@ namespace osu.Game.Input.Handlers PressedActions = pressedActions; } } + + /// + /// An that is triggered when a frame containing replay statistics arrives. + /// + public class ReplayStatisticsFrameInput : IInput + { + /// + /// The frame containing the statistics. + /// + public ReplayFrame Frame; + + public void Apply(InputState state, IInputStateChangeHandler handler) + { + handler.HandleInputStateChange(new ReplayStatisticsFrameEvent(state, this, Frame)); + } + } + + /// + /// An that is triggered when a frame containing replay statistics arrives. + /// + public class ReplayStatisticsFrameEvent : InputStateChangeEvent + { + /// + /// The frame containing the statistics. + /// + public readonly ReplayFrame Frame; + + public ReplayStatisticsFrameEvent(InputState state, IInput input, ReplayFrame frame) + : base(state, input) + { + Frame = frame; + } + } } } diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index d3ee10dd23..f889d15485 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Input.StateChanges; using osu.Game.Input.Handlers; using osu.Game.Replays; @@ -174,5 +175,19 @@ namespace osu.Game.Rulesets.Replays return Frames[index].Time; } + + public sealed override void CollectPendingInputs(List inputs) + { + base.CollectPendingInputs(inputs); + + CollectReplayInputs(inputs); + + if (CurrentFrame?.Header != null) + inputs.Add(new ReplayStatisticsFrameInput { Frame = CurrentFrame }); + } + + protected virtual void CollectReplayInputs(List inputs) + { + } } } From 39e1d65976ba2d35e0c7729b21ca75fdf1745864 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 31 Jan 2022 18:43:10 +0900 Subject: [PATCH 0430/5199] Make ScoreProcessor write all judgement types --- osu.Game/Rulesets/Scoring/HitResult.cs | 2 +- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 5599ed96a3..a254f9b760 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -176,7 +176,7 @@ namespace osu.Game.Rulesets.Scoring /// /// An array of all scorable s. /// - public static readonly HitResult[] SCORABLE_TYPES = ((HitResult[])Enum.GetValues(typeof(HitResult))).Where(r => r.IsScorable()).ToArray(); + public static readonly HitResult[] ALL_TYPES = ((HitResult[])Enum.GetValues(typeof(HitResult))).ToArray(); /// /// Whether a is valid within a given range. diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index fc24972b8e..fc5e982521 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -125,6 +125,8 @@ namespace osu.Game.Rulesets.Scoring if (result.FailedAtJudgement) return; + scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) + 1; + if (!result.Type.IsScorable()) return; @@ -151,8 +153,6 @@ namespace osu.Game.Rulesets.Scoring rollingMaxBaseScore += result.Judgement.MaxNumericResult; } - scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) + 1; - hitEvents.Add(CreateHitEvent(result)); lastHitObject = result.HitObject; @@ -175,6 +175,8 @@ namespace osu.Game.Rulesets.Scoring if (result.FailedAtJudgement) return; + scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) - 1; + if (!result.Type.IsScorable()) return; @@ -186,8 +188,6 @@ namespace osu.Game.Rulesets.Scoring rollingMaxBaseScore -= result.Judgement.MaxNumericResult; } - scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) - 1; - Debug.Assert(hitEvents.Count > 0); lastHitObject = hitEvents[^1].LastHitObject; hitEvents.RemoveAt(hitEvents.Count - 1); @@ -346,7 +346,7 @@ namespace osu.Game.Rulesets.Scoring score.Accuracy = Accuracy.Value; score.Rank = Rank.Value; - foreach (var result in HitResultExtensions.SCORABLE_TYPES) + foreach (var result in HitResultExtensions.ALL_TYPES) score.Statistics[result] = GetStatistic(result); score.HitEvents = hitEvents; From 4fb565e15f1be7d6fd740090f24cd2d98552aabb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 31 Jan 2022 18:54:23 +0900 Subject: [PATCH 0431/5199] Reset ScoreProcessor from statistics replay frames --- .../Gameplay/TestSceneScoreProcessor.cs | 37 +++++++++ .../Rulesets/Scoring/JudgementProcessor.cs | 12 +++ osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 77 +++++++++++++++++-- osu.Game/Rulesets/UI/RulesetInputManager.cs | 32 +++++--- osu.Game/Screens/Play/Player.cs | 1 + 5 files changed, 144 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index 432e3df95e..bcaa70d062 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -1,11 +1,17 @@ // 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 NUnit.Framework; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Online.Spectator; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Tests.Visual; @@ -42,6 +48,37 @@ namespace osu.Game.Tests.Gameplay Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(Judgement.LARGE_BONUS_SCORE)); } + [Test] + public void TestResetFromReplayFrame() + { + var beatmap = new Beatmap { HitObjects = { new HitCircle() } }; + + var scoreProcessor = new ScoreProcessor(); + scoreProcessor.ApplyBeatmap(beatmap); + + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new TestJudgement(HitResult.Great)) { Type = HitResult.Great }); + Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000)); + Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1)); + + // Reset with a miss instead. + scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame + { + Header = new FrameHeader(0, 0, 0, new Dictionary { { HitResult.Miss, 1 } }, DateTimeOffset.Now) + }); + + Assert.That(scoreProcessor.TotalScore.Value, Is.Zero); + Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1)); + + // Reset with no judged hit. + scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame + { + Header = new FrameHeader(0, 0, 0, new Dictionary(), DateTimeOffset.Now) + }); + + Assert.That(scoreProcessor.TotalScore.Value, Is.Zero); + Assert.That(scoreProcessor.JudgedHits, Is.Zero); + } + private class TestJudgement : Judgement { public override HitResult MaxResult { get; } diff --git a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs index c3c4a2c949..f38155240e 100644 --- a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs +++ b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Scoring { @@ -107,6 +108,17 @@ namespace osu.Game.Rulesets.Scoring JudgedHits = 0; } + public virtual void ResetFromReplayFrame(Ruleset ruleset, ReplayFrame frame) + { + if (frame.Header == null) + return; + + JudgedHits = 0; + + foreach ((_, int count) in frame.Header.Statistics) + JudgedHits += count; + } + /// /// Creates the that represents the scoring result for a . /// diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index fc5e982521..37ee3771df 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -7,9 +7,11 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Utils; +using osu.Game.Extensions; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Replays; using osu.Game.Scoring; namespace osu.Game.Rulesets.Scoring @@ -18,6 +20,11 @@ namespace osu.Game.Rulesets.Scoring { private const double max_score = 1000000; + /// + /// Invoked when this was reset from a replay frame. + /// + public event Action OnResetFromReplayFrame; + /// /// The current total score. /// @@ -329,12 +336,6 @@ namespace osu.Game.Rulesets.Scoring HighestCombo.Value = 0; } - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - hitEvents.Clear(); - } - /// /// Retrieve a score populated with data for the current play this processor is responsible for. /// @@ -351,6 +352,70 @@ namespace osu.Game.Rulesets.Scoring score.HitEvents = hitEvents; } + + /// + /// Maximum for a normal hit (i.e. not tick/bonus) for this ruleset. Only populated via . + /// + private HitResult? maxNormalResult; + + public override void ResetFromReplayFrame(Ruleset ruleset, ReplayFrame frame) + { + base.ResetFromReplayFrame(ruleset, frame); + + if (frame.Header == null) + return; + + baseScore = 0; + rollingMaxBaseScore = 0; + HighestCombo.Value = frame.Header.MaxCombo; + + foreach ((HitResult result, int count) in frame.Header.Statistics) + { + // Bonus scores are counted separately directly from the statistics dictionary later on. + if (!result.IsScorable() || result.IsBonus()) + continue; + + // The maximum result of this judgement if it wasn't a miss. + // E.g. For a GOOD judgement, the max result is either GREAT/PERFECT depending on which one the ruleset uses (osu!: GREAT, osu!mania: PERFECT). + HitResult maxResult; + + switch (result) + { + case HitResult.LargeTickHit: + case HitResult.LargeTickMiss: + maxResult = HitResult.LargeTickHit; + break; + + case HitResult.SmallTickHit: + case HitResult.SmallTickMiss: + maxResult = HitResult.SmallTickHit; + break; + + default: + maxResult = maxNormalResult ??= ruleset.GetHitResults().OrderByDescending(kvp => Judgement.ToNumericResult(kvp.result)).First().result; + break; + } + + for (int i = 0; i < count; i++) + { + baseScore += Judgement.ToNumericResult(result); + rollingMaxBaseScore += Judgement.ToNumericResult(maxResult); + } + } + + scoreResultCounts.Clear(); + scoreResultCounts.AddRange(frame.Header.Statistics); + + updateScore(); + + OnResetFromReplayFrame?.Invoke(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + hitEvents.Clear(); + } } public enum ScoringMode diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 370c99ffaf..050ec82071 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -16,6 +16,7 @@ using osu.Game.Configuration; using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.Input.Handlers; +using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using static osu.Game.Input.Handlers.ReplayInputHandler; @@ -24,6 +25,11 @@ namespace osu.Game.Rulesets.UI public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler, IHasRecordingHandler where T : struct { + private readonly Ruleset ruleset; + + [Resolved(CanBeNull = true)] + private ScoreProcessor scoreProcessor { get; set; } + private ReplayRecorder recorder; public ReplayRecorder Recorder @@ -51,6 +57,8 @@ namespace osu.Game.Rulesets.UI protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) { + this.ruleset = ruleset.CreateInstance(); + InternalChild = KeyBindingContainer = CreateKeyBindingContainer(ruleset, variant, unique) .WithChild(content = new Container { RelativeSizeAxes = Axes.Both }); @@ -66,17 +74,23 @@ namespace osu.Game.Rulesets.UI public override void HandleInputStateChange(InputStateChangeEvent inputStateChange) { - if (inputStateChange is ReplayStateChangeEvent replayStateChanged) + switch (inputStateChange) { - foreach (var action in replayStateChanged.ReleasedActions) - KeyBindingContainer.TriggerReleased(action); + case ReplayStateChangeEvent stateChangeEvent: + foreach (var action in stateChangeEvent.ReleasedActions) + KeyBindingContainer.TriggerReleased(action); - foreach (var action in replayStateChanged.PressedActions) - KeyBindingContainer.TriggerPressed(action); - } - else - { - base.HandleInputStateChange(inputStateChange); + foreach (var action in stateChangeEvent.PressedActions) + KeyBindingContainer.TriggerPressed(action); + break; + + case ReplayStatisticsFrameEvent statisticsStateChangeEvent: + scoreProcessor?.ResetFromReplayFrame(ruleset, statisticsStateChangeEvent.Frame); + break; + + default: + base.HandleInputStateChange(inputStateChange); + break; } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index cfca2d0a3d..e0fcca4eb7 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -165,6 +165,7 @@ namespace osu.Game.Screens.Play PrepareReplay(); ScoreProcessor.NewJudgement += result => ScoreProcessor.PopulateScore(Score.ScoreInfo); + ScoreProcessor.OnResetFromReplayFrame += () => ScoreProcessor.PopulateScore(Score.ScoreInfo); gameActive.BindValueChanged(_ => updatePauseOnFocusLostState(), true); } From e5601772a99aba7374077f986f158a1a839f2264 Mon Sep 17 00:00:00 2001 From: Spooghetti420 Date: Mon, 31 Jan 2022 15:00:36 +0000 Subject: [PATCH 0432/5199] Make incompatible with --- osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs | 3 +++ osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index 42dc7fdc1f..1abca970d5 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.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.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Objects; @@ -25,6 +26,8 @@ namespace osu.Game.Rulesets.Mania.Mods public override ModType Type => ModType.Conversion; + public override Type[] IncompatibleMods => new[] { typeof(ManiaModInvert) }; + public const double END_NOTE_ALLOW_THRESHOLD = 0.5; public void ApplyToBeatmap(IBeatmap beatmap) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs index 1ea45c295c..4cbdaee323 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs @@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Mania.Mods public override ModType Type => ModType.Conversion; + public override Type[] IncompatibleMods => new[] { typeof(ManiaModHoldOff) }; + public void ApplyToBeatmap(IBeatmap beatmap) { var maniaBeatmap = (ManiaBeatmap)beatmap; From 2f88efd3c3c2f66f0bc8104f737cbc532527251f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 00:53:56 +0900 Subject: [PATCH 0433/5199] Pass column in rather than accessing parent --- osu.Game.Rulesets.Mania/UI/Column.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index a6c64e9b2d..dc3b86ac7f 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Mania.UI }, background, TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }, - new ColumnTouchInputArea() + new ColumnTouchInputArea(this) }; hitPolicy = new OrderedHitPolicy(HitObjectContainer); @@ -143,17 +143,19 @@ namespace osu.Game.Rulesets.Mania.UI public class ColumnTouchInputArea : Drawable { + private readonly Column column; + [Resolved(canBeNull: true)] private ManiaInputManager maniaInputManager { get; set; } private KeyBindingContainer keyBindingContainer; - public ColumnTouchInputArea() + public ColumnTouchInputArea(Column column) { RelativeSizeAxes = Axes.Both; - } - private Column column => (Column)Parent; + this.column = column; + } protected override void LoadComplete() { From 9227211a441eb2df90a2e0504e6bdaa08f94bfe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 31 Jan 2022 22:56:27 +0100 Subject: [PATCH 0434/5199] Privatise `shouldAlternate` --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index db8bd217f6..f68f2b5bee 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -25,7 +25,6 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) }; public override ModType Type => ModType.DifficultyIncrease; public override IconUsage? Icon => FontAwesome.Solid.Keyboard; - public bool ShouldAlternate => !isBreakTime.Value && introEnded; private bool introEnded; private double earliestStartTime; @@ -34,6 +33,8 @@ namespace osu.Game.Rulesets.Osu.Mods private OsuAction? lastActionPressed; private DrawableRuleset ruleset; + private bool shouldAlternate => !isBreakTime.Value && introEnded; + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { ruleset = drawableRuleset; @@ -82,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Mods } public bool OnPressed(KeyBindingPressEvent e) - => mod.ShouldAlternate && mod.onPressed(e.Action); + => mod.shouldAlternate && mod.onPressed(e.Action); public void OnReleased(KeyBindingReleaseEvent e) { From c50577e25ff3d20c307cbec120b8436558cb6aa3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 1 Feb 2022 10:48:41 +0900 Subject: [PATCH 0435/5199] Apply suggestion from review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 37ee3771df..79861c0ecc 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -396,11 +396,8 @@ namespace osu.Game.Rulesets.Scoring break; } - for (int i = 0; i < count; i++) - { - baseScore += Judgement.ToNumericResult(result); - rollingMaxBaseScore += Judgement.ToNumericResult(maxResult); - } + baseScore += count * Judgement.ToNumericResult(result); + rollingMaxBaseScore += count * Judgement.ToNumericResult(maxResult); } scoreResultCounts.Clear(); From 5a6d57efb77728df1d09dab9837a75e36311f611 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 12:43:35 +0900 Subject: [PATCH 0436/5199] Update fastlane and dependencies --- Gemfile.lock | 147 +++++++++++++++++++++++++++++---------------- fastlane/README.md | 2 +- 2 files changed, 95 insertions(+), 54 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8ac863c9a8..86c8baabe6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,53 +1,75 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.3) - addressable (2.7.0) + CFPropertyList (3.0.5) + rexml + addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) + artifactory (3.0.15) atomos (0.1.3) - aws-eventstream (1.1.0) - aws-partitions (1.413.0) - aws-sdk-core (3.110.0) + aws-eventstream (1.2.0) + aws-partitions (1.551.0) + aws-sdk-core (3.125.5) aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.239.0) + aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.40.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-kms (1.53.0) + aws-sdk-core (~> 3, >= 3.125.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.87.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-s3 (1.111.3) + aws-sdk-core (~> 3, >= 3.125.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.1) - aws-sigv4 (1.2.2) + aws-sigv4 (~> 1.4) + aws-sigv4 (1.4.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) - claide (1.0.3) + claide (1.1.0) colored (1.2) colored2 (3.1.2) commander-fastlane (4.4.6) highline (~> 1.7.2) declarative (0.0.20) - declarative-option (0.1.0) - digest-crc (0.6.3) + digest-crc (0.6.4) rake (>= 12.0.0, < 14.0.0) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) dotenv (2.7.6) - emoji_regex (3.2.1) - excon (0.78.1) - faraday (1.2.0) - multipart-post (>= 1.2, < 3) - ruby2_keywords + emoji_regex (3.2.3) + excon (0.90.0) + faraday (1.9.3) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) faraday-cookie_jar (0.0.7) faraday (>= 0.8.0) http-cookie (~> 1.0.0) - faraday_middleware (1.0.0) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.0.3) + multipart-post (>= 1.2, < 3) + faraday-net_http (1.0.1) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + faraday_middleware (1.2.0) faraday (~> 1.0) - fastimage (2.2.1) - fastlane (2.170.0) + fastimage (2.2.6) + fastlane (2.181.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) + artifactory (~> 3.0) aws-sdk-s3 (~> 1.0) babosa (>= 1.0.3, < 2.0.0) bundler (>= 1.12.0, < 3.0.0) @@ -68,6 +90,7 @@ GEM jwt (>= 2.1.0, < 3) mini_magick (>= 4.9.4, < 5.0.0) multipart-post (~> 2.0.0) + naturally (~> 2.2) plist (>= 3.1.0, < 4.0.0) rubyzip (>= 2.0.0, < 3.0.0) security (= 0.1.3) @@ -94,65 +117,80 @@ GEM representable (~> 3.0) retriable (>= 2.0, < 4.0) signet (~> 0.12) - google-cloud-core (1.5.0) + google-apis-core (0.4.2) + addressable (~> 2.5, >= 2.5.1) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.a) + rexml + webrick + google-apis-iamcredentials_v1 (0.10.0) + google-apis-core (>= 0.4, < 2.a) + google-apis-storage_v1 (0.11.0) + google-apis-core (>= 0.4, < 2.a) + google-cloud-core (1.6.0) google-cloud-env (~> 1.0) google-cloud-errors (~> 1.0) - google-cloud-env (1.4.0) + google-cloud-env (1.5.0) faraday (>= 0.17.3, < 2.0) - google-cloud-errors (1.0.1) - google-cloud-storage (1.29.2) - addressable (~> 2.5) + google-cloud-errors (1.2.0) + google-cloud-storage (1.36.0) + addressable (~> 2.8) digest-crc (~> 0.4) - google-api-client (~> 0.33) - google-cloud-core (~> 1.2) - googleauth (~> 0.9) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.1) + google-cloud-core (~> 1.6) + googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (0.14.0) + googleauth (0.17.1) faraday (>= 0.17.3, < 2.0) jwt (>= 1.4, < 3.0) memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) - signet (~> 0.14) + signet (~> 0.15) highline (1.7.10) - http-cookie (1.0.3) + http-cookie (1.0.4) domain_name (~> 0.5) httpclient (2.8.3) - jmespath (1.4.0) - json (2.5.1) - jwt (2.2.2) + jmespath (1.5.0) + json (2.6.1) + jwt (2.3.0) memoist (0.16.2) mini_magick (4.11.0) - mini_mime (1.0.2) + mini_mime (1.1.2) mini_portile2 (2.4.0) multi_json (1.15.0) multipart-post (2.0.0) nanaimo (0.3.0) - naturally (2.2.0) + naturally (2.2.1) nokogiri (1.10.10) mini_portile2 (~> 2.4.0) - os (1.1.1) - plist (3.5.0) + os (1.1.4) + plist (3.6.0) public_suffix (4.0.6) - rake (13.0.3) - representable (3.0.4) + rake (13.0.6) + representable (3.1.1) declarative (< 0.1.0) - declarative-option (< 0.2.0) + trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) + rexml (3.2.5) rouge (2.0.7) - ruby2_keywords (0.0.2) - rubyzip (2.3.0) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) security (0.1.3) - signet (0.14.0) - addressable (~> 2.3) + signet (0.16.0) + addressable (~> 2.8) faraday (>= 0.17.3, < 2.0) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) simctl (1.6.8) CFPropertyList naturally - slack-notifier (2.3.2) + slack-notifier (2.4.0) souyuz (0.9.1) fastlane (>= 1.103.0) highline (~> 1.7) @@ -160,6 +198,7 @@ GEM terminal-notifier (2.0.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) + trailblazer-option (0.1.2) tty-cursor (0.7.1) tty-screen (0.8.1) tty-spinner (0.9.3) @@ -167,18 +206,20 @@ GEM uber (0.1.0) unf (0.1.4) unf_ext - unf_ext (0.0.7.7) - unicode-display_width (1.7.0) + unf_ext (0.0.8) + unicode-display_width (1.8.0) + webrick (1.7.0) word_wrap (1.0.0) - xcodeproj (1.19.0) + xcodeproj (1.21.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.3.0) + rexml (~> 3.2.4) xcpretty (0.3.0) rouge (~> 2.0.7) - xcpretty-travis-formatter (1.0.0) + xcpretty-travis-formatter (1.0.1) xcpretty (~> 0.2, >= 0.0.7) PLATFORMS diff --git a/fastlane/README.md b/fastlane/README.md index a400ed9516..8273fdaa5d 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -12,7 +12,7 @@ Install _fastlane_ using ``` [sudo] gem install fastlane -NV ``` -or alternatively using `brew cask install fastlane` +or alternatively using `brew install fastlane` # Available Actions ## Android From aa492270dd9738bf9cf8c7dc0c4eb7543e155f8a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 13:30:26 +0900 Subject: [PATCH 0437/5199] Ignore `FodyWeavers.xml` files in subdirectories These are created when building specific projects rather than the main solution (typically iOS / android) and of no use to us. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index de6a3ac848..5b19270ab9 100644 --- a/.gitignore +++ b/.gitignore @@ -339,3 +339,4 @@ inspectcode # Fody (pulled in by Realm) - schema file FodyWeavers.xsd +**/FodyWeavers.xml From db973fb34876e1329c215f08dcee2dc7039ae422 Mon Sep 17 00:00:00 2001 From: dekrain Date: Tue, 1 Feb 2022 06:28:18 +0100 Subject: [PATCH 0438/5199] Add basic tooltip for leaderboard scores --- .../Online/Leaderboards/LeaderboardScore.cs | 5 +- .../Leaderboards/LeaderboardScoreTooltip.cs | 225 ++++++++++++++++++ 2 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 906e09b8c1..7779043ccd 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -32,7 +32,7 @@ using osu.Game.Utils; namespace osu.Game.Online.Leaderboards { - public class LeaderboardScore : OsuClickableContainer, IHasContextMenu + public class LeaderboardScore : OsuClickableContainer, IHasContextMenu, IHasCustomTooltip { public const float HEIGHT = 60; @@ -70,6 +70,9 @@ namespace osu.Game.Online.Leaderboards [Resolved] private Storage storage { get; set; } + public ITooltip GetCustomTooltip() => new LeaderboardScoreTooltip(); + public ScoreInfo TooltipContent => Score; + public LeaderboardScore(ScoreInfo score, int? rank, bool isOnlineScope = true) { Score = score; diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs new file mode 100644 index 0000000000..78064c33bc --- /dev/null +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -0,0 +1,225 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Game.Scoring; +using osuTK; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Online.Leaderboards +{ + public class LeaderboardScoreTooltip : VisibilityContainer, ITooltip + { + private OsuSpriteText timestampLabel; + private FillFlowContainer topScoreStatistics; + private FillFlowContainer bottomScoreStatistics; + private FillFlowContainer modStatistics; + + public LeaderboardScoreTooltip() + { + AutoSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 5; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.7f, + Colour = Colour4.Black, + }, + new GridContainer + { + Margin = new MarginPadding(5f), + AutoSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + // Info row + new Drawable[] + { + timestampLabel = new OsuSpriteText() + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + } + }, + // Mods row + new Drawable[] + { + modStatistics = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + } + }, + // Actual stats rows + new Drawable[] + { + topScoreStatistics = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + } + }, + new Drawable[] + { + bottomScoreStatistics = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + } + }, + } + } + }; + } + + private ScoreInfo currentScore; + + public void SetContent(ScoreInfo score) + { + if (currentScore == score) + return; + currentScore = score; + + timestampLabel.Text = $"Played on {score.Date.ToLocalTime():d MMMM yyyy HH:mm}"; + + modStatistics.Clear(); + topScoreStatistics.Clear(); + bottomScoreStatistics.Clear(); + + foreach (var mod in score.Mods) + { + modStatistics.Add(new ModCell(mod)); + } + + foreach (var result in score.GetStatisticsForDisplay()) + { + (result.Result > HitResult.Perfect + ? bottomScoreStatistics + : topScoreStatistics + ).Add(new HitResultCell(result)); + } + } + + protected override void PopIn() => this.FadeIn(20, Easing.OutQuint); + protected override void PopOut() => this.FadeOut(80, Easing.OutQuint); + + public void Move(Vector2 pos) => Position = pos; + + private class HitResultCell : CompositeDrawable + { + readonly private string DisplayName; + readonly private HitResult Result; + readonly private int Count; + + public HitResultCell(HitResultDisplayStatistic stat) + { + AutoSizeAxes = Axes.Both; + Padding = new MarginPadding{ Horizontal = 5f }; + + DisplayName = stat.DisplayName; + Result = stat.Result; + Count = stat.Count; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChild = new FillFlowContainer + { + Height = 12, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(2f, 0f), + Children = new Drawable[] + { + new CircularContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#222") + }, + new OsuSpriteText + { + Padding = new MarginPadding{ Horizontal = 2f }, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), + Text = DisplayName.ToUpperInvariant(), + Colour = colours.ForHitResult(Result), + } + } + }, + new OsuSpriteText + { + RelativeSizeAxes = Axes.Y, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + Text = Count.ToString(), + }, + } + }; + } + } + + private class ModCell : CompositeDrawable + { + readonly private Mod Mod; + + public ModCell(Mod mod) + { + AutoSizeAxes = Axes.Both; + Padding = new MarginPadding{ Horizontal = 5f }; + Mod = mod; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new FillFlowContainer + { + Height = 15, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(2f, 0f), + Children = new Drawable[] + { + new ModIcon(Mod, showTooltip: false).With(icon => + { + icon.Scale = new Vector2(15f / icon.Height); + }), + new OsuSpriteText + { + RelativeSizeAxes = Axes.Y, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + Text = Mod.SettingDescription, + } + } + }; + } + } + } +} From 502e6af008757204e53f04eb5cbfe7f586558989 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 26 Jan 2022 00:21:54 +0900 Subject: [PATCH 0439/5199] Remove PlayingUsers list from SpectatorClient --- .../Gameplay/TestSceneSpectatorPlayback.cs | 38 ------------------- ...TestSceneMultiplayerGameplayLeaderboard.cs | 2 +- osu.Game/Online/Spectator/SpectatorClient.cs | 10 ----- .../Dashboard/CurrentlyPlayingDisplay.cs | 30 +++++++-------- .../Visual/Spectator/TestSpectatorClient.cs | 7 ++-- 5 files changed, 19 insertions(+), 68 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 90abdf2ba3..75369661a0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -3,12 +3,9 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; -using System.Diagnostics; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -20,7 +17,6 @@ using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; -using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Replays; using osu.Game.Replays.Legacy; @@ -47,8 +43,6 @@ namespace osu.Game.Tests.Visual.Gameplay private Replay replay; - private readonly IBindableList users = new BindableList(); - private TestReplayRecorder recorder; private ManualClock manualClock; @@ -57,9 +51,6 @@ namespace osu.Game.Tests.Visual.Gameplay private TestFramedReplayInputHandler replayHandler; - [Resolved] - private IAPIProvider api { get; set; } - [Resolved] private SpectatorClient spectatorClient { get; set; } @@ -78,35 +69,6 @@ namespace osu.Game.Tests.Visual.Gameplay spectatorClient.OnNewFrames += onNewFrames; - users.BindTo(spectatorClient.PlayingUsers); - users.BindCollectionChanged((obj, args) => - { - switch (args.Action) - { - case NotifyCollectionChangedAction.Add: - Debug.Assert(args.NewItems != null); - - foreach (int user in args.NewItems) - { - if (user == api.LocalUser.Value.Id) - spectatorClient.WatchUser(user); - } - - break; - - case NotifyCollectionChangedAction.Remove: - Debug.Assert(args.OldItems != null); - - foreach (int user in args.OldItems) - { - if (user == api.LocalUser.Value.Id) - spectatorClient.StopWatchingUser(user); - } - - break; - } - }, true); - Children = new Drawable[] { new GridContainer diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 9b8e67b07a..8b71dfac21 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -115,7 +115,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void RandomlyUpdateState() { - foreach (int userId in PlayingUsers) + foreach ((int userId, _) in PlayingUserStates) { if (RNG.NextBool()) continue; diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index fddb94fad7..c3e70edcd3 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -37,9 +37,6 @@ namespace osu.Game.Online.Spectator private readonly List watchingUsers = new List(); - public IBindableList PlayingUsers => playingUsers; - private readonly BindableList playingUsers = new BindableList(); - public IBindableDictionary PlayingUserStates => playingUserStates; private readonly BindableDictionary playingUserStates = new BindableDictionary(); @@ -88,10 +85,7 @@ namespace osu.Game.Online.Spectator BeginPlayingInternal(currentState); } else - { - playingUsers.Clear(); playingUserStates.Clear(); - } }), true); } @@ -99,9 +93,6 @@ namespace osu.Game.Online.Spectator { Schedule(() => { - if (!playingUsers.Contains(userId)) - playingUsers.Add(userId); - // UserBeganPlaying() is called by the server regardless of whether the local user is watching the remote user, and is called a further time when the remote user is watched. // This may be a temporary thing (see: https://github.com/ppy/osu-server-spectator/blob/2273778e02cfdb4a9c6a934f2a46a8459cb5d29c/osu.Server.Spectator/Hubs/SpectatorHub.cs#L28-L29). // We don't want the user states to update unless the player is being watched, otherwise calling BindUserBeganPlaying() can lead to double invocations. @@ -118,7 +109,6 @@ namespace osu.Game.Online.Spectator { Schedule(() => { - playingUsers.Remove(userId); playingUserStates.Remove(userId); OnUserFinishedPlaying?.Invoke(userId, state); diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index fde20575fc..afbfb9f7ef 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -1,7 +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.Collections.Specialized; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Dashboard { internal class CurrentlyPlayingDisplay : CompositeDrawable { - private readonly IBindableList playingUsers = new BindableList(); + private readonly IBindableDictionary playingUserStates = new BindableDictionary(); private FillFlowContainer userFlow; @@ -51,18 +51,20 @@ namespace osu.Game.Overlays.Dashboard { base.LoadComplete(); - playingUsers.BindTo(spectatorClient.PlayingUsers); - playingUsers.BindCollectionChanged(onUsersChanged, true); + playingUserStates.BindTo(spectatorClient.PlayingUserStates); + playingUserStates.BindCollectionChanged(onUsersChanged, true); } - private void onUsersChanged(object sender, NotifyCollectionChangedEventArgs e) => Schedule(() => + private void onUsersChanged(object sender, NotifyDictionaryChangedEventArgs e) => Schedule(() => { switch (e.Action) { - case NotifyCollectionChangedAction.Add: - foreach (int id in e.NewItems.OfType().ToArray()) + case NotifyDictionaryChangedAction.Add: + Debug.Assert(e.NewItems != null); + + foreach ((int userId, _) in e.NewItems) { - users.GetUserAsync(id).ContinueWith(task => + users.GetUserAsync(userId).ContinueWith(task => { var user = task.GetResultSafely(); @@ -71,7 +73,7 @@ namespace osu.Game.Overlays.Dashboard Schedule(() => { // user may no longer be playing. - if (!playingUsers.Contains(user.Id)) + if (!playingUserStates.ContainsKey(user.Id)) return; userFlow.Add(createUserPanel(user)); @@ -81,13 +83,11 @@ namespace osu.Game.Overlays.Dashboard break; - case NotifyCollectionChangedAction.Remove: - foreach (int u in e.OldItems.OfType()) - userFlow.FirstOrDefault(card => card.User.Id == u)?.Expire(); - break; + case NotifyDictionaryChangedAction.Remove: + Debug.Assert(e.OldItems != null); - case NotifyCollectionChangedAction.Reset: - userFlow.Clear(); + foreach ((int userId, _) in e.OldItems) + userFlow.FirstOrDefault(card => card.User.Id == userId)?.Expire(); break; } }); diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index 1a1d493249..7848a825f4 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -42,7 +41,7 @@ namespace osu.Game.Tests.Visual.Spectator { OnNewFrames += (i, bundle) => { - if (PlayingUsers.Contains(i)) + if (PlayingUserStates.ContainsKey(i)) lastReceivedUserFrames[i] = bundle.Frames[^1]; }; } @@ -65,7 +64,7 @@ namespace osu.Game.Tests.Visual.Spectator /// The user to end play for. public void EndPlay(int userId) { - if (!PlayingUsers.Contains(userId)) + if (!PlayingUserStates.ContainsKey(userId)) return; ((ISpectatorClient)this).UserFinishedPlaying(userId, new SpectatorState @@ -131,7 +130,7 @@ namespace osu.Game.Tests.Visual.Spectator protected override Task WatchUserInternal(int userId) { // When newly watching a user, the server sends the playing state immediately. - if (PlayingUsers.Contains(userId)) + if (PlayingUserStates.ContainsKey(userId)) sendPlayingState(userId); return Task.CompletedTask; From c2b775c0a39cebce5cf2d2494266484f17223094 Mon Sep 17 00:00:00 2001 From: dekrain Date: Tue, 1 Feb 2022 06:45:59 +0100 Subject: [PATCH 0440/5199] Minor alignment adjustments --- .../Leaderboards/LeaderboardScoreTooltip.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 78064c33bc..bea589e47f 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -199,7 +199,8 @@ namespace osu.Game.Online.Leaderboards [BackgroundDependencyLoader] private void load() { - InternalChild = new FillFlowContainer + FillFlowContainer container; + InternalChild = container = new FillFlowContainer { Height = 15, AutoSizeAxes = Axes.X, @@ -209,16 +210,25 @@ namespace osu.Game.Online.Leaderboards { new ModIcon(Mod, showTooltip: false).With(icon => { + icon.Origin = Anchor.CentreLeft; + icon.Anchor = Anchor.CentreLeft; icon.Scale = new Vector2(15f / icon.Height); }), - new OsuSpriteText - { - RelativeSizeAxes = Axes.Y, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), - Text = Mod.SettingDescription, - } } }; + + string description = Mod.SettingDescription; + if (!string.IsNullOrEmpty(description)) + { + container.Add(new OsuSpriteText + { + RelativeSizeAxes = Axes.Y, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + Text = Mod.SettingDescription, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + }); + } } } } From 781cb9f18d55e21b153967ee389c1a45ca934e0a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 26 Jan 2022 01:45:11 +0900 Subject: [PATCH 0441/5199] Move HasPassed/HasFailed into GameplayState --- .../TestSceneTaikoSuddenDeath.cs | 2 +- .../Visual/Gameplay/TestSceneFailAnimation.cs | 2 +- .../Visual/Gameplay/TestSceneFailJudgement.cs | 2 +- .../Visual/Gameplay/TestScenePause.cs | 12 ++++++------ .../TestScenePlayerScoreSubmission.cs | 4 ++-- .../Gameplay/TestSceneStoryboardWithOutro.cs | 2 +- .../Navigation/TestSceneScreenNavigation.cs | 2 +- .../Multiplayer/MultiplayerPlayerLoader.cs | 2 +- osu.Game/Screens/Play/GameplayState.cs | 10 ++++++++++ osu.Game/Screens/Play/Player.cs | 19 ++++++------------- 10 files changed, 30 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs index 0be005e1c4..eec88d7bf8 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Player.ScoreProcessor.NewJudgement += b => judged = true; }); AddUntilStep("swell judged", () => judged); - AddAssert("failed", () => Player.HasFailed); + AddAssert("failed", () => Player.GameplayState.HasFailed); } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs index 7167d3120a..744227c55e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void AddCheckSteps() { - AddUntilStep("wait for fail", () => Player.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddUntilStep("wait for fail overlay", () => ((FailPlayer)Player).FailOverlay.State.Value == Visibility.Visible); // The pause screen and fail animation both ramp frequency. diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs index fa27e1abdd..6430c29dfa 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void AddCheckSteps() { - AddUntilStep("wait for fail", () => Player.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddUntilStep("wait for multiple judgements", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits > 1); AddAssert("total number of results == 1", () => { diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 04676f656f..ea0255ab76 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -185,7 +185,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestPauseAfterFail() { - AddUntilStep("wait for fail", () => Player.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddUntilStep("fail overlay shown", () => Player.FailOverlayVisible); confirmClockRunning(false); @@ -201,7 +201,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestExitFromFailedGameplayAfterFailAnimation() { - AddUntilStep("wait for fail", () => Player.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddUntilStep("wait for fail overlay shown", () => Player.FailOverlayVisible); confirmClockRunning(false); @@ -213,7 +213,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestExitFromFailedGameplayDuringFailAnimation() { - AddUntilStep("wait for fail", () => Player.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); // will finish the fail animation and show the fail/pause screen. AddStep("attempt exit via pause key", () => Player.ExitViaPause()); @@ -227,7 +227,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestQuickRetryFromFailedGameplay() { - AddUntilStep("wait for fail", () => Player.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddStep("quick retry", () => Player.GameplayClockContainer.ChildrenOfType().First().Action?.Invoke()); confirmExited(); @@ -236,7 +236,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestQuickExitFromFailedGameplay() { - AddUntilStep("wait for fail", () => Player.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddStep("quick exit", () => Player.GameplayClockContainer.ChildrenOfType().First().Action?.Invoke()); confirmExited(); @@ -341,7 +341,7 @@ namespace osu.Game.Tests.Visual.Gameplay { confirmClockRunning(false); confirmNotExited(); - AddAssert("player not failed", () => !Player.HasFailed); + AddAssert("player not failed", () => !Player.GameplayState.HasFailed); AddAssert("pause overlay shown", () => Player.PauseOverlayVisible); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index a9675a2ee2..58b5df2612 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -159,7 +159,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for token request", () => Player.TokenCreationRequested); - AddUntilStep("wait for fail", () => Player.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddStep("exit", () => Player.Exit()); AddAssert("ensure no submission", () => Player.SubmittedScore == null); @@ -176,7 +176,7 @@ namespace osu.Game.Tests.Visual.Gameplay addFakeHit(); - AddUntilStep("wait for fail", () => Player.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddStep("exit", () => Player.Exit()); AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index 69798dcb82..b87183cbc7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("set storyboard duration to 0.6s", () => currentStoryboardDuration = 600); }); - AddUntilStep("wait for fail", () => Player.HasFailed); + AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration); AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible); } diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index ed484e03f6..63984a1bed 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -209,7 +209,7 @@ namespace osu.Game.Tests.Visual.Navigation return (player = Game.ScreenStack.CurrentScreen as Player) != null; }); - AddUntilStep("wait for fail", () => player.HasFailed); + AddUntilStep("wait for fail", () => player.GameplayState.HasFailed); AddUntilStep("wait for track stop", () => !Game.MusicController.IsPlaying); AddAssert("Ensure time before preview point", () => Game.MusicController.CurrentTrack.CurrentTime < beatmap().BeatmapInfo.Metadata.PreviewTime); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayerLoader.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayerLoader.cs index 470ba59a76..772651727e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayerLoader.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayerLoader.cs @@ -9,7 +9,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { public class MultiplayerPlayerLoader : PlayerLoader { - public bool GameplayPassed => player?.GameplayPassed == true; + public bool GameplayPassed => player?.GameplayState.HasPassed == true; private Player player; diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs index 83881f739d..60fbfa6644 100644 --- a/osu.Game/Screens/Play/GameplayState.cs +++ b/osu.Game/Screens/Play/GameplayState.cs @@ -39,6 +39,16 @@ namespace osu.Game.Screens.Play /// public readonly Score Score; + /// + /// Whether gameplay completed without the user failing. + /// + public bool HasPassed { get; set; } + + /// + /// Whether the user failed during gameplay. + /// + public bool HasFailed { get; set; } + /// /// A bindable tracking the last judgement result applied to any hit object. /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0312789b12..814aacd2fa 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -72,15 +72,8 @@ namespace osu.Game.Screens.Play /// protected virtual bool PauseOnFocusLost => true; - /// - /// Whether gameplay has completed without the user having failed. - /// - public bool GameplayPassed { get; private set; } - public Action RestartRequested; - public bool HasFailed { get; private set; } - private Bindable mouseWheelDisabled; private readonly Bindable storyboardReplacesBackground = new Bindable(); @@ -559,7 +552,7 @@ namespace osu.Game.Screens.Play if (showDialogFirst && !pauseOrFailDialogVisible) { // if the fail animation is currently in progress, accelerate it (it will show the pause dialog on completion). - if (ValidForResume && HasFailed) + if (ValidForResume && GameplayState.HasFailed) { failAnimationLayer.FinishTransforms(true); return; @@ -678,7 +671,7 @@ namespace osu.Game.Screens.Play resultsDisplayDelegate?.Cancel(); resultsDisplayDelegate = null; - GameplayPassed = false; + GameplayState.HasPassed = false; ValidForResume = true; skipOutroOverlay.Hide(); return; @@ -688,7 +681,7 @@ namespace osu.Game.Screens.Play if (HealthProcessor.HasFailed) return; - GameplayPassed = true; + GameplayState.HasPassed = true; // Setting this early in the process means that even if something were to go wrong in the order of events following, there // is no chance that a user could return to the (already completed) Player instance from a child screen. @@ -804,7 +797,7 @@ namespace osu.Game.Screens.Play if (!CheckModsAllowFailure()) return false; - HasFailed = true; + GameplayState.HasFailed = true; Score.ScoreInfo.Passed = false; // There is a chance that we could be in a paused state as the ruleset's internal clock (see FrameStabilityContainer) @@ -859,13 +852,13 @@ namespace osu.Game.Screens.Play // replays cannot be paused and exit immediately && !DrawableRuleset.HasReplayLoaded.Value // cannot pause if we are already in a fail state - && !HasFailed; + && !GameplayState.HasFailed; private bool canResume => // cannot resume from a non-paused state GameplayClockContainer.IsPaused.Value // cannot resume if we are already in a fail state - && !HasFailed + && !GameplayState.HasFailed // already resuming && !IsResuming; From 38e075c522f72371742e4d02a948f7a53a0aac5d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 26 Jan 2022 02:02:31 +0900 Subject: [PATCH 0442/5199] Add HasQuit gameplay state --- osu.Game/Screens/Play/GameplayState.cs | 5 +++++ osu.Game/Screens/Play/Player.cs | 3 +++ 2 files changed, 8 insertions(+) diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs index 60fbfa6644..64e873b3bb 100644 --- a/osu.Game/Screens/Play/GameplayState.cs +++ b/osu.Game/Screens/Play/GameplayState.cs @@ -49,6 +49,11 @@ namespace osu.Game.Screens.Play /// public bool HasFailed { get; set; } + /// + /// Whether the user quit gameplay without either having either passed or failed. + /// + public bool HasQuit { get; set; } + /// /// A bindable tracking the last judgement result applied to any hit object. /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 814aacd2fa..f6a2310826 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -983,6 +983,9 @@ namespace osu.Game.Screens.Play public override bool OnExiting(IScreen next) { + if (!GameplayState.HasPassed && !GameplayState.HasFailed) + GameplayState.HasQuit = true; + screenSuspension?.RemoveAndDisposeImmediately(); failAnimationLayer?.RemoveFilters(); From fd287e06f2bbe0f1115961184f837f0cd30927fe Mon Sep 17 00:00:00 2001 From: dekrain Date: Tue, 1 Feb 2022 06:51:00 +0100 Subject: [PATCH 0443/5199] Add missing license header --- osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index bea589e47f..ea02c238f3 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -1,3 +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 osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Containers; From d7b939277e81d99eb36aeb25f9aa71846b8b90c6 Mon Sep 17 00:00:00 2001 From: dekrain Date: Tue, 1 Feb 2022 07:10:00 +0100 Subject: [PATCH 0444/5199] Code quality improvements --- .../Leaderboards/LeaderboardScoreTooltip.cs | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index ea02c238f3..0b2de3e2c8 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -19,10 +19,10 @@ namespace osu.Game.Online.Leaderboards { public class LeaderboardScoreTooltip : VisibilityContainer, ITooltip { - private OsuSpriteText timestampLabel; - private FillFlowContainer topScoreStatistics; - private FillFlowContainer bottomScoreStatistics; - private FillFlowContainer modStatistics; + private readonly OsuSpriteText timestampLabel; + private readonly FillFlowContainer topScoreStatistics; + private readonly FillFlowContainer bottomScoreStatistics; + private readonly FillFlowContainer modStatistics; public LeaderboardScoreTooltip() { @@ -58,7 +58,7 @@ namespace osu.Game.Online.Leaderboards // Info row new Drawable[] { - timestampLabel = new OsuSpriteText() + timestampLabel = new OsuSpriteText { Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), } @@ -98,8 +98,9 @@ namespace osu.Game.Online.Leaderboards public void SetContent(ScoreInfo score) { - if (currentScore == score) + if (currentScore.Equals(score)) return; + currentScore = score; timestampLabel.Text = $"Played on {score.Date.ToLocalTime():d MMMM yyyy HH:mm}"; @@ -116,9 +117,9 @@ namespace osu.Game.Online.Leaderboards foreach (var result in score.GetStatisticsForDisplay()) { (result.Result > HitResult.Perfect - ? bottomScoreStatistics - : topScoreStatistics - ).Add(new HitResultCell(result)); + ? bottomScoreStatistics + : topScoreStatistics + ).Add(new HitResultCell(result)); } } @@ -129,9 +130,9 @@ namespace osu.Game.Online.Leaderboards private class HitResultCell : CompositeDrawable { - readonly private string DisplayName; - readonly private HitResult Result; - readonly private int Count; + private readonly string DisplayName; + private readonly HitResult Result; + private readonly int Count; public HitResultCell(HitResultDisplayStatistic stat) { @@ -190,7 +191,7 @@ namespace osu.Game.Online.Leaderboards private class ModCell : CompositeDrawable { - readonly private Mod Mod; + private readonly Mod Mod; public ModCell(Mod mod) { From e1b57c4bf61d97070021f154a25cbd802b3133dc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 16:07:57 +0900 Subject: [PATCH 0445/5199] Fix inspections --- .../Leaderboards/LeaderboardScoreTooltip.cs | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 0b2de3e2c8..7825a50a0b 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -119,7 +119,7 @@ namespace osu.Game.Online.Leaderboards (result.Result > HitResult.Perfect ? bottomScoreStatistics : topScoreStatistics - ).Add(new HitResultCell(result)); + ).Add(new HitResultCell(result)); } } @@ -130,18 +130,18 @@ namespace osu.Game.Online.Leaderboards private class HitResultCell : CompositeDrawable { - private readonly string DisplayName; - private readonly HitResult Result; - private readonly int Count; + private readonly string displayName; + private readonly HitResult result; + private readonly int count; public HitResultCell(HitResultDisplayStatistic stat) { AutoSizeAxes = Axes.Both; - Padding = new MarginPadding{ Horizontal = 5f }; + Padding = new MarginPadding { Horizontal = 5f }; - DisplayName = stat.DisplayName; - Result = stat.Result; - Count = stat.Count; + displayName = stat.DisplayName; + result = stat.Result; + count = stat.Count; } [BackgroundDependencyLoader] @@ -169,12 +169,12 @@ namespace osu.Game.Online.Leaderboards }, new OsuSpriteText { - Padding = new MarginPadding{ Horizontal = 2f }, + Padding = new MarginPadding { Horizontal = 2f }, Anchor = Anchor.Centre, Origin = Anchor.Centre, Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), - Text = DisplayName.ToUpperInvariant(), - Colour = colours.ForHitResult(Result), + Text = displayName.ToUpperInvariant(), + Colour = colours.ForHitResult(result), } } }, @@ -182,7 +182,7 @@ namespace osu.Game.Online.Leaderboards { RelativeSizeAxes = Axes.Y, Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), - Text = Count.ToString(), + Text = count.ToString(), }, } }; @@ -191,13 +191,13 @@ namespace osu.Game.Online.Leaderboards private class ModCell : CompositeDrawable { - private readonly Mod Mod; + private readonly Mod mod; public ModCell(Mod mod) { AutoSizeAxes = Axes.Both; - Padding = new MarginPadding{ Horizontal = 5f }; - Mod = mod; + Padding = new MarginPadding { Horizontal = 5f }; + this.mod = mod; } [BackgroundDependencyLoader] @@ -212,7 +212,7 @@ namespace osu.Game.Online.Leaderboards Spacing = new Vector2(2f, 0f), Children = new Drawable[] { - new ModIcon(Mod, showTooltip: false).With(icon => + new ModIcon(mod, showTooltip: false).With(icon => { icon.Origin = Anchor.CentreLeft; icon.Anchor = Anchor.CentreLeft; @@ -221,14 +221,15 @@ namespace osu.Game.Online.Leaderboards } }; - string description = Mod.SettingDescription; + string description = mod.SettingDescription; + if (!string.IsNullOrEmpty(description)) { container.Add(new OsuSpriteText { RelativeSizeAxes = Axes.Y, Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), - Text = Mod.SettingDescription, + Text = mod.SettingDescription, Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, }); From 855135c51e63f8a2095e7ae5dc8402a5df1b1920 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 16:13:27 +0900 Subject: [PATCH 0446/5199] Fix potential nullref during display due to incorrect equality check --- osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 7825a50a0b..85db59b3ea 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -15,6 +15,8 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; +#nullable enable + namespace osu.Game.Online.Leaderboards { public class LeaderboardScoreTooltip : VisibilityContainer, ITooltip @@ -94,14 +96,14 @@ namespace osu.Game.Online.Leaderboards }; } - private ScoreInfo currentScore; + private ScoreInfo? displayedScore; public void SetContent(ScoreInfo score) { - if (currentScore.Equals(score)) + if (displayedScore?.Equals(score) == true) return; - currentScore = score; + displayedScore = score; timestampLabel.Text = $"Played on {score.Date.ToLocalTime():d MMMM yyyy HH:mm}"; From fdb52a8fd70105516a2b4fb32cb6b3b4657d98ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 16:20:40 +0900 Subject: [PATCH 0447/5199] Remove gap in tooltip display between statistics --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 7779043ccd..b4aaebd4e4 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -186,7 +186,6 @@ namespace osu.Game.Online.Leaderboards Anchor = Anchor.BottomLeft, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Spacing = new Vector2(10f, 0f), Margin = new MarginPadding { Left = edge_margin }, Children = statisticsLabels }, @@ -231,7 +230,6 @@ namespace osu.Game.Online.Leaderboards Origin = Anchor.BottomRight, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Spacing = new Vector2(1), ChildrenEnumerable = Score.Mods.Select(mod => new ModIcon(mod) { Scale = new Vector2(0.375f) }) }, }, @@ -316,6 +314,7 @@ namespace osu.Game.Online.Leaderboards { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, + Padding = new MarginPadding { Right = 10 }, Children = new Drawable[] { new Container From 3ca2c906842209fe36e6d67ee7ace35f2cfad747 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 16:35:05 +0900 Subject: [PATCH 0448/5199] Add test scores in `BeatmapLeaderboard` test scene with more mods --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 48230ff9e9..7292ec96ed 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -197,7 +197,22 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] + { + new OsuModHidden(), + new OsuModHardRock(), + new OsuModFlashlight + { + FollowDelay = { Value = 200 } + }, + new OsuModDifficultyAdjust + { + CircleSize = { Value = 8 }, + ApproachRate = { Value = 7 }, + OverallDifficulty = { Value = 6 }, + DrainRate = { Value = 5 }, + } + }, Ruleset = new OsuRuleset().RulesetInfo, BeatmapInfo = beatmapInfo, User = new APIUser @@ -217,7 +232,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, User = new APIUser @@ -237,7 +252,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -258,7 +273,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -279,7 +294,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -300,7 +315,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.9826, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -321,7 +336,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.9654, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -342,7 +357,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.6025, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -363,7 +378,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.5140, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -384,7 +399,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.4222, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, From 8eace12fe3b5b585657cfec9ad733c129dd4d73a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 16:35:22 +0900 Subject: [PATCH 0449/5199] Synchronise (roughly) backgrounds of all custom tooltips --- .../Drawables/DifficultyIconTooltip.cs | 1 + .../Leaderboards/LeaderboardScoreTooltip.cs | 29 ++++++++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index ec4bcbd65f..aba01a1294 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -30,6 +30,7 @@ namespace osu.Game.Beatmaps.Drawables { background = new Box { + Alpha = 0.9f, RelativeSizeAxes = Axes.Both }, new FillFlowContainer diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 85db59b3ea..7b88c0d534 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -21,24 +21,31 @@ namespace osu.Game.Online.Leaderboards { public class LeaderboardScoreTooltip : VisibilityContainer, ITooltip { - private readonly OsuSpriteText timestampLabel; - private readonly FillFlowContainer topScoreStatistics; - private readonly FillFlowContainer bottomScoreStatistics; - private readonly FillFlowContainer modStatistics; + private OsuSpriteText timestampLabel = null!; + private FillFlowContainer topScoreStatistics = null!; + private FillFlowContainer bottomScoreStatistics = null!; + private FillFlowContainer modStatistics = null!; public LeaderboardScoreTooltip() { AutoSizeAxes = Axes.Both; + AutoSizeDuration = 200; + AutoSizeEasing = Easing.OutQuint; + Masking = true; CornerRadius = 5; + } + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { InternalChildren = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Alpha = 0.7f, - Colour = Colour4.Black, + Alpha = 0.9f, + Colour = colours.Gray3, }, new GridContainer { @@ -118,10 +125,10 @@ namespace osu.Game.Online.Leaderboards foreach (var result in score.GetStatisticsForDisplay()) { - (result.Result > HitResult.Perfect - ? bottomScoreStatistics - : topScoreStatistics - ).Add(new HitResultCell(result)); + if (result.Result > HitResult.Perfect) + bottomScoreStatistics.Add(new HitResultCell(result)); + else + topScoreStatistics.Add(new HitResultCell(result)); } } @@ -171,7 +178,7 @@ namespace osu.Game.Online.Leaderboards }, new OsuSpriteText { - Padding = new MarginPadding { Horizontal = 2f }, + Padding = new MarginPadding(2), Anchor = Anchor.Centre, Origin = Anchor.Centre, Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), From f87920cd83d060542ac77784fdefef09f9521348 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 16:43:26 +0900 Subject: [PATCH 0450/5199] Remove unnecessary `GridContainer` and list mods verticall to give more space --- .../Leaderboards/LeaderboardScoreTooltip.cs | 61 ++++++++----------- 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 7b88c0d534..8c349d56e0 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -47,55 +47,42 @@ namespace osu.Game.Online.Leaderboards Alpha = 0.9f, Colour = colours.Gray3, }, - new GridContainer + new FillFlowContainer { Margin = new MarginPadding(5f), + Spacing = new Vector2(10), AutoSizeAxes = Axes.Both, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - }, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] + Direction = FillDirection.Vertical, + Children = new Drawable[] { // Info row - new Drawable[] + timestampLabel = new OsuSpriteText { - timestampLabel = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), - } + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), }, // Mods row - new Drawable[] + modStatistics = new FillFlowContainer { - modStatistics = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - } + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, }, - // Actual stats rows - new Drawable[] + new FillFlowContainer { - topScoreStatistics = new FillFlowContainer + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - } - }, - new Drawable[] - { - bottomScoreStatistics = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, + // Actual stats rows + topScoreStatistics = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + }, + bottomScoreStatistics = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + }, } }, } From 0f83f77d2b3fc8de0d615361e53130f5958c94c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 16:52:53 +0900 Subject: [PATCH 0451/5199] Add xmldoc for new `ResetFromReplayFrame` method --- osu.Game/Rulesets/Scoring/JudgementProcessor.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs index f38155240e..a643c31daa 100644 --- a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs +++ b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs @@ -108,6 +108,14 @@ namespace osu.Game.Rulesets.Scoring JudgedHits = 0; } + /// + /// Reset all statistics based on header information contained within a replay frame. + /// + /// + /// If the provided replay frame does not have any header information, this will be a noop. + /// + /// The ruleset to be used for retrieving statistics. + /// The replay frame to read header statistics from. public virtual void ResetFromReplayFrame(Ruleset ruleset, ReplayFrame frame) { if (frame.Header == null) From 15479ae046ebf96b22367565333622a573dd59ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 16:55:28 +0900 Subject: [PATCH 0452/5199] Add test coverage of no header doing nothing --- osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index bcaa70d062..70ba868de6 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -60,6 +60,12 @@ namespace osu.Game.Tests.Gameplay Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000)); Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1)); + // No header shouldn't cause any change + scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame()); + + Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000)); + Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1)); + // Reset with a miss instead. scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame { From bfe6218ed51c6451f85046998e225de2bdcbe7e6 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Tue, 1 Feb 2022 12:43:58 +0100 Subject: [PATCH 0453/5199] Change default orientation to SensorLandscape --- osu.Android/GameplayScreenRotationLocker.cs | 2 +- osu.Android/OsuGameActivity.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Android/GameplayScreenRotationLocker.cs b/osu.Android/GameplayScreenRotationLocker.cs index 25bd659a5d..ab91b4e3b3 100644 --- a/osu.Android/GameplayScreenRotationLocker.cs +++ b/osu.Android/GameplayScreenRotationLocker.cs @@ -27,7 +27,7 @@ namespace osu.Android { gameActivity.RunOnUiThread(() => { - gameActivity.RequestedOrientation = userPlaying.NewValue ? ScreenOrientation.Locked : ScreenOrientation.FullUser; + gameActivity.RequestedOrientation = userPlaying.NewValue ? ScreenOrientation.Locked : OsuGameActivity.DEFAULT_ORIENTATION; }); } } diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index c9fb539d8a..e6679b61a6 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -17,7 +17,7 @@ using osu.Game.Database; namespace osu.Android { - [Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser)] + [Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true, ScreenOrientation = DEFAULT_ORIENTATION)] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osr", DataHost = "*", DataMimeType = "*/*")] @@ -39,6 +39,8 @@ namespace osu.Android [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })] public class OsuGameActivity : AndroidGameActivity { + public const ScreenOrientation DEFAULT_ORIENTATION = ScreenOrientation.SensorLandscape; + private static readonly string[] osu_url_schemes = { "osu", "osump" }; private OsuGameAndroid game; From 41007169f722febe2217ff805cf95cdb6d81c3dd Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 1 Feb 2022 15:51:41 +0900 Subject: [PATCH 0454/5199] Give SpectatorState a user state --- .../Visual/Gameplay/TestSceneSpectator.cs | 2 +- .../Online/Spectator/SpectatingUserState.cs | 33 +++++++++++++++++++ osu.Game/Online/Spectator/SpectatorClient.cs | 10 +++++- osu.Game/Online/Spectator/SpectatorState.cs | 7 ++-- osu.Game/Rulesets/UI/ReplayRecorder.cs | 4 ++- osu.Game/Screens/Play/Player.cs | 2 +- 6 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Online/Spectator/SpectatingUserState.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 917b3c89a8..86d6de6975 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -211,7 +211,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("send frames and finish play", () => { spectatorClient.HandleFrame(new OsuReplayFrame(1000, Vector2.Zero)); - spectatorClient.EndPlaying(); + spectatorClient.EndPlaying(new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()) { HasPassed = true }); }); // We can't access API because we're an "online" test. diff --git a/osu.Game/Online/Spectator/SpectatingUserState.cs b/osu.Game/Online/Spectator/SpectatingUserState.cs new file mode 100644 index 0000000000..c7ba4ba248 --- /dev/null +++ b/osu.Game/Online/Spectator/SpectatingUserState.cs @@ -0,0 +1,33 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Online.Spectator +{ + public enum SpectatingUserState + { + /// + /// The spectated user has not yet played. + /// + Idle, + + /// + /// The spectated user is currently playing. + /// + Playing, + + /// + /// The spectated user has successfully completed gameplay. + /// + Completed, + + /// + /// The spectator user has failed during gameplay. + /// + Failed, + + /// + /// The spectated user has quit during gameplay. + /// + Quit + } +} diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index c3e70edcd3..f4161e1db9 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -138,6 +138,7 @@ namespace osu.Game.Online.Spectator currentState.BeatmapID = score.ScoreInfo.BeatmapInfo.OnlineID; currentState.RulesetID = score.ScoreInfo.RulesetID; currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray(); + currentState.State = SpectatingUserState.Playing; currentBeatmap = state.Beatmap; currentScore = score; @@ -148,7 +149,7 @@ namespace osu.Game.Online.Spectator public void SendFrames(FrameDataBundle data) => lastSend = SendFramesInternal(data); - public void EndPlaying() + public void EndPlaying(GameplayState state) { // This method is most commonly called via Dispose(), which is can be asynchronous (via the AsyncDisposalQueue). // We probably need to find a better way to handle this... @@ -163,6 +164,13 @@ namespace osu.Game.Online.Spectator IsPlaying = false; currentBeatmap = null; + if (state.HasPassed) + currentState.State = SpectatingUserState.Completed; + else if (state.HasFailed) + currentState.State = SpectatingUserState.Failed; + else + currentState.State = SpectatingUserState.Quit; + EndPlayingInternal(currentState); }); } diff --git a/osu.Game/Online/Spectator/SpectatorState.cs b/osu.Game/Online/Spectator/SpectatorState.cs index ebb91e4dd2..fc62f16bba 100644 --- a/osu.Game/Online/Spectator/SpectatorState.cs +++ b/osu.Game/Online/Spectator/SpectatorState.cs @@ -24,14 +24,17 @@ namespace osu.Game.Online.Spectator [Key(2)] public IEnumerable Mods { get; set; } = Enumerable.Empty(); + [Key(3)] + public SpectatingUserState State { get; set; } + public bool Equals(SpectatorState other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return BeatmapID == other.BeatmapID && Mods.SequenceEqual(other.Mods) && RulesetID == other.RulesetID; + return BeatmapID == other.BeatmapID && Mods.SequenceEqual(other.Mods) && RulesetID == other.RulesetID && State == other.State; } - public override string ToString() => $"Beatmap:{BeatmapID} Mods:{string.Join(',', Mods)} Ruleset:{RulesetID}"; + public override string ToString() => $"Beatmap:{BeatmapID} Mods:{string.Join(',', Mods)} Ruleset:{RulesetID} State:{State}"; } } diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index 976f95cef8..277040b2a6 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -55,7 +55,9 @@ namespace osu.Game.Rulesets.UI protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - spectatorClient?.EndPlaying(); + + if (spectatorClient != null && gameplayState != null) + spectatorClient.EndPlaying(gameplayState); } protected override void Update() diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index f6a2310826..1aa6cded48 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1000,7 +1000,7 @@ namespace osu.Game.Screens.Play // EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous. // To resolve test failures, forcefully end playing synchronously when this screen exits. // Todo: Replace this with a more permanent solution once osu-framework has a synchronous cleanup method. - spectatorClient.EndPlaying(); + spectatorClient.EndPlaying(GameplayState); // GameplayClockContainer performs seeks / start / stop operations on the beatmap's track. // as we are no longer the current screen, we cannot guarantee the track is still usable. From f4210f7a302a3bad2130181800106cdcefeb2020 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 26 Jan 2022 22:21:07 +0900 Subject: [PATCH 0455/5199] Rework spectator components to use new user state --- osu.Game/Online/Spectator/SpectatorClient.cs | 8 +--- .../Dashboard/CurrentlyPlayingDisplay.cs | 42 ++++++++++++------- osu.Game/Screens/Spectate/SpectatorScreen.cs | 33 ++++++++------- 3 files changed, 45 insertions(+), 38 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index f4161e1db9..fb7526347b 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -93,12 +93,8 @@ namespace osu.Game.Online.Spectator { Schedule(() => { - // UserBeganPlaying() is called by the server regardless of whether the local user is watching the remote user, and is called a further time when the remote user is watched. - // This may be a temporary thing (see: https://github.com/ppy/osu-server-spectator/blob/2273778e02cfdb4a9c6a934f2a46a8459cb5d29c/osu.Server.Spectator/Hubs/SpectatorHub.cs#L28-L29). - // We don't want the user states to update unless the player is being watched, otherwise calling BindUserBeganPlaying() can lead to double invocations. if (watchingUsers.Contains(userId)) playingUserStates[userId] = state; - OnUserBeganPlaying?.Invoke(userId, state); }); @@ -109,8 +105,8 @@ namespace osu.Game.Online.Spectator { Schedule(() => { - playingUserStates.Remove(userId); - + if (watchingUsers.Contains(userId)) + playingUserStates[userId] = state; OnUserFinishedPlaying?.Invoke(userId, state); }); diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index afbfb9f7ef..383f17d8d2 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Dashboard { internal class CurrentlyPlayingDisplay : CompositeDrawable { - private readonly IBindableDictionary playingUserStates = new BindableDictionary(); + private readonly IBindableDictionary userStates = new BindableDictionary(); private FillFlowContainer userFlow; @@ -51,33 +51,32 @@ namespace osu.Game.Overlays.Dashboard { base.LoadComplete(); - playingUserStates.BindTo(spectatorClient.PlayingUserStates); - playingUserStates.BindCollectionChanged(onUsersChanged, true); + userStates.BindTo(spectatorClient.PlayingUserStates); + userStates.BindCollectionChanged(onUserStatesChanged, true); } - private void onUsersChanged(object sender, NotifyDictionaryChangedEventArgs e) => Schedule(() => + private void onUserStatesChanged(object sender, NotifyDictionaryChangedEventArgs e) => Schedule(() => { switch (e.Action) { case NotifyDictionaryChangedAction.Add: + case NotifyDictionaryChangedAction.Replace: Debug.Assert(e.NewItems != null); - foreach ((int userId, _) in e.NewItems) + foreach ((int userId, SpectatorState state) in e.NewItems) { + if (state.State != SpectatingUserState.Playing) + { + removePlayingUser(userId); + continue; + } + users.GetUserAsync(userId).ContinueWith(task => { var user = task.GetResultSafely(); - if (user == null) return; - - Schedule(() => - { - // user may no longer be playing. - if (!playingUserStates.ContainsKey(user.Id)) - return; - - userFlow.Add(createUserPanel(user)); - }); + if (user != null) + Schedule(() => addPlayingUser(user)); }); } @@ -87,9 +86,20 @@ namespace osu.Game.Overlays.Dashboard Debug.Assert(e.OldItems != null); foreach ((int userId, _) in e.OldItems) - userFlow.FirstOrDefault(card => card.User.Id == userId)?.Expire(); + removePlayingUser(userId); break; } + + void addPlayingUser(APIUser user) + { + // user may no longer be playing. + if (!userStates.TryGetValue(user.Id, out var state2) || state2.State != SpectatingUserState.Playing) + return; + + userFlow.Add(createUserPanel(user)); + } + + void removePlayingUser(int userId) => userFlow.FirstOrDefault(card => card.User.Id == userId)?.Expire(); }); private PlayingUserPanel createUserPanel(APIUser user) => diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 3cf9f79611..783ba494eb 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Spectate [Resolved] private UserLookupCache userLookupCache { get; set; } - private readonly IBindableDictionary playingUserStates = new BindableDictionary(); + private readonly IBindableDictionary userStates = new BindableDictionary(); private readonly Dictionary userMap = new Dictionary(); private readonly Dictionary gameplayStates = new Dictionary(); @@ -77,8 +77,8 @@ namespace osu.Game.Screens.Spectate userMap[u.Id] = u; } - playingUserStates.BindTo(spectatorClient.PlayingUserStates); - playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true); + userStates.BindTo(spectatorClient.PlayingUserStates); + userStates.BindCollectionChanged(onUserStatesChanged, true); realmSubscription = realm.RegisterForNotifications( realm => realm.All().Where(s => !s.DeletePending), beatmapsChanged); @@ -99,7 +99,7 @@ namespace osu.Game.Screens.Spectate { foreach ((int userId, _) in userMap) { - if (!playingUserStates.TryGetValue(userId, out var userState)) + if (!userStates.TryGetValue(userId, out var userState)) continue; if (beatmapSet.Beatmaps.Any(b => b.OnlineID == userState.BeatmapID)) @@ -107,40 +107,41 @@ namespace osu.Game.Screens.Spectate } } - private void onPlayingUserStatesChanged(object sender, NotifyDictionaryChangedEventArgs e) + private void onUserStatesChanged(object sender, NotifyDictionaryChangedEventArgs e) { switch (e.Action) { case NotifyDictionaryChangedAction.Add: foreach ((int userId, var state) in e.NewItems.AsNonNull()) - onUserStateAdded(userId, state); + onUserStateChanged(userId, state); break; case NotifyDictionaryChangedAction.Remove: - foreach ((int userId, var _) in e.OldItems.AsNonNull()) + foreach ((int userId, _) in e.OldItems.AsNonNull()) onUserStateRemoved(userId); break; case NotifyDictionaryChangedAction.Replace: - foreach ((int userId, var _) in e.OldItems.AsNonNull()) - onUserStateRemoved(userId); - foreach ((int userId, var state) in e.NewItems.AsNonNull()) - onUserStateAdded(userId, state); + onUserStateChanged(userId, state); break; } } - private void onUserStateAdded(int userId, SpectatorState state) + private void onUserStateChanged(int userId, SpectatorState newState) { - if (state.RulesetID == null || state.BeatmapID == null) + if (newState.RulesetID == null || newState.BeatmapID == null) return; if (!userMap.ContainsKey(userId)) return; - Schedule(() => OnUserStateChanged(userId, state)); - updateGameplayState(userId); + // Do nothing for failed/completed states. + if (newState.State == SpectatingUserState.Playing) + { + Schedule(() => OnUserStateChanged(userId, newState)); + updateGameplayState(userId); + } } private void onUserStateRemoved(int userId) @@ -162,7 +163,7 @@ namespace osu.Game.Screens.Spectate Debug.Assert(userMap.ContainsKey(userId)); var user = userMap[userId]; - var spectatorState = playingUserStates[userId]; + var spectatorState = userStates[userId]; var resolvedRuleset = rulesets.AvailableRulesets.FirstOrDefault(r => r.OnlineID == spectatorState.RulesetID)?.CreateInstance(); if (resolvedRuleset == null) From b75c08c9ab07232d44b1bde38f68f2054547b91c Mon Sep 17 00:00:00 2001 From: Spooghetti420 <83249561+Spooghetti420@users.noreply.github.com> Date: Tue, 1 Feb 2022 13:36:36 +0000 Subject: [PATCH 0456/5199] Improve beat length logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index 1abca970d5..6e942f672b 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -65,8 +65,8 @@ namespace osu.Game.Rulesets.Mania.Mods public static double GetNoteDurationInBeatLength(HoldNote holdNote, ManiaBeatmap beatmap) { - double bpmAtNoteTime = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BPM; - return (60 * holdNote.Duration) / (1000 * bpmAtNoteTime); + double beatLength = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BeatLength; + return holdNote.Duration / beatLength; } } } From 39524f3dd296d44cfb8187507a416e02a47b040c Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 1 Feb 2022 22:26:30 +0800 Subject: [PATCH 0457/5199] Split total pp into 2 lines --- .../Statistics/PerformanceStatisticTooltip.cs | 85 ++++++++++++++++--- 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs index 44e5c366bb..739fac9d63 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs @@ -72,16 +72,73 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics UpdateDisplay(performance); } - private Drawable createAttributeItem(PerformanceDisplayAttribute attribute, PerformanceDisplayAttribute perfectAttribute) + private Drawable createItemForTotal(PerformanceDisplayAttribute attribute, PerformanceDisplayAttribute perfectAttribute) { - bool isTotal = attribute.PropertyName == nameof(PerformanceAttributes.Total); - float fraction = (float)(attribute.Value / perfectAttribute.Value); - if (float.IsNaN(fraction)) - fraction = 0; - string text = fraction.ToLocalisableString("0%").ToString(); + return + new GridContainer + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Bottom = 10 }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 250), + new Dimension(GridSizeMode.AutoSize) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] + { + new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Font = OsuFont.GetFont(weight: FontWeight.Regular), + Text = attribute.DisplayName, + Colour = titleColor + }, + new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Font = OsuFont.GetFont(weight: FontWeight.SemiBold), + Text = Math.Round(attribute.Value, MidpointRounding.AwayFromZero).ToLocalisableString(), + Colour = titleColor + } + }, + new Drawable[] + { + new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Font = OsuFont.GetFont(weight: FontWeight.Regular), + Text = "Maximum", + Colour = OsuColour.Gray(0.7f) + }, + new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Font = OsuFont.GetFont(weight: FontWeight.Regular), + Text = Math.Round(perfectAttribute.Value, MidpointRounding.AwayFromZero).ToLocalisableString(), + Colour = OsuColour.Gray(0.7f) + } + } + } + }; + } - if (isTotal) - text = (int)Math.Round(attribute.Value, MidpointRounding.AwayFromZero) + "/" + (int)Math.Round(perfectAttribute.Value, MidpointRounding.AwayFromZero); + private Drawable createItemForAttribute(PerformanceDisplayAttribute attribute, PerformanceDisplayAttribute perfectAttribute) + { + float percentage = (float)(attribute.Value / perfectAttribute.Value); + if (float.IsNaN(percentage)) + percentage = 0; + string text = percentage.ToLocalisableString("0%").ToString(); return new GridContainer { @@ -106,7 +163,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Anchor = Anchor.CentreLeft, Font = OsuFont.GetFont(weight: FontWeight.Regular), Text = attribute.DisplayName, - Colour = isTotal ? titleColor : textColour + Colour = textColour }, new Bar { @@ -115,8 +172,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Width = 130, Height = 5, BackgroundColour = Color4.White.Opacity(0.5f), - Colour = isTotal ? titleColor : textColour, - Length = fraction, + Colour = textColour, + Length = percentage, Margin = new MarginPadding { Left = 5, Right = 5 } }, new OsuSpriteText @@ -125,7 +182,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Anchor = Anchor.CentreLeft, Font = OsuFont.GetFont(weight: FontWeight.SemiBold), Text = text, - Colour = isTotal ? titleColor : textColour + Colour = textColour } } } @@ -142,7 +199,9 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics foreach (PerformanceDisplayAttribute attr in displayAttributes) { - Content.Add(createAttributeItem(attr, perfectDisplayAttributes.First(a => a.PropertyName == attr.PropertyName))); + Content.Add(attr.PropertyName == nameof(PerformanceAttributes.Total) + ? createItemForTotal(attr, perfectDisplayAttributes.First(a => a.PropertyName == attr.PropertyName)) + : createItemForAttribute(attr, perfectDisplayAttributes.First(a => a.PropertyName == attr.PropertyName))); } } From b06128ffa5f4845bd0c9ae6dabe11120336b5cf4 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 1 Feb 2022 22:26:55 +0800 Subject: [PATCH 0458/5199] Rename "Final PP" to "Achieved PP" --- osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs b/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs index f210c669a6..e8c4c71913 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Difficulty /// public virtual IEnumerable GetAttributesForDisplay() { - yield return new PerformanceDisplayAttribute(nameof(Total), "Final PP", Total); + yield return new PerformanceDisplayAttribute(nameof(Total), "Achieved PP", Total); } } } From eee020f8e4a17db7a1af6be611cbc0b3c5554852 Mon Sep 17 00:00:00 2001 From: dekrain Date: Tue, 1 Feb 2022 20:26:52 +0100 Subject: [PATCH 0459/5199] Cleanup tooltip layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../Leaderboards/LeaderboardScoreTooltip.cs | 42 ++++++------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 8c349d56e0..a0eea94501 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -10,7 +10,6 @@ using osuTK; using osu.Game.Graphics.Sprites; using osu.Game.Graphics; using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; @@ -49,7 +48,7 @@ namespace osu.Game.Online.Leaderboards }, new FillFlowContainer { - Margin = new MarginPadding(5f), + Margin = new MarginPadding(5), Spacing = new Vector2(10), AutoSizeAxes = Axes.Both, Direction = FillDirection.Vertical, @@ -63,8 +62,10 @@ namespace osu.Game.Online.Leaderboards // Mods row modStatistics = new FillFlowContainer { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Full, + Spacing = new Vector2(5, 0), }, new FillFlowContainer { @@ -77,11 +78,13 @@ namespace osu.Game.Online.Leaderboards { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), }, bottomScoreStatistics = new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), }, } }, @@ -133,7 +136,6 @@ namespace osu.Game.Online.Leaderboards public HitResultCell(HitResultDisplayStatistic stat) { AutoSizeAxes = Axes.Both; - Padding = new MarginPadding { Horizontal = 5f }; displayName = stat.DisplayName; result = stat.Result; @@ -148,35 +150,17 @@ namespace osu.Game.Online.Leaderboards Height = 12, AutoSizeAxes = Axes.X, Direction = FillDirection.Horizontal, - Spacing = new Vector2(2f, 0f), + Spacing = new Vector2(5f, 0f), Children = new Drawable[] { - new CircularContainer + new OsuSpriteText { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("#222") - }, - new OsuSpriteText - { - Padding = new MarginPadding(2), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), - Text = displayName.ToUpperInvariant(), - Colour = colours.ForHitResult(result), - } - } + Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), + Text = displayName.ToUpperInvariant(), + Colour = colours.ForHitResult(result), }, new OsuSpriteText { - RelativeSizeAxes = Axes.Y, Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), Text = count.ToString(), }, @@ -192,7 +176,6 @@ namespace osu.Game.Online.Leaderboards public ModCell(Mod mod) { AutoSizeAxes = Axes.Both; - Padding = new MarginPadding { Horizontal = 5f }; this.mod = mod; } @@ -228,6 +211,7 @@ namespace osu.Game.Online.Leaderboards Text = mod.SettingDescription, Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, + Margin = new MarginPadding { Top = 1 }, }); } } From 3d7af805a3cd0905fef3bfc15544330d3bdcd912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:16:28 +0100 Subject: [PATCH 0460/5199] Fix `BeatmapMetadata` not using its user param correctly --- osu.Game/Beatmaps/BeatmapMetadata.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 1514d3af7a..f6666a6ea9 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -46,7 +46,7 @@ namespace osu.Game.Beatmaps public BeatmapMetadata(RealmUser? user = null) { - Author = new RealmUser(); + Author = user ?? new RealmUser(); } [UsedImplicitly] // Realm From a378e78ced3e0b5dfe6dafa3803060b1c9d118ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:17:27 +0100 Subject: [PATCH 0461/5199] Fix `RealmLive` unnecessarily passing ID around Appears to have never been needed. When the `retrieveFromID` method was created in 81b5717ae793e334fdcf8efd72d20debfb49e9ca, it didn't use the `id` parameter for anything either. --- osu.Game/Database/RealmLive.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index 186e801425..ecfececaa4 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -63,7 +63,7 @@ namespace osu.Game.Database return; } - perform(retrieveFromID(r, ID)); + perform(retrieveFromID(r)); RealmLiveStatistics.USAGE_ASYNC.Value++; }); } @@ -85,7 +85,7 @@ namespace osu.Game.Database return realm.Run(r => { - var returnData = perform(retrieveFromID(r, ID)); + var returnData = perform(retrieveFromID(r)); RealmLiveStatistics.USAGE_ASYNC.Value++; if (returnData is RealmObjectBase realmObject && realmObject.IsManaged) @@ -139,11 +139,11 @@ namespace osu.Game.Database } dataIsFromUpdateThread = true; - data = retrieveFromID(realm.Realm, ID); + data = retrieveFromID(realm.Realm); RealmLiveStatistics.USAGE_UPDATE_REFETCH.Value++; } - private T retrieveFromID(Realm realm, Guid id) + private T retrieveFromID(Realm realm) { var found = realm.Find(ID); From 16e0cc6a2b393312f90da6320d6d9e4d00685e54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:20:59 +0100 Subject: [PATCH 0462/5199] Remove `IIpcHost` param from `ScoreManager` No longer used since 3e3b9bc963583fa5f6ad3b822ad3e6e3818aa06c. --- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 8363c41437..1713e73905 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -247,7 +247,7 @@ namespace osu.Game var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, Host, () => difficultyCache, LocalConfig)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, () => difficultyCache, LocalConfig)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true)); dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API)); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 6f9cce2d3c..532c6b42a3 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -32,7 +32,7 @@ namespace osu.Game.Scoring private readonly ScoreModelManager scoreModelManager; public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, Scheduler scheduler, - IIpcHost importHost = null, Func difficulties = null, OsuConfigManager configManager = null) + Func difficulties = null, OsuConfigManager configManager = null) { this.realm = realm; this.scheduler = scheduler; From c6a65ccfeddc3dd190d38b0629fbb9efbd64fab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:22:22 +0100 Subject: [PATCH 0463/5199] Remove unused parameter from `createContent()` No longer used since 513e470b525c4948d793555beb0df0928b3b9019. --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 1e6899e05f..0c12eff503 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Edit.Timing } Columns = createHeaders(); - Content = value.Select((g, i) => createContent(i, g)).ToArray().ToRectangular(); + Content = value.Select(createContent).ToArray().ToRectangular(); } } @@ -76,7 +76,7 @@ namespace osu.Game.Screens.Edit.Timing return columns.ToArray(); } - private Drawable[] createContent(int index, ControlPointGroup group) + private Drawable[] createContent(ControlPointGroup group) { return new Drawable[] { From 1fa2bf5d69f0c92755f9191acb4ae686b160fa9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:27:14 +0100 Subject: [PATCH 0464/5199] Remove unused parameter from `createColourBars()` No longer used since b61aa660c63f0e126f32643ef17e7bb458d2a1ae. --- .../Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index a8141c57da..7903e54960 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osuTK; @@ -49,7 +48,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { InternalChild = new FillFlowContainer { @@ -127,7 +126,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters } }; - createColourBars(colours); + createColourBars(); } protected override void LoadComplete() @@ -150,7 +149,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters iconLate.Rotation = -Rotation; } - private void createColourBars(OsuColour colours) + private void createColourBars() { var windows = HitWindows.GetAllAvailableWindows().ToArray(); From a94702b3aeaac4b1dfe1e0adeb71140650bcaabd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:30:28 +0100 Subject: [PATCH 0465/5199] Remove unused parameters in `LegacyComboCounter` No longer used since 9bb8a43bcee61d11f26598fff2228af1f07a4d17. --- osu.Game/Screens/Play/HUD/LegacyComboCounter.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index 567e4386c6..9510453ba5 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -109,7 +109,7 @@ namespace osu.Game.Screens.Play.HUD return; if (isRolling) - onDisplayedCountRolling(displayedCount, value); + onDisplayedCountRolling(value); else if (displayedCount + 1 == value) onDisplayedCountIncrement(value); else @@ -151,7 +151,7 @@ namespace osu.Game.Screens.Play.HUD if (prev + 1 == Current.Value) onCountIncrement(prev, Current.Value); else - onCountChange(prev, Current.Value); + onCountChange(Current.Value); } else { @@ -226,7 +226,7 @@ namespace osu.Game.Screens.Play.HUD transformRoll(currentValue, newValue); } - private void onCountChange(int currentValue, int newValue) + private void onCountChange(int newValue) { scheduledPopOutCurrentId++; @@ -236,7 +236,7 @@ namespace osu.Game.Screens.Play.HUD DisplayedCount = newValue; } - private void onDisplayedCountRolling(int currentValue, int newValue) + private void onDisplayedCountRolling(int newValue) { if (newValue == 0) displayedCountSpriteText.FadeOut(fade_out_duration); From 07d09b3520210f1233cb024e8419548f342c4001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:32:58 +0100 Subject: [PATCH 0466/5199] Remove unused parameter from `createGameplayComponents()` No longer used since 136843c8e450507ad0527622af851d648afb1545. --- osu.Game/Screens/Play/Player.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 1556e752ab..240620b686 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -251,7 +251,7 @@ namespace osu.Game.Screens.Play { // underlay and gameplay should have access to the skinning sources. createUnderlayComponents(), - createGameplayComponents(Beatmap.Value, playableBeatmap) + createGameplayComponents(Beatmap.Value) } }, FailOverlay = new FailOverlay @@ -364,7 +364,7 @@ namespace osu.Game.Screens.Play private Drawable createUnderlayComponents() => DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }; - private Drawable createGameplayComponents(IWorkingBeatmap working, IBeatmap playableBeatmap) => new ScalingContainer(ScalingMode.Gameplay) + private Drawable createGameplayComponents(IWorkingBeatmap working) => new ScalingContainer(ScalingMode.Gameplay) { Children = new Drawable[] { From 994fb966b6ba27baf7c178a7483ccf353db46fc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:37:19 +0100 Subject: [PATCH 0467/5199] Remove `Host` ctor param from `SkinModelManager` No longer used since 29d074bdb8629ff480207498c0eeec61b010a530. --- osu.Game/Skinning/SkinManager.cs | 2 +- osu.Game/Skinning/SkinModelManager.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 06bd0abc9f..bad559d9fe 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -77,7 +77,7 @@ namespace osu.Game.Skinning userFiles = new StorageBackedResourceStore(storage.GetStorageForDirectory("files")); - skinModelManager = new SkinModelManager(storage, realm, host, this); + skinModelManager = new SkinModelManager(storage, realm, this); var defaultSkins = new[] { diff --git a/osu.Game/Skinning/SkinModelManager.cs b/osu.Game/Skinning/SkinModelManager.cs index 0af31100a9..33e49ce486 100644 --- a/osu.Game/Skinning/SkinModelManager.cs +++ b/osu.Game/Skinning/SkinModelManager.cs @@ -27,7 +27,7 @@ namespace osu.Game.Skinning private readonly IStorageResourceProvider skinResources; - public SkinModelManager(Storage storage, RealmAccess realm, GameHost host, IStorageResourceProvider skinResources) + public SkinModelManager(Storage storage, RealmAccess realm, IStorageResourceProvider skinResources) : base(storage, realm) { this.skinResources = skinResources; From 75101b11052c2f02471a8fed6129f9d0e4d5eb76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:39:52 +0100 Subject: [PATCH 0468/5199] Remove unused ruleset ctor params from test beatmap model managers No longer used since 00e3af3366e80214c7f0c956d144000a78cd0b56. --- .../Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 4 ++-- osu.Game/Tests/Visual/EditorTestScene.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index f9161816e7..79cb424803 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -173,14 +173,14 @@ namespace osu.Game.Tests.Online protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue) { - return new TestBeatmapModelManager(this, storage, realm, rulesets, onlineLookupQueue); + return new TestBeatmapModelManager(this, storage, realm, onlineLookupQueue); } internal class TestBeatmapModelManager : BeatmapModelManager { private readonly TestBeatmapManager testBeatmapManager; - public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) + public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) : base(databaseAccess, storage, beatmapOnlineLookupQueue) { this.testBeatmapManager = testBeatmapManager; diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index f7d62a8694..bcf169bb1e 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue) { - return new TestBeatmapModelManager(storage, realm, rulesets, onlineLookupQueue); + return new TestBeatmapModelManager(storage, realm, onlineLookupQueue); } protected override WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore resources, IResourceStore storage, WorkingBeatmap defaultBeatmap, GameHost host) @@ -150,7 +150,7 @@ namespace osu.Game.Tests.Visual internal class TestBeatmapModelManager : BeatmapModelManager { - public TestBeatmapModelManager(Storage storage, RealmAccess databaseAccess, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) + public TestBeatmapModelManager(Storage storage, RealmAccess databaseAccess, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) : base(databaseAccess, storage, beatmapOnlineLookupQueue) { } From e042f29ee3a12e60eb6d06165db93af1c37faf8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:41:18 +0100 Subject: [PATCH 0469/5199] Remove skin ctor param from `LegacyCatchComboCounter` No longer used since 004798d61d44d88114acfd7c2861378261eab107. --- .../Skinning/Legacy/CatchLegacySkinTransformer.cs | 2 +- .../Skinning/Legacy/LegacyCatchComboCounter.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index faad95e386..b2a555f89d 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy case CatchSkinComponents.CatchComboCounter: if (providesComboCounter) - return new LegacyCatchComboCounter(Skin); + return new LegacyCatchComboCounter(); return null; diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs index 33c3867f5a..b4d29988d9 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy private readonly LegacyRollingCounter explosion; - public LegacyCatchComboCounter(ISkin skin) + public LegacyCatchComboCounter() { AutoSizeAxes = Axes.Both; From e4028b8fc1b86df31a6882480797cf0094c0a027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:42:58 +0100 Subject: [PATCH 0470/5199] Remove index ctor param from `ColumnHitObjectArea` No longer used since 5692cecaa47e9e071fb6079d9f3847b8b1de0972. --- .../Skinning/TestSceneColumnHitObjectArea.cs | 4 ++-- osu.Game.Rulesets.Mania/UI/Column.cs | 2 +- osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs index 215f8fb1d5..8034341d15 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { RelativeSizeAxes = Axes.Both, Width = 0.5f, - Child = new ColumnHitObjectArea(0, new HitObjectContainer()) + Child = new ColumnHitObjectArea(new HitObjectContainer()) { RelativeSizeAxes = Axes.Both } @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { RelativeSizeAxes = Axes.Both, Width = 0.5f, - Child = new ColumnHitObjectArea(1, new HitObjectContainer()) + Child = new ColumnHitObjectArea(new HitObjectContainer()) { RelativeSizeAxes = Axes.Both } diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index dc3b86ac7f..a04f5ef98e 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Mania.UI sampleTriggerSource = new GameplaySampleTriggerSource(HitObjectContainer), // For input purposes, the background is added at the highest depth, but is then proxied back below all other elements background.CreateProxy(), - HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both }, + HitObjectArea = new ColumnHitObjectArea(HitObjectContainer) { RelativeSizeAxes = Axes.Both }, new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea()) { RelativeSizeAxes = Axes.Both diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs index f69d2aafdc..51c138f5e1 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components private readonly Drawable hitTarget; - public ColumnHitObjectArea(int columnIndex, HitObjectContainer hitObjectContainer) + public ColumnHitObjectArea(HitObjectContainer hitObjectContainer) : base(hitObjectContainer) { AddRangeInternal(new[] From 7cdf63c6543034a7517ec17949874d16bdd1b47f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:44:59 +0100 Subject: [PATCH 0471/5199] Remove unused `FindProvider()` methods No longer needed since 39f99bf785676c443f37248668eb9bf45006032e. --- osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs | 1 - osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs | 2 -- osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs | 1 - osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs | 4 ---- 4 files changed, 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index 1f01ba601b..a36f07ff7b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -118,7 +118,6 @@ namespace osu.Game.Rulesets.Osu.Tests public Drawable GetDrawableComponent(ISkinComponent component) => null; public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null; public ISample GetSample(ISampleInfo sampleInfo) => null; - public ISkin FindProvider(Func lookupFunction) => null; public IBindable GetConfig(TLookup lookup) { diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs index 34f70659e3..5553c67141 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs @@ -131,8 +131,6 @@ namespace osu.Game.Tests.Gameplay public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); - public ISkin FindProvider(Func lookupFunction) => null; - public IBindable GetConfig(TLookup lookup) { switch (lookup) diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs index 785f31386d..4209f188cc 100644 --- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs +++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs @@ -61,7 +61,6 @@ namespace osu.Game.Tests.NonVisual.Skinning public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotSupportedException(); public ISample GetSample(ISampleInfo sampleInfo) => throw new NotSupportedException(); public IBindable GetConfig(TLookup lookup) => throw new NotSupportedException(); - public ISkin FindProvider(Func lookupFunction) => null; } private class TestAnimationTimeReference : IAnimationTimeReference diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index 3e8ba69e01..35130f3109 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -301,8 +301,6 @@ namespace osu.Game.Tests.Visual.Gameplay public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); - - public ISkin FindProvider(Func lookupFunction) => throw new NotImplementedException(); } private class SecondarySource : ISkin @@ -314,8 +312,6 @@ namespace osu.Game.Tests.Visual.Gameplay public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); - - public ISkin FindProvider(Func lookupFunction) => throw new NotImplementedException(); } [Cached(typeof(ISkinSource))] From b978010b481fd8e46a7b0448b813a3e547d375ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:47:55 +0100 Subject: [PATCH 0472/5199] Remove unused `allowMissing` parameter in audio file check test No longer used since 7f95400f466beafe6fa94c002cfd6ed8dc3f4f94. --- .../Editing/Checks/CheckTooShortAudioFilesTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs b/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs index 242fec2f68..53c85defae 100644 --- a/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Editing.Checks beatmap.BeatmapInfo.BeatmapSet.Files.Add(CheckTestHelpers.CreateMockFile("jpg")); // Should fail to load, but not produce an error due to the extension not being expected to load. - Assert.IsEmpty(check.Run(getContext(null, allowMissing: true))); + Assert.IsEmpty(check.Run(getContext(null))); } [Test] @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Editing.Checks { using (var resourceStream = TestResources.OpenResource("Samples/missing.mp3")) { - Assert.IsEmpty(check.Run(getContext(resourceStream, allowMissing: true))); + Assert.IsEmpty(check.Run(getContext(resourceStream))); } } @@ -107,7 +107,7 @@ namespace osu.Game.Tests.Editing.Checks } } - private BeatmapVerifierContext getContext(Stream resourceStream, bool allowMissing = false) + private BeatmapVerifierContext getContext(Stream resourceStream) { var mockWorkingBeatmap = new Mock(beatmap, null, null); mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny())).Returns(resourceStream); From 3674ed15ce7cc4e5b0e50303e04db4af6eba3164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:52:16 +0100 Subject: [PATCH 0473/5199] Remove unused game host parameter No longer used since eeccf836ec04d2e3aac1fec93c15b48aa6672352. --- .../Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 79cb424803..94e61eaee0 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Online { Dependencies.Cache(rulesets = new RulesetStore(Realm)); Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); - Dependencies.CacheAs(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API, host)); + Dependencies.CacheAs(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API)); } [SetUp] @@ -196,7 +196,7 @@ namespace osu.Game.Tests.Online internal class TestBeatmapModelDownloader : BeatmapModelDownloader { - public TestBeatmapModelDownloader(IModelImporter importer, IAPIProvider apiProvider, GameHost gameHost) + public TestBeatmapModelDownloader(IModelImporter importer, IAPIProvider apiProvider) : base(importer, apiProvider) { } From 35b76532903254b3ea434f7fccf9ef95d07be72c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 09:13:19 +0900 Subject: [PATCH 0474/5199] Revert mod flow changes and add visual test coverage showing an overflow case --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 12 +++++++----- .../Online/Leaderboards/LeaderboardScoreTooltip.cs | 5 ++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 7292ec96ed..667fd08084 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -203,14 +203,16 @@ namespace osu.Game.Tests.Visual.SongSelect new OsuModHardRock(), new OsuModFlashlight { - FollowDelay = { Value = 200 } + FollowDelay = { Value = 200 }, + SizeMultiplier = { Value = 5 }, }, new OsuModDifficultyAdjust { - CircleSize = { Value = 8 }, - ApproachRate = { Value = 7 }, - OverallDifficulty = { Value = 6 }, - DrainRate = { Value = 5 }, + CircleSize = { Value = 11 }, + ApproachRate = { Value = 10 }, + OverallDifficulty = { Value = 10 }, + DrainRate = { Value = 10 }, + ExtendedLimits = { Value = true } } }, Ruleset = new OsuRuleset().RulesetInfo, diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index a0eea94501..c26e9e6802 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -62,9 +62,8 @@ namespace osu.Game.Online.Leaderboards // Mods row modStatistics = new FillFlowContainer { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Full, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, Spacing = new Vector2(5, 0), }, new FillFlowContainer From b4fd1ecba20a10858a1219beddabf8122517fa22 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Feb 2022 11:02:01 +0800 Subject: [PATCH 0475/5199] Hide attribute if the maximum is 0 --- .../Statistics/PerformanceStatisticTooltip.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs index 739fac9d63..bd6eb057c6 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs @@ -137,8 +137,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics { float percentage = (float)(attribute.Value / perfectAttribute.Value); if (float.IsNaN(percentage)) - percentage = 0; - string text = percentage.ToLocalisableString("0%").ToString(); + return null; return new GridContainer { @@ -181,7 +180,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, Font = OsuFont.GetFont(weight: FontWeight.SemiBold), - Text = text, + Text = percentage.ToLocalisableString("0%"), Colour = textColour } } @@ -199,9 +198,12 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics foreach (PerformanceDisplayAttribute attr in displayAttributes) { - Content.Add(attr.PropertyName == nameof(PerformanceAttributes.Total) + var attributeItem = attr.PropertyName == nameof(PerformanceAttributes.Total) ? createItemForTotal(attr, perfectDisplayAttributes.First(a => a.PropertyName == attr.PropertyName)) - : createItemForAttribute(attr, perfectDisplayAttributes.First(a => a.PropertyName == attr.PropertyName))); + : createItemForAttribute(attr, perfectDisplayAttributes.First(a => a.PropertyName == attr.PropertyName)); + + if (attributeItem != null) + Content.Add(attributeItem); } } From d065e32ca1f8c77b361593016bedf0487540fcff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 13:23:49 +0900 Subject: [PATCH 0476/5199] Fix crash due to `MatchLeaderboardScore`s not having populated rulesets --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 2 +- .../OnlinePlay/Match/Components/MatchLeaderboardScore.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index b4aaebd4e4..c2393a5de5 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -71,7 +71,7 @@ namespace osu.Game.Online.Leaderboards private Storage storage { get; set; } public ITooltip GetCustomTooltip() => new LeaderboardScoreTooltip(); - public ScoreInfo TooltipContent => Score; + public virtual ScoreInfo TooltipContent => Score; public LeaderboardScore(ScoreInfo score, int? rank, bool isOnlineScope = true) { diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboardScore.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboardScore.cs index 799c44cc28..cf7e33fd63 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboardScore.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboardScore.cs @@ -14,6 +14,8 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components { private readonly APIUserScoreAggregate score; + public override ScoreInfo TooltipContent => null; // match aggregate scores can't show statistics that the custom tooltip displays. + public MatchLeaderboardScore(APIUserScoreAggregate score, int? rank, bool isOnlineScope = true) : base(score.CreateScoreInfo(), rank, isOnlineScope) { From ddc8094a75babd5fad94ea8b692b8e12a899c461 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 13:34:23 +0900 Subject: [PATCH 0477/5199] Update description --- osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index 6e942f672b..a65938184c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mania.Mods public override double ScoreMultiplier => 1; - public override string Description => @"Turns all hold notes into normal notes. No coordination required."; + public override string Description => @"Replaces all hold notes with normal notes."; public override IconUsage? Icon => FontAwesome.Solid.DotCircle; From 0036d0e26db709f1661e3cff89364ccb7c6f94b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 13:58:13 +0900 Subject: [PATCH 0478/5199] Move alternate mod to "conversion" category --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index f68f2b5bee..936bb290b4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => @"Don't use the same key twice in a row!"; public override double ScoreMultiplier => 1.0; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) }; - public override ModType Type => ModType.DifficultyIncrease; + public override ModType Type => ModType.Conversion; public override IconUsage? Icon => FontAwesome.Solid.Keyboard; private bool introEnded; diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 7e8974b5ed..2faecbcda2 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -159,7 +159,6 @@ namespace osu.Game.Rulesets.Osu new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()), new OsuModHidden(), new MultiMod(new OsuModFlashlight(), new OsuModBlinds()), - new OsuModAlternate(), }; case ModType.Conversion: @@ -170,6 +169,7 @@ namespace osu.Game.Rulesets.Osu new OsuModClassic(), new OsuModRandom(), new OsuModMirror(), + new OsuModAlternate(), }; case ModType.Automation: From fed63abd83715cf1f904a53038999b7625e41f4f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 14:02:48 +0900 Subject: [PATCH 0479/5199] Sanitise interceptor logic to now require two separate check paths --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index 936bb290b4..9d389432e5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -33,8 +33,6 @@ namespace osu.Game.Rulesets.Osu.Mods private OsuAction? lastActionPressed; private DrawableRuleset ruleset; - private bool shouldAlternate => !isBreakTime.Value && introEnded; - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { ruleset = drawableRuleset; @@ -54,16 +52,22 @@ namespace osu.Game.Rulesets.Osu.Mods }; } - private bool onPressed(OsuAction key) + private bool checkCorrectAction(OsuAction action) { - if (lastActionPressed == key) + if (isBreakTime.Value) + return true; + + if (!introEnded) + return true; + + if (lastActionPressed != action) { - ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint); + // User alternated correctly + lastActionPressed = action; return true; } - lastActionPressed = key; - + ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint); return false; } @@ -83,7 +87,8 @@ namespace osu.Game.Rulesets.Osu.Mods } public bool OnPressed(KeyBindingPressEvent e) - => mod.shouldAlternate && mod.onPressed(e.Action); + // if the pressed action is incorrect, block it from reaching gameplay. + => !mod.checkCorrectAction(e.Action); public void OnReleased(KeyBindingReleaseEvent e) { From c7a192cc5fae75bca23b5a714d6ff2fde49b6bbf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 14:04:10 +0900 Subject: [PATCH 0480/5199] Only handle `LeftButton` and `RightButton` actions There are definitely going to be other actions used in the future, which would immediately cause this mod to fail. Limiting handling to left/right buttons only is the correct way forward. --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index 9d389432e5..b9f25bd1cf 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -60,9 +60,20 @@ namespace osu.Game.Rulesets.Osu.Mods if (!introEnded) return true; + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + break; + + // Any action which is not left or right button should be ignored. + default: + return true; + } + if (lastActionPressed != action) { - // User alternated correctly + // User alternated correctly. lastActionPressed = action; return true; } From c5c4c85006ffd4a34e05ba33d6e81bb9acf8acc8 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Feb 2022 13:29:18 +0800 Subject: [PATCH 0481/5199] Lazily create content of StatisticItem --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 22 +++++++++------- osu.Game.Rulesets.Osu/OsuRuleset.cs | 25 +++++++++++-------- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 22 +++++++++------- .../Ranking/Statistics/StatisticContainer.cs | 2 +- .../Ranking/Statistics/StatisticItem.cs | 12 +++++++-- .../Ranking/Statistics/StatisticsPanel.cs | 10 +++++--- 6 files changed, 59 insertions(+), 34 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 2098c7f5d8..8ac8001457 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -370,21 +370,25 @@ namespace osu.Game.Rulesets.Mania { Columns = new[] { - new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents) - { - RelativeSizeAxes = Axes.X, - Height = 250 - }), + new StatisticItem("Timing Distribution", + true, + () => new HitEventTimingDistributionGraph(score.HitEvents) + { + RelativeSizeAxes = Axes.X, + Height = 250 + }), } }, new StatisticRow { Columns = new[] { - new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[] - { - new UnstableRate(score.HitEvents) - })) + new StatisticItem(string.Empty, + true, + () => new SimpleStatisticTable(3, new SimpleStatisticItem[] + { + new UnstableRate(score.HitEvents) + })) } } }; diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 18e4bb259c..b6f417b7fe 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -278,7 +278,8 @@ namespace osu.Game.Rulesets.Osu Columns = new[] { new StatisticItem("Timing Distribution", - new HitEventTimingDistributionGraph(timedHitEvents) + true, + () => new HitEventTimingDistributionGraph(timedHitEvents) { RelativeSizeAxes = Axes.X, Height = 250 @@ -289,21 +290,25 @@ namespace osu.Game.Rulesets.Osu { Columns = new[] { - new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score, playableBeatmap) - { - RelativeSizeAxes = Axes.X, - Height = 250 - }), + new StatisticItem("Accuracy Heatmap", + true, + () => new AccuracyHeatmap(score, playableBeatmap) + { + RelativeSizeAxes = Axes.X, + Height = 250 + }), } }, new StatisticRow { Columns = new[] { - new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[] - { - new UnstableRate(timedHitEvents) - })) + new StatisticItem(string.Empty, + true, + () => new SimpleStatisticTable(3, new SimpleStatisticItem[] + { + new UnstableRate(timedHitEvents) + })) } } }; diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index ca860f24c3..2a64b8dddd 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -213,21 +213,25 @@ namespace osu.Game.Rulesets.Taiko { Columns = new[] { - new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(timedHitEvents) - { - RelativeSizeAxes = Axes.X, - Height = 250 - }), + new StatisticItem("Timing Distribution", + true, + () => new HitEventTimingDistributionGraph(timedHitEvents) + { + RelativeSizeAxes = Axes.X, + Height = 250 + }), } }, new StatisticRow { Columns = new[] { - new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[] - { - new UnstableRate(timedHitEvents) - })) + new StatisticItem(string.Empty, + true, + () => new SimpleStatisticTable(3, new SimpleStatisticItem[] + { + new UnstableRate(timedHitEvents) + })) } } }; diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs index 485d24d024..f3bd2c6fc1 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Ranking.Statistics RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Margin = new MarginPadding { Top = 15 }, - Child = item.Content + Child = item.Content() } }, }, diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs index 4903983759..5e6ddf445b 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticItem.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 JetBrains.Annotations; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -20,22 +21,29 @@ namespace osu.Game.Screens.Ranking.Statistics /// /// The content to be displayed. /// - public readonly Drawable Content; + public readonly Func Content; /// /// The of this row. This can be thought of as the column dimension of an encompassing . /// public readonly Dimension Dimension; + /// + /// Whether this item requires hit events. If true, the will not be created if no hit events are available. + /// + public readonly bool RequiresHitEvents; + /// /// Creates a new , to be displayed inside a in the results screen. /// /// The name of the item. Can be to hide the item header. + /// Whether this item requires hit events. If true, the will not be created if no hit events are available. /// The content to be displayed. /// The of this item. This can be thought of as the column dimension of an encompassing . - public StatisticItem([NotNull] string name, [NotNull] Drawable content, [CanBeNull] Dimension dimension = null) + public StatisticItem([NotNull] string name, bool requiresHitEvents, [NotNull] Func content, [CanBeNull] Dimension dimension = null) { Name = name; + RequiresHitEvents = requiresHitEvents; Content = content; Dimension = dimension; } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 567a2307dd..14cdfe2f03 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -118,6 +118,10 @@ namespace osu.Game.Screens.Ranking.Statistics foreach (var row in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap)) { + var columnsToDisplay = newScore.HitEvents.Count == 0 + ? row.Columns?.Where(c => !c.RequiresHitEvents).ToArray() + : row.Columns; + rows.Add(new GridContainer { Anchor = Anchor.TopCentre, @@ -126,14 +130,14 @@ namespace osu.Game.Screens.Ranking.Statistics AutoSizeAxes = Axes.Y, Content = new[] { - row.Columns?.Select(c => new StatisticContainer(c) + columnsToDisplay?.Select(c => new StatisticContainer(c) { Anchor = Anchor.Centre, Origin = Anchor.Centre, }).Cast().ToArray() }, - ColumnDimensions = Enumerable.Range(0, row.Columns?.Length ?? 0) - .Select(i => row.Columns[i].Dimension ?? new Dimension()).ToArray(), + ColumnDimensions = Enumerable.Range(0, columnsToDisplay?.Length ?? 0) + .Select(i => columnsToDisplay?[i].Dimension ?? new Dimension()).ToArray(), RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } }); } From a2affefb0aa8ca794ff329a1a7e65e3502450f35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 14:33:17 +0900 Subject: [PATCH 0482/5199] Avoid checking gameplay clock time in `Update` method --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index b9f25bd1cf..46b97dd23b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -16,7 +16,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModAlternate : Mod, IApplicableToDrawableRuleset, IApplicableToPlayer, IUpdatableByPlayfield + public class OsuModAlternate : Mod, IApplicableToDrawableRuleset, IApplicableToPlayer { public override string Name => @"Alternate"; public override string Acronym => @"AL"; @@ -26,20 +26,23 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Conversion; public override IconUsage? Icon => FontAwesome.Solid.Keyboard; - private bool introEnded; - private double earliestStartTime; + private double firstObjectValidJudgementTime; private IBindable isBreakTime; private const double flash_duration = 1000; private OsuAction? lastActionPressed; private DrawableRuleset ruleset; + private IFrameStableClock gameplayClock; + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { ruleset = drawableRuleset; drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this)); var firstHitObject = ruleset.Objects.FirstOrDefault(); - earliestStartTime = (firstHitObject?.StartTime ?? 0) - (firstHitObject?.HitWindows.WindowFor(HitResult.Meh) ?? 0); + firstObjectValidJudgementTime = (firstHitObject?.StartTime ?? 0) - (firstHitObject?.HitWindows.WindowFor(HitResult.Meh) ?? 0); + + gameplayClock = drawableRuleset.FrameStableClock; } public void ApplyToPlayer(Player player) @@ -57,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (isBreakTime.Value) return true; - if (!introEnded) + if (gameplayClock.CurrentTime < firstObjectValidJudgementTime) return true; switch (action) @@ -82,12 +85,6 @@ namespace osu.Game.Rulesets.Osu.Mods return false; } - public void Update(Playfield playfield) - { - if (!introEnded) - introEnded = playfield.Clock.CurrentTime > earliestStartTime; - } - private class InputInterceptor : Component, IKeyBindingHandler { private readonly OsuModAlternate mod; From 3ba5d88914d09e1725ee3abaec9fd44699e3973c Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Feb 2022 13:41:51 +0800 Subject: [PATCH 0483/5199] Update statistics item display logic --- .../Ranking/Statistics/StatisticsPanel.cs | 131 +++++++++--------- 1 file changed, 69 insertions(+), 62 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 14cdfe2f03..9d89aa213c 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -74,85 +74,92 @@ namespace osu.Game.Screens.Ranking.Statistics if (newScore == null) return; - if (newScore.HitEvents.Count == 0) + spinner.Show(); + + var localCancellationSource = loadCancellation = new CancellationTokenSource(); + IBeatmap playableBeatmap = null; + + // Todo: The placement of this is temporary. Eventually we'll both generate the playable beatmap _and_ run through it in a background task to generate the hit events. + Task.Run(() => { - content.Add(new FillFlowContainer + playableBeatmap = beatmapManager.GetWorkingBeatmap(newScore.BeatmapInfo).GetPlayableBeatmap(newScore.Ruleset, newScore.Mods); + }, loadCancellation.Token).ContinueWith(t => Schedule(() => + { + var rows = new FillFlowContainer { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Direction = FillDirection.Vertical, - Children = new Drawable[] + Spacing = new Vector2(30, 15), + Alpha = 0 + }; + + bool panelIsEmpty = true; + bool hitEventsAvailable = newScore.HitEvents.Count != 0; + + foreach (var row in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap)) + { + var columnsToDisplay = hitEventsAvailable + ? row.Columns + : row.Columns?.Where(c => !c.RequiresHitEvents).ToArray(); + + if (columnsToDisplay?.Any() ?? false) + panelIsEmpty = false; + + rows.Add(new GridContainer { - new MessagePlaceholder("Extended statistics are only available after watching a replay!"), - new ReplayDownloadButton(newScore) + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] { - Scale = new Vector2(1.5f), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + columnsToDisplay?.Select(c => new StatisticContainer(c) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }).Cast().ToArray() }, - } - }); - } - else - { - spinner.Show(); + ColumnDimensions = Enumerable.Range(0, columnsToDisplay?.Length ?? 0) + .Select(i => columnsToDisplay?[i].Dimension ?? new Dimension()).ToArray(), + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } + }); + } - var localCancellationSource = loadCancellation = new CancellationTokenSource(); - IBeatmap playableBeatmap = null; - - // Todo: The placement of this is temporary. Eventually we'll both generate the playable beatmap _and_ run through it in a background task to generate the hit events. - Task.Run(() => + if (!hitEventsAvailable) { - playableBeatmap = beatmapManager.GetWorkingBeatmap(newScore.BeatmapInfo).GetPlayableBeatmap(newScore.Ruleset, newScore.Mods); - }, loadCancellation.Token).ContinueWith(t => Schedule(() => - { - var rows = new FillFlowContainer + rows.Add(new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, Direction = FillDirection.Vertical, - Spacing = new Vector2(30, 15), - Alpha = 0 - }; - - foreach (var row in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap)) - { - var columnsToDisplay = newScore.HitEvents.Count == 0 - ? row.Columns?.Where(c => !c.RequiresHitEvents).ToArray() - : row.Columns; - - rows.Add(new GridContainer + Children = new Drawable[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Content = new[] + new MessagePlaceholder(panelIsEmpty + ? "Extended statistics are only available after watching a replay!" + : "More statistics available after watching a replay!"), + new ReplayDownloadButton(newScore) { - columnsToDisplay?.Select(c => new StatisticContainer(c) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }).Cast().ToArray() + Scale = new Vector2(1.5f), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, }, - ColumnDimensions = Enumerable.Range(0, columnsToDisplay?.Length ?? 0) - .Select(i => columnsToDisplay?[i].Dimension ?? new Dimension()).ToArray(), - RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } - }); - } + } + }); + } - LoadComponentAsync(rows, d => - { - if (!Score.Value.Equals(newScore)) - return; + LoadComponentAsync(rows, d => + { + if (!Score.Value.Equals(newScore)) + return; - spinner.Hide(); - content.Add(d); - d.FadeIn(250, Easing.OutQuint); - }, localCancellationSource.Token); - }), localCancellationSource.Token); - } + spinner.Hide(); + content.Add(d); + d.FadeIn(250, Easing.OutQuint); + }, localCancellationSource.Token); + }), localCancellationSource.Token); } protected override bool OnClick(ClickEvent e) From 6e60e68b809be7e1d93f3a80371f1ba551d45c43 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 2 Feb 2022 14:44:06 +0900 Subject: [PATCH 0484/5199] Change from click to mousedown+mouseup and only play when cursor is visible --- osu.Game/Graphics/Cursor/MenuCursor.cs | 29 +++++++++++++++----------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index a89d8dac71..0cc751ea21 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -76,18 +76,6 @@ namespace osu.Game.Graphics.Cursor return base.OnMouseMove(e); } - protected override bool OnClick(ClickEvent e) - { - var channel = tapSample.GetChannel(); - - // scale to [-0.75, 0.75] so that the sample isn't fully panned left or right (sounds weird) - channel.Balance.Value = ((activeCursor.X / DrawWidth) * 2 - 1) * 0.75; - channel.Frequency.Value = 0.99 + RNG.NextDouble(0.02); - channel.Play(); - - return base.OnClick(e); - } - protected override bool OnMouseDown(MouseDownEvent e) { if (State.Value == Visibility.Visible) @@ -105,6 +93,8 @@ namespace osu.Game.Graphics.Cursor dragRotationState = DragRotationState.DragStarted; positionMouseDown = e.MousePosition; } + + playTapSample(); } return base.OnMouseDown(e); @@ -122,6 +112,9 @@ namespace osu.Game.Graphics.Cursor activeCursor.RotateTo(0, 600 * (1 + Math.Abs(activeCursor.Rotation / 720)), Easing.OutElasticHalf); dragRotationState = DragRotationState.NotDragging; } + + if (State.Value == Visibility.Visible) + playTapSample(0.8); } base.OnMouseUp(e); @@ -139,6 +132,18 @@ namespace osu.Game.Graphics.Cursor activeCursor.ScaleTo(0.6f, 250, Easing.In); } + private void playTapSample(double baseFrequency = 1f) + { + const float random_range = 0.02f; + SampleChannel channel = tapSample.GetChannel(); + + // Scale to [-0.75, 0.75] so that the sample isn't fully panned left or right (sounds weird) + channel.Balance.Value = ((activeCursor.X / DrawWidth) * 2 - 1) * 0.75; + channel.Frequency.Value = baseFrequency - (random_range / 2f) + RNG.NextDouble(random_range); + + channel.Play(); + } + public class Cursor : Container { private Container cursorContainer; From 0c5da9370a239b957f6d7a2fce929c0b7cff323c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 14:51:55 +0900 Subject: [PATCH 0485/5199] Fix rulesets potentially being marked `Available` even when methods are missing Came up when running the game after the recent breaking changes (https://github.com/ppy/osu/pull/16722), where two template rulesets I had loaded were erroring on startup but still being marked as available, allowing them to crash the game on attempting to initiate relpay logic. These cases are already handled for first-time ruleset loading via the `GetTypes()` enumeration in `RulesetStore.addRuleset`, but when consistency checking already present rulesets the only runtime validation being done was `ruleset.CreateInstance()`, which does not handle missing types or methods. --- osu.Game/Rulesets/RulesetStore.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index d017d54ed9..dd25005006 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -149,6 +149,10 @@ namespace osu.Game.Rulesets var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo ?? throw new RulesetLoadException(@"Instantiation failure"); + // If a ruleset isn't up-to-date with the API, it could cause a crash at an arbitrary point of execution. + // To eagerly handle cases of missing implementations, enumerate all types here and mark as non-available on throw. + resolvedType.Assembly.GetTypes(); + r.Name = instanceInfo.Name; r.ShortName = instanceInfo.ShortName; r.InstantiationInfo = instanceInfo.InstantiationInfo; From e7d72f1823a5a97db761b0712851128942668366 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 15:10:56 +0900 Subject: [PATCH 0486/5199] Revert recent changes --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 80 +++++++++++++++---- 1 file changed, 66 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index a383b533fd..cfe3312415 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -11,8 +11,8 @@ using osu.Game.Rulesets.Osu.UI; using osuTK; using System.Linq; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Configuration; using osu.Framework.Bindables; +using osu.Game.Configuration; namespace osu.Game.Rulesets.Osu.Mods { @@ -34,10 +34,16 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 1.0f, }; - private double? lastUpdate; + private const float spin_radius = 30; + + private Vector2? prevCursorPos; + private OsuInputManager inputManager; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { + // Grab the input manager for future use + inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; + // Hide judgment displays and follow points drawableRuleset.Playfield.DisplayJudgements.Value = false; (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); @@ -45,29 +51,75 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { + var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; double currentTime = playfield.Clock.CurrentTime; - if (currentTime - (lastUpdate ?? double.MinValue) < 100) - return; - - Vector2 cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; - + // Move all currently alive object to new destination foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { var h = drawable.HitObject; - if (currentTime < h.StartTime && (drawable is DrawableHitCircle || drawable is DrawableSlider)) + switch (drawable) { - double timeMoving = currentTime - (h.StartTime - h.TimePreempt); - float percentDoneMoving = (float)(timeMoving / h.TimePreempt); - float percentDistLeft = Math.Clamp(AssistStrength.Value - percentDoneMoving + 0.1f, 0, 1); + case DrawableHitCircle circle: - Vector2 targetPos = drawable.Position + percentDistLeft * (cursorPos - drawable.Position); - drawable.MoveTo(targetPos, h.StartTime - currentTime); + // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast + circle.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + // FIXME: some circles cause flash at original(?) position when clicked too early + + break; + + case DrawableSlider slider: + + // Move slider to cursor + if (currentTime < h.StartTime) + { + slider.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + } + // Move slider so that sliderball stays on the cursor + else + { + slider.HeadCircle.Hide(); // hide flash, triangles, ... so they don't move with slider + slider.MoveTo(cursorPos - slider.Ball.DrawPosition); + // FIXME: some sliders re-appearing at their original position for a single frame when they're done + } + + break; + + case DrawableSpinner spinner: + + // Move spinner _next_ to cursor + if (currentTime < h.StartTime) + { + spinner.MoveTo(cursorPos + new Vector2(0, -spin_radius), Math.Max(0, h.StartTime - currentTime - 10)); + } + else + { + // Move spinner visually + Vector2 delta = spin_radius * (spinner.Position - prevCursorPos ?? cursorPos).Normalized(); + const float angle = 3 * MathF.PI / 180; // radians per update, arbitrary value + + // Rotation matrix + var targetPos = new Vector2( + delta.X * MathF.Cos(angle) - delta.Y * MathF.Sin(angle) + cursorPos.X, + delta.X * MathF.Sin(angle) + delta.Y * MathF.Cos(angle) + cursorPos.Y + ); + + spinner.MoveTo(targetPos); + + // Move spinner logically + if (inputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false) + { + // Arbitrary value, might lead to some inconsistencies depending on clock rate, replay, ... + spinner.RotationTracker.AddRotation(2 * MathF.PI); + } + } + + break; } } - lastUpdate = currentTime; + prevCursorPos = cursorPos; } } } From 104256a054150963b6ffd5a4b68c14fedcfff834 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 15:06:04 +0900 Subject: [PATCH 0487/5199] Add test coverage --- .../Mods/TestSceneOsuModAimAssist.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs new file mode 100644 index 0000000000..8fa7e0cd09 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.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. + +using NUnit.Framework; +using osu.Game.Rulesets.Osu.Mods; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public class TestSceneOsuModAimAssist : OsuModTestScene + { + [Test] + public void TestAimAssist() + { + var mod = new OsuModAimAssist(); + + CreateModTest(new ModTestData + { + Autoplay = false, + Mod = mod, + }); + } + } +} From 334ed2c9c488ec63e786d9197f756aa5bdaeb3b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 15:36:09 +0900 Subject: [PATCH 0488/5199] Fix sliders moving before they are actually hit --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index cfe3312415..a3c5638ea7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Mods case DrawableSlider slider: // Move slider to cursor - if (currentTime < h.StartTime) + if (!slider.HeadCircle.Result.HasResult) { slider.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); } From f07502ac5fadab49871be06c51bb29d95a7cb32a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 16:15:06 +0900 Subject: [PATCH 0489/5199] Use simple damp easing rather than transforms --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 69 ++++++++++--------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index a3c5638ea7..db83ee09af 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -2,17 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.UI; -using osu.Game.Rulesets.Osu.UI; -using osuTK; using System.Linq; -using osu.Game.Rulesets.Osu.Objects; using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Utils; using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; +using osuTK; namespace osu.Game.Rulesets.Osu.Mods { @@ -26,17 +27,18 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; + private IFrameStableClock gameplayClock; + [SettingSource("Assist strength", "Change the distance notes should travel towards you.", 0)] - public BindableFloat AssistStrength { get; } = new BindableFloat(0.3f) + public BindableFloat AssistStrength { get; } = new BindableFloat(0.5f) { Precision = 0.05f, - MinValue = 0.0f, + MinValue = 0.1f, MaxValue = 1.0f, }; - private const float spin_radius = 30; + private const float spin_radius = 50; - private Vector2? prevCursorPos; private OsuInputManager inputManager; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) @@ -44,6 +46,8 @@ namespace osu.Game.Rulesets.Osu.Mods // Grab the input manager for future use inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; + gameplayClock = drawableRuleset.FrameStableClock; + // Hide judgment displays and follow points drawableRuleset.Playfield.DisplayJudgements.Value = false; (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); @@ -62,10 +66,7 @@ namespace osu.Game.Rulesets.Osu.Mods switch (drawable) { case DrawableHitCircle circle: - - // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast - circle.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); - // FIXME: some circles cause flash at original(?) position when clicked too early + easeTo(circle, cursorPos); break; @@ -74,13 +75,13 @@ namespace osu.Game.Rulesets.Osu.Mods // Move slider to cursor if (!slider.HeadCircle.Result.HasResult) { - slider.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + easeTo(slider, cursorPos); } // Move slider so that sliderball stays on the cursor else { slider.HeadCircle.Hide(); // hide flash, triangles, ... so they don't move with slider - slider.MoveTo(cursorPos - slider.Ball.DrawPosition); + easeTo(slider, cursorPos - slider.Ball.DrawPosition); // FIXME: some sliders re-appearing at their original position for a single frame when they're done } @@ -91,35 +92,39 @@ namespace osu.Game.Rulesets.Osu.Mods // Move spinner _next_ to cursor if (currentTime < h.StartTime) { - spinner.MoveTo(cursorPos + new Vector2(0, -spin_radius), Math.Max(0, h.StartTime - currentTime - 10)); + easeTo(spinner, cursorPos + new Vector2(0, -spin_radius)); } else { // Move spinner visually - Vector2 delta = spin_radius * (spinner.Position - prevCursorPos ?? cursorPos).Normalized(); - const float angle = 3 * MathF.PI / 180; // radians per update, arbitrary value - - // Rotation matrix - var targetPos = new Vector2( - delta.X * MathF.Cos(angle) - delta.Y * MathF.Sin(angle) + cursorPos.X, - delta.X * MathF.Sin(angle) + delta.Y * MathF.Cos(angle) + cursorPos.Y - ); - - spinner.MoveTo(targetPos); + Vector2 delta = new Vector2(spin_radius); + float angle = (float)gameplayClock.CurrentTime * 10; // Move spinner logically if (inputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false) { - // Arbitrary value, might lead to some inconsistencies depending on clock rate, replay, ... - spinner.RotationTracker.AddRotation(2 * MathF.PI); + var targetPos = new Vector2( + delta.X * MathF.Cos(angle) - delta.Y * MathF.Sin(angle) + cursorPos.X, + delta.X * MathF.Sin(angle) + delta.Y * MathF.Cos(angle) + cursorPos.Y + ); + + easeTo(spinner, targetPos); } } break; } } + } - prevCursorPos = cursorPos; + private void easeTo(DrawableHitObject hitObject, Vector2 destination) + { + double dampLength = Interpolation.Lerp(500, 50, AssistStrength.Value); + + float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); + float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); + + hitObject.Position = new Vector2(x, y); } } } From 987aa5a21c043360d71f0155b52d70e77ddd3a5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 16:15:14 +0900 Subject: [PATCH 0490/5199] Add testing of different strengths --- .../Mods/TestSceneOsuModAimAssist.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs index 8fa7e0cd09..b8310bc4e7 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs @@ -8,15 +8,19 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { public class TestSceneOsuModAimAssist : OsuModTestScene { - [Test] - public void TestAimAssist() + [TestCase(0.1f)] + [TestCase(0.5f)] + [TestCase(1)] + public void TestAimAssist(float strength) { - var mod = new OsuModAimAssist(); - CreateModTest(new ModTestData { + Mod = new OsuModAimAssist + { + AssistStrength = { Value = strength }, + }, + PassCondition = () => true, Autoplay = false, - Mod = mod, }); } } From 2e46404fe5e10f5279730ad61f90be5a805830b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 16:15:49 +0900 Subject: [PATCH 0491/5199] Remove spinner support for now --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index db83ee09af..e3a68bcbcc 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -37,8 +37,6 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 1.0f, }; - private const float spin_radius = 50; - private OsuInputManager inputManager; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) @@ -82,34 +80,6 @@ namespace osu.Game.Rulesets.Osu.Mods { slider.HeadCircle.Hide(); // hide flash, triangles, ... so they don't move with slider easeTo(slider, cursorPos - slider.Ball.DrawPosition); - // FIXME: some sliders re-appearing at their original position for a single frame when they're done - } - - break; - - case DrawableSpinner spinner: - - // Move spinner _next_ to cursor - if (currentTime < h.StartTime) - { - easeTo(spinner, cursorPos + new Vector2(0, -spin_radius)); - } - else - { - // Move spinner visually - Vector2 delta = new Vector2(spin_radius); - float angle = (float)gameplayClock.CurrentTime * 10; - - // Move spinner logically - if (inputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false) - { - var targetPos = new Vector2( - delta.X * MathF.Cos(angle) - delta.Y * MathF.Sin(angle) + cursorPos.X, - delta.X * MathF.Sin(angle) + delta.Y * MathF.Cos(angle) + cursorPos.Y - ); - - easeTo(spinner, targetPos); - } } break; From 6e41a6e704512ea2a355ae23d5793435ce464ef7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 16:26:10 +0900 Subject: [PATCH 0492/5199] Tidy up code into a presentable state --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 31 +++++-------------- 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index e3a68bcbcc..ed4b139e00 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; @@ -23,30 +22,26 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Acronym => "AA"; public override IconUsage? Icon => FontAwesome.Solid.MousePointer; public override ModType Type => ModType.Fun; - public override string Description => "No need to chase the circle, the circle chases you"; + public override string Description => "No need to chase the circle – the circle chases you!"; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; private IFrameStableClock gameplayClock; - [SettingSource("Assist strength", "Change the distance notes should travel towards you.", 0)] + [SettingSource("Assist strength", "How much this mod will assist you.", 0)] public BindableFloat AssistStrength { get; } = new BindableFloat(0.5f) { Precision = 0.05f, - MinValue = 0.1f, + MinValue = 0.05f, MaxValue = 1.0f, }; - private OsuInputManager inputManager; - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - // Grab the input manager for future use - inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; - gameplayClock = drawableRuleset.FrameStableClock; - // Hide judgment displays and follow points + // Hide judgment displays and follow points as they won't make any sense. + // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. drawableRuleset.Playfield.DisplayJudgements.Value = false; (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); } @@ -54,33 +49,21 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; - double currentTime = playfield.Clock.CurrentTime; - // Move all currently alive object to new destination - foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) + foreach (var drawable in playfield.HitObjectContainer.AliveObjects) { - var h = drawable.HitObject; - switch (drawable) { case DrawableHitCircle circle: easeTo(circle, cursorPos); - break; case DrawableSlider slider: - // Move slider to cursor if (!slider.HeadCircle.Result.HasResult) - { easeTo(slider, cursorPos); - } - // Move slider so that sliderball stays on the cursor else - { - slider.HeadCircle.Hide(); // hide flash, triangles, ... so they don't move with slider easeTo(slider, cursorPos - slider.Ball.DrawPosition); - } break; } @@ -89,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Mods private void easeTo(DrawableHitObject hitObject, Vector2 destination) { - double dampLength = Interpolation.Lerp(500, 50, AssistStrength.Value); + double dampLength = Interpolation.Lerp(3000, 40, AssistStrength.Value); float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); From 4758de226beda4f592aecef6f2ec2d8d0c390f03 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 16:27:59 +0900 Subject: [PATCH 0493/5199] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index f85a96f819..963b61a2d3 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index aa6fb93aa0..cc68393eae 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index fbb4688588..846617c2f7 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + From 5e3d124eef996801dcb315cc48ed7499dc4aa0ef Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Feb 2022 17:20:22 +0800 Subject: [PATCH 0494/5199] Add scrolling to the extended statistics panel --- .../Ranking/Statistics/StatisticsPanel.cs | 78 +++++++++++++------ 1 file changed, 56 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 9d89aa213c..1c96e640b4 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Beatmaps; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Placeholders; using osu.Game.Scoring; @@ -85,15 +86,21 @@ namespace osu.Game.Screens.Ranking.Statistics playableBeatmap = beatmapManager.GetWorkingBeatmap(newScore.BeatmapInfo).GetPlayableBeatmap(newScore.Ruleset, newScore.Mods); }, loadCancellation.Token).ContinueWith(t => Schedule(() => { - var rows = new FillFlowContainer + FillFlowContainer rows; + Container container = new OsuScrollContainer(Direction.Vertical) { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Direction = FillDirection.Vertical, - Spacing = new Vector2(30, 15), - Alpha = 0 + Alpha = 0, + Children = new[] + { + rows = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + } + } }; bool panelIsEmpty = true; @@ -107,6 +114,8 @@ namespace osu.Game.Screens.Ranking.Statistics if (columnsToDisplay?.Any() ?? false) panelIsEmpty = false; + else + continue; rows.Add(new GridContainer { @@ -114,6 +123,7 @@ namespace osu.Game.Screens.Ranking.Statistics Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Bottom = 15 }, Content = new[] { columnsToDisplay?.Select(c => new StatisticContainer(c) @@ -130,27 +140,51 @@ namespace osu.Game.Screens.Ranking.Statistics if (!hitEventsAvailable) { - rows.Add(new FillFlowContainer + if (panelIsEmpty) { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] + // Replace the scroll container with fill flow container to get the message centered. + container = new FillFlowContainer { - new MessagePlaceholder(panelIsEmpty - ? "Extended statistics are only available after watching a replay!" - : "More statistics available after watching a replay!"), - new ReplayDownloadButton(newScore) + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - Scale = new Vector2(1.5f), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - } - }); + new MessagePlaceholder("Extended statistics are only available after watching a replay!"), + new ReplayDownloadButton(newScore) + { + Scale = new Vector2(1.5f), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + }; + } + else + { + rows.Add(new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Children = new Drawable[] + { + new MessagePlaceholder("More statistics available after watching a replay!"), + new ReplayDownloadButton(newScore) + { + Scale = new Vector2(1.5f), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + }); + } } - LoadComponentAsync(rows, d => + LoadComponentAsync(container, d => { if (!Score.Value.Equals(newScore)) return; From 6a482827fea64cbc0869700489e4feb9389f86d6 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Feb 2022 17:23:03 +0800 Subject: [PATCH 0495/5199] Fix weird line breaking --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 6 ++---- osu.Game.Rulesets.Osu/OsuRuleset.cs | 9 +++------ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 6 ++---- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 8ac8001457..0ea1cd5fc7 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -370,8 +370,7 @@ namespace osu.Game.Rulesets.Mania { Columns = new[] { - new StatisticItem("Timing Distribution", - true, + new StatisticItem("Timing Distribution", true, () => new HitEventTimingDistributionGraph(score.HitEvents) { RelativeSizeAxes = Axes.X, @@ -383,8 +382,7 @@ namespace osu.Game.Rulesets.Mania { Columns = new[] { - new StatisticItem(string.Empty, - true, + new StatisticItem(string.Empty, true, () => new SimpleStatisticTable(3, new SimpleStatisticItem[] { new UnstableRate(score.HitEvents) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index b6f417b7fe..2bf47100c1 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -277,8 +277,7 @@ namespace osu.Game.Rulesets.Osu { Columns = new[] { - new StatisticItem("Timing Distribution", - true, + new StatisticItem("Timing Distribution", true, () => new HitEventTimingDistributionGraph(timedHitEvents) { RelativeSizeAxes = Axes.X, @@ -290,8 +289,7 @@ namespace osu.Game.Rulesets.Osu { Columns = new[] { - new StatisticItem("Accuracy Heatmap", - true, + new StatisticItem("Accuracy Heatmap", true, () => new AccuracyHeatmap(score, playableBeatmap) { RelativeSizeAxes = Axes.X, @@ -303,8 +301,7 @@ namespace osu.Game.Rulesets.Osu { Columns = new[] { - new StatisticItem(string.Empty, - true, + new StatisticItem(string.Empty, true, () => new SimpleStatisticTable(3, new SimpleStatisticItem[] { new UnstableRate(timedHitEvents) diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 2a64b8dddd..fe4116b4a6 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -213,8 +213,7 @@ namespace osu.Game.Rulesets.Taiko { Columns = new[] { - new StatisticItem("Timing Distribution", - true, + new StatisticItem("Timing Distribution", true, () => new HitEventTimingDistributionGraph(timedHitEvents) { RelativeSizeAxes = Axes.X, @@ -226,8 +225,7 @@ namespace osu.Game.Rulesets.Taiko { Columns = new[] { - new StatisticItem(string.Empty, - true, + new StatisticItem(string.Empty, true, () => new SimpleStatisticTable(3, new SimpleStatisticItem[] { new UnstableRate(timedHitEvents) From 90e30bc9e8b3e7058aebfa83336383d52d0e0efb Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Feb 2022 17:26:17 +0800 Subject: [PATCH 0496/5199] Remove useless null checks --- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 1c96e640b4..8b69f83055 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -110,9 +110,9 @@ namespace osu.Game.Screens.Ranking.Statistics { var columnsToDisplay = hitEventsAvailable ? row.Columns - : row.Columns?.Where(c => !c.RequiresHitEvents).ToArray(); + : row.Columns.Where(c => !c.RequiresHitEvents).ToArray(); - if (columnsToDisplay?.Any() ?? false) + if (columnsToDisplay.Any()) panelIsEmpty = false; else continue; @@ -132,8 +132,8 @@ namespace osu.Game.Screens.Ranking.Statistics Origin = Anchor.Centre, }).Cast().ToArray() }, - ColumnDimensions = Enumerable.Range(0, columnsToDisplay?.Length ?? 0) - .Select(i => columnsToDisplay?[i].Dimension ?? new Dimension()).ToArray(), + ColumnDimensions = Enumerable.Range(0, columnsToDisplay.Length) + .Select(i => columnsToDisplay[i].Dimension ?? new Dimension()).ToArray(), RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } }); } From 042574660c10d6a28c29a81d6f45f5b8547f956a Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Feb 2022 17:29:03 +0800 Subject: [PATCH 0497/5199] Rename "Content" to "CreateContent" --- .../Ranking/Statistics/StatisticContainer.cs | 2 +- .../Screens/Ranking/Statistics/StatisticItem.cs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs index f3bd2c6fc1..79f813ef64 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Ranking.Statistics RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Margin = new MarginPadding { Top = 15 }, - Child = item.Content() + Child = item.CreateContent() } }, }, diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs index 5e6ddf445b..cb5ba4b9fe 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs @@ -19,9 +19,9 @@ namespace osu.Game.Screens.Ranking.Statistics public readonly string Name; /// - /// The content to be displayed. + /// A function returning the content to be displayed. /// - public readonly Func Content; + public readonly Func CreateContent; /// /// The of this row. This can be thought of as the column dimension of an encompassing . @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Ranking.Statistics public readonly Dimension Dimension; /// - /// Whether this item requires hit events. If true, the will not be created if no hit events are available. + /// Whether this item requires hit events. If true, will not be called if no hit events are available. /// public readonly bool RequiresHitEvents; @@ -37,14 +37,14 @@ namespace osu.Game.Screens.Ranking.Statistics /// Creates a new , to be displayed inside a in the results screen. /// /// The name of the item. Can be to hide the item header. - /// Whether this item requires hit events. If true, the will not be created if no hit events are available. - /// The content to be displayed. + /// Whether this item requires hit events. If true, will not be called if no hit events are available. + /// A function returning the content to be displayed. /// The of this item. This can be thought of as the column dimension of an encompassing . - public StatisticItem([NotNull] string name, bool requiresHitEvents, [NotNull] Func content, [CanBeNull] Dimension dimension = null) + public StatisticItem([NotNull] string name, bool requiresHitEvents, [NotNull] Func createContent, [CanBeNull] Dimension dimension = null) { Name = name; RequiresHitEvents = requiresHitEvents; - Content = content; + CreateContent = createContent; Dimension = dimension; } } From 36bfef4f54f4f116ec7f84747db2c4c93a883d0d Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Feb 2022 17:32:16 +0800 Subject: [PATCH 0498/5199] Dispose container before replacing --- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 8b69f83055..2327f60f81 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -143,6 +143,7 @@ namespace osu.Game.Screens.Ranking.Statistics if (panelIsEmpty) { // Replace the scroll container with fill flow container to get the message centered. + container.Dispose(); container = new FillFlowContainer { RelativeSizeAxes = Axes.Both, From b5fb3b7dae02bdbaeec37e535d447bb09fcfbaeb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 2 Feb 2022 18:42:20 +0900 Subject: [PATCH 0499/5199] Fix crash when selecting swap mod as freemod --- .../TestSceneMultiplayerMatchSubScreen.cs | 25 +++++++++++++++++++ .../Participants/ParticipantPanel.cs | 8 ++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 9d14d80d07..869fb17317 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -15,8 +15,12 @@ using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Taiko; +using osu.Game.Rulesets.Taiko.Mods; +using osu.Game.Rulesets.UI; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; +using osu.Game.Screens.OnlinePlay.Multiplayer.Participants; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; @@ -77,6 +81,27 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for join", () => RoomJoined); } + [Test] + public void TestTaikoOnlyMod() + { + AddStep("add playlist item", () => + { + SelectedRoom.Value.Playlist.Add(new PlaylistItem + { + Beatmap = { Value = new TestBeatmap(new TaikoRuleset().RulesetInfo).BeatmapInfo }, + Ruleset = { Value = new TaikoRuleset().RulesetInfo }, + AllowedMods = { new TaikoModSwap() } + }); + }); + + ClickButtonWhenEnabled(); + + AddUntilStep("wait for join", () => RoomJoined); + + AddStep("select swap mod", () => Client.ChangeUserMods(API.LocalUser.Value.OnlineID, new[] { new TaikoModSwap() })); + AddUntilStep("participant panel has mod", () => this.ChildrenOfType().Any(p => p.ChildrenOfType().Any(m => m.Mod is TaikoModSwap))); + } + [Test] public void TestSettingValidity() { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 8fbaebadfe..96a665f33d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -18,6 +19,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Screens.Play.HUD; using osu.Game.Users; @@ -184,8 +186,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants const double fade_time = 50; - // Todo: Should use the room's selected item to determine ruleset. - var ruleset = rulesets.GetRuleset(0)?.CreateInstance(); + var currentItem = Playlist.GetCurrentItem(); + Debug.Assert(currentItem != null); + + var ruleset = rulesets.GetRuleset(currentItem.RulesetID)?.CreateInstance(); int? currentModeRank = ruleset != null ? User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank : null; userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty; From b0023b9809d15cdb7f19d5a2f6be9c0d047bf9fc Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Feb 2022 19:00:46 +0800 Subject: [PATCH 0500/5199] Also dispose `rows` --- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 2327f60f81..62f9eb8506 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -143,6 +143,7 @@ namespace osu.Game.Screens.Ranking.Statistics if (panelIsEmpty) { // Replace the scroll container with fill flow container to get the message centered. + rows.Dispose(); container.Dispose(); container = new FillFlowContainer { From 1e19c7046a71de056cc975deaa045eb2eff46c8b Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Feb 2022 19:02:29 +0800 Subject: [PATCH 0501/5199] Use spacing instead of bottom margin --- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 62f9eb8506..68414c4d49 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -98,7 +98,8 @@ namespace osu.Game.Screens.Ranking.Statistics rows = new FillFlowContainer { RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(30, 15) } } }; @@ -123,7 +124,6 @@ namespace osu.Game.Screens.Ranking.Statistics Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Bottom = 15 }, Content = new[] { columnsToDisplay?.Select(c => new StatisticContainer(c) From 3c2a6fe2086a1eb560a4aefe1805a8d402b1d153 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Feb 2022 19:07:14 +0800 Subject: [PATCH 0502/5199] Don't prompt for a replay if no item requires hit events --- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 68414c4d49..08be1de652 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -105,6 +105,7 @@ namespace osu.Game.Screens.Ranking.Statistics }; bool panelIsEmpty = true; + bool panelIsComplete = true; bool hitEventsAvailable = newScore.HitEvents.Count != 0; foreach (var row in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap)) @@ -113,6 +114,9 @@ namespace osu.Game.Screens.Ranking.Statistics ? row.Columns : row.Columns.Where(c => !c.RequiresHitEvents).ToArray(); + if (columnsToDisplay.Length < row.Columns.Length) + panelIsComplete = false; + if (columnsToDisplay.Any()) panelIsEmpty = false; else @@ -163,7 +167,7 @@ namespace osu.Game.Screens.Ranking.Statistics } }; } - else + else if (!panelIsComplete) { rows.Add(new FillFlowContainer { From 19eb9ad8a7e86b384ca65648b95e35e37c2ce068 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 23:02:38 +0900 Subject: [PATCH 0503/5199] Reorder `StatisticsItem` constructor to make a touch more sense --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 20 ++++++------ osu.Game.Rulesets.Osu/OsuRuleset.cs | 31 +++++++++---------- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 20 ++++++------ .../Ranking/Statistics/StatisticItem.cs | 4 +-- 4 files changed, 34 insertions(+), 41 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 0ea1cd5fc7..ffb26b224f 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -370,23 +370,21 @@ namespace osu.Game.Rulesets.Mania { Columns = new[] { - new StatisticItem("Timing Distribution", true, - () => new HitEventTimingDistributionGraph(score.HitEvents) - { - RelativeSizeAxes = Axes.X, - Height = 250 - }), + new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(score.HitEvents) + { + RelativeSizeAxes = Axes.X, + Height = 250 + }, true), } }, new StatisticRow { Columns = new[] { - new StatisticItem(string.Empty, true, - () => new SimpleStatisticTable(3, new SimpleStatisticItem[] - { - new UnstableRate(score.HitEvents) - })) + new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[] + { + new UnstableRate(score.HitEvents) + }), true) } } }; diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 2bf47100c1..1122a869b7 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -277,35 +277,32 @@ namespace osu.Game.Rulesets.Osu { Columns = new[] { - new StatisticItem("Timing Distribution", true, - () => new HitEventTimingDistributionGraph(timedHitEvents) - { - RelativeSizeAxes = Axes.X, - Height = 250 - }), + new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(timedHitEvents) + { + RelativeSizeAxes = Axes.X, + Height = 250 + }, true), } }, new StatisticRow { Columns = new[] { - new StatisticItem("Accuracy Heatmap", true, - () => new AccuracyHeatmap(score, playableBeatmap) - { - RelativeSizeAxes = Axes.X, - Height = 250 - }), + new StatisticItem("Accuracy Heatmap", () => new AccuracyHeatmap(score, playableBeatmap) + { + RelativeSizeAxes = Axes.X, + Height = 250 + }, true), } }, new StatisticRow { Columns = new[] { - new StatisticItem(string.Empty, true, - () => new SimpleStatisticTable(3, new SimpleStatisticItem[] - { - new UnstableRate(timedHitEvents) - })) + new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[] + { + new UnstableRate(timedHitEvents) + }), true) } } }; diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index fe4116b4a6..21c99c0d2f 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -213,23 +213,21 @@ namespace osu.Game.Rulesets.Taiko { Columns = new[] { - new StatisticItem("Timing Distribution", true, - () => new HitEventTimingDistributionGraph(timedHitEvents) - { - RelativeSizeAxes = Axes.X, - Height = 250 - }), + new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(timedHitEvents) + { + RelativeSizeAxes = Axes.X, + Height = 250 + }, true), } }, new StatisticRow { Columns = new[] { - new StatisticItem(string.Empty, true, - () => new SimpleStatisticTable(3, new SimpleStatisticItem[] - { - new UnstableRate(timedHitEvents) - })) + new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[] + { + new UnstableRate(timedHitEvents) + }), true) } } }; diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs index cb5ba4b9fe..3cfc38e263 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs @@ -37,10 +37,10 @@ namespace osu.Game.Screens.Ranking.Statistics /// Creates a new , to be displayed inside a in the results screen. /// /// The name of the item. Can be to hide the item header. - /// Whether this item requires hit events. If true, will not be called if no hit events are available. /// A function returning the content to be displayed. + /// Whether this item requires hit events. If true, will not be called if no hit events are available. /// The of this item. This can be thought of as the column dimension of an encompassing . - public StatisticItem([NotNull] string name, bool requiresHitEvents, [NotNull] Func createContent, [CanBeNull] Dimension dimension = null) + public StatisticItem([NotNull] string name, [NotNull] Func createContent, bool requiresHitEvents = false, [CanBeNull] Dimension dimension = null) { Name = name; RequiresHitEvents = requiresHitEvents; From 9d1d13c7152592d31a212fc04b04aac1fac171b1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 2 Feb 2022 23:00:43 +0900 Subject: [PATCH 0504/5199] Fix up TestSpectatorClient implementation Rather than using a list which is supposed to be updated "client"-side, now uses the "server"-side list. --- .../Visual/Spectator/TestSpectatorClient.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index 7848a825f4..6862cda88c 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -39,11 +39,7 @@ namespace osu.Game.Tests.Visual.Spectator public TestSpectatorClient() { - OnNewFrames += (i, bundle) => - { - if (PlayingUserStates.ContainsKey(i)) - lastReceivedUserFrames[i] = bundle.Frames[^1]; - }; + OnNewFrames += (i, bundle) => lastReceivedUserFrames[i] = bundle.Frames[^1]; } /// @@ -62,16 +58,20 @@ namespace osu.Game.Tests.Visual.Spectator /// Ends play for an arbitrary user. /// /// The user to end play for. - public void EndPlay(int userId) + /// The spectator state to end play with. + public void EndPlay(int userId, SpectatingUserState state = SpectatingUserState.Quit) { - if (!PlayingUserStates.ContainsKey(userId)) + if (!userBeatmapDictionary.ContainsKey(userId)) return; ((ISpectatorClient)this).UserFinishedPlaying(userId, new SpectatorState { BeatmapID = userBeatmapDictionary[userId], RulesetID = 0, + State = state }); + + userBeatmapDictionary.Remove(userId); } public new void Schedule(Action action) => base.Schedule(action); @@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Spectator protected override Task WatchUserInternal(int userId) { // When newly watching a user, the server sends the playing state immediately. - if (PlayingUserStates.ContainsKey(userId)) + if (userBeatmapDictionary.ContainsKey(userId)) sendPlayingState(userId); return Task.CompletedTask; @@ -144,6 +144,7 @@ namespace osu.Game.Tests.Visual.Spectator { BeatmapID = userBeatmapDictionary[userId], RulesetID = 0, + State = SpectatingUserState.Playing }); } } From 589f5e7a31f53275f7cd001adbe4deedb1e18473 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 2 Feb 2022 23:09:38 +0900 Subject: [PATCH 0505/5199] Update test which has now been resolved --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index d28e5203e3..9f8470446c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -155,11 +155,12 @@ namespace osu.Game.Tests.Visual.Gameplay waitForPlayer(); checkPaused(true); + sendFrames(); - finish(); + finish(SpectatingUserState.Failed); - checkPaused(false); - // TODO: should replay until running out of frames then fail + checkPaused(false); // Should continue playing until out of frames + checkPaused(true); } [Test] @@ -246,7 +247,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void start(int? beatmapId = null) => AddStep("start play", () => spectatorClient.StartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId)); - private void finish() => AddStep("end play", () => spectatorClient.EndPlay(streamingUser.Id)); + private void finish(SpectatingUserState state = SpectatingUserState.Quit) => AddStep("end play", () => spectatorClient.EndPlay(streamingUser.Id, state)); private void checkPaused(bool state) => AddUntilStep($"game is {(state ? "paused" : "playing")}", () => player.ChildrenOfType().First().IsPaused.Value == state); From fcbba3d9481ca396acd9885593065ea782362475 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 2 Feb 2022 23:11:29 +0900 Subject: [PATCH 0506/5199] Rename PlayingUserStates -> WatchingUserStates --- .../Visual/Gameplay/TestSceneSpectatorHost.cs | 4 ++-- .../TestSceneMultiplayerGameplayLeaderboard.cs | 2 +- osu.Game/Online/Spectator/SpectatorClient.cs | 12 ++++++------ .../Overlays/Dashboard/CurrentlyPlayingDisplay.cs | 2 +- osu.Game/Screens/Spectate/SpectatorScreen.cs | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs index 409cec4cf6..6d6b0bf89e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs @@ -37,8 +37,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestClientSendsCorrectRuleset() { - AddUntilStep("spectator client sending frames", () => spectatorClient.PlayingUserStates.ContainsKey(dummy_user_id)); - AddAssert("spectator client sent correct ruleset", () => spectatorClient.PlayingUserStates[dummy_user_id].RulesetID == Ruleset.Value.OnlineID); + AddUntilStep("spectator client sending frames", () => spectatorClient.WatchingUserStates.ContainsKey(dummy_user_id)); + AddAssert("spectator client sent correct ruleset", () => spectatorClient.WatchingUserStates[dummy_user_id].RulesetID == Ruleset.Value.OnlineID); } public override void TearDownSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 8b71dfac21..55450b36e2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -115,7 +115,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void RandomlyUpdateState() { - foreach ((int userId, _) in PlayingUserStates) + foreach ((int userId, _) in WatchingUserStates) { if (RNG.NextBool()) continue; diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 156d544960..3646c51d94 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -37,8 +37,8 @@ namespace osu.Game.Online.Spectator private readonly List watchingUsers = new List(); - public IBindableDictionary PlayingUserStates => playingUserStates; - private readonly BindableDictionary playingUserStates = new BindableDictionary(); + public IBindableDictionary WatchingUserStates => watchingUserStates; + private readonly BindableDictionary watchingUserStates = new BindableDictionary(); private IBeatmap? currentBeatmap; private Score? currentScore; @@ -85,7 +85,7 @@ namespace osu.Game.Online.Spectator BeginPlayingInternal(currentState); } else - playingUserStates.Clear(); + watchingUserStates.Clear(); }), true); } @@ -94,7 +94,7 @@ namespace osu.Game.Online.Spectator Schedule(() => { if (watchingUsers.Contains(userId)) - playingUserStates[userId] = state; + watchingUserStates[userId] = state; OnUserBeganPlaying?.Invoke(userId, state); }); @@ -106,7 +106,7 @@ namespace osu.Game.Online.Spectator Schedule(() => { if (watchingUsers.Contains(userId)) - playingUserStates[userId] = state; + watchingUserStates[userId] = state; OnUserFinishedPlaying?.Invoke(userId, state); }); @@ -193,7 +193,7 @@ namespace osu.Game.Online.Spectator Schedule(() => { watchingUsers.Remove(userId); - playingUserStates.Remove(userId); + watchingUserStates.Remove(userId); StopWatchingUserInternal(userId); }); } diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index 383f17d8d2..975402a443 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Dashboard { base.LoadComplete(); - userStates.BindTo(spectatorClient.PlayingUserStates); + userStates.BindTo(spectatorClient.WatchingUserStates); userStates.BindCollectionChanged(onUserStatesChanged, true); } diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 783ba494eb..0b2a7cecf6 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Spectate userMap[u.Id] = u; } - userStates.BindTo(spectatorClient.PlayingUserStates); + userStates.BindTo(spectatorClient.WatchingUserStates); userStates.BindCollectionChanged(onUserStatesChanged, true); realmSubscription = realm.RegisterForNotifications( From 81a22dbd29543b3ff5387669bd6ce1fc048a4811 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 2 Feb 2022 23:19:43 +0900 Subject: [PATCH 0507/5199] Add back playing users list --- osu.Game/Online/Spectator/SpectatorClient.cs | 28 +++++++++-- .../Dashboard/CurrentlyPlayingDisplay.cs | 48 ++++++++----------- 2 files changed, 45 insertions(+), 31 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 3646c51d94..9e168411b0 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -35,16 +35,28 @@ namespace osu.Game.Online.Spectator /// public abstract IBindable IsConnected { get; } + /// + /// The states of all users currently being watched. + /// + public IBindableDictionary WatchingUserStates => watchingUserStates; + + /// + /// A global list of all players currently playing. + /// + public IBindableList PlayingUsers => playingUsers; + + /// + /// All users currently being watched. + /// private readonly List watchingUsers = new List(); - public IBindableDictionary WatchingUserStates => watchingUserStates; private readonly BindableDictionary watchingUserStates = new BindableDictionary(); + private readonly BindableList playingUsers = new BindableList(); + private readonly SpectatorState currentState = new SpectatorState(); private IBeatmap? currentBeatmap; private Score? currentScore; - private readonly SpectatorState currentState = new SpectatorState(); - /// /// Whether the local user is playing. /// @@ -85,7 +97,10 @@ namespace osu.Game.Online.Spectator BeginPlayingInternal(currentState); } else + { watchingUserStates.Clear(); + playingUsers.Clear(); + } }), true); } @@ -93,8 +108,12 @@ namespace osu.Game.Online.Spectator { Schedule(() => { + if (!playingUsers.Contains(userId)) + playingUsers.Add(userId); + if (watchingUsers.Contains(userId)) watchingUserStates[userId] = state; + OnUserBeganPlaying?.Invoke(userId, state); }); @@ -105,8 +124,11 @@ namespace osu.Game.Online.Spectator { Schedule(() => { + playingUsers.Remove(userId); + if (watchingUsers.Contains(userId)) watchingUserStates[userId] = state; + OnUserFinishedPlaying?.Invoke(userId, state); }); diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index 975402a443..02ef28f825 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.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.Collections.Specialized; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; @@ -22,7 +23,7 @@ namespace osu.Game.Overlays.Dashboard { internal class CurrentlyPlayingDisplay : CompositeDrawable { - private readonly IBindableDictionary userStates = new BindableDictionary(); + private readonly IBindableList playingUsers = new BindableList(); private FillFlowContainer userFlow; @@ -51,55 +52,46 @@ namespace osu.Game.Overlays.Dashboard { base.LoadComplete(); - userStates.BindTo(spectatorClient.WatchingUserStates); - userStates.BindCollectionChanged(onUserStatesChanged, true); + playingUsers.BindTo(spectatorClient.PlayingUsers); + playingUsers.BindCollectionChanged(onPlayingUsersChanged, true); } - private void onUserStatesChanged(object sender, NotifyDictionaryChangedEventArgs e) => Schedule(() => + private void onPlayingUsersChanged(object sender, NotifyCollectionChangedEventArgs e) => Schedule(() => { switch (e.Action) { - case NotifyDictionaryChangedAction.Add: - case NotifyDictionaryChangedAction.Replace: + case NotifyCollectionChangedAction.Add: Debug.Assert(e.NewItems != null); - foreach ((int userId, SpectatorState state) in e.NewItems) + foreach (int userId in e.NewItems) { - if (state.State != SpectatingUserState.Playing) - { - removePlayingUser(userId); - continue; - } - users.GetUserAsync(userId).ContinueWith(task => { var user = task.GetResultSafely(); if (user != null) - Schedule(() => addPlayingUser(user)); + { + Schedule(() => + { + // user may no longer be playing. + if (!playingUsers.Contains(user.Id)) + return; + + userFlow.Add(createUserPanel(user)); + }); + } }); } break; - case NotifyDictionaryChangedAction.Remove: + case NotifyCollectionChangedAction.Remove: Debug.Assert(e.OldItems != null); - foreach ((int userId, _) in e.OldItems) - removePlayingUser(userId); + foreach (int userId in e.OldItems) + userFlow.FirstOrDefault(card => card.User.Id == userId)?.Expire(); break; } - - void addPlayingUser(APIUser user) - { - // user may no longer be playing. - if (!userStates.TryGetValue(user.Id, out var state2) || state2.State != SpectatingUserState.Playing) - return; - - userFlow.Add(createUserPanel(user)); - } - - void removePlayingUser(int userId) => userFlow.FirstOrDefault(card => card.User.Id == userId)?.Expire(); }); private PlayingUserPanel createUserPanel(APIUser user) => From 074a69163558c6ee6c2b177e97ed53126ab8b805 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 2 Feb 2022 17:16:35 +0300 Subject: [PATCH 0508/5199] Set keyboard step to `0.1` for difficulty adjust sliders --- osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index 45873a321a..c8e7284f5d 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -105,6 +105,7 @@ namespace osu.Game.Rulesets.Mods { ShowsDefaultIndicator = false, Current = currentNumber, + KeyboardStep = 0.1f, } }; From 74637444070838cc55bd0d7e04f9580f13552737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 2 Feb 2022 19:17:33 +0100 Subject: [PATCH 0509/5199] Fix osu! autoplay-like mods not declaring incompatibility with `AimAssist` --- osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs index 106edfb623..2668013321 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModAutoplay : ModAutoplay { - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAimAssist), typeof(OsuModAutopilot), typeof(OsuModSpunOut) }).ToArray(); public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs index f478790134..ff31cfcd18 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModCinema : ModCinema { - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAimAssist), typeof(OsuModAutopilot), typeof(OsuModSpunOut) }).ToArray(); public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { From dca1bddabb789867e34562e20bba19d02e22fab1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 2 Feb 2022 21:25:50 +0300 Subject: [PATCH 0510/5199] Lock supported interface orientation to landscape for iPhone --- osu.Game.Rulesets.Catch.Tests.iOS/Info.plist | 5 +++++ osu.Game.Rulesets.Mania.Tests.iOS/Info.plist | 5 +++++ osu.Game.Rulesets.Osu.Tests.iOS/Info.plist | 5 +++++ osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist | 5 +++++ osu.Game.Tests.iOS/Info.plist | 5 +++++ osu.iOS/Info.plist | 5 +++++ 6 files changed, 30 insertions(+) diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist index 3ba1886d98..88317b6393 100644 --- a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist @@ -24,6 +24,11 @@ armv7 UISupportedInterfaceOrientations + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist index 09ed2dd007..8d05c2bb73 100644 --- a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist @@ -24,6 +24,11 @@ armv7 UISupportedInterfaceOrientations + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist index dd032ef1c1..63ad313a70 100644 --- a/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist @@ -24,6 +24,11 @@ armv7 UISupportedInterfaceOrientations + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist index ac658cd14e..3c76622ee3 100644 --- a/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist @@ -24,6 +24,11 @@ armv7 UISupportedInterfaceOrientations + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown diff --git a/osu.Game.Tests.iOS/Info.plist b/osu.Game.Tests.iOS/Info.plist index 1a89345bc5..835e958bc6 100644 --- a/osu.Game.Tests.iOS/Info.plist +++ b/osu.Game.Tests.iOS/Info.plist @@ -24,6 +24,11 @@ armv7 UISupportedInterfaceOrientations + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist index 2592f909ce..1156c0954c 100644 --- a/osu.iOS/Info.plist +++ b/osu.iOS/Info.plist @@ -40,6 +40,11 @@ NSMicrophoneUsageDescription We don't really use the microphone. UISupportedInterfaceOrientations + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown From 4aa4df69f258bd4027c8c90abba8adaa6a3a7b28 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 2 Feb 2022 22:17:23 +0300 Subject: [PATCH 0511/5199] Reorder iOS landscape orientations to prioritise "Landscape Right" "Landscape Right" is often the proper default for landscape-only applications. Matches up with all other landscape-only iOS games I have locally. --- osu.Game.Rulesets.Catch.Tests.iOS/Info.plist | 4 ++-- osu.Game.Rulesets.Mania.Tests.iOS/Info.plist | 4 ++-- osu.Game.Rulesets.Osu.Tests.iOS/Info.plist | 4 ++-- osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist | 4 ++-- osu.Game.Tests.iOS/Info.plist | 4 ++-- osu.iOS/Info.plist | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist index 88317b6393..33ddac6dfb 100644 --- a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist @@ -25,15 +25,15 @@ UISupportedInterfaceOrientations - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft XSAppIconAssets Assets.xcassets/AppIcon.appiconset diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist index 8d05c2bb73..78349334b4 100644 --- a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist @@ -25,15 +25,15 @@ UISupportedInterfaceOrientations - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft XSAppIconAssets Assets.xcassets/AppIcon.appiconset diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist index 63ad313a70..b9f371c049 100644 --- a/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist @@ -25,15 +25,15 @@ UISupportedInterfaceOrientations - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft XSAppIconAssets Assets.xcassets/AppIcon.appiconset diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist index 3c76622ee3..65c47d2115 100644 --- a/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist @@ -25,15 +25,15 @@ UISupportedInterfaceOrientations - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft XSAppIconAssets Assets.xcassets/AppIcon.appiconset diff --git a/osu.Game.Tests.iOS/Info.plist b/osu.Game.Tests.iOS/Info.plist index 835e958bc6..ed0c2e4dbf 100644 --- a/osu.Game.Tests.iOS/Info.plist +++ b/osu.Game.Tests.iOS/Info.plist @@ -25,15 +25,15 @@ UISupportedInterfaceOrientations - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft XSAppIconAssets Assets.xcassets/AppIcon.appiconset diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist index 1156c0954c..02968b87a7 100644 --- a/osu.iOS/Info.plist +++ b/osu.iOS/Info.plist @@ -41,15 +41,15 @@ We don't really use the microphone. UISupportedInterfaceOrientations - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft XSAppIconAssets Assets.xcassets/AppIcon.appiconset From 82f9ad63f514f1220c44bb087d58a154a1e1f2c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 2 Feb 2022 20:41:25 +0100 Subject: [PATCH 0512/5199] Fix flashlight size multiplier printing with too many decimal digits --- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 2 +- osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 2 +- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 2 +- osu.Game/Rulesets/Mods/ModFlashlight.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 2d92c925d7..d576ea3df8 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Mods public override double ScoreMultiplier => 1.12; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] - public override BindableNumber SizeMultiplier { get; } = new BindableNumber + public override BindableFloat SizeMultiplier { get; } = new BindableFloat { MinValue = 0.5f, MaxValue = 1.5f, diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 1ee4ea12e3..8ef5bfd94c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Mods public override Type[] IncompatibleMods => new[] { typeof(ModHidden) }; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] - public override BindableNumber SizeMultiplier { get; } = new BindableNumber + public override BindableFloat SizeMultiplier { get; } = new BindableFloat { MinValue = 0.5f, MaxValue = 3f, diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index b4eff57c55..38c84be295 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Mods }; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] - public override BindableNumber SizeMultiplier { get; } = new BindableNumber + public override BindableFloat SizeMultiplier { get; } = new BindableFloat { MinValue = 0.5f, MaxValue = 2f, diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index fb07c687bb..beec785fe8 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public override double ScoreMultiplier => 1.12; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] - public override BindableNumber SizeMultiplier { get; } = new BindableNumber + public override BindableFloat SizeMultiplier { get; } = new BindableFloat { MinValue = 0.5f, MaxValue = 1.5f, diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index e6487c6b29..b449f3f64d 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mods public override string Description => "Restricted view area."; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] - public abstract BindableNumber SizeMultiplier { get; } + public abstract BindableFloat SizeMultiplier { get; } [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] public abstract BindableBool ComboBasedSize { get; } From e2fcdc394b25cbeb0af8a74c4e68bd79f722f8c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 16:29:00 +0100 Subject: [PATCH 0513/5199] Extract method for difficulty switch menu creation --- osu.Game/Screens/Edit/Editor.cs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 2fead84deb..71a19c4425 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -806,6 +806,15 @@ namespace osu.Game.Screens.Edit fileMenuItems.Add(new EditorMenuItemSpacer()); + fileMenuItems.Add(createDifficultySwitchMenu()); + + fileMenuItems.Add(new EditorMenuItemSpacer()); + fileMenuItems.Add(new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit)); + return fileMenuItems; + } + + private EditorMenuItem createDifficultySwitchMenu() + { var beatmapSet = playableBeatmap.BeatmapInfo.BeatmapSet; Debug.Assert(beatmapSet != null); @@ -818,20 +827,13 @@ namespace osu.Game.Screens.Edit difficultyItems.Add(new EditorMenuItemSpacer()); foreach (var beatmap in rulesetBeatmaps.OrderBy(b => b.StarRating)) - difficultyItems.Add(createDifficultyMenuItem(beatmap)); + { + bool isCurrentDifficulty = playableBeatmap.BeatmapInfo.Equals(beatmap); + difficultyItems.Add(new DifficultyMenuItem(beatmap, isCurrentDifficulty, SwitchToDifficulty)); + } } - fileMenuItems.Add(new EditorMenuItem("Change difficulty") { Items = difficultyItems }); - - fileMenuItems.Add(new EditorMenuItemSpacer()); - fileMenuItems.Add(new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit)); - return fileMenuItems; - } - - private DifficultyMenuItem createDifficultyMenuItem(BeatmapInfo beatmapInfo) - { - bool isCurrentDifficulty = playableBeatmap.BeatmapInfo.Equals(beatmapInfo); - return new DifficultyMenuItem(beatmapInfo, isCurrentDifficulty, SwitchToDifficulty); + return new EditorMenuItem("Change difficulty") { Items = difficultyItems }; } protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, GetState(nextBeatmap)); From 3386f038ba0857f47934c438d7d8a7024e06285c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 16:34:02 +0100 Subject: [PATCH 0514/5199] Add new difficulty creation menu --- osu.Game/Screens/Edit/Editor.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 71a19c4425..61c5fd2ca4 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -77,6 +77,9 @@ namespace osu.Game.Screens.Edit [Resolved] private BeatmapManager beatmapManager { get; set; } + [Resolved] + private RulesetStore rulesets { get; set; } + [Resolved] private Storage storage { get; set; } @@ -806,6 +809,7 @@ namespace osu.Game.Screens.Edit fileMenuItems.Add(new EditorMenuItemSpacer()); + fileMenuItems.Add(createDifficultyCreationMenu()); fileMenuItems.Add(createDifficultySwitchMenu()); fileMenuItems.Add(new EditorMenuItemSpacer()); @@ -813,6 +817,16 @@ namespace osu.Game.Screens.Edit return fileMenuItems; } + private EditorMenuItem createDifficultyCreationMenu() + { + var rulesetItems = new List(); + + foreach (var ruleset in rulesets.AvailableRulesets.OrderBy(ruleset => ruleset.OnlineID)) + rulesetItems.Add(new EditorMenuItem(ruleset.Name)); + + return new EditorMenuItem("Create new difficulty") { Items = rulesetItems }; + } + private EditorMenuItem createDifficultySwitchMenu() { var beatmapSet = playableBeatmap.BeatmapInfo.BeatmapSet; From b613aedeb81ea46bcb664b9b76feac179252778f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 16:36:18 +0100 Subject: [PATCH 0515/5199] Fix menu item width changing when hovered --- osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs index 4267b82bb7..4ecc543ffd 100644 --- a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs @@ -117,6 +117,7 @@ namespace osu.Game.Graphics.UserInterface { NormalText = new OsuSpriteText { + AlwaysPresent = true, // ensures that the menu item does not change width when switching between normal and bold text. Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(size: text_size), @@ -124,7 +125,7 @@ namespace osu.Game.Graphics.UserInterface }, BoldText = new OsuSpriteText { - AlwaysPresent = true, + AlwaysPresent = true, // ensures that the menu item does not change width when switching between normal and bold text. Alpha = 0, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, From dc96c4888bfa5fa04ddd4228b9bf218d206a71c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 17:49:17 +0100 Subject: [PATCH 0516/5199] Add support for creating new blank difficulties --- osu.Game/Beatmaps/BeatmapManager.cs | 32 +++++++++++++++++++++- osu.Game/Beatmaps/BeatmapMetadata.cs | 16 ++++++++++- osu.Game/Beatmaps/BeatmapModelManager.cs | 23 ++++++++++++++-- osu.Game/Database/RealmObjectExtensions.cs | 11 +++++++- osu.Game/Models/RealmUser.cs | 6 +++- osu.Game/Screens/Edit/Editor.cs | 7 +++-- osu.Game/Screens/Edit/EditorLoader.cs | 11 ++++++-- 7 files changed, 95 insertions(+), 11 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index e4fdb3d471..38ba244f28 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -73,7 +73,9 @@ namespace osu.Game.Beatmaps new BeatmapModelManager(realm, storage, onlineLookupQueue); /// - /// Create a new . + /// Create a new beatmap set, backed by a model, + /// with a single difficulty which is backed by a model + /// and represented by the returned usable . /// public WorkingBeatmap CreateNew(RulesetInfo ruleset, APIUser user) { @@ -105,6 +107,34 @@ namespace osu.Game.Beatmaps return imported.PerformRead(s => GetWorkingBeatmap(s.Beatmaps.First())); } + /// + /// Add a new difficulty to the beatmap set represented by the provided . + /// The new difficulty will be backed by a model + /// and represented by the returned . + /// + public WorkingBeatmap CreateNewBlankDifficulty(BeatmapSetInfo beatmapSetInfo, RulesetInfo rulesetInfo) + { + // fetch one of the existing difficulties to copy timing points and metadata from, + // so that the user doesn't have to fill all of that out again. + // this silently assumes that all difficulties have the same timing points and metadata, + // but cases where this isn't true seem rather rare / pathological. + var referenceBeatmap = GetWorkingBeatmap(beatmapSetInfo.Beatmaps.First()); + + var newBeatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo(rulesetInfo, new BeatmapDifficulty(), referenceBeatmap.Metadata.DeepClone()) + }; + + foreach (var timingPoint in referenceBeatmap.Beatmap.ControlPointInfo.TimingPoints) + newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone()); + + var createdBeatmapInfo = beatmapModelManager.AddDifficultyToBeatmapSet(beatmapSetInfo, newBeatmap); + return GetWorkingBeatmap(createdBeatmapInfo); + } + + // TODO: add back support for making a copy of another difficulty + // (likely via a separate `CopyDifficulty()` method). + /// /// Delete a beatmap difficulty. /// diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index f6666a6ea9..3a24c4808f 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -7,6 +7,7 @@ using Newtonsoft.Json; using osu.Framework.Testing; using osu.Game.Models; using osu.Game.Users; +using osu.Game.Utils; using Realms; #nullable enable @@ -16,7 +17,7 @@ namespace osu.Game.Beatmaps [ExcludeFromDynamicCompile] [Serializable] [MapTo("BeatmapMetadata")] - public class BeatmapMetadata : RealmObject, IBeatmapMetadataInfo + public class BeatmapMetadata : RealmObject, IBeatmapMetadataInfo, IDeepCloneable { public string Title { get; set; } = string.Empty; @@ -57,5 +58,18 @@ namespace osu.Game.Beatmaps IUser IBeatmapMetadataInfo.Author => Author; public override string ToString() => this.GetDisplayTitle(); + + public BeatmapMetadata DeepClone() => new BeatmapMetadata(Author.DeepClone()) + { + Title = Title, + TitleUnicode = TitleUnicode, + Artist = Artist, + ArtistUnicode = ArtistUnicode, + Source = Source, + Tags = Tags, + PreviewTime = PreviewTime, + AudioFile = AudioFile, + BackgroundFile = BackgroundFile + }; } } diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index e8104f2ecb..2ab5ac1db9 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -49,7 +49,6 @@ namespace osu.Game.Beatmaps public virtual void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null) { var setInfo = beatmapInfo.BeatmapSet; - Debug.Assert(setInfo != null); // Difficulty settings must be copied first due to the clone in `Beatmap<>.BeatmapInfo_Set`. @@ -85,6 +84,24 @@ namespace osu.Game.Beatmaps WorkingBeatmapCache?.Invalidate(beatmapInfo); } + /// + /// Add a new difficulty to the beatmap set represented by the provided . + /// + public BeatmapInfo AddDifficultyToBeatmapSet(BeatmapSetInfo beatmapSetInfo, Beatmap beatmap) + { + return Realm.Run(realm => + { + var beatmapInfo = beatmap.BeatmapInfo; + + beatmapSetInfo.Beatmaps.Add(beatmapInfo); + beatmapInfo.BeatmapSet = beatmapSetInfo; + + Save(beatmapInfo, beatmap); + + return beatmapInfo.Detach(); + }); + } + private static string getFilename(BeatmapInfo beatmapInfo) { var metadata = beatmapInfo.Metadata; @@ -103,9 +120,9 @@ namespace osu.Game.Beatmaps public void Update(BeatmapSetInfo item) { - Realm.Write(realm => + Realm.Write(r => { - var existing = realm.Find(item.ID); + var existing = r.Find(item.ID); item.CopyChangesToRealm(existing); }); } diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index 7a0ca2c85a..f89bbbe19d 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -58,7 +58,16 @@ namespace osu.Game.Database if (existing != null) copyChangesToRealm(beatmap, existing); else - d.Beatmaps.Add(beatmap); + { + var newBeatmap = new BeatmapInfo + { + ID = beatmap.ID, + BeatmapSet = d, + Ruleset = d.Realm.Find(beatmap.Ruleset.ShortName) + }; + d.Beatmaps.Add(newBeatmap); + copyChangesToRealm(beatmap, newBeatmap); + } } }); diff --git a/osu.Game/Models/RealmUser.cs b/osu.Game/Models/RealmUser.cs index 5fccff597c..18c849cf0a 100644 --- a/osu.Game/Models/RealmUser.cs +++ b/osu.Game/Models/RealmUser.cs @@ -2,12 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Game.Database; using osu.Game.Users; +using osu.Game.Utils; using Realms; namespace osu.Game.Models { - public class RealmUser : EmbeddedObject, IUser, IEquatable + public class RealmUser : EmbeddedObject, IUser, IEquatable, IDeepCloneable { public int OnlineID { get; set; } = 1; @@ -22,5 +24,7 @@ namespace osu.Game.Models return OnlineID == other.OnlineID && Username == other.Username; } + + public RealmUser DeepClone() => (RealmUser)this.Detach().MemberwiseClone(); } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 61c5fd2ca4..df8e326c5b 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -822,11 +822,14 @@ namespace osu.Game.Screens.Edit var rulesetItems = new List(); foreach (var ruleset in rulesets.AvailableRulesets.OrderBy(ruleset => ruleset.OnlineID)) - rulesetItems.Add(new EditorMenuItem(ruleset.Name)); + rulesetItems.Add(new EditorMenuItem(ruleset.Name, MenuItemType.Standard, () => createNewDifficulty(ruleset))); return new EditorMenuItem("Create new difficulty") { Items = rulesetItems }; } + private void createNewDifficulty(RulesetInfo rulesetInfo) + => loader?.ScheduleSwitchToNewDifficulty(editorBeatmap.BeatmapInfo.BeatmapSet, rulesetInfo, GetState()); + private EditorMenuItem createDifficultySwitchMenu() { var beatmapSet = playableBeatmap.BeatmapInfo.BeatmapSet; @@ -850,7 +853,7 @@ namespace osu.Game.Screens.Edit return new EditorMenuItem("Change difficulty") { Items = difficultyItems }; } - protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, GetState(nextBeatmap)); + protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleSwitchToExistingDifficulty(nextBeatmap, GetState(nextBeatmap)); private void cancelExit() { diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 15d70e28b6..731bc75b52 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -10,6 +10,7 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; @@ -78,7 +79,13 @@ namespace osu.Game.Screens.Edit } } - public void ScheduleDifficultySwitch(BeatmapInfo nextBeatmap, EditorState editorState) + public void ScheduleSwitchToNewDifficulty(BeatmapSetInfo beatmapSetInfo, RulesetInfo rulesetInfo, EditorState editorState) + => scheduleDifficultySwitch(() => beatmapManager.CreateNewBlankDifficulty(beatmapSetInfo, rulesetInfo), editorState); + + public void ScheduleSwitchToExistingDifficulty(BeatmapInfo beatmapInfo, EditorState editorState) + => scheduleDifficultySwitch(() => beatmapManager.GetWorkingBeatmap(beatmapInfo), editorState); + + private void scheduleDifficultySwitch(Func nextBeatmap, EditorState editorState) { scheduledDifficultySwitch?.Cancel(); ValidForResume = true; @@ -87,7 +94,7 @@ namespace osu.Game.Screens.Edit scheduledDifficultySwitch = Schedule(() => { - Beatmap.Value = beatmapManager.GetWorkingBeatmap(nextBeatmap); + Beatmap.Value = nextBeatmap.Invoke(); state = editorState; // This screen is a weird exception to the rule that nothing after song select changes the global beatmap. From 0d51c015addfcd10fdcc24f6ef37446ef7c1fdb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 18:34:33 +0100 Subject: [PATCH 0517/5199] Add basic test coverage for new difficulty creation --- .../Editing/TestSceneEditorBeatmapCreation.cs | 40 +++++++++++++++++++ osu.Game/Screens/Edit/Editor.cs | 4 +- osu.Game/Tests/Visual/EditorTestScene.cs | 2 + 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index e3fb44534b..9ceff86426 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -90,5 +90,45 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("track length changed", () => Beatmap.Value.Track.Length > 60000); } + + [Test] + public void TestCreateNewDifficulty() + { + string firstDifficultyName = Guid.NewGuid().ToString(); + string secondDifficultyName = Guid.NewGuid().ToString(); + + AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName); + AddStep("save beatmap", () => Editor.Save()); + AddAssert("new beatmap persisted", () => + { + var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == firstDifficultyName); + var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID); + + return beatmap != null + && beatmap.DifficultyName == firstDifficultyName + && set != null + && set.PerformRead(s => s.Beatmaps.Single().ID == beatmap.ID); + }); + + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddUntilStep("wait for created", () => + { + string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName != firstDifficultyName; + }); + + AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = secondDifficultyName); + AddStep("save beatmap", () => Editor.Save()); + AddAssert("new beatmap persisted", () => + { + var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == secondDifficultyName); + var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID); + + return beatmap != null + && beatmap.DifficultyName == secondDifficultyName + && set != null + && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName)); + }); + } } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index df8e326c5b..3d7677ce19 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -822,12 +822,12 @@ namespace osu.Game.Screens.Edit var rulesetItems = new List(); foreach (var ruleset in rulesets.AvailableRulesets.OrderBy(ruleset => ruleset.OnlineID)) - rulesetItems.Add(new EditorMenuItem(ruleset.Name, MenuItemType.Standard, () => createNewDifficulty(ruleset))); + rulesetItems.Add(new EditorMenuItem(ruleset.Name, MenuItemType.Standard, () => CreateNewDifficulty(ruleset))); return new EditorMenuItem("Create new difficulty") { Items = rulesetItems }; } - private void createNewDifficulty(RulesetInfo rulesetInfo) + protected void CreateNewDifficulty(RulesetInfo rulesetInfo) => loader?.ScheduleSwitchToNewDifficulty(editorBeatmap.BeatmapInfo.BeatmapSet, rulesetInfo, GetState()); private EditorMenuItem createDifficultySwitchMenu() diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index bcf169bb1e..52d61ed422 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -107,6 +107,8 @@ namespace osu.Game.Tests.Visual public new void SwitchToDifficulty(BeatmapInfo beatmapInfo) => base.SwitchToDifficulty(beatmapInfo); + public new void CreateNewDifficulty(RulesetInfo rulesetInfo) => base.CreateNewDifficulty(rulesetInfo); + public new bool HasUnsavedChanges => base.HasUnsavedChanges; public TestEditor(EditorLoader loader = null) From 54bb6ad40c992cb55de194ed12aac4686fea6ee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 18:56:19 +0100 Subject: [PATCH 0518/5199] Fix working beatmaps not seeing new difficulties after add --- osu.Game/Beatmaps/BeatmapManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 38ba244f28..d9b0ec3170 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -129,6 +129,8 @@ namespace osu.Game.Beatmaps newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone()); var createdBeatmapInfo = beatmapModelManager.AddDifficultyToBeatmapSet(beatmapSetInfo, newBeatmap); + + workingBeatmapCache.Invalidate(createdBeatmapInfo.BeatmapSet); return GetWorkingBeatmap(createdBeatmapInfo); } From 87e2e83288ec72a569207d0854dbba49bf4880b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 19:25:59 +0100 Subject: [PATCH 0519/5199] Add test coverage for difficulty name clash cases --- .../Editing/TestSceneEditorBeatmapCreation.cs | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 9ceff86426..31348c1e17 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -130,5 +130,59 @@ namespace osu.Game.Tests.Visual.Editing && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName)); }); } + + [Test] + public void TestCreateNewBeatmapFailsWithBlankNamedDifficulties() + { + Guid setId = Guid.Empty; + + AddStep("retrieve set ID", () => setId = EditorBeatmap.BeatmapInfo.BeatmapSet!.ID); + AddStep("save beatmap", () => Editor.Save()); + AddAssert("new beatmap persisted", () => + { + var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId); + return set != null && set.PerformRead(s => s.Beatmaps.Count == 1 && s.Files.Count == 1); + }); + + AddStep("try to create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddAssert("beatmap set unchanged", () => + { + var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId); + return set != null && set.PerformRead(s => s.Beatmaps.Count == 1 && s.Files.Count == 1); + }); + } + + [Test] + public void TestCreateNewBeatmapFailsWithSameNamedDifficulties() + { + Guid setId = Guid.Empty; + const string duplicate_difficulty_name = "duplicate"; + + AddStep("retrieve set ID", () => setId = EditorBeatmap.BeatmapInfo.BeatmapSet!.ID); + AddStep("set difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = duplicate_difficulty_name); + AddStep("save beatmap", () => Editor.Save()); + AddAssert("new beatmap persisted", () => + { + var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId); + return set != null && set.PerformRead(s => s.Beatmaps.Count == 1 && s.Files.Count == 1); + }); + + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddUntilStep("wait for created", () => + { + string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName != duplicate_difficulty_name; + }); + + AddStep("set difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = duplicate_difficulty_name); + AddStep("try to save beatmap", () => Editor.Save()); + AddAssert("beatmap set not corrupted", () => + { + var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId); + // the difficulty was already created at the point of the switch. + // what we want to check is that both difficulties do not use the same file. + return set != null && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Files.Count == 2); + }); + } } } From 4f1aac9345f8e4b292d079b630c2a00e62a264ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 19:42:07 +0100 Subject: [PATCH 0520/5199] Add safeties preventing creating multiple difficulties with same name --- osu.Game/Beatmaps/BeatmapModelManager.cs | 6 ++++++ osu.Game/Screens/Edit/Editor.cs | 16 ++++++++++++---- osu.Game/Screens/Edit/EditorLoader.cs | 16 +++++++++++++++- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 2ab5ac1db9..327e10e643 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -71,6 +71,12 @@ namespace osu.Game.Beatmaps // AddFile generally handles updating/replacing files, but this is a case where the filename may have also changed so let's delete for simplicity. var existingFileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)); + string targetFilename = getFilename(beatmapInfo); + + // ensure that two difficulties from the set don't point at the same beatmap file. + if (setInfo.Beatmaps.Any(b => string.Equals(b.Path, targetFilename, StringComparison.OrdinalIgnoreCase))) + throw new InvalidOperationException($"{setInfo.GetDisplayString()} already has a difficulty with the name of '{beatmapInfo.DifficultyName}'."); + if (existingFileInfo != null) DeleteFile(setInfo, existingFileInfo); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 3d7677ce19..64b23ccda8 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -386,12 +386,20 @@ namespace osu.Game.Screens.Edit return; } + try + { + // save the loaded beatmap's data stream. + beatmapManager.Save(editorBeatmap.BeatmapInfo, editorBeatmap.PlayableBeatmap, editorBeatmap.BeatmapSkin); + } + catch (Exception ex) + { + // can fail e.g. due to duplicated difficulty names. + Logger.Error(ex, ex.Message); + return; + } + // no longer new after first user-triggered save. isNewBeatmap = false; - - // save the loaded beatmap's data stream. - beatmapManager.Save(editorBeatmap.BeatmapInfo, editorBeatmap.PlayableBeatmap, editorBeatmap.BeatmapSkin); - updateLastSavedHash(); } diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 731bc75b52..de47411fdc 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -6,6 +6,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; @@ -80,7 +81,20 @@ namespace osu.Game.Screens.Edit } public void ScheduleSwitchToNewDifficulty(BeatmapSetInfo beatmapSetInfo, RulesetInfo rulesetInfo, EditorState editorState) - => scheduleDifficultySwitch(() => beatmapManager.CreateNewBlankDifficulty(beatmapSetInfo, rulesetInfo), editorState); + => scheduleDifficultySwitch(() => + { + try + { + return beatmapManager.CreateNewBlankDifficulty(beatmapSetInfo, rulesetInfo); + } + catch (Exception ex) + { + // if the beatmap creation fails (e.g. due to duplicated difficulty names), + // bring the user back to the previous beatmap as a best-effort. + Logger.Error(ex, ex.Message); + return Beatmap.Value; + } + }, editorState); public void ScheduleSwitchToExistingDifficulty(BeatmapInfo beatmapInfo, EditorState editorState) => scheduleDifficultySwitch(() => beatmapManager.GetWorkingBeatmap(beatmapInfo), editorState); From afc48d86df1be79ca37d02cb3fdee604d7f7ecaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 19:50:02 +0100 Subject: [PATCH 0521/5199] Add failing test coverage for save after safeties addition --- .../Editing/TestSceneEditorBeatmapCreation.cs | 1 + osu.Game/Screens/Edit/Editor.cs | 13 +++++++++---- osu.Game/Tests/Visual/EditorTestScene.cs | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 31348c1e17..a14c9aded3 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -109,6 +109,7 @@ namespace osu.Game.Tests.Visual.Editing && set != null && set.PerformRead(s => s.Beatmaps.Single().ID == beatmap.ID); }); + AddAssert("can save again", () => Editor.Save()); AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); AddUntilStep("wait for created", () => diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 64b23ccda8..78c5c862f7 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -378,12 +378,16 @@ namespace osu.Game.Screens.Edit Clipboard.Content.Value = state.ClipboardContent; }); - protected void Save() + /// + /// Saves the currently edited beatmap. + /// + /// Whether the save was successful. + protected bool Save() { if (!canSave) { notifications?.Post(new SimpleErrorNotification { Text = "Saving is not supported for this ruleset yet, sorry!" }); - return; + return false; } try @@ -395,12 +399,13 @@ namespace osu.Game.Screens.Edit { // can fail e.g. due to duplicated difficulty names. Logger.Error(ex, ex.Message); - return; + return false; } // no longer new after first user-triggered save. isNewBeatmap = false; updateLastSavedHash(); + return true; } protected override void Update() @@ -809,7 +814,7 @@ namespace osu.Game.Screens.Edit { var fileMenuItems = new List { - new EditorMenuItem("Save", MenuItemType.Standard, Save) + new EditorMenuItem("Save", MenuItemType.Standard, () => Save()) }; if (RuntimeInfo.IsDesktop) diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 52d61ed422..da13c6862f 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual public new void Redo() => base.Redo(); - public new void Save() => base.Save(); + public new bool Save() => base.Save(); public new void Cut() => base.Cut(); From 47429fb0c68b6df09b73056a8a7a5b8d3fe2e4ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 19:54:57 +0100 Subject: [PATCH 0522/5199] Fix same-name safety firing wrongly --- osu.Game/Beatmaps/BeatmapModelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 327e10e643..d4df4b7870 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -74,7 +74,7 @@ namespace osu.Game.Beatmaps string targetFilename = getFilename(beatmapInfo); // ensure that two difficulties from the set don't point at the same beatmap file. - if (setInfo.Beatmaps.Any(b => string.Equals(b.Path, targetFilename, StringComparison.OrdinalIgnoreCase))) + if (setInfo.Beatmaps.Any(b => b.ID != beatmapInfo.ID && string.Equals(b.Path, targetFilename, StringComparison.OrdinalIgnoreCase))) throw new InvalidOperationException($"{setInfo.GetDisplayString()} already has a difficulty with the name of '{beatmapInfo.DifficultyName}'."); if (existingFileInfo != null) From a8ffc4fc2a08b27af6e295ee97cfee3d208d4bc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 19:58:21 +0100 Subject: [PATCH 0523/5199] Add editor override to respect `IsolateSavingFromDatabase` --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/Tests/Visual/EditorTestScene.cs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index d9b0ec3170..9c9b995955 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -112,7 +112,7 @@ namespace osu.Game.Beatmaps /// The new difficulty will be backed by a model /// and represented by the returned . /// - public WorkingBeatmap CreateNewBlankDifficulty(BeatmapSetInfo beatmapSetInfo, RulesetInfo rulesetInfo) + public virtual WorkingBeatmap CreateNewBlankDifficulty(BeatmapSetInfo beatmapSetInfo, RulesetInfo rulesetInfo) { // fetch one of the existing difficulties to copy timing points and metadata from, // so that the user doesn't have to fill all of that out again. diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index da13c6862f..331bf04644 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -136,6 +136,12 @@ namespace osu.Game.Tests.Visual return new TestWorkingBeatmapCache(this, audioManager, resources, storage, defaultBeatmap, host); } + public override WorkingBeatmap CreateNewBlankDifficulty(BeatmapSetInfo beatmapSetInfo, RulesetInfo rulesetInfo) + { + // don't actually care about properly creating a difficulty for this context. + return TestBeatmap; + } + private class TestWorkingBeatmapCache : WorkingBeatmapCache { private readonly TestBeatmapManager testBeatmapManager; From 62537eb4aa5f458fd959846a8bf318b16e2acef0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 3 Feb 2022 12:44:33 +0900 Subject: [PATCH 0524/5199] Fix spectator not completing --- osu.Game/Screens/Spectate/SpectatorScreen.cs | 21 +++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 0b2a7cecf6..460352ee07 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -112,6 +112,7 @@ namespace osu.Game.Screens.Spectate switch (e.Action) { case NotifyDictionaryChangedAction.Add: + case NotifyDictionaryChangedAction.Replace: foreach ((int userId, var state) in e.NewItems.AsNonNull()) onUserStateChanged(userId, state); break; @@ -120,11 +121,6 @@ namespace osu.Game.Screens.Spectate foreach ((int userId, _) in e.OldItems.AsNonNull()) onUserStateRemoved(userId); break; - - case NotifyDictionaryChangedAction.Replace: - foreach ((int userId, var state) in e.NewItems.AsNonNull()) - onUserStateChanged(userId, state); - break; } } @@ -136,11 +132,18 @@ namespace osu.Game.Screens.Spectate if (!userMap.ContainsKey(userId)) return; - // Do nothing for failed/completed states. - if (newState.State == SpectatingUserState.Playing) + switch (newState.State) { - Schedule(() => OnUserStateChanged(userId, newState)); - updateGameplayState(userId); + case SpectatingUserState.Completed: + // Make sure that gameplay completes to the end. + if (gameplayStates.TryGetValue(userId, out var gameplayState)) + gameplayState.Score.Replay.HasReceivedAllFrames = true; + break; + + case SpectatingUserState.Playing: + Schedule(() => OnUserStateChanged(userId, newState)); + updateGameplayState(userId); + break; } } From aff36d4e163c96c9dd35ea280a1b630806b63d10 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 3 Feb 2022 11:52:37 +0800 Subject: [PATCH 0525/5199] Refactor `populateStatistics` to avoid disposing --- .../Ranking/Statistics/StatisticsPanel.cs | 128 +++++++++--------- 1 file changed, 63 insertions(+), 65 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 08be1de652..cbb4e5089d 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -86,88 +86,86 @@ namespace osu.Game.Screens.Ranking.Statistics playableBeatmap = beatmapManager.GetWorkingBeatmap(newScore.BeatmapInfo).GetPlayableBeatmap(newScore.Ruleset, newScore.Mods); }, loadCancellation.Token).ContinueWith(t => Schedule(() => { - FillFlowContainer rows; - Container container = new OsuScrollContainer(Direction.Vertical) - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Alpha = 0, - Children = new[] - { - rows = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(30, 15) - } - } - }; - - bool panelIsEmpty = true; - bool panelIsComplete = true; bool hitEventsAvailable = newScore.HitEvents.Count != 0; + Container container; - foreach (var row in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap)) + var statisticRows = newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap); + + if (!hitEventsAvailable && statisticRows.SelectMany(r => r.Columns).All(c => c.RequiresHitEvents)) { - var columnsToDisplay = hitEventsAvailable - ? row.Columns - : row.Columns.Where(c => !c.RequiresHitEvents).ToArray(); - - if (columnsToDisplay.Length < row.Columns.Length) - panelIsComplete = false; - - if (columnsToDisplay.Any()) - panelIsEmpty = false; - else - continue; - - rows.Add(new GridContainer + container = new FillFlowContainer { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Content = new[] + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - columnsToDisplay?.Select(c => new StatisticContainer(c) + new MessagePlaceholder("Extended statistics are only available after watching a replay!"), + new ReplayDownloadButton(newScore) { + Scale = new Vector2(1.5f), Anchor = Anchor.Centre, Origin = Anchor.Centre, - }).Cast().ToArray() - }, - ColumnDimensions = Enumerable.Range(0, columnsToDisplay.Length) - .Select(i => columnsToDisplay[i].Dimension ?? new Dimension()).ToArray(), - RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } - }); + }, + } + }; } - - if (!hitEventsAvailable) + else { - if (panelIsEmpty) + FillFlowContainer rows; + container = new OsuScrollContainer(Direction.Vertical) { - // Replace the scroll container with fill flow container to get the message centered. - rows.Dispose(); - container.Dispose(); - container = new FillFlowContainer + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0, + Children = new[] { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Direction = FillDirection.Vertical, - Children = new Drawable[] + rows = new FillFlowContainer { - new MessagePlaceholder("Extended statistics are only available after watching a replay!"), - new ReplayDownloadButton(newScore) + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(30, 15) + } + } + }; + + bool panelIsComplete = true; + + foreach (var row in statisticRows) + { + var columnsToDisplay = hitEventsAvailable + ? row.Columns + : row.Columns.Where(c => !c.RequiresHitEvents).ToArray(); + + if (columnsToDisplay.Length < row.Columns.Length) + panelIsComplete = false; + + if (columnsToDisplay.Length == 0) + continue; + + rows.Add(new GridContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] + { + columnsToDisplay?.Select(c => new StatisticContainer(c) { - Scale = new Vector2(1.5f), Anchor = Anchor.Centre, Origin = Anchor.Centre, - }, - } - }; + }).Cast().ToArray() + }, + ColumnDimensions = Enumerable.Range(0, columnsToDisplay.Length) + .Select(i => columnsToDisplay[i].Dimension ?? new Dimension()).ToArray(), + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } + }); } - else if (!panelIsComplete) + + if (!hitEventsAvailable && !panelIsComplete) { rows.Add(new FillFlowContainer { From d9a43b4c4ce50d58ff4d12393c9b4f4c66bc602d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 3 Feb 2022 13:16:54 +0900 Subject: [PATCH 0526/5199] Fix API requests not completing when offline --- osu.Game/Online/API/APIAccess.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 8d91548149..1d2abe0c7f 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -399,7 +399,10 @@ namespace osu.Game.Online.API lock (queue) { if (state.Value == APIState.Offline) + { + request.Fail(new WebException("Disconnected from server")); return; + } queue.Enqueue(request); } From 62fa915193ba65a386d647ba63b0e5d422d18c64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 13:58:55 +0900 Subject: [PATCH 0527/5199] Standardise exception messages for local-user-logged-out flows --- osu.Game/Online/API/APIAccess.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 1d2abe0c7f..a1b8e5bee7 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -400,7 +400,7 @@ namespace osu.Game.Online.API { if (state.Value == APIState.Offline) { - request.Fail(new WebException("Disconnected from server")); + failFromAPIOffline(request); return; } @@ -419,7 +419,7 @@ namespace osu.Game.Online.API if (failOldRequests) { foreach (var req in oldQueueRequests) - req.Fail(new WebException(@"Disconnected from server")); + failFromAPIOffline(req); } } } @@ -440,6 +440,13 @@ namespace osu.Game.Online.API flushQueue(); } + private void failFromAPIOffline(APIRequest req) + { + Debug.Assert(state.Value == APIState.Offline); + + req.Fail(new WebException(@"User not logged in")); + } + private static APIUser createGuestUser() => new GuestUser(); protected override void Dispose(bool isDisposing) From a69c7a9de6f80b3b95b628abc1b5711c6338743e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 14:09:27 +0900 Subject: [PATCH 0528/5199] Split exceptions back out to give better messaging --- osu.Game/Online/API/APIAccess.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index a1b8e5bee7..c5302a393c 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -400,7 +400,7 @@ namespace osu.Game.Online.API { if (state.Value == APIState.Offline) { - failFromAPIOffline(request); + request.Fail(new WebException(@"User not logged in")); return; } @@ -419,7 +419,7 @@ namespace osu.Game.Online.API if (failOldRequests) { foreach (var req in oldQueueRequests) - failFromAPIOffline(req); + req.Fail(new WebException($@"Request failed from flush operation (state {state.Value})")); } } } @@ -440,13 +440,6 @@ namespace osu.Game.Online.API flushQueue(); } - private void failFromAPIOffline(APIRequest req) - { - Debug.Assert(state.Value == APIState.Offline); - - req.Fail(new WebException(@"User not logged in")); - } - private static APIUser createGuestUser() => new GuestUser(); protected override void Dispose(bool isDisposing) From c8ce00b26a5c9ca1049274cd7eee7e0c2f54e075 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 14:50:40 +0900 Subject: [PATCH 0529/5199] Trigger a re-layout of HUD components when scoring mode is changed This is a simple way of fixing the layout of scoring elements overlapping due to different score display width requirements of different scoring modes. It will only resolve the case where a user hasn't customsied the layout of the default skins, but as this is a very simple / low effort implementation for the most common scenario, I think it makes sense. Closes https://github.com/ppy/osu/issues/16067. --- osu.Game/Screens/Play/HUDOverlay.cs | 30 +++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index fdb5d418f3..628452fbc8 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -17,6 +17,7 @@ using osu.Game.Input.Bindings; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; @@ -83,10 +84,7 @@ namespace osu.Game.Screens.Play Children = new Drawable[] { CreateFailingLayer(), - mainComponents = new SkinnableTargetContainer(SkinnableTarget.MainHUDComponents) - { - RelativeSizeAxes = Axes.Both, - }, + mainComponents = new MainComponentsContainer(), topRightElements = new FillFlowContainer { Anchor = Anchor.TopRight, @@ -325,5 +323,29 @@ namespace osu.Game.Screens.Play break; } } + + private class MainComponentsContainer : SkinnableTargetContainer + { + private Bindable scoringMode; + + [Resolved] + private OsuConfigManager config { get; set; } + + public MainComponentsContainer() + : base(SkinnableTarget.MainHUDComponents) + { + RelativeSizeAxes = Axes.Both; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // When the scoring mode changes, relative positions of elements may change (see DefaultSkin.GetDrawableComponent). + // This is a best effort implementation for cases where users haven't customised layouts. + scoringMode = config.GetBindable(OsuSetting.ScoreDisplayMode); + scoringMode.BindValueChanged(val => Reload()); + } + } } } From 6355ac66634834124adfb30be16a3a3d138ada9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 15:10:06 +0900 Subject: [PATCH 0530/5199] Wait for `DialogOverlay` load in more tests Apparently the previous fix was not enough as this can still be seen failing (https://github.com/ppy/osu/runs/5046718623?check_suite_focus=true). This change is copying from what other tests use seemingly reliably, such as `TestScenePerformFromScreen`) --- osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index ed484e03f6..e31377b96e 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -128,6 +128,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("choose clear all scores", () => InputManager.Key(Key.Number4)); + AddUntilStep("wait for dialog display", () => Game.Dependencies.Get().IsLoaded); AddUntilStep("wait for dialog", () => Game.Dependencies.Get().CurrentDialog != null); AddStep("confirm deletion", () => InputManager.Key(Key.Number1)); AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get().CurrentDialog == null); @@ -172,6 +173,7 @@ namespace osu.Game.Tests.Visual.Navigation InputManager.Click(MouseButton.Left); }); + AddUntilStep("wait for dialog display", () => Game.Dependencies.Get().IsLoaded); AddUntilStep("wait for dialog", () => Game.Dependencies.Get().CurrentDialog != null); AddStep("confirm deletion", () => InputManager.Key(Key.Number1)); AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get().CurrentDialog == null); From 41aa4b8cca63be85c7a8434618c0dc3189f04f87 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 16:04:05 +0900 Subject: [PATCH 0531/5199] Fix `TestSelectingFilteredRuleset` failing under visual tests due to using local database --- .../SongSelect/TestSceneBeatmapCarousel.cs | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index a23bc620ec..4e46901e08 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -601,7 +601,7 @@ namespace osu.Game.Tests.Visual.SongSelect { BeatmapSetInfo testMixed = null; - createCarousel(); + createCarousel(new List()); AddStep("add mixed ruleset beatmapset", () => { @@ -765,22 +765,22 @@ namespace osu.Game.Tests.Visual.SongSelect { bool changed = false; - createCarousel(c => + if (beatmapSets == null) + { + beatmapSets = new List(); + + for (int i = 1; i <= (count ?? set_count); i++) + { + beatmapSets.Add(randomDifficulties + ? TestResources.CreateTestBeatmapSetInfo() + : TestResources.CreateTestBeatmapSetInfo(3)); + } + } + + createCarousel(beatmapSets, c => { carouselAdjust?.Invoke(c); - if (beatmapSets == null) - { - beatmapSets = new List(); - - for (int i = 1; i <= (count ?? set_count); i++) - { - beatmapSets.Add(randomDifficulties - ? TestResources.CreateTestBeatmapSetInfo() - : TestResources.CreateTestBeatmapSetInfo(3)); - } - } - carousel.Filter(initialCriteria?.Invoke() ?? new FilterCriteria()); carousel.BeatmapSetsChanged = () => changed = true; carousel.BeatmapSets = beatmapSets; @@ -789,7 +789,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("Wait for load", () => changed); } - private void createCarousel(Action carouselAdjust = null, Container target = null) + private void createCarousel(List beatmapSets, Action carouselAdjust = null, Container target = null) { AddStep("Create carousel", () => { @@ -803,6 +803,8 @@ namespace osu.Game.Tests.Visual.SongSelect carouselAdjust?.Invoke(carousel); + carousel.BeatmapSets = beatmapSets; + (target ?? this).Child = carousel; }); } From e65996efc31b5c17d7386baa1548d204889849ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 17:14:38 +0900 Subject: [PATCH 0532/5199] Rename variable to match purpose better --- osu.Game/Screens/Select/SongSelect.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 44af63a554..ee807762bf 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -110,7 +110,7 @@ namespace osu.Game.Screens.Select private double audioFeedbackLastPlaybackTime; - private bool randomClicked; + private bool randomSelectionPending; [Resolved] private MusicController music { get; set; } @@ -321,12 +321,12 @@ namespace osu.Game.Screens.Select { NextRandom = () => { - randomClicked = true; + randomSelectionPending = true; Carousel.SelectNextRandom(); }, PreviousRandom = () => { - randomClicked = true; + randomSelectionPending = true; Carousel.SelectPreviousRandom(); } }, null), @@ -498,7 +498,7 @@ namespace osu.Game.Screens.Select { if (beatmap != null && beatmapInfoPrevious != null && Time.Current - audioFeedbackLastPlaybackTime >= 50) { - if (randomClicked) + if (randomSelectionPending) sampleRandomBeatmap.Play(); else if (beatmap.BeatmapSet?.ID == beatmapInfoPrevious.BeatmapSet?.ID) sampleChangeDifficulty.Play(); @@ -508,7 +508,7 @@ namespace osu.Game.Screens.Select audioFeedbackLastPlaybackTime = Time.Current; } - randomClicked = false; + randomSelectionPending = false; beatmapInfoPrevious = beatmap; } From a27d0572ed3d5c8b4c5f082015de2f6c33b86005 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 3 Feb 2022 17:00:40 +0800 Subject: [PATCH 0533/5199] Add test cases for manual testing --- .../Ranking/TestSceneStatisticsPanel.cs | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index f64b7b2b65..35281a85eb 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -6,10 +6,18 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; using osu.Game.Tests.Resources; using osuTK; @@ -41,6 +49,24 @@ namespace osu.Game.Tests.Visual.Ranking loadPanel(TestResources.CreateTestScoreInfo()); } + [Test] + public void TestScoreInRulesetWhereAllStatsRequireHitEvents() + { + loadPanel(TestResources.CreateTestScoreInfo(new TestRulesetAllStatsRequireHitEvents().RulesetInfo)); + } + + [Test] + public void TestScoreInRulesetWhereNoStatsRequireHitEvents() + { + loadPanel(TestResources.CreateTestScoreInfo(new TestRulesetNoStatsRequireHitEvents().RulesetInfo)); + } + + [Test] + public void TestScoreInMixedRuleset() + { + loadPanel(TestResources.CreateTestScoreInfo(new TestRulesetMixed().RulesetInfo)); + } + [Test] public void TestNullScore() { @@ -75,5 +101,134 @@ namespace osu.Game.Tests.Visual.Ranking return hitEvents; } + + private class TestRuleset : Ruleset + { + public override IEnumerable GetModsFor(ModType type) + { + throw new NotImplementedException(); + } + + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) + { + throw new NotImplementedException(); + } + + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) + { + throw new NotImplementedException(); + } + + public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) + { + throw new NotImplementedException(); + } + + public override string Description => string.Empty; + + public override string ShortName => string.Empty; + + protected static Drawable CreatePlaceholderStatistic(string message) => new Container + { + RelativeSizeAxes = Axes.X, + Masking = true, + CornerRadius = 20, + Height = 250, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.5f), + Alpha = 0.5f + }, + new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Text = message, + Margin = new MarginPadding { Left = 20 } + } + } + }; + } + + private class TestRulesetAllStatsRequireHitEvents : TestRuleset + { + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) + { + return new[] + { + new StatisticRow + { + Columns = new[] + { + new StatisticItem("Statistic Requiring Hit Events 1", + () => CreatePlaceholderStatistic("Placeholder statistic. Requires hit events"), true) + } + }, + new StatisticRow + { + Columns = new[] + { + new StatisticItem("Statistic Requiring Hit Events 2", + () => CreatePlaceholderStatistic("Placeholder statistic. Requires hit events"), true) + } + } + }; + } + } + + private class TestRulesetNoStatsRequireHitEvents : TestRuleset + { + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) + { + return new[] + { + new StatisticRow + { + Columns = new[] + { + new StatisticItem("Statistic Not Requiring Hit Events 1", + () => CreatePlaceholderStatistic("Placeholder statistic. Does not require hit events")) + } + }, + new StatisticRow + { + Columns = new[] + { + new StatisticItem("Statistic Not Requiring Hit Events 2", + () => CreatePlaceholderStatistic("Placeholder statistic. Does not require hit events")) + } + } + }; + } + } + + private class TestRulesetMixed : TestRuleset + { + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) + { + return new[] + { + new StatisticRow + { + Columns = new[] + { + new StatisticItem("Statistic Requiring Hit Events", + () => CreatePlaceholderStatistic("Placeholder statistic. Requires hit events"), true) + } + }, + new StatisticRow + { + Columns = new[] + { + new StatisticItem("Statistic Not Requiring Hit Events", + () => CreatePlaceholderStatistic("Placeholder statistic. Does not require hit events")) + } + } + }; + } + } } } From 6d6327d3da550900a717b166a8220f09ab1dde16 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 18:40:10 +0900 Subject: [PATCH 0534/5199] Fix test beatmap loading potentially performing selection before carousel itself is loaded --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 0f5c708ab8..f17daa8697 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -108,7 +108,7 @@ namespace osu.Game.Screens.Select set { loadedTestBeatmaps = true; - loadBeatmapSets(value); + Schedule(() => loadBeatmapSets(value)); } } From 6974c2d255f400be9401479df9e782c79a2c04b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 19:00:03 +0900 Subject: [PATCH 0535/5199] Remove weird `panelIsComplete` flag and replace LINQ with simple `foreach` --- .../Ranking/Statistics/StatisticsPanel.cs | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index cbb4e5089d..898bd69b2c 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.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.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -131,41 +132,48 @@ namespace osu.Game.Screens.Ranking.Statistics } }; - bool panelIsComplete = true; + bool anyRequiredHitEvents = false; foreach (var row in statisticRows) { - var columnsToDisplay = hitEventsAvailable - ? row.Columns - : row.Columns.Where(c => !c.RequiresHitEvents).ToArray(); + var columns = row.Columns; - if (columnsToDisplay.Length < row.Columns.Length) - panelIsComplete = false; - - if (columnsToDisplay.Length == 0) + if (columns.Length == 0) continue; + var columnContent = new List(); + var dimensions = new List(); + + foreach (var col in columns) + { + if (!hitEventsAvailable && col.RequiresHitEvents) + { + anyRequiredHitEvents = true; + continue; + } + + columnContent.Add(new StatisticContainer(col) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + + dimensions.Add(col.Dimension ?? new Dimension()); + } + rows.Add(new GridContainer { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Content = new[] - { - columnsToDisplay?.Select(c => new StatisticContainer(c) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }).Cast().ToArray() - }, - ColumnDimensions = Enumerable.Range(0, columnsToDisplay.Length) - .Select(i => columnsToDisplay[i].Dimension ?? new Dimension()).ToArray(), + Content = new[] { columnContent.ToArray() }, + ColumnDimensions = dimensions.ToArray(), RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } }); } - if (!hitEventsAvailable && !panelIsComplete) + if (anyRequiredHitEvents) { rows.Add(new FillFlowContainer { From 47d577ec9c822edb762c91067fb7c17ea97a766a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 19:17:56 +0900 Subject: [PATCH 0536/5199] Add back constructor for ruleset compatibility --- osu.Game/Screens/Ranking/Statistics/StatisticItem.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs index 3cfc38e263..b43fbbdeee 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs @@ -33,6 +33,12 @@ namespace osu.Game.Screens.Ranking.Statistics /// public readonly bool RequiresHitEvents; + [Obsolete("Use constructor which takes creation function instead.")] // Can be removed 20220803. + public StatisticItem([NotNull] string name, [NotNull] Drawable content, [CanBeNull] Dimension dimension = null) + : this(name, () => content, true, dimension) + { + } + /// /// Creates a new , to be displayed inside a in the results screen. /// From ad47649d1c5044c64c4a4f8d50ff739031019221 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 19:38:53 +0900 Subject: [PATCH 0537/5199] Make `BeatmapModelManager.Save` non-virtual --- osu.Game/Beatmaps/BeatmapModelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index d4df4b7870..a9da221e08 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -46,7 +46,7 @@ namespace osu.Game.Beatmaps /// The to save the content against. The file referenced by will be replaced. /// The content to write. /// The beatmap content to write, null if to be omitted. - public virtual void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null) + public void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null) { var setInfo = beatmapInfo.BeatmapSet; Debug.Assert(setInfo != null); From bef0a2da210c7505adef18314ec12312b3ee59d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 19:39:24 +0900 Subject: [PATCH 0538/5199] Remove return type from `AddDifficultyToBeatmapSet` Also removes a pointless realm encapsulation. --- osu.Game/Beatmaps/BeatmapManager.cs | 6 +++--- osu.Game/Beatmaps/BeatmapModelManager.cs | 15 +++++---------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 9c9b995955..d60cfdee9e 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -128,10 +128,10 @@ namespace osu.Game.Beatmaps foreach (var timingPoint in referenceBeatmap.Beatmap.ControlPointInfo.TimingPoints) newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone()); - var createdBeatmapInfo = beatmapModelManager.AddDifficultyToBeatmapSet(beatmapSetInfo, newBeatmap); + beatmapModelManager.AddDifficultyToBeatmapSet(beatmapSetInfo, newBeatmap); - workingBeatmapCache.Invalidate(createdBeatmapInfo.BeatmapSet); - return GetWorkingBeatmap(createdBeatmapInfo); + workingBeatmapCache.Invalidate(beatmapSetInfo); + return GetWorkingBeatmap(newBeatmap.BeatmapInfo); } // TODO: add back support for making a copy of another difficulty diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index a9da221e08..b9f0af8833 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -93,19 +93,14 @@ namespace osu.Game.Beatmaps /// /// Add a new difficulty to the beatmap set represented by the provided . /// - public BeatmapInfo AddDifficultyToBeatmapSet(BeatmapSetInfo beatmapSetInfo, Beatmap beatmap) + public void AddDifficultyToBeatmapSet(BeatmapSetInfo beatmapSetInfo, Beatmap beatmap) { - return Realm.Run(realm => - { - var beatmapInfo = beatmap.BeatmapInfo; + var beatmapInfo = beatmap.BeatmapInfo; - beatmapSetInfo.Beatmaps.Add(beatmapInfo); - beatmapInfo.BeatmapSet = beatmapSetInfo; + beatmapSetInfo.Beatmaps.Add(beatmapInfo); + beatmapInfo.BeatmapSet = beatmapSetInfo; - Save(beatmapInfo, beatmap); - - return beatmapInfo.Detach(); - }); + Save(beatmapInfo, beatmap); } private static string getFilename(BeatmapInfo beatmapInfo) From 40953751b542281dca26e24c323e48b73b2a6c60 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Thu, 3 Feb 2022 13:28:49 +0100 Subject: [PATCH 0539/5199] Use `ScreenOrientation.FullUser` on Android tablets --- osu.Android/GameplayScreenRotationLocker.cs | 2 +- osu.Android/OsuGameActivity.cs | 25 ++++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/osu.Android/GameplayScreenRotationLocker.cs b/osu.Android/GameplayScreenRotationLocker.cs index ab91b4e3b3..1b7893e5b9 100644 --- a/osu.Android/GameplayScreenRotationLocker.cs +++ b/osu.Android/GameplayScreenRotationLocker.cs @@ -27,7 +27,7 @@ namespace osu.Android { gameActivity.RunOnUiThread(() => { - gameActivity.RequestedOrientation = userPlaying.NewValue ? ScreenOrientation.Locked : OsuGameActivity.DEFAULT_ORIENTATION; + gameActivity.RequestedOrientation = userPlaying.NewValue ? ScreenOrientation.Locked : gameActivity.ScreenOrientation; }); } } diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index e6679b61a6..7de597fe88 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.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.IO; using System.Linq; @@ -8,16 +9,18 @@ using System.Threading.Tasks; using Android.App; using Android.Content; using Android.Content.PM; -using Android.Net; +using Android.Graphics; using Android.OS; using Android.Provider; using Android.Views; using osu.Framework.Android; using osu.Game.Database; +using Debug = System.Diagnostics.Debug; +using Uri = Android.Net.Uri; namespace osu.Android { - [Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true, ScreenOrientation = DEFAULT_ORIENTATION)] + [Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true)] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osr", DataHost = "*", DataMimeType = "*/*")] @@ -39,10 +42,10 @@ namespace osu.Android [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })] public class OsuGameActivity : AndroidGameActivity { - public const ScreenOrientation DEFAULT_ORIENTATION = ScreenOrientation.SensorLandscape; - private static readonly string[] osu_url_schemes = { "osu", "osump" }; + public ScreenOrientation ScreenOrientation = ScreenOrientation.Unspecified; + private OsuGameAndroid game; protected override Framework.Game CreateGame() => game = new OsuGameAndroid(this); @@ -56,8 +59,20 @@ namespace osu.Android // reference: https://developer.android.com/reference/android/app/Activity#onNewIntent(android.content.Intent) handleIntent(Intent); + Debug.Assert(Window != null); + Window.AddFlags(WindowManagerFlags.Fullscreen); Window.AddFlags(WindowManagerFlags.KeepScreenOn); + + Debug.Assert(WindowManager?.DefaultDisplay != null); + Debug.Assert(Resources?.DisplayMetrics != null); + + Point displaySize = new Point(); + WindowManager.DefaultDisplay.GetSize(displaySize); + float smallestWidthDp = Math.Min(displaySize.X, displaySize.Y) / Resources.DisplayMetrics.Density; + bool isTablet = smallestWidthDp >= 600f; + + RequestedOrientation = ScreenOrientation = isTablet ? ScreenOrientation.FullUser : ScreenOrientation.SensorLandscape; } protected override void OnNewIntent(Intent intent) => handleIntent(intent); @@ -106,7 +121,7 @@ namespace osu.Android cursor.MoveToFirst(); - var filenameColumn = cursor.GetColumnIndex(OpenableColumns.DisplayName); + int filenameColumn = cursor.GetColumnIndex(OpenableColumns.DisplayName); string filename = cursor.GetString(filenameColumn); // SharpCompress requires archive streams to be seekable, which the stream opened by From f2850601486ec6a8b4b4a88d76b13c75fddd0320 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 3 Feb 2022 21:50:15 +0900 Subject: [PATCH 0540/5199] Fix MultiSpectatorScreen not continuing to results --- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 5 ++++- osu.Game/Screens/Play/SoloSpectator.cs | 2 +- osu.Game/Screens/Spectate/SpectatorScreen.cs | 16 ++++++++++------ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 4646f42d63..bbdd7a3d56 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -215,8 +215,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate protected override void StartGameplay(int userId, SpectatorGameplayState spectatorGameplayState) => instances.Single(i => i.UserId == userId).LoadScore(spectatorGameplayState.Score); - protected override void EndGameplay(int userId) + protected override void EndGameplay(int userId, SpectatorState state) { + if (state.State == SpectatingUserState.Completed || state.State == SpectatingUserState.Failed) + return; + RemoveUser(userId); var instance = instances.Single(i => i.UserId == userId); diff --git a/osu.Game/Screens/Play/SoloSpectator.cs b/osu.Game/Screens/Play/SoloSpectator.cs index b530965269..a710db6d24 100644 --- a/osu.Game/Screens/Play/SoloSpectator.cs +++ b/osu.Game/Screens/Play/SoloSpectator.cs @@ -180,7 +180,7 @@ namespace osu.Game.Screens.Play scheduleStart(spectatorGameplayState); } - protected override void EndGameplay(int userId) + protected override void EndGameplay(int userId, SpectatorState state) { scheduledStart?.Cancel(); immediateSpectatorGameplayState = null; diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 460352ee07..7b58f669a0 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -118,8 +118,8 @@ namespace osu.Game.Screens.Spectate break; case NotifyDictionaryChangedAction.Remove: - foreach ((int userId, _) in e.OldItems.AsNonNull()) - onUserStateRemoved(userId); + foreach ((int userId, SpectatorState state) in e.OldItems.AsNonNull()) + onUserStateRemoved(userId, state); break; } } @@ -147,7 +147,7 @@ namespace osu.Game.Screens.Spectate } } - private void onUserStateRemoved(int userId) + private void onUserStateRemoved(int userId, SpectatorState state) { if (!userMap.ContainsKey(userId)) return; @@ -158,7 +158,7 @@ namespace osu.Game.Screens.Spectate gameplayState.Score.Replay.HasReceivedAllFrames = true; gameplayStates.Remove(userId); - Schedule(() => EndGameplay(userId)); + Schedule(() => EndGameplay(userId, state)); } private void updateGameplayState(int userId) @@ -212,7 +212,8 @@ namespace osu.Game.Screens.Spectate /// Ends gameplay for a user. /// /// The user to end gameplay for. - protected abstract void EndGameplay(int userId); + /// The final user state. + protected abstract void EndGameplay(int userId, SpectatorState state); /// /// Stops spectating a user. @@ -220,7 +221,10 @@ namespace osu.Game.Screens.Spectate /// The user to stop spectating. protected void RemoveUser(int userId) { - onUserStateRemoved(userId); + if (!userStates.TryGetValue(userId, out var state)) + return; + + onUserStateRemoved(userId, state); users.Remove(userId); userMap.Remove(userId); From 84171962e56faa9b5310450d01d0faedeffd2146 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Thu, 3 Feb 2022 13:55:04 +0100 Subject: [PATCH 0541/5199] Change name and add xmldoc --- osu.Android/GameplayScreenRotationLocker.cs | 2 +- osu.Android/OsuGameActivity.cs | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Android/GameplayScreenRotationLocker.cs b/osu.Android/GameplayScreenRotationLocker.cs index 1b7893e5b9..2e83f784d3 100644 --- a/osu.Android/GameplayScreenRotationLocker.cs +++ b/osu.Android/GameplayScreenRotationLocker.cs @@ -27,7 +27,7 @@ namespace osu.Android { gameActivity.RunOnUiThread(() => { - gameActivity.RequestedOrientation = userPlaying.NewValue ? ScreenOrientation.Locked : gameActivity.ScreenOrientation; + gameActivity.RequestedOrientation = userPlaying.NewValue ? ScreenOrientation.Locked : gameActivity.DefaultOrientation; }); } } diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index 7de597fe88..eebd079f68 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -44,7 +44,11 @@ namespace osu.Android { private static readonly string[] osu_url_schemes = { "osu", "osump" }; - public ScreenOrientation ScreenOrientation = ScreenOrientation.Unspecified; + /// + /// The default screen orientation. + /// + /// Adjusted on startup to match expected UX for the current device type (phone/tablet). + public ScreenOrientation DefaultOrientation = ScreenOrientation.Unspecified; private OsuGameAndroid game; @@ -72,7 +76,7 @@ namespace osu.Android float smallestWidthDp = Math.Min(displaySize.X, displaySize.Y) / Resources.DisplayMetrics.Density; bool isTablet = smallestWidthDp >= 600f; - RequestedOrientation = ScreenOrientation = isTablet ? ScreenOrientation.FullUser : ScreenOrientation.SensorLandscape; + RequestedOrientation = DefaultOrientation = isTablet ? ScreenOrientation.FullUser : ScreenOrientation.SensorLandscape; } protected override void OnNewIntent(Intent intent) => handleIntent(intent); From d4ebff6ea1f381bad406c692ab15b097fbdefd2b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 3 Feb 2022 23:18:22 +0900 Subject: [PATCH 0542/5199] Add failing test --- .../Multiplayer/TestSceneMultiplayer.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 3563869d8b..27bf1c209c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -871,6 +871,52 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("queue is empty", () => this.ChildrenOfType().Single().Items.Count == 0); } + [Test] + public void TestGameplayStartsWhileInSpectatorScreen() + { + createRoom(() => new Room + { + Name = { Value = "Test Room" }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + } + } + }); + + AddStep("join other user and make host", () => + { + client.AddUser(new APIUser { Id = 1234 }); + client.TransferHost(1234); + }); + + AddStep("set local user spectating", () => client.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating)); + AddUntilStep("wait for spectating state", () => client.LocalUser?.State == MultiplayerUserState.Spectating); + + runGameplay(); + + AddStep("exit gameplay for other user", () => client.ChangeUserState(1234, MultiplayerUserState.Idle)); + AddUntilStep("wait for room to be idle", () => client.Room?.State == MultiplayerRoomState.Open); + + runGameplay(); + + void runGameplay() + { + AddStep("start match by other user", () => + { + client.ChangeUserState(1234, MultiplayerUserState.Ready); + client.StartMatch(); + }); + + AddUntilStep("wait for loading", () => client.Room?.State == MultiplayerRoomState.WaitingForLoad); + AddStep("set player loaded", () => client.ChangeUserState(1234, MultiplayerUserState.Loaded)); + AddUntilStep("wait for gameplay to start", () => client.Room?.State == MultiplayerRoomState.Playing); + } + } + private void enterGameplay() { pressReadyButton(); From 6dc0f3fd960cc5a4fd914123388a0585901e0243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Feb 2022 18:14:30 +0100 Subject: [PATCH 0543/5199] Merge difficulty creation methods into one One of them wasn't really doing much anymore and was more obfuscating what was actually happening at this point. --- osu.Game/Beatmaps/BeatmapManager.cs | 14 +++++++++----- osu.Game/Beatmaps/BeatmapModelManager.cs | 13 ------------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index d60cfdee9e..633eb8f15e 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -120,15 +120,19 @@ namespace osu.Game.Beatmaps // but cases where this isn't true seem rather rare / pathological. var referenceBeatmap = GetWorkingBeatmap(beatmapSetInfo.Beatmaps.First()); - var newBeatmap = new Beatmap - { - BeatmapInfo = new BeatmapInfo(rulesetInfo, new BeatmapDifficulty(), referenceBeatmap.Metadata.DeepClone()) - }; + var newBeatmapInfo = new BeatmapInfo(rulesetInfo, new BeatmapDifficulty(), referenceBeatmap.Metadata.DeepClone()); + // populate circular beatmap set info <-> beatmap info references manually. + // several places like `BeatmapModelManager.Save()` or `GetWorkingBeatmap()` + // rely on them being freely traversable in both directions for correct operation. + beatmapSetInfo.Beatmaps.Add(newBeatmapInfo); + newBeatmapInfo.BeatmapSet = beatmapSetInfo; + + var newBeatmap = new Beatmap { BeatmapInfo = newBeatmapInfo }; foreach (var timingPoint in referenceBeatmap.Beatmap.ControlPointInfo.TimingPoints) newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone()); - beatmapModelManager.AddDifficultyToBeatmapSet(beatmapSetInfo, newBeatmap); + beatmapModelManager.Save(newBeatmapInfo, newBeatmap); workingBeatmapCache.Invalidate(beatmapSetInfo); return GetWorkingBeatmap(newBeatmap.BeatmapInfo); diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index b9f0af8833..4c680bbcc9 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -90,19 +90,6 @@ namespace osu.Game.Beatmaps WorkingBeatmapCache?.Invalidate(beatmapInfo); } - /// - /// Add a new difficulty to the beatmap set represented by the provided . - /// - public void AddDifficultyToBeatmapSet(BeatmapSetInfo beatmapSetInfo, Beatmap beatmap) - { - var beatmapInfo = beatmap.BeatmapInfo; - - beatmapSetInfo.Beatmaps.Add(beatmapInfo); - beatmapInfo.BeatmapSet = beatmapSetInfo; - - Save(beatmapInfo, beatmap); - } - private static string getFilename(BeatmapInfo beatmapInfo) { var metadata = beatmapInfo.Metadata; From ee1feae8062919f2992bd41b23cabd19aa49f345 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Feb 2022 11:06:18 +0900 Subject: [PATCH 0544/5199] Remove unnecessary ruleset ordering Co-authored-by: Salman Ahmed --- osu.Game/Screens/Edit/Editor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 78c5c862f7..5503a62ba2 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -834,7 +834,7 @@ namespace osu.Game.Screens.Edit { var rulesetItems = new List(); - foreach (var ruleset in rulesets.AvailableRulesets.OrderBy(ruleset => ruleset.OnlineID)) + foreach (var ruleset in rulesets.AvailableRulesets) rulesetItems.Add(new EditorMenuItem(ruleset.Name, MenuItemType.Standard, () => CreateNewDifficulty(ruleset))); return new EditorMenuItem("Create new difficulty") { Items = rulesetItems }; From bbef12e72c7bbaaf41c9a9b34b9d90ce5b4cfc2e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Feb 2022 05:45:12 +0300 Subject: [PATCH 0545/5199] Refactor `ExpandingControlContainer` to no longer rely on controls --- ...iner.cs => TestSceneExpandingContainer.cs} | 40 ++---------- osu.Game/Overlays/ExpandableSlider.cs | 4 +- osu.Game/Overlays/ExpandingButtonContainer.cs | 6 +- ...trolContainer.cs => ExpandingContainer.cs} | 64 ++++++------------- osu.Game/Overlays/IExpandable.cs | 11 ---- osu.Game/Overlays/IExpandableControl.cs | 12 ---- 6 files changed, 28 insertions(+), 109 deletions(-) rename osu.Game.Tests/Visual/UserInterface/{TestSceneExpandingControlContainer.cs => TestSceneExpandingContainer.cs} (74%) rename osu.Game/Overlays/{ExpandingControlContainer.cs => ExpandingContainer.cs} (54%) delete mode 100644 osu.Game/Overlays/IExpandableControl.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingControlContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs similarity index 74% rename from osu.Game.Tests/Visual/UserInterface/TestSceneExpandingControlContainer.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs index 48089566cc..f63591311f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingControlContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs @@ -1,20 +1,16 @@ // 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.Bindables; using osu.Framework.Graphics; -using osu.Framework.Testing; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Settings.Sections; using osuTK; -using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface { - public class TestSceneExpandingControlContainer : OsuManualInputManagerTestScene + public class TestSceneExpandingContainer : OsuManualInputManagerTestScene { private TestExpandingContainer container; private SettingsToolboxGroup toolboxGroup; @@ -84,44 +80,22 @@ namespace osu.Game.Tests.Visual.UserInterface } /// - /// Tests hovering over controls expands the parenting container appropriately and does not contract until hover is lost from container. + /// Tests hovering expands the container and does not contract until hover is lost. /// [Test] - public void TestHoveringControlExpandsContainer() + public void TestHoveringExpandsContainer() { AddAssert("ensure container contracted", () => !container.Expanded.Value); - AddStep("hover slider", () => InputManager.MoveMouseTo(slider1)); + AddStep("hover container", () => InputManager.MoveMouseTo(container)); AddAssert("container expanded", () => container.Expanded.Value); AddAssert("controls expanded", () => slider1.Expanded.Value && slider2.Expanded.Value); - AddStep("hover group top", () => InputManager.MoveMouseTo(toolboxGroup.ScreenSpaceDrawQuad.TopLeft + new Vector2(5))); - AddAssert("container still expanded", () => container.Expanded.Value); - AddAssert("controls still expanded", () => slider1.Expanded.Value && slider2.Expanded.Value); - AddStep("hover away", () => InputManager.MoveMouseTo(Vector2.Zero)); AddAssert("container contracted", () => !container.Expanded.Value); AddAssert("controls contracted", () => !slider1.Expanded.Value && !slider2.Expanded.Value); } - /// - /// Tests dragging a UI control (e.g. ) outside its parenting container does not contract it until dragging is finished. - /// - [Test] - public void TestDraggingControlOutsideDoesntContractContainer() - { - AddStep("hover slider", () => InputManager.MoveMouseTo(slider1)); - AddAssert("container expanded", () => container.Expanded.Value); - - AddStep("hover slider nub", () => InputManager.MoveMouseTo(slider1.ChildrenOfType().Single())); - AddStep("hold slider nub", () => InputManager.PressButton(MouseButton.Left)); - AddStep("drag outside container", () => InputManager.MoveMouseTo(Vector2.Zero)); - AddAssert("container still expanded", () => container.Expanded.Value); - - AddStep("release slider nub", () => InputManager.ReleaseButton(MouseButton.Left)); - AddAssert("container contracted", () => !container.Expanded.Value); - } - /// /// Tests expanding a container will expand underlying groups if contracted. /// @@ -157,21 +131,21 @@ namespace osu.Game.Tests.Visual.UserInterface } /// - /// Tests expanding a container via does not get contracted by losing hover. + /// Tests expanding a container via does not get contracted by losing hover. /// [Test] public void TestExpandingContainerDoesntGetContractedByHover() { AddStep("expand container", () => container.Expanded.Value = true); - AddStep("hover control", () => InputManager.MoveMouseTo(slider1)); + AddStep("hover container", () => InputManager.MoveMouseTo(container)); AddAssert("container still expanded", () => container.Expanded.Value); AddStep("hover away", () => InputManager.MoveMouseTo(Vector2.Zero)); AddAssert("container still expanded", () => container.Expanded.Value); } - private class TestExpandingContainer : ExpandingControlContainer + private class TestExpandingContainer : ExpandingContainer { public TestExpandingContainer() : base(120, 250) diff --git a/osu.Game/Overlays/ExpandableSlider.cs b/osu.Game/Overlays/ExpandableSlider.cs index d346b9b22c..062de98659 100644 --- a/osu.Game/Overlays/ExpandableSlider.cs +++ b/osu.Game/Overlays/ExpandableSlider.cs @@ -17,7 +17,7 @@ namespace osu.Game.Overlays /// /// An implementation for the UI slider bar control. /// - public class ExpandableSlider : CompositeDrawable, IExpandableControl, IHasCurrentValue + public class ExpandableSlider : CompositeDrawable, IExpandable, IHasCurrentValue where T : struct, IEquatable, IComparable, IConvertible where TSlider : OsuSliderBar, new() { @@ -72,8 +72,6 @@ namespace osu.Game.Overlays public BindableBool Expanded { get; } = new BindableBool(); - bool IExpandable.ShouldBeExpanded => IsHovered || slider.IsDragged; - public override bool HandlePositionalInput => true; public ExpandableSlider() diff --git a/osu.Game/Overlays/ExpandingButtonContainer.cs b/osu.Game/Overlays/ExpandingButtonContainer.cs index d7ff285707..8fb3e1b550 100644 --- a/osu.Game/Overlays/ExpandingButtonContainer.cs +++ b/osu.Game/Overlays/ExpandingButtonContainer.cs @@ -1,17 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Graphics.UserInterface; - namespace osu.Game.Overlays { /// - /// An with a long hover expansion delay for buttons. + /// An with a long hover expansion delay. /// /// /// Mostly used for buttons with explanatory labels, in which the label would display after a "long hover". /// - public class ExpandingButtonContainer : ExpandingControlContainer + public class ExpandingButtonContainer : ExpandingContainer { protected ExpandingButtonContainer(float contractedWidth, float expandedWidth) : base(contractedWidth, expandedWidth) diff --git a/osu.Game/Overlays/ExpandingControlContainer.cs b/osu.Game/Overlays/ExpandingContainer.cs similarity index 54% rename from osu.Game/Overlays/ExpandingControlContainer.cs rename to osu.Game/Overlays/ExpandingContainer.cs index 859e4bcd25..ea3fffcb78 100644 --- a/osu.Game/Overlays/ExpandingControlContainer.cs +++ b/osu.Game/Overlays/ExpandingContainer.cs @@ -1,23 +1,19 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using osu.Framework.Testing; using osu.Framework.Threading; using osu.Game.Graphics.Containers; namespace osu.Game.Overlays { /// - /// Represents a with the ability to expand/contract when hovering the controls within it. + /// Represents a with the ability to expand/contract on hover. /// - /// The type of UI control to lookup for hover expansion. - public class ExpandingControlContainer : Container, IExpandingContainer - where TControl : class, IDrawable + public class ExpandingContainer : Container, IExpandingContainer { private readonly float contractedWidth; private readonly float expandedWidth; @@ -33,7 +29,7 @@ namespace osu.Game.Overlays protected FillFlowContainer FillFlow { get; } - protected ExpandingControlContainer(float contractedWidth, float expandedWidth) + protected ExpandingContainer(float contractedWidth, float expandedWidth) { this.contractedWidth = contractedWidth; this.expandedWidth = expandedWidth; @@ -57,7 +53,6 @@ namespace osu.Game.Overlays } private ScheduledDelegate hoverExpandEvent; - private TControl activeControl; protected override void LoadComplete() { @@ -69,61 +64,38 @@ namespace osu.Game.Overlays }, true); } - protected override void Update() - { - base.Update(); - - // if the container was expanded from hovering over a control, we have to check per-frame whether we can contract it back. - // that's because contracting the container depends not only on whether it's no longer hovered, - // but also on whether the hovered control is no longer in a dragged state (if it was). - if (hoverExpandEvent != null && !IsHovered && (activeControl == null || !isControlActive(activeControl))) - { - hoverExpandEvent?.Cancel(); - - Expanded.Value = false; - hoverExpandEvent = null; - activeControl = null; - } - } - protected override bool OnHover(HoverEvent e) { - queueExpandIfHovering(); + updateHoverExpansion(); return true; } protected override bool OnMouseMove(MouseMoveEvent e) { - queueExpandIfHovering(); + updateHoverExpansion(); return base.OnMouseMove(e); } - private void queueExpandIfHovering() + protected override void OnHoverLost(HoverLostEvent e) { - // if the same control is hovered or dragged, let the scheduled expand play out.. - if (activeControl != null && isControlActive(activeControl)) + if (hoverExpandEvent != null) + { + hoverExpandEvent?.Cancel(); + hoverExpandEvent = null; + + Expanded.Value = false; return; + } - // ..otherwise check whether a new control is hovered, and if so, queue a new hover operation. - hoverExpandEvent?.Cancel(); - - // usually we wouldn't use ChildrenOfType in implementations, but this is the simplest way - // to handle cases like the editor where the controls may be nested within a child hierarchy. - activeControl = FillFlow.ChildrenOfType().FirstOrDefault(isControlActive); - - if (activeControl != null && !Expanded.Value) - hoverExpandEvent = Scheduler.AddDelayed(() => Expanded.Value = true, HoverExpansionDelay); + base.OnHoverLost(e); } - /// - /// Whether the given control is currently active, by checking whether it's hovered or dragged. - /// - private bool isControlActive(TControl control) + private void updateHoverExpansion() { - if (control is IExpandable expandable) - return expandable.ShouldBeExpanded; + hoverExpandEvent?.Cancel(); - return control.IsHovered || control.IsDragged; + if (IsHovered && !Expanded.Value) + hoverExpandEvent = Scheduler.AddDelayed(() => Expanded.Value = true, HoverExpansionDelay); } } } diff --git a/osu.Game/Overlays/IExpandable.cs b/osu.Game/Overlays/IExpandable.cs index f998fc7b9f..770ac97847 100644 --- a/osu.Game/Overlays/IExpandable.cs +++ b/osu.Game/Overlays/IExpandable.cs @@ -3,7 +3,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays { @@ -16,15 +15,5 @@ namespace osu.Game.Overlays /// Whether this drawable is in an expanded state. /// BindableBool Expanded { get; } - - /// - /// Whether this drawable should be/stay expanded by a parenting . - /// By default, this is when this drawable is in a hovered or dragged state. - /// - /// - /// This is defined for certain controls which may have a child handling dragging instead. - /// (e.g. in which dragging is handled by their underlying control). - /// - bool ShouldBeExpanded => IsHovered || IsDragged; } } diff --git a/osu.Game/Overlays/IExpandableControl.cs b/osu.Game/Overlays/IExpandableControl.cs deleted file mode 100644 index 2cda6f467b..0000000000 --- a/osu.Game/Overlays/IExpandableControl.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Overlays -{ - /// - /// An interface for UI controls with the ability to expand/contract. - /// - public interface IExpandableControl : IExpandable - { - } -} From 4728919bcaaa7151f3d3036a629212f8e395f570 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Feb 2022 15:45:27 +0900 Subject: [PATCH 0546/5199] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index f89994cd56..04c543750e 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 50cef71b26..83c3593edb 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 9ec0f1c0a0..b0c056ea21 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - + From 52fdf0349f32a73ad1022a9b5ef26de9c39edc36 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Feb 2022 16:07:05 +0900 Subject: [PATCH 0547/5199] Add safe area support via `ScalingContainer` --- .../Graphics/Containers/ScalingContainer.cs | 10 +++++++- osu.Game/OsuGameBase.cs | 24 ++++++++++++------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index d2b1e5e523..f9bd571131 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -101,6 +101,9 @@ namespace osu.Game.Graphics.Containers } } + [Resolved] + private ISafeArea safeArea { get; set; } + [BackgroundDependencyLoader] private void load(OsuConfigManager config) { @@ -118,6 +121,8 @@ namespace osu.Game.Graphics.Containers posY = config.GetBindable(OsuSetting.ScalingPositionY); posY.ValueChanged += _ => updateSize(); + + safeArea.SafeAreaPadding.BindValueChanged(_ => updateSize()); } protected override void LoadComplete() @@ -161,7 +166,10 @@ namespace osu.Game.Graphics.Containers var targetSize = scaling ? new Vector2(sizeX.Value, sizeY.Value) : Vector2.One; var targetPosition = scaling ? new Vector2(posX.Value, posY.Value) * (Vector2.One - targetSize) : Vector2.Zero; - bool requiresMasking = scaling && targetSize != Vector2.One; + bool requiresMasking = scaling && targetSize != Vector2.One + // For the top level scaling container, for now we apply masking if safe areas are in use. + // In the future this can likely be removed as more of the actual UI supports overflowing into the safe areas. + || (targetMode == ScalingMode.Everything && safeArea.SafeAreaPadding.Value.Total != Vector2.Zero); if (requiresMasking) sizableContainer.Masking = true; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 1713e73905..5f87abcfed 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -299,16 +299,22 @@ namespace osu.Game GlobalActionContainer globalBindings; - var mainContent = new Drawable[] + base.Content.Add(new SafeAreaContainer { - MenuCursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }, - // to avoid positional input being blocked by children, ensure the GlobalActionContainer is above everything. - globalBindings = new GlobalActionContainer(this) - }; - - MenuCursorContainer.Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both }; - - base.Content.Add(CreateScalingContainer().WithChildren(mainContent)); + RelativeSizeAxes = Axes.Both, + Child = CreateScalingContainer().WithChildren(new Drawable[] + { + (MenuCursorContainer = new MenuCursorContainer + { + RelativeSizeAxes = Axes.Both + }).WithChild(content = new OsuTooltipContainer(MenuCursorContainer.Cursor) + { + RelativeSizeAxes = Axes.Both + }), + // to avoid positional input being blocked by children, ensure the GlobalActionContainer is above everything. + globalBindings = new GlobalActionContainer(this) + }) + }); KeyBindingStore = new RealmKeyBindingStore(realm, keyCombinationProvider); KeyBindingStore.Register(globalBindings, RulesetStore.AvailableRulesets); From 1444df4d50ce0e2d9417f0958d4a39f76ae8e85e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Feb 2022 16:37:11 +0900 Subject: [PATCH 0548/5199] Add test scene for playing with safe areas --- .../TestSceneSafeAreaHandling.cs | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneSafeAreaHandling.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSafeAreaHandling.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSafeAreaHandling.cs new file mode 100644 index 0000000000..676ae1276b --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSafeAreaHandling.cs @@ -0,0 +1,116 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Overlays.Settings; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneSafeAreaHandling : OsuGameTestScene + { + private SafeAreaDefiningContainer safeAreaContainer; + + private static BindableSafeArea safeArea; + + private readonly Bindable safeAreaPaddingTop = new BindableFloat { MinValue = 0, MaxValue = 200 }; + private readonly Bindable safeAreaPaddingBottom = new BindableFloat { MinValue = 0, MaxValue = 200 }; + private readonly Bindable safeAreaPaddingLeft = new BindableFloat { MinValue = 0, MaxValue = 200 }; + private readonly Bindable safeAreaPaddingRight = new BindableFloat { MinValue = 0, MaxValue = 200 }; + + protected override void LoadComplete() + { + base.LoadComplete(); + + // Usually this would be placed between the host and the game, but that's a bit of a pain to do with the test scene hierarchy. + + // Add is required for the container to get a size (and give out correct metrics to the usages in SafeAreaContainer). + Add( + safeAreaContainer = new SafeAreaDefiningContainer(safeArea = new BindableSafeArea()) + { + RelativeSizeAxes = Axes.Both + }); + + // Cache is required for the test game to see the safe area. + Dependencies.CacheAs(safeAreaContainer); + } + + public override void SetUpSteps() + { + AddStep("Add adjust controls", () => + { + Add(new Container + { + Depth = float.MinValue, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + Alpha = 0.8f, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + Width = 400, + Children = new Drawable[] + { + new SettingsSlider + { + Current = safeAreaPaddingTop, + LabelText = "Top" + }, + new SettingsSlider + { + Current = safeAreaPaddingBottom, + LabelText = "Bottom" + }, + new SettingsSlider + { + Current = safeAreaPaddingLeft, + LabelText = "Left" + }, + new SettingsSlider + { + Current = safeAreaPaddingRight, + LabelText = "Right" + }, + } + } + } + }); + + safeAreaPaddingTop.BindValueChanged(_ => updateSafeArea()); + safeAreaPaddingBottom.BindValueChanged(_ => updateSafeArea()); + safeAreaPaddingLeft.BindValueChanged(_ => updateSafeArea()); + safeAreaPaddingRight.BindValueChanged(_ => updateSafeArea()); + }); + + base.SetUpSteps(); + } + + private void updateSafeArea() + { + safeArea.Value = new MarginPadding + { + Top = safeAreaPaddingTop.Value, + Bottom = safeAreaPaddingBottom.Value, + Left = safeAreaPaddingLeft.Value, + Right = safeAreaPaddingRight.Value, + }; + } + + [Test] + public void TestSafeArea() + { + } + } +} From 30d2c7ba6a9355e88a3f605dd68d23728ede247a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Feb 2022 17:07:21 +0900 Subject: [PATCH 0549/5199] Add parenthesis to disambiguify conditionals --- osu.Game/Graphics/Containers/ScalingContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index f9bd571131..aa4e3a7fde 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -166,7 +166,7 @@ namespace osu.Game.Graphics.Containers var targetSize = scaling ? new Vector2(sizeX.Value, sizeY.Value) : Vector2.One; var targetPosition = scaling ? new Vector2(posX.Value, posY.Value) * (Vector2.One - targetSize) : Vector2.Zero; - bool requiresMasking = scaling && targetSize != Vector2.One + bool requiresMasking = (scaling && targetSize != Vector2.One) // For the top level scaling container, for now we apply masking if safe areas are in use. // In the future this can likely be removed as more of the actual UI supports overflowing into the safe areas. || (targetMode == ScalingMode.Everything && safeArea.SafeAreaPadding.Value.Total != Vector2.Zero); From b41655d5b905cd28425e2851f6900f24f7a82893 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 3 Feb 2022 23:22:08 +0900 Subject: [PATCH 0550/5199] Fix crash when gameplay starts while in multi-spectator screen --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 1 + .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 27bf1c209c..8f6ba6375f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -914,6 +914,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for loading", () => client.Room?.State == MultiplayerRoomState.WaitingForLoad); AddStep("set player loaded", () => client.ChangeUserState(1234, MultiplayerUserState.Loaded)); AddUntilStep("wait for gameplay to start", () => client.Room?.State == MultiplayerRoomState.Playing); + AddUntilStep("wait for local user to enter spectator", () => multiplayerComponents.CurrentScreen is MultiSpectatorScreen); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 4bd68f2034..020217eac6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -457,6 +457,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return; } + // The beatmap is queried asynchronously when the selected item changes. + // This is an issue with MultiSpectatorScreen which is effectively in an always "ready" state and receives LoadRequested() callbacks + // even when it is not truly ready (i.e. the beatmap hasn't been selected by the client yet). For the time being, a simple fix to this is to ignore the callback. + // Note that spectator will be entered automatically when the client is capable of doing so via beatmap availability callbacks (see: updateBeatmapAvailability()). + if (client.LocalUser?.State == MultiplayerUserState.Spectating && Beatmap.IsDefault) + return; + StartPlay(); readyClickOperation?.Dispose(); From 0473c6c52f9156954eb7c782a421a540cbc35e5d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 4 Feb 2022 17:53:30 +0900 Subject: [PATCH 0551/5199] Also handle null SelectedItem for safety --- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 020217eac6..a397493bab 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -461,7 +461,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer // This is an issue with MultiSpectatorScreen which is effectively in an always "ready" state and receives LoadRequested() callbacks // even when it is not truly ready (i.e. the beatmap hasn't been selected by the client yet). For the time being, a simple fix to this is to ignore the callback. // Note that spectator will be entered automatically when the client is capable of doing so via beatmap availability callbacks (see: updateBeatmapAvailability()). - if (client.LocalUser?.State == MultiplayerUserState.Spectating && Beatmap.IsDefault) + if (client.LocalUser?.State == MultiplayerUserState.Spectating && (SelectedItem.Value == null || Beatmap.IsDefault)) return; StartPlay(); From 8fc4d0c6f51053184b5d8952214aa652cf6f7c1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Feb 2022 18:20:17 +0900 Subject: [PATCH 0552/5199] Add override edge rule to overflow above home indicator on iOS --- osu.Game/OsuGameBase.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 5f87abcfed..594e7a10c4 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -301,6 +301,7 @@ namespace osu.Game base.Content.Add(new SafeAreaContainer { + SafeAreaOverrideEdges = Edges.Bottom, RelativeSizeAxes = Axes.Both, Child = CreateScalingContainer().WithChildren(new Drawable[] { From 6457cf8d9b70264e46f757b01116b85ef393c163 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Feb 2022 18:45:38 +0900 Subject: [PATCH 0553/5199] Fix weird formatting in `TestSceneSafeArea` --- .../Visual/UserInterface/TestSceneSafeAreaHandling.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSafeAreaHandling.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSafeAreaHandling.cs index 676ae1276b..8b4e3f6d3a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneSafeAreaHandling.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSafeAreaHandling.cs @@ -29,11 +29,10 @@ namespace osu.Game.Tests.Visual.UserInterface // Usually this would be placed between the host and the game, but that's a bit of a pain to do with the test scene hierarchy. // Add is required for the container to get a size (and give out correct metrics to the usages in SafeAreaContainer). - Add( - safeAreaContainer = new SafeAreaDefiningContainer(safeArea = new BindableSafeArea()) - { - RelativeSizeAxes = Axes.Both - }); + Add(safeAreaContainer = new SafeAreaDefiningContainer(safeArea = new BindableSafeArea()) + { + RelativeSizeAxes = Axes.Both + }); // Cache is required for the test game to see the safe area. Dependencies.CacheAs(safeAreaContainer); From 915d63f6dea742d853f7e635c3d18093d30de815 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Feb 2022 18:58:29 +0900 Subject: [PATCH 0554/5199] Limit safe area bottom override to iOS only --- osu.Game/OsuGameBase.cs | 8 +++++++- osu.iOS/OsuGameIOS.cs | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 594e7a10c4..97d2e64072 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -89,6 +89,12 @@ namespace osu.Game } } + /// + /// The that the game should be drawn over at a top level. + /// Defaults to . + /// + public virtual Edges SafeAreaOverrideEdges { get; set; } + protected OsuConfigManager LocalConfig { get; private set; } protected SessionStatics SessionStatics { get; private set; } @@ -301,7 +307,7 @@ namespace osu.Game base.Content.Add(new SafeAreaContainer { - SafeAreaOverrideEdges = Edges.Bottom, + SafeAreaOverrideEdges = SafeAreaOverrideEdges, RelativeSizeAxes = Axes.Both, Child = CreateScalingContainer().WithChildren(new Drawable[] { diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index 702aef45f5..cf14745be1 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -3,6 +3,7 @@ using System; using Foundation; +using osu.Framework.Graphics; using osu.Game; using osu.Game.Updater; using osu.Game.Utils; @@ -18,6 +19,11 @@ namespace osu.iOS protected override BatteryInfo CreateBatteryInfo() => new IOSBatteryInfo(); + public override Edges SafeAreaOverrideEdges => + // iOS shows a home indicator at the bottom, and adds a safe area to account for this. + // Because we have the home indicator (mostly) hidden we don't really care about drawing in this region. + Edges.Bottom; + private class IOSBatteryInfo : BatteryInfo { public override double ChargeLevel => Battery.ChargeLevel; From dd63b1a3500be04b494623a51806121364720ba2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 4 Feb 2022 19:03:52 +0900 Subject: [PATCH 0555/5199] Fix broken spectator playback test scene --- .../Gameplay/TestSceneSpectatorPlayback.cs | 221 +++++++----------- 1 file changed, 85 insertions(+), 136 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 4af254866a..69bf2a7a38 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -3,12 +3,8 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; -using System.Diagnostics; using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -20,7 +16,6 @@ using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; -using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Replays; using osu.Game.Replays.Legacy; @@ -32,6 +27,7 @@ using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Play; +using osu.Game.Tests.Visual.Spectator; using osu.Game.Tests.Visual.UserInterface; using osuTK; using osuTK.Graphics; @@ -47,138 +43,105 @@ namespace osu.Game.Tests.Visual.Gameplay private Replay replay; - private readonly IBindableList users = new BindableList(); - - private TestReplayRecorder recorder; - private ManualClock manualClock; private OsuSpriteText latencyDisplay; private TestFramedReplayInputHandler replayHandler; - [Resolved] - private IAPIProvider api { get; set; } - - [Resolved] - private SpectatorClient spectatorClient { get; set; } - - [Cached] - private GameplayState gameplayState = new GameplayState(new Beatmap(), new OsuRuleset(), Array.Empty()); - [SetUpSteps] public void SetUpSteps() { - AddStep("Reset recorder state", cleanUpState); - AddStep("Setup containers", () => { replay = new Replay(); manualClock = new ManualClock(); + SpectatorClient spectatorClient; + + Child = new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new[] + { + (typeof(SpectatorClient), (object)(spectatorClient = new TestSpectatorClient())), + (typeof(GameplayState), new GameplayState(new Beatmap(), new OsuRuleset(), Array.Empty())) + }, + Children = new Drawable[] + { + spectatorClient, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + { + Recorder = new TestReplayRecorder + { + ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), + }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Brown, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Text = "Sending", + Scale = new Vector2(3), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new TestInputConsumer() + } + }, + } + }, + new Drawable[] + { + playbackManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + { + Clock = new FramedClock(manualClock), + ReplayInputHandler = replayHandler = new TestFramedReplayInputHandler(replay) + { + GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos), + }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.DarkBlue, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Text = "Receiving", + Scale = new Vector2(3), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new TestInputConsumer() + } + }, + } + } + } + }, + latencyDisplay = new OsuSpriteText() + } + }; spectatorClient.OnNewFrames += onNewFrames; - - users.BindTo(spectatorClient.PlayingUsers); - users.BindCollectionChanged((obj, args) => - { - switch (args.Action) - { - case NotifyCollectionChangedAction.Add: - Debug.Assert(args.NewItems != null); - - foreach (int user in args.NewItems) - { - if (user == api.LocalUser.Value.Id) - spectatorClient.WatchUser(user); - } - - break; - - case NotifyCollectionChangedAction.Remove: - Debug.Assert(args.OldItems != null); - - foreach (int user in args.OldItems) - { - if (user == api.LocalUser.Value.Id) - spectatorClient.StopWatchingUser(user); - } - - break; - } - }, true); - - Children = new Drawable[] - { - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] - { - recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) - { - Recorder = recorder = new TestReplayRecorder - { - ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), - }, - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - Colour = Color4.Brown, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Text = "Sending", - Scale = new Vector2(3), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new TestInputConsumer() - } - }, - } - }, - new Drawable[] - { - playbackManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) - { - Clock = new FramedClock(manualClock), - ReplayInputHandler = replayHandler = new TestFramedReplayInputHandler(replay) - { - GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos), - }, - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - Colour = Color4.DarkBlue, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Text = "Receiving", - Scale = new Vector2(3), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new TestInputConsumer() - } - }, - } - } - } - }, - latencyDisplay = new OsuSpriteText() - }; }); } @@ -238,20 +201,6 @@ namespace osu.Game.Tests.Visual.Gameplay manualClock.CurrentTime = time.Value; } - [TearDownSteps] - public void TearDown() - { - AddStep("stop recorder", cleanUpState); - } - - private void cleanUpState() - { - // Ensure previous recorder is disposed else it may affect the global playing state of `SpectatorClient`. - recorder?.RemoveAndDisposeImmediately(); - recorder = null; - spectatorClient.OnNewFrames -= onNewFrames; - } - public class TestFramedReplayInputHandler : FramedReplayInputHandler { public TestFramedReplayInputHandler(Replay replay) From fa3d1115fa9535d288e9cc74756b3cc68548ed5b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 4 Feb 2022 19:17:50 +0900 Subject: [PATCH 0556/5199] Remove online api requirement --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 69bf2a7a38..a4d8460846 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -36,8 +36,6 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneSpectatorPlayback : OsuManualInputManagerTestScene { - protected override bool UseOnlineAPI => true; - private TestRulesetInputManager playbackManager; private TestRulesetInputManager recordingManager; From 503025b970b1df6bb4935c13df7c4f9d82e56036 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Feb 2022 19:19:44 +0900 Subject: [PATCH 0557/5199] Fix completely incorrect and dangerous usage of bindable binding --- osu.Game/Graphics/Containers/ScalingContainer.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index aa4e3a7fde..f505a62a33 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -23,6 +23,8 @@ namespace osu.Game.Graphics.Containers private Bindable posX; private Bindable posY; + private Bindable safeAreaPadding; + private readonly ScalingMode? targetMode; private Bindable scalingMode; @@ -101,11 +103,8 @@ namespace osu.Game.Graphics.Containers } } - [Resolved] - private ISafeArea safeArea { get; set; } - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, ISafeArea safeArea) { scalingMode = config.GetBindable(OsuSetting.Scaling); scalingMode.ValueChanged += _ => updateSize(); @@ -122,7 +121,8 @@ namespace osu.Game.Graphics.Containers posY = config.GetBindable(OsuSetting.ScalingPositionY); posY.ValueChanged += _ => updateSize(); - safeArea.SafeAreaPadding.BindValueChanged(_ => updateSize()); + safeAreaPadding = safeArea.SafeAreaPadding.GetBoundCopy(); + safeAreaPadding.BindValueChanged(_ => updateSize()); } protected override void LoadComplete() @@ -169,7 +169,7 @@ namespace osu.Game.Graphics.Containers bool requiresMasking = (scaling && targetSize != Vector2.One) // For the top level scaling container, for now we apply masking if safe areas are in use. // In the future this can likely be removed as more of the actual UI supports overflowing into the safe areas. - || (targetMode == ScalingMode.Everything && safeArea.SafeAreaPadding.Value.Total != Vector2.Zero); + || (targetMode == ScalingMode.Everything && safeAreaPadding.Value.Total != Vector2.Zero); if (requiresMasking) sizableContainer.Masking = true; From 0f48c0131ca99a6b9f3283ebbd037cd991e181b6 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 4 Feb 2022 15:42:52 +0900 Subject: [PATCH 0558/5199] Layer playback of beatmap-changed and random-beatmap samples --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 ++++ osu.Game/Screens/Select/SongSelect.cs | 21 +++------------------ 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index f17daa8697..3d5ed70dda 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -154,6 +154,7 @@ namespace osu.Game.Screens.Select private readonly DrawablePool setPool = new DrawablePool(100); private Sample spinSample; + private Sample randomSelectSample; private int visibleSetsCount; @@ -178,6 +179,7 @@ namespace osu.Game.Screens.Select private void load(OsuConfigManager config, AudioManager audio) { spinSample = audio.Samples.Get("SongSelect/random-spin"); + randomSelectSample = audio.Samples.Get(@"SongSelect/select-random"); config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm); config.BindWith(OsuSetting.SongSelectRightMouseScroll, RightClickScrollingEnabled); @@ -495,6 +497,8 @@ namespace osu.Game.Screens.Select var chan = spinSample.GetChannel(); chan.Frequency.Value = 1f + Math.Min(1f, distance / visibleSetsCount); chan.Play(); + + randomSelectSample?.Play(); } private void select(CarouselItem item) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index ee807762bf..f5b11448f8 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -100,7 +100,6 @@ namespace osu.Game.Screens.Select private Sample sampleChangeDifficulty; private Sample sampleChangeBeatmap; - private Sample sampleRandomBeatmap; private Container carouselContainer; @@ -110,8 +109,6 @@ namespace osu.Game.Screens.Select private double audioFeedbackLastPlaybackTime; - private bool randomSelectionPending; - [Resolved] private MusicController music { get; set; } @@ -291,7 +288,6 @@ namespace osu.Game.Screens.Select sampleChangeDifficulty = audio.Samples.Get(@"SongSelect/select-difficulty"); sampleChangeBeatmap = audio.Samples.Get(@"SongSelect/select-expand"); - sampleRandomBeatmap = audio.Samples.Get(@"SongSelect/select-random"); SampleConfirm = audio.Samples.Get(@"SongSelect/confirm-selection"); if (dialogOverlay != null) @@ -319,16 +315,8 @@ namespace osu.Game.Screens.Select (new FooterButtonMods { Current = Mods }, ModSelect), (new FooterButtonRandom { - NextRandom = () => - { - randomSelectionPending = true; - Carousel.SelectNextRandom(); - }, - PreviousRandom = () => - { - randomSelectionPending = true; - Carousel.SelectPreviousRandom(); - } + NextRandom = () => Carousel.SelectNextRandom(), + PreviousRandom = Carousel.SelectPreviousRandom }, null), (new FooterButtonOptions(), BeatmapOptions) }; @@ -498,9 +486,7 @@ namespace osu.Game.Screens.Select { if (beatmap != null && beatmapInfoPrevious != null && Time.Current - audioFeedbackLastPlaybackTime >= 50) { - if (randomSelectionPending) - sampleRandomBeatmap.Play(); - else if (beatmap.BeatmapSet?.ID == beatmapInfoPrevious.BeatmapSet?.ID) + if (beatmap.BeatmapSet?.ID == beatmapInfoPrevious.BeatmapSet?.ID) sampleChangeDifficulty.Play(); else sampleChangeBeatmap.Play(); @@ -508,7 +494,6 @@ namespace osu.Game.Screens.Select audioFeedbackLastPlaybackTime = Time.Current; } - randomSelectionPending = false; beatmapInfoPrevious = beatmap; } From e2262bf3b25f14f625e76bec34118203299c31d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Feb 2022 20:33:15 +0900 Subject: [PATCH 0559/5199] Schedule all calls to `updateSize` for safety --- osu.Game/Graphics/Containers/ScalingContainer.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index f505a62a33..b3423345b1 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -52,7 +52,7 @@ namespace osu.Game.Graphics.Containers return; allowScaling = value; - if (IsLoaded) updateSize(); + if (IsLoaded) Scheduler.AddOnce(updateSize); } } @@ -107,29 +107,29 @@ namespace osu.Game.Graphics.Containers private void load(OsuConfigManager config, ISafeArea safeArea) { scalingMode = config.GetBindable(OsuSetting.Scaling); - scalingMode.ValueChanged += _ => updateSize(); + scalingMode.ValueChanged += _ => Scheduler.AddOnce(updateSize); sizeX = config.GetBindable(OsuSetting.ScalingSizeX); - sizeX.ValueChanged += _ => updateSize(); + sizeX.ValueChanged += _ => Scheduler.AddOnce(updateSize); sizeY = config.GetBindable(OsuSetting.ScalingSizeY); - sizeY.ValueChanged += _ => updateSize(); + sizeY.ValueChanged += _ => Scheduler.AddOnce(updateSize); posX = config.GetBindable(OsuSetting.ScalingPositionX); - posX.ValueChanged += _ => updateSize(); + posX.ValueChanged += _ => Scheduler.AddOnce(updateSize); posY = config.GetBindable(OsuSetting.ScalingPositionY); - posY.ValueChanged += _ => updateSize(); + posY.ValueChanged += _ => Scheduler.AddOnce(updateSize); safeAreaPadding = safeArea.SafeAreaPadding.GetBoundCopy(); - safeAreaPadding.BindValueChanged(_ => updateSize()); + safeAreaPadding.BindValueChanged(_ => Scheduler.AddOnce(updateSize)); } protected override void LoadComplete() { base.LoadComplete(); - updateSize(); + Scheduler.AddOnce(updateSize); sizableContainer.FinishTransforms(); } From b5dde6f1ad22d1c80502d307dee061556e3e404f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Feb 2022 21:38:52 +0900 Subject: [PATCH 0560/5199] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 04c543750e..71525a7acb 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 83c3593edb..c8f634284b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index b0c056ea21..5978f6d685 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + From 5e47ce333ce42831d8b4d48fdd4aa4437956587a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Feb 2022 16:10:49 +0300 Subject: [PATCH 0561/5199] Change `SafeAreaOverrideEdges` to be get-only and protected --- osu.Game/OsuGameBase.cs | 2 +- osu.iOS/OsuGameIOS.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 97d2e64072..5b2eb5607a 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -93,7 +93,7 @@ namespace osu.Game /// The that the game should be drawn over at a top level. /// Defaults to . /// - public virtual Edges SafeAreaOverrideEdges { get; set; } + protected virtual Edges SafeAreaOverrideEdges => Edges.None; protected OsuConfigManager LocalConfig { get; private set; } diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index cf14745be1..9c1795e45e 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -19,7 +19,7 @@ namespace osu.iOS protected override BatteryInfo CreateBatteryInfo() => new IOSBatteryInfo(); - public override Edges SafeAreaOverrideEdges => + protected override Edges SafeAreaOverrideEdges => // iOS shows a home indicator at the bottom, and adds a safe area to account for this. // Because we have the home indicator (mostly) hidden we don't really care about drawing in this region. Edges.Bottom; From d62885f30b64cd2cfbac451591b952ff9663a5a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Feb 2022 22:31:41 +0900 Subject: [PATCH 0562/5199] Don't schedule call to `updateSize` in `LoadComplete` to ensure following `FinishTransforms` runs as expected Co-authored-by: Salman Ahmed --- osu.Game/Graphics/Containers/ScalingContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index b3423345b1..0d543bdbc8 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -129,7 +129,7 @@ namespace osu.Game.Graphics.Containers { base.LoadComplete(); - Scheduler.AddOnce(updateSize); + updateSize(); sizableContainer.FinishTransforms(); } From 750f90e7288b04f0abe4b7c4abb4805d52d5fda8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 4 Feb 2022 22:38:40 +0900 Subject: [PATCH 0563/5199] Simplify TestSpectatorClient implementation --- .../Tests/Visual/Spectator/TestSpectatorClient.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index 1a1d493249..453c086604 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -40,11 +39,7 @@ namespace osu.Game.Tests.Visual.Spectator public TestSpectatorClient() { - OnNewFrames += (i, bundle) => - { - if (PlayingUsers.Contains(i)) - lastReceivedUserFrames[i] = bundle.Frames[^1]; - }; + OnNewFrames += (i, bundle) => lastReceivedUserFrames[i] = bundle.Frames[^1]; } /// @@ -65,7 +60,7 @@ namespace osu.Game.Tests.Visual.Spectator /// The user to end play for. public void EndPlay(int userId) { - if (!PlayingUsers.Contains(userId)) + if (!userBeatmapDictionary.ContainsKey(userId)) return; ((ISpectatorClient)this).UserFinishedPlaying(userId, new SpectatorState @@ -73,6 +68,8 @@ namespace osu.Game.Tests.Visual.Spectator BeatmapID = userBeatmapDictionary[userId], RulesetID = 0, }); + + userBeatmapDictionary.Remove(userId); } public new void Schedule(Action action) => base.Schedule(action); @@ -131,7 +128,7 @@ namespace osu.Game.Tests.Visual.Spectator protected override Task WatchUserInternal(int userId) { // When newly watching a user, the server sends the playing state immediately. - if (PlayingUsers.Contains(userId)) + if (userBeatmapDictionary.ContainsKey(userId)) sendPlayingState(userId); return Task.CompletedTask; From dd8fc710fafc1319bb16613f056b3cf9285d4308 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Fri, 4 Feb 2022 15:48:46 +0100 Subject: [PATCH 0564/5199] removed wiggle duration --- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 853c974a04..fae9d785fb 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -31,15 +31,6 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 2f, Precision = 0.1f }; - - [SettingSource("Duration", "Milliseconds per wiggle")] - public BindableInt WiggleDuration { get; } = new BindableInt(90) - { - MinValue = 40, - MaxValue = 300, - Precision = 5 - }; - protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) => drawableOnApplyCustomUpdateState(hitObject, state); protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) => drawableOnApplyCustomUpdateState(hitObject, state); @@ -57,18 +48,18 @@ namespace osu.Game.Rulesets.Osu.Mods Random objRand = new Random((int)osuObject.StartTime); // Wiggle all objects during TimePreempt - int amountWiggles = (int)osuObject.TimePreempt / WiggleDuration.Value; + int amountWiggles = (int)osuObject.TimePreempt / 70; void wiggle() { float nextAngle = (float)(objRand.NextDouble() * 2 * Math.PI); float nextDist = (float)(objRand.NextDouble() * WiggleStrength.Value * 7); - drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), WiggleDuration.Value); + drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), 70); } for (int i = 0; i < amountWiggles; i++) { - using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * WiggleDuration.Value)) + using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * 70)) wiggle(); } @@ -76,11 +67,11 @@ namespace osu.Game.Rulesets.Osu.Mods if (!(osuObject is IHasDuration endTime)) return; - amountWiggles = (int)(endTime.Duration / WiggleDuration.Value); + amountWiggles = (int)(endTime.Duration / 70); for (int i = 0; i < amountWiggles; i++) { - using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * WiggleDuration.Value)) + using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * 70)) wiggle(); } } From eb25730b614ececc5f6672e298dc5e786984c92e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 5 Feb 2022 16:12:58 +0900 Subject: [PATCH 0565/5199] Revert "Merge pull request #16716 from peppy/carousel-less-invalidations" This reverts commit 8d13e0514b27ce5e6f587ba8918c165d3e1ddbbf, reversing changes made to 95582a9023488da37edb3b9236f6b4aeb197ccb9. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 +- .../Screens/Select/Carousel/CarouselHeader.cs | 29 ++++---- .../Carousel/DrawableCarouselBeatmap.cs | 10 ++- .../Carousel/DrawableCarouselBeatmapSet.cs | 6 +- .../Select/Carousel/DrawableCarouselItem.cs | 71 +++++++------------ 5 files changed, 54 insertions(+), 68 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 3d5ed70dda..c3d340ac61 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -925,8 +925,10 @@ namespace osu.Game.Screens.Select // child items (difficulties) are still visible. item.Header.X = offsetX(dist, visibleHalfHeight) - (parent?.X ?? 0); - // We are applying alpha to the header here such that we can layer alpha transformations on top. - item.Header.Alpha = Math.Clamp(1.75f - 1.5f * dist, 0, 1); + // We are applying a multiplicative alpha (which is internally done by nesting an + // additional container and setting that container's alpha) such that we can + // layer alpha transformations on top. + item.SetMultiplicativeAlpha(Math.Clamp(1.75f - 1.5f * dist, 0, 1)); } private enum PendingScrollOperation diff --git a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs index 533694b265..ed3aea3445 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs @@ -21,6 +21,8 @@ namespace osu.Game.Screens.Select.Carousel { public class CarouselHeader : Container { + public Container BorderContainer; + public readonly Bindable State = new Bindable(CarouselItemState.NotSelected); private readonly HoverLayer hoverLayer; @@ -35,14 +37,17 @@ namespace osu.Game.Screens.Select.Carousel RelativeSizeAxes = Axes.X; Height = DrawableCarouselItem.MAX_HEIGHT; - Masking = true; - CornerRadius = corner_radius; - BorderColour = new Color4(221, 255, 255, 255); - - InternalChildren = new Drawable[] + InternalChild = BorderContainer = new Container { - Content, - hoverLayer = new HoverLayer() + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = corner_radius, + BorderColour = new Color4(221, 255, 255, 255), + Children = new Drawable[] + { + Content, + hoverLayer = new HoverLayer() + } }; } @@ -61,21 +66,21 @@ namespace osu.Game.Screens.Select.Carousel case CarouselItemState.NotSelected: hoverLayer.InsetForBorder = false; - BorderThickness = 0; - EdgeEffect = new EdgeEffectParameters + BorderContainer.BorderThickness = 0; + BorderContainer.EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, Offset = new Vector2(1), Radius = 10, - Colour = Color4.Black.Opacity(0.5f), + Colour = Color4.Black.Opacity(100), }; break; case CarouselItemState.Selected: hoverLayer.InsetForBorder = true; - BorderThickness = border_thickness; - EdgeEffect = new EdgeEffectParameters + BorderContainer.BorderThickness = border_thickness; + BorderContainer.EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, Colour = new Color4(130, 204, 255, 150), diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index a3483aa60a..3576b77ae8 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -36,9 +36,9 @@ namespace osu.Game.Screens.Select.Carousel /// /// The height of a carousel beatmap, including vertical spacing. /// - public const float HEIGHT = header_height + CAROUSEL_BEATMAP_SPACING; + public const float HEIGHT = height + CAROUSEL_BEATMAP_SPACING; - private const float header_height = MAX_HEIGHT * 0.6f; + private const float height = MAX_HEIGHT * 0.6f; private readonly BeatmapInfo beatmapInfo; @@ -67,18 +67,16 @@ namespace osu.Game.Screens.Select.Carousel private CancellationTokenSource starDifficultyCancellationSource; public DrawableCarouselBeatmap(CarouselBeatmap panel) - : base(header_height) { beatmapInfo = panel.BeatmapInfo; Item = panel; - - // Difficulty panels should start hidden for a better initial effect. - Hide(); } [BackgroundDependencyLoader(true)] private void load(BeatmapManager manager, SongSelect songSelect) { + Header.Height = height; + if (songSelect != null) { startRequested = b => songSelect.FinaliseSelection(b); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 63c004f4bc..618c5cf5ec 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -122,10 +122,12 @@ namespace osu.Game.Screens.Select.Carousel }, }; - background.DelayedLoadComplete += d => d.FadeInFromZero(750, Easing.OutQuint); - mainFlow.DelayedLoadComplete += d => d.FadeInFromZero(500, Easing.OutQuint); + background.DelayedLoadComplete += fadeContentIn; + mainFlow.DelayedLoadComplete += fadeContentIn; } + private void fadeContentIn(Drawable d) => d.FadeInFromZero(750, Easing.OutQuint); + protected override void Deselected() { base.Deselected(); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 5e7ca0825a..cde3edad39 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -60,10 +60,12 @@ namespace osu.Game.Screens.Select.Carousel } } - protected DrawableCarouselItem(float headerHeight = MAX_HEIGHT) + protected DrawableCarouselItem() { RelativeSizeAxes = Axes.X; + Alpha = 0; + InternalChildren = new Drawable[] { MovementContainer = new Container @@ -71,20 +73,18 @@ namespace osu.Game.Screens.Select.Carousel RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - Header = new CarouselHeader - { - Height = headerHeight, - }, + Header = new CarouselHeader(), Content = new Container { RelativeSizeAxes = Axes.Both, - Y = headerHeight, } } }, }; } + public void SetMultiplicativeAlpha(float alpha) => Header.BorderContainer.Alpha = alpha; + protected override void LoadComplete() { base.LoadComplete(); @@ -92,6 +92,12 @@ namespace osu.Game.Screens.Select.Carousel UpdateItem(); } + protected override void Update() + { + base.Update(); + Content.Y = Header.Height; + } + protected virtual void UpdateItem() { if (item == null) @@ -115,56 +121,29 @@ namespace osu.Game.Screens.Select.Carousel private void onStateChange(ValueChangedEvent _) => Scheduler.AddOnce(ApplyState); - private CarouselItemState? lastAppliedState; - protected virtual void ApplyState() { + // Use the fact that we know the precise height of the item from the model to avoid the need for AutoSize overhead. + // Additionally, AutoSize doesn't work well due to content starting off-screen and being masked away. + Height = Item.TotalHeight; + Debug.Assert(Item != null); - if (lastAppliedState != Item.State.Value) + switch (Item.State.Value) { - lastAppliedState = Item.State.Value; + case CarouselItemState.NotSelected: + Deselected(); + break; - // Use the fact that we know the precise height of the item from the model to avoid the need for AutoSize overhead. - // Additionally, AutoSize doesn't work well due to content starting off-screen and being masked away. - Height = Item.TotalHeight; - - switch (lastAppliedState) - { - case CarouselItemState.NotSelected: - Deselected(); - break; - - case CarouselItemState.Selected: - Selected(); - break; - } + case CarouselItemState.Selected: + Selected(); + break; } if (!Item.Visible) - Hide(); + this.FadeOut(300, Easing.OutQuint); else - Show(); - } - - private bool isVisible = true; - - public override void Show() - { - if (isVisible) - return; - - isVisible = true; - this.FadeIn(250); - } - - public override void Hide() - { - if (!isVisible) - return; - - isVisible = false; - this.FadeOut(300, Easing.OutQuint); + this.FadeIn(250); } protected virtual void Selected() From 2e1a9f137994a3085a592d216c075054126ceb38 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sat, 5 Feb 2022 21:13:16 +0800 Subject: [PATCH 0566/5199] Add performance breakdown as statistic item in extended statistics panel --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 11 + .../Statistics/PerformanceBreakdownChart.cs | 244 ++++++++++++++++++ 2 files changed, 255 insertions(+) create mode 100644 osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 428e7b9df5..769e83ec00 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -298,6 +298,17 @@ namespace osu.Game.Rulesets.Osu } }, new StatisticRow + { + Columns = new[] + { + new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }), + } + }, + new StatisticRow { Columns = new[] { diff --git a/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs b/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs new file mode 100644 index 0000000000..b58974eb96 --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs @@ -0,0 +1,244 @@ +// 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 System.Threading; +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Scoring; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Ranking.Statistics +{ + public class PerformanceBreakdownChart : Container + { + private readonly ScoreInfo score; + + private Drawable spinner; + private Drawable content; + private GridContainer chart; + private OsuSpriteText achievedPerformance; + private OsuSpriteText maximumPerformance; + + private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + + [Resolved] + private ScorePerformanceCache performanceCache { get; set; } + + [Resolved] + private BeatmapDifficultyCache difficultyCache { get; set; } + + [Resolved] + private BeatmapManager beatmapManager { get; set; } + + public PerformanceBreakdownChart(ScoreInfo score) + { + this.score = score; + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new[] + { + spinner = new LoadingSpinner(true) + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre + }, + content = new FillFlowContainer + { + Alpha = 0, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.6f, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Spacing = new Vector2(15, 15), + Children = new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.X, + Width = 0.8f, + AutoSizeAxes = Axes.Y, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] + { + new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 18), + Text = "Achieved PP", + Colour = Color4Extensions.FromHex("#66FFCC") + }, + achievedPerformance = new OsuSpriteText + { + Origin = Anchor.CentreRight, + Anchor = Anchor.CentreRight, + Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 18), + Colour = Color4Extensions.FromHex("#66FFCC") + } + }, + new Drawable[] + { + new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 18), + Text = "Maximum", + Colour = OsuColour.Gray(0.7f) + }, + maximumPerformance = new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 18), + Colour = OsuColour.Gray(0.7f) + } + } + } + }, + chart = new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.AutoSize) + } + } + } + } + }; + + spinner.Show(); + + new PerformanceBreakdownCalculator(beatmapManager, difficultyCache, performanceCache) + .CalculateAsync(score, cancellationTokenSource.Token) + .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely()))); + } + + private void setPerformanceValue(PerformanceBreakdown breakdown) + { + spinner.Hide(); + content.FadeIn(200); + + var displayAttributes = breakdown.Performance.GetAttributesForDisplay(); + var perfectDisplayAttributes = breakdown.PerfectPerformance.GetAttributesForDisplay(); + + setTotalValues( + displayAttributes.First(a => a.PropertyName == nameof(PerformanceAttributes.Total)), + perfectDisplayAttributes.First(a => a.PropertyName == nameof(PerformanceAttributes.Total)) + ); + + var rowDimensions = new List(); + var rows = new List(); + + foreach (PerformanceDisplayAttribute attr in displayAttributes) + { + if (attr.PropertyName == nameof(PerformanceAttributes.Total)) continue; + + var row = createAttributeRow(attr, perfectDisplayAttributes.First(a => a.PropertyName == attr.PropertyName)); + + if (row != null) + { + rows.Add(row); + rowDimensions.Add(new Dimension(GridSizeMode.AutoSize)); + } + } + + chart.RowDimensions = rowDimensions.ToArray(); + chart.Content = rows.ToArray(); + } + + private void setTotalValues(PerformanceDisplayAttribute attribute, PerformanceDisplayAttribute perfectAttribute) + { + achievedPerformance.Text = Math.Round(attribute.Value, MidpointRounding.AwayFromZero).ToLocalisableString(); + maximumPerformance.Text = Math.Round(perfectAttribute.Value, MidpointRounding.AwayFromZero).ToLocalisableString(); + } + + [CanBeNull] + private Drawable[] createAttributeRow(PerformanceDisplayAttribute attribute, PerformanceDisplayAttribute perfectAttribute) + { + float percentage = (float)(attribute.Value / perfectAttribute.Value); + if (float.IsNaN(percentage)) + return null; + + return new Drawable[] + { + new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Font = OsuFont.GetFont(weight: FontWeight.Regular), + Text = attribute.DisplayName, + Colour = Colour4.White + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = 10, Right = 10 }, + Child = new Bar + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + CornerRadius = 2.5f, + Masking = true, + Height = 5, + BackgroundColour = Color4.White.Opacity(0.5f), + AccentColour = Color4Extensions.FromHex("#66FFCC"), + Length = percentage + } + }, + new OsuSpriteText + { + Origin = Anchor.CentreRight, + Anchor = Anchor.CentreRight, + Font = OsuFont.GetFont(weight: FontWeight.SemiBold), + Text = percentage.ToLocalisableString("0%"), + Colour = Colour4.White + } + }; + } + + protected override void Dispose(bool isDisposing) + { + cancellationTokenSource?.Cancel(); + base.Dispose(isDisposing); + } + } +} From c35ef917a171564750d54c3bee09d24339d27ee2 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sat, 5 Feb 2022 21:18:23 +0800 Subject: [PATCH 0567/5199] Remove tooltip from performance statistic --- .../Statistics/PerformanceStatistic.cs | 43 ++-- .../Statistics/PerformanceStatisticTooltip.cs | 212 ------------------ 2 files changed, 14 insertions(+), 241 deletions(-) delete mode 100644 osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index 158fd82b29..859b42d66d 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -2,21 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Cursor; -using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets.Difficulty; using osu.Game.Scoring; namespace osu.Game.Screens.Ranking.Expanded.Statistics { - public class PerformanceStatistic : StatisticDisplay, IHasCustomTooltip + public class PerformanceStatistic : StatisticDisplay { private readonly ScoreInfo score; @@ -26,15 +22,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics private RollingCounter counter; - [Resolved] - private ScorePerformanceCache performanceCache { get; set; } - - [Resolved] - private BeatmapDifficultyCache difficultyCache { get; set; } - - [Resolved] - private BeatmapManager beatmapManager { get; set; } - public PerformanceStatistic(ScoreInfo score) : base("PP") { @@ -42,21 +29,23 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics } [BackgroundDependencyLoader] - private void load() + private void load(ScorePerformanceCache performanceCache) { - new PerformanceBreakdownCalculator(beatmapManager, difficultyCache, performanceCache) - .CalculateAsync(score, cancellationTokenSource.Token) - .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely()))); + if (score.PP.HasValue) + { + setPerformanceValue(score.PP.Value); + } + else + { + performanceCache.CalculatePerformanceAsync(score, cancellationTokenSource.Token) + .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely().Total)), cancellationTokenSource.Token); + } } - private void setPerformanceValue(PerformanceBreakdown breakdown) + private void setPerformanceValue(double? pp) { - // Don't display the tooltip if "Total" is the only item - if (breakdown != null && breakdown.Performance.GetAttributesForDisplay().Count() > 1) - { - TooltipContent = breakdown; - performance.Value = (int)Math.Round(breakdown.Performance.Total, MidpointRounding.AwayFromZero); - } + if (pp.HasValue) + performance.Value = (int)Math.Round(pp.Value, MidpointRounding.AwayFromZero); } public override void Appear() @@ -76,9 +65,5 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre }; - - public ITooltip GetCustomTooltip() => new PerformanceStatisticTooltip(); - - public PerformanceBreakdown TooltipContent { get; private set; } } } diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs deleted file mode 100644 index bd6eb057c6..0000000000 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ /dev/null @@ -1,212 +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.Linq; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Extensions.LocalisationExtensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets.Difficulty; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Screens.Ranking.Expanded.Statistics -{ - public class PerformanceStatisticTooltip : VisibilityContainer, ITooltip - { - private readonly Box background; - private Colour4 titleColor; - private Colour4 textColour; - - protected override Container Content { get; } - - public PerformanceStatisticTooltip() - { - AutoSizeAxes = Axes.Both; - Masking = true; - CornerRadius = 5; - - InternalChildren = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both - }, - Content = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Left = 10, Right = 10, Top = 5, Bottom = 5 } - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.Gray3; - titleColor = colours.Blue; - textColour = colours.BlueLighter; - } - - protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); - - protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); - - private PerformanceBreakdown currentPerformance; - - public void SetContent(PerformanceBreakdown performance) - { - if (performance == currentPerformance) - return; - - currentPerformance = performance; - - UpdateDisplay(performance); - } - - private Drawable createItemForTotal(PerformanceDisplayAttribute attribute, PerformanceDisplayAttribute perfectAttribute) - { - return - new GridContainer - { - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Bottom = 10 }, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 250), - new Dimension(GridSizeMode.AutoSize) - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] - { - new OsuSpriteText - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Font = OsuFont.GetFont(weight: FontWeight.Regular), - Text = attribute.DisplayName, - Colour = titleColor - }, - new OsuSpriteText - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Font = OsuFont.GetFont(weight: FontWeight.SemiBold), - Text = Math.Round(attribute.Value, MidpointRounding.AwayFromZero).ToLocalisableString(), - Colour = titleColor - } - }, - new Drawable[] - { - new OsuSpriteText - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Font = OsuFont.GetFont(weight: FontWeight.Regular), - Text = "Maximum", - Colour = OsuColour.Gray(0.7f) - }, - new OsuSpriteText - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Font = OsuFont.GetFont(weight: FontWeight.Regular), - Text = Math.Round(perfectAttribute.Value, MidpointRounding.AwayFromZero).ToLocalisableString(), - Colour = OsuColour.Gray(0.7f) - } - } - } - }; - } - - private Drawable createItemForAttribute(PerformanceDisplayAttribute attribute, PerformanceDisplayAttribute perfectAttribute) - { - float percentage = (float)(attribute.Value / perfectAttribute.Value); - if (float.IsNaN(percentage)) - return null; - - return new GridContainer - { - AutoSizeAxes = Axes.Both, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 110), - new Dimension(GridSizeMode.Absolute, 140), - new Dimension(GridSizeMode.AutoSize) - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] - { - new OsuSpriteText - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Font = OsuFont.GetFont(weight: FontWeight.Regular), - Text = attribute.DisplayName, - Colour = textColour - }, - new Bar - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Width = 130, - Height = 5, - BackgroundColour = Color4.White.Opacity(0.5f), - Colour = textColour, - Length = percentage, - Margin = new MarginPadding { Left = 5, Right = 5 } - }, - new OsuSpriteText - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Font = OsuFont.GetFont(weight: FontWeight.SemiBold), - Text = percentage.ToLocalisableString("0%"), - Colour = textColour - } - } - } - }; - } - - protected virtual void UpdateDisplay(PerformanceBreakdown performance) - { - Content.Clear(); - - var displayAttributes = performance.Performance.GetAttributesForDisplay(); - - var perfectDisplayAttributes = performance.PerfectPerformance.GetAttributesForDisplay(); - - foreach (PerformanceDisplayAttribute attr in displayAttributes) - { - var attributeItem = attr.PropertyName == nameof(PerformanceAttributes.Total) - ? createItemForTotal(attr, perfectDisplayAttributes.First(a => a.PropertyName == attr.PropertyName)) - : createItemForAttribute(attr, perfectDisplayAttributes.First(a => a.PropertyName == attr.PropertyName)); - - if (attributeItem != null) - Content.Add(attributeItem); - } - } - - public void Move(Vector2 pos) => Position = pos; - } -} From 440b674bb017f54affd7f201580936ced8a8a126 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sat, 5 Feb 2022 21:30:35 +0800 Subject: [PATCH 0568/5199] Add statistic item for mania & taiko --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 11 +++++++++++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index ffb26b224f..aa9b4d5f1d 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -378,6 +378,17 @@ namespace osu.Game.Rulesets.Mania } }, new StatisticRow + { + Columns = new[] + { + new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }), + } + }, + new StatisticRow { Columns = new[] { diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 21c99c0d2f..37e959f8b4 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -221,6 +221,17 @@ namespace osu.Game.Rulesets.Taiko } }, new StatisticRow + { + Columns = new[] + { + new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }), + } + }, + new StatisticRow { Columns = new[] { From 0b1fef38af1660d6c17c87bdb0c94cff9770cfb0 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sat, 5 Feb 2022 21:36:34 +0800 Subject: [PATCH 0569/5199] Use the playable beatmap provided in `CreateStatisticsForScore` --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- .../PerformanceBreakdownCalculator.cs | 17 ++++++++--------- .../Statistics/PerformanceBreakdownChart.cs | 9 ++++----- 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index aa9b4d5f1d..9027b71069 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -381,7 +381,7 @@ namespace osu.Game.Rulesets.Mania { Columns = new[] { - new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score) + new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 769e83ec00..68e91e42a7 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -301,7 +301,7 @@ namespace osu.Game.Rulesets.Osu { Columns = new[] { - new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score) + new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 37e959f8b4..ddc594a70a 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -224,7 +224,7 @@ namespace osu.Game.Rulesets.Taiko { Columns = new[] { - new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score) + new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs index 46342b237c..d68c2edb40 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -16,13 +16,13 @@ namespace osu.Game.Rulesets.Difficulty { public class PerformanceBreakdownCalculator { - private readonly BeatmapManager beatmapManager; + private readonly IBeatmap playableBeatmap; private readonly BeatmapDifficultyCache difficultyCache; private readonly ScorePerformanceCache performanceCache; - public PerformanceBreakdownCalculator(BeatmapManager beatmapManager, BeatmapDifficultyCache difficultyCache, ScorePerformanceCache performanceCache) + public PerformanceBreakdownCalculator(IBeatmap playableBeatmap, BeatmapDifficultyCache difficultyCache, ScorePerformanceCache performanceCache) { - this.beatmapManager = beatmapManager; + this.playableBeatmap = playableBeatmap; this.difficultyCache = difficultyCache; this.performanceCache = performanceCache; } @@ -46,14 +46,13 @@ namespace osu.Game.Rulesets.Difficulty return Task.Run(async () => { Ruleset ruleset = score.Ruleset.CreateInstance(); - IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(score.BeatmapInfo).GetPlayableBeatmap(score.Ruleset, score.Mods); ScoreInfo perfectPlay = score.DeepClone(); perfectPlay.Accuracy = 1; perfectPlay.Passed = true; // calculate max combo var difficulty = await difficultyCache.GetDifficultyAsync( - beatmap.BeatmapInfo, + playableBeatmap.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken @@ -65,10 +64,10 @@ namespace osu.Game.Rulesets.Difficulty perfectPlay.MaxCombo = difficulty.Value.MaxCombo; // create statistics assuming all hit objects have perfect hit result - var statistics = beatmap.HitObjects - .SelectMany(getPerfectHitResults) - .GroupBy(hr => hr, (hr, list) => (hitResult: hr, count: list.Count())) - .ToDictionary(pair => pair.hitResult, pair => pair.count); + var statistics = playableBeatmap.HitObjects + .SelectMany(getPerfectHitResults) + .GroupBy(hr => hr, (hr, list) => (hitResult: hr, count: list.Count())) + .ToDictionary(pair => pair.hitResult, pair => pair.count); perfectPlay.Statistics = statistics; // calculate total score diff --git a/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs b/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs index b58974eb96..fb9c93f0e6 100644 --- a/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs +++ b/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs @@ -26,6 +26,7 @@ namespace osu.Game.Screens.Ranking.Statistics public class PerformanceBreakdownChart : Container { private readonly ScoreInfo score; + private readonly IBeatmap playableBeatmap; private Drawable spinner; private Drawable content; @@ -41,12 +42,10 @@ namespace osu.Game.Screens.Ranking.Statistics [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } - [Resolved] - private BeatmapManager beatmapManager { get; set; } - - public PerformanceBreakdownChart(ScoreInfo score) + public PerformanceBreakdownChart(ScoreInfo score, IBeatmap playableBeatmap) { this.score = score; + this.playableBeatmap = playableBeatmap; } [BackgroundDependencyLoader] @@ -146,7 +145,7 @@ namespace osu.Game.Screens.Ranking.Statistics spinner.Show(); - new PerformanceBreakdownCalculator(beatmapManager, difficultyCache, performanceCache) + new PerformanceBreakdownCalculator(playableBeatmap, difficultyCache, performanceCache) .CalculateAsync(score, cancellationTokenSource.Token) .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely()))); } From ee6d4b25836d8051e4802b02c68075c28e2494b4 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sat, 5 Feb 2022 21:39:01 +0800 Subject: [PATCH 0570/5199] Move performance breakdown to the top to prevent re-ordering after watching replay --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 12 ++++++------ osu.Game.Rulesets.Osu/OsuRuleset.cs | 22 +++++++++++----------- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 12 ++++++------ 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 9027b71069..180b9ef71b 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -370,22 +370,22 @@ namespace osu.Game.Rulesets.Mania { Columns = new[] { - new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(score.HitEvents) + new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap) { RelativeSizeAxes = Axes.X, - Height = 250 - }, true), + AutoSizeAxes = Axes.Y + }), } }, new StatisticRow { Columns = new[] { - new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap) + new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(score.HitEvents) { RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y - }), + Height = 250 + }, true), } }, new StatisticRow diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 68e91e42a7..ad00a025a1 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -275,6 +275,17 @@ namespace osu.Game.Rulesets.Osu return new[] { + new StatisticRow + { + Columns = new[] + { + new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }), + } + }, new StatisticRow { Columns = new[] @@ -298,17 +309,6 @@ namespace osu.Game.Rulesets.Osu } }, new StatisticRow - { - Columns = new[] - { - new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y - }), - } - }, - new StatisticRow { Columns = new[] { diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index ddc594a70a..e56aabaf9d 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -213,22 +213,22 @@ namespace osu.Game.Rulesets.Taiko { Columns = new[] { - new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(timedHitEvents) + new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap) { RelativeSizeAxes = Axes.X, - Height = 250 - }, true), + AutoSizeAxes = Axes.Y + }), } }, new StatisticRow { Columns = new[] { - new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap) + new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(timedHitEvents) { RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y - }), + Height = 250 + }, true), } }, new StatisticRow From b31c1513f6ef0e7c17e7a3af39bb19174084255a Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sat, 5 Feb 2022 22:41:04 +0800 Subject: [PATCH 0571/5199] Fix test failure The cursor was clicking too far to the right. --- osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 988f429ff5..167acc94c4 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Ranking AddStep("click to right of panel", () => { var expandedPanel = this.ChildrenOfType().Single(p => p.State == PanelState.Expanded); - InputManager.MoveMouseTo(expandedPanel.ScreenSpaceDrawQuad.TopRight + new Vector2(100, 0)); + InputManager.MoveMouseTo(expandedPanel.ScreenSpaceDrawQuad.TopRight + new Vector2(50, 0)); InputManager.Click(MouseButton.Left); }); From f78c853bc752f33d5fff6688fd286182e8ecd476 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 6 Feb 2022 10:59:53 +0800 Subject: [PATCH 0572/5199] Calculate max combo locally in `PerformanceBreakdownCalculator` --- .../PerformanceBreakdownCalculator.cs | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs index d68c2edb40..87e60fbe9d 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -51,17 +51,7 @@ namespace osu.Game.Rulesets.Difficulty perfectPlay.Passed = true; // calculate max combo - var difficulty = await difficultyCache.GetDifficultyAsync( - playableBeatmap.BeatmapInfo, - score.Ruleset, - score.Mods, - cancellationToken - ).ConfigureAwait(false); - - if (difficulty == null) - return null; - - perfectPlay.MaxCombo = difficulty.Value.MaxCombo; + perfectPlay.MaxCombo = calculateMaxCombo(playableBeatmap); // create statistics assuming all hit objects have perfect hit result var statistics = playableBeatmap.HitObjects @@ -86,11 +76,23 @@ namespace osu.Game.Rulesets.Difficulty } // calculate performance for this perfect score + var difficulty = await difficultyCache.GetDifficultyAsync( + playableBeatmap.BeatmapInfo, + score.Ruleset, + score.Mods, + cancellationToken + ).ConfigureAwait(false); + // ScorePerformanceCache is not used to avoid caching multiple copies of essentially identical perfect performance attributes - return ruleset.CreatePerformanceCalculator(difficulty.Value.Attributes, perfectPlay)?.Calculate(); + return difficulty == null ? null : ruleset.CreatePerformanceCalculator(difficulty.Value.Attributes, perfectPlay)?.Calculate(); }, cancellationToken); } + private int calculateMaxCombo(IBeatmap beatmap) + { + return beatmap.HitObjects.SelectMany(getPerfectHitResults).Count(r => r.AffectsCombo()); + } + private IEnumerable getPerfectHitResults(HitObject hitObject) { foreach (HitObject nested in hitObject.NestedHitObjects) From 56c90a21ceec78749b3b76f26c46c54cb3533169 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 6 Feb 2022 11:22:12 +0800 Subject: [PATCH 0573/5199] Add a todo --- osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs index 87e60fbe9d..3d384f5914 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -51,6 +51,7 @@ namespace osu.Game.Rulesets.Difficulty perfectPlay.Passed = true; // calculate max combo + // todo: Get max combo from difficulty calculator instead when diffcalc properly supports lazer-first scores perfectPlay.MaxCombo = calculateMaxCombo(playableBeatmap); // create statistics assuming all hit objects have perfect hit result From c2416c490e3b81c1fe7aa61c90200f9757fed7c8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Feb 2022 11:29:39 +0900 Subject: [PATCH 0574/5199] Fix crash on disconnection during multi-spectate --- .../OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 4646f42d63..bff7023102 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -228,7 +227,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public override bool OnBackButton() { - Debug.Assert(multiplayerClient.Room != null); + if (multiplayerClient.Room == null) + return base.OnBackButton(); // On a manual exit, set the player back to idle unless gameplay has finished. if (multiplayerClient.Room.State != MultiplayerRoomState.Open) From 10bdb7240ff3615f53cb7a0ce61b88f5b3d3428c Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 8 Feb 2022 14:36:29 +0800 Subject: [PATCH 0575/5199] Pre-check for divisor zero and add explanation --- .../Ranking/Statistics/PerformanceBreakdownChart.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs b/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs index fb9c93f0e6..5b42554716 100644 --- a/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs +++ b/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs @@ -12,6 +12,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -192,10 +193,13 @@ namespace osu.Game.Screens.Ranking.Statistics [CanBeNull] private Drawable[] createAttributeRow(PerformanceDisplayAttribute attribute, PerformanceDisplayAttribute perfectAttribute) { - float percentage = (float)(attribute.Value / perfectAttribute.Value); - if (float.IsNaN(percentage)) + // Don't display the attribute if its maximum is 0 + // For example, flashlight bonus would be zero if flashlight mod isn't on + if (Precision.AlmostEquals(perfectAttribute.Value, 0f)) return null; + float percentage = (float)(attribute.Value / perfectAttribute.Value); + return new Drawable[] { new OsuSpriteText From 9c2d57d70783cdc71c8a8ee5439dcbc5eec41073 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Feb 2022 19:36:16 +0900 Subject: [PATCH 0576/5199] Add failing test --- .../Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 94e61eaee0..343fc7e6e0 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -92,7 +92,8 @@ namespace osu.Game.Tests.Online AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); AddUntilStep("wait for import", () => beatmaps.CurrentImport != null); - addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); + AddAssert("ensure beatmap available", () => beatmaps.IsAvailableLocally(testBeatmapSet)); + addAvailabilityCheckStep("state is locally available", BeatmapAvailability.LocallyAvailable); } [Test] From b1a73996ba69de0cd1e02c9cb25266d52d29e2b7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Feb 2022 19:36:41 +0900 Subject: [PATCH 0577/5199] Fix incorrect check for beatmap availability --- osu.Game/Stores/BeatmapImporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index 709dd67087..2a2786b861 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -165,7 +165,7 @@ namespace osu.Game.Stores public override bool IsAvailableLocally(BeatmapSetInfo model) { - return Realm.Run(realm => realm.All().Any(b => b.OnlineID == model.OnlineID)); + return Realm.Run(realm => realm.All().Any(b => b.OnlineID == model.OnlineID)); } public override string HumanisedModelName => "beatmap"; From 0d99017178af1b373212f4174242e7bf8ee224a3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Feb 2022 20:27:08 +0900 Subject: [PATCH 0578/5199] Add state tests --- .../Visual/Gameplay/TestSceneSpectator.cs | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 9f8470446c..65583919d6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -235,6 +235,71 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("last frame has header", () => lastBundle.Frames[^1].Header != null); } + [Test] + public void TestPlayingState() + { + loadSpectatingScreen(); + + start(); + sendFrames(); + waitForPlayer(); + AddUntilStep("state is playing", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Playing); + } + + [Test] + public void TestCompletionState() + { + loadSpectatingScreen(); + + start(); + sendFrames(); + waitForPlayer(); + + AddStep("send completion", () => spectatorClient.EndPlay(streamingUser.Id, SpectatingUserState.Completed)); + AddUntilStep("state is completed", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Completed); + + start(); + sendFrames(); + waitForPlayer(); + AddUntilStep("state is playing", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Playing); + } + + [Test] + public void TestQuitState() + { + loadSpectatingScreen(); + + start(); + sendFrames(); + waitForPlayer(); + + AddStep("send quit", () => spectatorClient.EndPlay(streamingUser.Id, SpectatingUserState.Quit)); + AddUntilStep("state is quit", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Quit); + + start(); + sendFrames(); + waitForPlayer(); + AddUntilStep("state is playing", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Playing); + } + + [Test] + public void TestFailedState() + { + loadSpectatingScreen(); + + start(); + sendFrames(); + waitForPlayer(); + + AddStep("send failed", () => spectatorClient.EndPlay(streamingUser.Id, SpectatingUserState.Failed)); + AddUntilStep("state is failed", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Failed); + + start(); + sendFrames(); + waitForPlayer(); + AddUntilStep("state is playing", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Playing); + } + private OsuFramedReplayInputHandler replayHandler => (OsuFramedReplayInputHandler)Stack.ChildrenOfType().First().ReplayInputHandler; From 4c76027178933d9bcade478495f0588746220a18 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Feb 2022 20:29:49 +0900 Subject: [PATCH 0579/5199] Rename completed state to passed --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 6 +++--- osu.Game/Online/Spectator/SpectatingUserState.cs | 10 +++++----- osu.Game/Online/Spectator/SpectatorClient.cs | 2 +- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 2 +- osu.Game/Screens/Spectate/SpectatorScreen.cs | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 65583919d6..a263bb1e6f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -247,7 +247,7 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestCompletionState() + public void TestPassedState() { loadSpectatingScreen(); @@ -255,8 +255,8 @@ namespace osu.Game.Tests.Visual.Gameplay sendFrames(); waitForPlayer(); - AddStep("send completion", () => spectatorClient.EndPlay(streamingUser.Id, SpectatingUserState.Completed)); - AddUntilStep("state is completed", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Completed); + AddStep("send passed", () => spectatorClient.EndPlay(streamingUser.Id, SpectatingUserState.Passed)); + AddUntilStep("state is passed", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Passed); start(); sendFrames(); diff --git a/osu.Game/Online/Spectator/SpectatingUserState.cs b/osu.Game/Online/Spectator/SpectatingUserState.cs index c7ba4ba248..30be57b739 100644 --- a/osu.Game/Online/Spectator/SpectatingUserState.cs +++ b/osu.Game/Online/Spectator/SpectatingUserState.cs @@ -6,7 +6,7 @@ namespace osu.Game.Online.Spectator public enum SpectatingUserState { /// - /// The spectated user has not yet played. + /// The spectated user is not yet playing. /// Idle, @@ -16,17 +16,17 @@ namespace osu.Game.Online.Spectator Playing, /// - /// The spectated user has successfully completed gameplay. + /// The spectated user has passed gameplay. /// - Completed, + Passed, /// - /// The spectator user has failed during gameplay. + /// The spectator user has failed gameplay. /// Failed, /// - /// The spectated user has quit during gameplay. + /// The spectated user has quit gameplay. /// Quit } diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 9e168411b0..ee5fdc917a 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -186,7 +186,7 @@ namespace osu.Game.Online.Spectator currentBeatmap = null; if (state.HasPassed) - currentState.State = SpectatingUserState.Completed; + currentState.State = SpectatingUserState.Passed; else if (state.HasFailed) currentState.State = SpectatingUserState.Failed; else diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 125e4b261c..60bf87ff61 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -216,7 +216,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate protected override void EndGameplay(int userId, SpectatorState state) { - if (state.State == SpectatingUserState.Completed || state.State == SpectatingUserState.Failed) + if (state.State == SpectatingUserState.Passed || state.State == SpectatingUserState.Failed) return; RemoveUser(userId); diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 7b58f669a0..394afb3a4c 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -134,7 +134,7 @@ namespace osu.Game.Screens.Spectate switch (newState.State) { - case SpectatingUserState.Completed: + case SpectatingUserState.Passed: // Make sure that gameplay completes to the end. if (gameplayStates.TryGetValue(userId, out var gameplayState)) gameplayState.Score.Replay.HasReceivedAllFrames = true; From c1766d8a411f1626255c1d74fbf0b634e7bc4610 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Feb 2022 20:29:53 +0900 Subject: [PATCH 0580/5199] Add paused state --- osu.Game/Online/Spectator/SpectatingUserState.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Online/Spectator/SpectatingUserState.cs b/osu.Game/Online/Spectator/SpectatingUserState.cs index 30be57b739..e00c2e1947 100644 --- a/osu.Game/Online/Spectator/SpectatingUserState.cs +++ b/osu.Game/Online/Spectator/SpectatingUserState.cs @@ -15,6 +15,11 @@ namespace osu.Game.Online.Spectator /// Playing, + /// + /// The spectated user is currently paused. Unused for the time being. + /// + Paused, + /// /// The spectated user has passed gameplay. /// From 79d1d54e336c2cb0eff7399912933eb32f24b19e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Feb 2022 20:35:38 +0900 Subject: [PATCH 0581/5199] Rename parameter to match other usages --- osu.Game/Stores/BeatmapImporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index 2a2786b861..e6b655589c 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -165,7 +165,7 @@ namespace osu.Game.Stores public override bool IsAvailableLocally(BeatmapSetInfo model) { - return Realm.Run(realm => realm.All().Any(b => b.OnlineID == model.OnlineID)); + return Realm.Run(realm => realm.All().Any(s => s.OnlineID == model.OnlineID)); } public override string HumanisedModelName => "beatmap"; From 886d1d2df637dcbac1e3b7147b3062ba576ce178 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Feb 2022 21:20:33 +0900 Subject: [PATCH 0582/5199] Refactorings --- .../Visual/Gameplay/TestSceneSpectator.cs | 3 ++- osu.Game/Online/Spectator/SpectatorClient.cs | 2 +- .../Dashboard/CurrentlyPlayingDisplay.cs | 20 +++++++++---------- .../Spectate/MultiSpectatorScreen.cs | 2 ++ osu.Game/Screens/Play/GameplayState.cs | 2 +- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index a263bb1e6f..e0946df2dc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -160,7 +160,8 @@ namespace osu.Game.Tests.Visual.Gameplay finish(SpectatingUserState.Failed); checkPaused(false); // Should continue playing until out of frames - checkPaused(true); + checkPaused(true); // And eventually stop after running out of frames and fail. + // Todo: Should check for + display a failed message. } [Test] diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index ee5fdc917a..ed6e355ddf 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -98,8 +98,8 @@ namespace osu.Game.Online.Spectator } else { - watchingUserStates.Clear(); playingUsers.Clear(); + watchingUserStates.Clear(); } }), true); } diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index 02ef28f825..117de88166 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -69,17 +69,17 @@ namespace osu.Game.Overlays.Dashboard { var user = task.GetResultSafely(); - if (user != null) - { - Schedule(() => - { - // user may no longer be playing. - if (!playingUsers.Contains(user.Id)) - return; + if (user == null) + return; - userFlow.Add(createUserPanel(user)); - }); - } + Schedule(() => + { + // user may no longer be playing. + if (!playingUsers.Contains(user.Id)) + return; + + userFlow.Add(createUserPanel(user)); + }); }); } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 60bf87ff61..55df8301da 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -216,6 +216,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate protected override void EndGameplay(int userId, SpectatorState state) { + // Allowed passed/failed users to complete their remaining replay frames. + // The failed state isn't really possible in multiplayer (yet?) but is added here just for safety in case it starts being used. if (state.State == SpectatingUserState.Passed || state.State == SpectatingUserState.Failed) return; diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs index 64e873b3bb..c6a072da74 100644 --- a/osu.Game/Screens/Play/GameplayState.cs +++ b/osu.Game/Screens/Play/GameplayState.cs @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Play public bool HasFailed { get; set; } /// - /// Whether the user quit gameplay without either having either passed or failed. + /// Whether the user quit gameplay without having either passed or failed. /// public bool HasQuit { get; set; } From c242a63b1177b4e772fc52dff5b4eb2030bd48b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Feb 2022 10:16:43 +0900 Subject: [PATCH 0583/5199] Fix playlist overlay null reference when attempting an empty selection As reported at https://github.com/ppy/osu/discussions/16829. --- osu.Game/Overlays/Music/PlaylistOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 59ade0918d..ce816f84f0 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -85,7 +85,7 @@ namespace osu.Game.Overlays.Music filter.Search.OnCommit += (sender, newText) => { - list.FirstVisibleSet.PerformRead(set => + list.FirstVisibleSet?.PerformRead(set => { BeatmapInfo toSelect = set.Beatmaps.FirstOrDefault(); From 4966c4e974c9a3e7b98910adeee5b658d6889e22 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 9 Feb 2022 11:51:47 +0900 Subject: [PATCH 0584/5199] Remove redundant parameter --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index e0946df2dc..7235bce789 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -274,7 +274,7 @@ namespace osu.Game.Tests.Visual.Gameplay sendFrames(); waitForPlayer(); - AddStep("send quit", () => spectatorClient.EndPlay(streamingUser.Id, SpectatingUserState.Quit)); + AddStep("send quit", () => spectatorClient.EndPlay(streamingUser.Id)); AddUntilStep("state is quit", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Quit); start(); From ffc4c64f7e7fb0951c9c3f3109497b37f50196b3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 9 Feb 2022 12:09:04 +0900 Subject: [PATCH 0585/5199] Unify namings across the board --- .../Visual/Gameplay/TestSceneSpectator.cs | 22 ++++++------ .../Visual/Gameplay/TestSceneSpectatorHost.cs | 4 +-- ...TestSceneMultiplayerGameplayLeaderboard.cs | 2 +- ...tingUserState.cs => SpectatedUserState.cs} | 4 +-- osu.Game/Online/Spectator/SpectatorClient.cs | 36 +++++++++---------- osu.Game/Online/Spectator/SpectatorState.cs | 2 +- .../Spectate/MultiSpectatorScreen.cs | 2 +- osu.Game/Screens/Spectate/SpectatorScreen.cs | 6 ++-- .../Visual/Spectator/TestSpectatorClient.cs | 4 +-- 9 files changed, 41 insertions(+), 41 deletions(-) rename osu.Game/Online/Spectator/{SpectatingUserState.cs => SpectatedUserState.cs} (90%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 7235bce789..157c248d69 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.Gameplay checkPaused(true); sendFrames(); - finish(SpectatingUserState.Failed); + finish(SpectatedUserState.Failed); checkPaused(false); // Should continue playing until out of frames checkPaused(true); // And eventually stop after running out of frames and fail. @@ -244,7 +244,7 @@ namespace osu.Game.Tests.Visual.Gameplay start(); sendFrames(); waitForPlayer(); - AddUntilStep("state is playing", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Playing); + AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing); } [Test] @@ -256,13 +256,13 @@ namespace osu.Game.Tests.Visual.Gameplay sendFrames(); waitForPlayer(); - AddStep("send passed", () => spectatorClient.EndPlay(streamingUser.Id, SpectatingUserState.Passed)); - AddUntilStep("state is passed", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Passed); + AddStep("send passed", () => spectatorClient.EndPlay(streamingUser.Id, SpectatedUserState.Passed)); + AddUntilStep("state is passed", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Passed); start(); sendFrames(); waitForPlayer(); - AddUntilStep("state is playing", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Playing); + AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing); } [Test] @@ -275,12 +275,12 @@ namespace osu.Game.Tests.Visual.Gameplay waitForPlayer(); AddStep("send quit", () => spectatorClient.EndPlay(streamingUser.Id)); - AddUntilStep("state is quit", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Quit); + AddUntilStep("state is quit", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Quit); start(); sendFrames(); waitForPlayer(); - AddUntilStep("state is playing", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Playing); + AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing); } [Test] @@ -292,13 +292,13 @@ namespace osu.Game.Tests.Visual.Gameplay sendFrames(); waitForPlayer(); - AddStep("send failed", () => spectatorClient.EndPlay(streamingUser.Id, SpectatingUserState.Failed)); - AddUntilStep("state is failed", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Failed); + AddStep("send failed", () => spectatorClient.EndPlay(streamingUser.Id, SpectatedUserState.Failed)); + AddUntilStep("state is failed", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Failed); start(); sendFrames(); waitForPlayer(); - AddUntilStep("state is playing", () => spectatorClient.WatchingUserStates[streamingUser.Id].State == SpectatingUserState.Playing); + AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing); } private OsuFramedReplayInputHandler replayHandler => @@ -313,7 +313,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void start(int? beatmapId = null) => AddStep("start play", () => spectatorClient.StartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId)); - private void finish(SpectatingUserState state = SpectatingUserState.Quit) => AddStep("end play", () => spectatorClient.EndPlay(streamingUser.Id, state)); + private void finish(SpectatedUserState state = SpectatedUserState.Quit) => AddStep("end play", () => spectatorClient.EndPlay(streamingUser.Id, state)); private void checkPaused(bool state) => AddUntilStep($"game is {(state ? "paused" : "playing")}", () => player.ChildrenOfType().First().IsPaused.Value == state); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs index 6d6b0bf89e..034519fbf8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs @@ -37,8 +37,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestClientSendsCorrectRuleset() { - AddUntilStep("spectator client sending frames", () => spectatorClient.WatchingUserStates.ContainsKey(dummy_user_id)); - AddAssert("spectator client sent correct ruleset", () => spectatorClient.WatchingUserStates[dummy_user_id].RulesetID == Ruleset.Value.OnlineID); + AddUntilStep("spectator client sending frames", () => spectatorClient.WatchedUserStates.ContainsKey(dummy_user_id)); + AddAssert("spectator client sent correct ruleset", () => spectatorClient.WatchedUserStates[dummy_user_id].RulesetID == Ruleset.Value.OnlineID); } public override void TearDownSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 55450b36e2..1322fbc96e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -115,7 +115,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void RandomlyUpdateState() { - foreach ((int userId, _) in WatchingUserStates) + foreach ((int userId, _) in WatchedUserStates) { if (RNG.NextBool()) continue; diff --git a/osu.Game/Online/Spectator/SpectatingUserState.cs b/osu.Game/Online/Spectator/SpectatedUserState.cs similarity index 90% rename from osu.Game/Online/Spectator/SpectatingUserState.cs rename to osu.Game/Online/Spectator/SpectatedUserState.cs index e00c2e1947..0f0a3068b8 100644 --- a/osu.Game/Online/Spectator/SpectatingUserState.cs +++ b/osu.Game/Online/Spectator/SpectatedUserState.cs @@ -3,7 +3,7 @@ namespace osu.Game.Online.Spectator { - public enum SpectatingUserState + public enum SpectatedUserState { /// /// The spectated user is not yet playing. @@ -26,7 +26,7 @@ namespace osu.Game.Online.Spectator Passed, /// - /// The spectator user has failed gameplay. + /// The spectated user has failed gameplay. /// Failed, diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index ed6e355ddf..a54ea0d9ee 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -38,7 +38,7 @@ namespace osu.Game.Online.Spectator /// /// The states of all users currently being watched. /// - public IBindableDictionary WatchingUserStates => watchingUserStates; + public IBindableDictionary WatchedUserStates => watchedUserStates; /// /// A global list of all players currently playing. @@ -48,9 +48,9 @@ namespace osu.Game.Online.Spectator /// /// All users currently being watched. /// - private readonly List watchingUsers = new List(); + private readonly List watchedUsers = new List(); - private readonly BindableDictionary watchingUserStates = new BindableDictionary(); + private readonly BindableDictionary watchedUserStates = new BindableDictionary(); private readonly BindableList playingUsers = new BindableList(); private readonly SpectatorState currentState = new SpectatorState(); @@ -85,8 +85,8 @@ namespace osu.Game.Online.Spectator if (connected.NewValue) { // get all the users that were previously being watched - int[] users = watchingUsers.ToArray(); - watchingUsers.Clear(); + int[] users = watchedUsers.ToArray(); + watchedUsers.Clear(); // resubscribe to watched users. foreach (int userId in users) @@ -99,7 +99,7 @@ namespace osu.Game.Online.Spectator else { playingUsers.Clear(); - watchingUserStates.Clear(); + watchedUserStates.Clear(); } }), true); } @@ -111,8 +111,8 @@ namespace osu.Game.Online.Spectator if (!playingUsers.Contains(userId)) playingUsers.Add(userId); - if (watchingUsers.Contains(userId)) - watchingUserStates[userId] = state; + if (watchedUsers.Contains(userId)) + watchedUserStates[userId] = state; OnUserBeganPlaying?.Invoke(userId, state); }); @@ -126,8 +126,8 @@ namespace osu.Game.Online.Spectator { playingUsers.Remove(userId); - if (watchingUsers.Contains(userId)) - watchingUserStates[userId] = state; + if (watchedUsers.Contains(userId)) + watchedUserStates[userId] = state; OnUserFinishedPlaying?.Invoke(userId, state); }); @@ -159,7 +159,7 @@ namespace osu.Game.Online.Spectator currentState.BeatmapID = score.ScoreInfo.BeatmapInfo.OnlineID; currentState.RulesetID = score.ScoreInfo.RulesetID; currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray(); - currentState.State = SpectatingUserState.Playing; + currentState.State = SpectatedUserState.Playing; currentBeatmap = state.Beatmap; currentScore = score; @@ -186,11 +186,11 @@ namespace osu.Game.Online.Spectator currentBeatmap = null; if (state.HasPassed) - currentState.State = SpectatingUserState.Passed; + currentState.State = SpectatedUserState.Passed; else if (state.HasFailed) - currentState.State = SpectatingUserState.Failed; + currentState.State = SpectatedUserState.Failed; else - currentState.State = SpectatingUserState.Quit; + currentState.State = SpectatedUserState.Quit; EndPlayingInternal(currentState); }); @@ -200,10 +200,10 @@ namespace osu.Game.Online.Spectator { Debug.Assert(ThreadSafety.IsUpdateThread); - if (watchingUsers.Contains(userId)) + if (watchedUsers.Contains(userId)) return; - watchingUsers.Add(userId); + watchedUsers.Add(userId); WatchUserInternal(userId); } @@ -214,8 +214,8 @@ namespace osu.Game.Online.Spectator // Todo: This should not be a thing, but requires framework changes. Schedule(() => { - watchingUsers.Remove(userId); - watchingUserStates.Remove(userId); + watchedUsers.Remove(userId); + watchedUserStates.Remove(userId); StopWatchingUserInternal(userId); }); } diff --git a/osu.Game/Online/Spectator/SpectatorState.cs b/osu.Game/Online/Spectator/SpectatorState.cs index fc62f16bba..77686d12da 100644 --- a/osu.Game/Online/Spectator/SpectatorState.cs +++ b/osu.Game/Online/Spectator/SpectatorState.cs @@ -25,7 +25,7 @@ namespace osu.Game.Online.Spectator public IEnumerable Mods { get; set; } = Enumerable.Empty(); [Key(3)] - public SpectatingUserState State { get; set; } + public SpectatedUserState State { get; set; } public bool Equals(SpectatorState other) { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 55df8301da..571b6b4324 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -218,7 +218,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { // Allowed passed/failed users to complete their remaining replay frames. // The failed state isn't really possible in multiplayer (yet?) but is added here just for safety in case it starts being used. - if (state.State == SpectatingUserState.Passed || state.State == SpectatingUserState.Failed) + if (state.State == SpectatedUserState.Passed || state.State == SpectatedUserState.Failed) return; RemoveUser(userId); diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 394afb3a4c..9d0f8abddc 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Spectate userMap[u.Id] = u; } - userStates.BindTo(spectatorClient.WatchingUserStates); + userStates.BindTo(spectatorClient.WatchedUserStates); userStates.BindCollectionChanged(onUserStatesChanged, true); realmSubscription = realm.RegisterForNotifications( @@ -134,13 +134,13 @@ namespace osu.Game.Screens.Spectate switch (newState.State) { - case SpectatingUserState.Passed: + case SpectatedUserState.Passed: // Make sure that gameplay completes to the end. if (gameplayStates.TryGetValue(userId, out var gameplayState)) gameplayState.Score.Replay.HasReceivedAllFrames = true; break; - case SpectatingUserState.Playing: + case SpectatedUserState.Playing: Schedule(() => OnUserStateChanged(userId, newState)); updateGameplayState(userId); break; diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index 6862cda88c..1322a99ea7 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Spectator /// /// The user to end play for. /// The spectator state to end play with. - public void EndPlay(int userId, SpectatingUserState state = SpectatingUserState.Quit) + public void EndPlay(int userId, SpectatedUserState state = SpectatedUserState.Quit) { if (!userBeatmapDictionary.ContainsKey(userId)) return; @@ -144,7 +144,7 @@ namespace osu.Game.Tests.Visual.Spectator { BeatmapID = userBeatmapDictionary[userId], RulesetID = 0, - State = SpectatingUserState.Playing + State = SpectatedUserState.Playing }); } } From 18251c9285a559ac3f4070cdc8538211175e18ab Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 9 Feb 2022 12:20:07 +0900 Subject: [PATCH 0586/5199] Clean up SpectatorScreen based on suggestions --- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 2 +- osu.Game/Screens/Play/SoloSpectator.cs | 2 +- osu.Game/Screens/Spectate/SpectatorScreen.cs | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 571b6b4324..e5eeeb3448 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -207,7 +207,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } } - protected override void OnUserStateChanged(int userId, SpectatorState spectatorState) + protected override void OnNewPlayingUserState(int userId, SpectatorState spectatorState) { } diff --git a/osu.Game/Screens/Play/SoloSpectator.cs b/osu.Game/Screens/Play/SoloSpectator.cs index a710db6d24..a0b07fcbd9 100644 --- a/osu.Game/Screens/Play/SoloSpectator.cs +++ b/osu.Game/Screens/Play/SoloSpectator.cs @@ -166,7 +166,7 @@ namespace osu.Game.Screens.Play automaticDownload.Current.BindValueChanged(_ => checkForAutomaticDownload()); } - protected override void OnUserStateChanged(int userId, SpectatorState spectatorState) + protected override void OnNewPlayingUserState(int userId, SpectatorState spectatorState) { clearDisplay(); showBeatmapPanel(spectatorState); diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 9d0f8abddc..9eb374f0f7 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -103,7 +103,7 @@ namespace osu.Game.Screens.Spectate continue; if (beatmapSet.Beatmaps.Any(b => b.OnlineID == userState.BeatmapID)) - updateGameplayState(userId); + startGameplay(userId); } } @@ -141,8 +141,8 @@ namespace osu.Game.Screens.Spectate break; case SpectatedUserState.Playing: - Schedule(() => OnUserStateChanged(userId, newState)); - updateGameplayState(userId); + Schedule(() => OnNewPlayingUserState(userId, newState)); + startGameplay(userId); break; } } @@ -161,7 +161,7 @@ namespace osu.Game.Screens.Spectate Schedule(() => EndGameplay(userId, state)); } - private void updateGameplayState(int userId) + private void startGameplay(int userId) { Debug.Assert(userMap.ContainsKey(userId)); @@ -195,11 +195,11 @@ namespace osu.Game.Screens.Spectate } /// - /// Invoked when a spectated user's state has changed. + /// Invoked when a spectated user's state has changed to a new state indicating the player is currently playing. /// /// The user whose state has changed. /// The new state. - protected abstract void OnUserStateChanged(int userId, [NotNull] SpectatorState spectatorState); + protected abstract void OnNewPlayingUserState(int userId, [NotNull] SpectatorState spectatorState); /// /// Starts gameplay for a user. From 1b8ada087d97ffdf8b62697a678ddaf8a8c4c654 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 9 Feb 2022 19:25:51 +0800 Subject: [PATCH 0587/5199] Set `NoDefaultExcludes` to true This allows files such as .editorconfig and .gitignore to be included in the nupkg --- Templates/osu.Game.Templates.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/Templates/osu.Game.Templates.csproj b/Templates/osu.Game.Templates.csproj index 31a24a301f..4624d3d771 100644 --- a/Templates/osu.Game.Templates.csproj +++ b/Templates/osu.Game.Templates.csproj @@ -15,6 +15,7 @@ true false content + true From f1c6fdb2afce097de6a636cfff97aef2b337e70f Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 9 Feb 2022 19:53:34 +0800 Subject: [PATCH 0588/5199] Update `.editorconfig`, `.gitignore` and DotSettings Basically just copied from root directory --- .../Rulesets/ruleset-empty/.editorconfig | 20 +-- Templates/Rulesets/ruleset-empty/.gitignore | 128 +++++++++++++----- ...ame.Rulesets.EmptyFreeform.sln.DotSettings | 83 +++++++++++- .../Rulesets/ruleset-example/.editorconfig | 20 +-- Templates/Rulesets/ruleset-example/.gitignore | 128 +++++++++++++----- ...osu.Game.Rulesets.Pippidon.sln.DotSettings | 83 +++++++++++- .../ruleset-scrolling-empty/.editorconfig | 20 +-- .../ruleset-scrolling-empty/.gitignore | 128 +++++++++++++----- ...me.Rulesets.EmptyScrolling.sln.DotSettings | 83 +++++++++++- .../ruleset-scrolling-example/.editorconfig | 20 +-- .../ruleset-scrolling-example/.gitignore | 128 +++++++++++++----- ...osu.Game.Rulesets.Pippidon.sln.DotSettings | 83 +++++++++++- 12 files changed, 696 insertions(+), 228 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/.editorconfig b/Templates/Rulesets/ruleset-empty/.editorconfig index f3badda9b3..b01749195f 100644 --- a/Templates/Rulesets/ruleset-empty/.editorconfig +++ b/Templates/Rulesets/ruleset-empty/.editorconfig @@ -10,14 +10,6 @@ trim_trailing_whitespace = true #Roslyn naming styles -#PascalCase for public and protected members -dotnet_naming_style.pascalcase.capitalization = pascal_case -dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected -dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event -dotnet_naming_rule.public_members_pascalcase.severity = error -dotnet_naming_rule.public_members_pascalcase.symbols = public_members -dotnet_naming_rule.public_members_pascalcase.style = pascalcase - #camelCase for private members dotnet_naming_style.camelcase.capitalization = camel_case @@ -121,7 +113,7 @@ dotnet_style_qualification_for_event = false:warning dotnet_style_predefined_type_for_locals_parameters_members = true:warning dotnet_style_predefined_type_for_member_access = true:warning csharp_style_var_when_type_is_apparent = true:none -csharp_style_var_for_built_in_types = true:none +csharp_style_var_for_built_in_types = false:warning csharp_style_var_elsewhere = true:silent #Style - modifiers @@ -165,7 +157,7 @@ csharp_style_unused_value_assignment_preference = discard_variable:warning #Style - variable declaration csharp_style_inlined_variable_declaration = true:warning -csharp_style_deconstructed_variable_declaration = true:warning +csharp_style_deconstructed_variable_declaration = false:silent #Style - other C# 7.x features dotnet_style_prefer_inferred_tuple_names = true:warning @@ -176,8 +168,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent #Style - C# 8 features csharp_prefer_static_local_function = true:warning csharp_prefer_simple_using_statement = true:silent -csharp_style_prefer_index_operator = true:warning -csharp_style_prefer_range_operator = true:warning +csharp_style_prefer_index_operator = false:silent +csharp_style_prefer_range_operator = false:silent csharp_style_prefer_switch_expression = false:none #Supressing roslyn built-in analyzers @@ -197,4 +189,6 @@ dotnet_diagnostic.IDE0069.severity = none dotnet_diagnostic.CA2225.severity = none # Banned APIs -dotnet_diagnostic.RS0030.severity = error \ No newline at end of file +dotnet_diagnostic.RS0030.severity = error + +dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text. diff --git a/Templates/Rulesets/ruleset-empty/.gitignore b/Templates/Rulesets/ruleset-empty/.gitignore index 940794e60f..5b19270ab9 100644 --- a/Templates/Rulesets/ruleset-empty/.gitignore +++ b/Templates/Rulesets/ruleset-empty/.gitignore @@ -1,7 +1,5 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.suo @@ -17,8 +15,6 @@ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ -x64/ -x86/ bld/ [Bb]in/ [Oo]bj/ @@ -42,11 +38,10 @@ TestResult.xml [Rr]eleasePS/ dlldata.c -# .NET Core +# DNX project.lock.json project.fragment.lock.json artifacts/ -**/Properties/launchSettings.json *_i.c *_p.c @@ -113,10 +108,6 @@ _TeamCity* # DotCover is a Code Coverage Tool *.dotCover -# Visual Studio code coverage results -*.coverage -*.coveragexml - # NCrunch _NCrunch_* .*crunch*.local.xml @@ -166,7 +157,7 @@ PublishScripts/ !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config -# NuGet v3's project.json files produces more ignorable files +# NuGet v3's project.json files produces more ignoreable files *.nuget.props *.nuget.targets @@ -196,10 +187,11 @@ ClientBin/ *~ *.dbmdl *.dbproj.schemaview -*.jfm *.pfx *.publishsettings +node_modules/ orleans.codegen.cs +Resource.designer.cs # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) @@ -219,7 +211,6 @@ UpgradeLog*.htm # SQL Server files *.mdf *.ldf -*.ndf # Business Intelligence projects *.rdl.data @@ -234,10 +225,6 @@ FakesAssemblies/ # Node.js Tools for Visual Studio .ntvs_analysis.dat -node_modules/ - -# Typescript v1 declaration files -typings/ # Visual Studio 6 build log *.plg @@ -245,9 +232,6 @@ typings/ # Visual Studio 6 workspace options file *.opt -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts @@ -263,26 +247,96 @@ paket-files/ # FAKE - F# Make .fake/ -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ - # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config +# Cake # +/tools/** +/build/tools/** +/build/temp/** -# Telerik's JustMock configuration file -*.jmconfig +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +# fastlane +fastlane/report.xml + +# inspectcode +inspectcodereport.xml +inspectcode + +# BenchmarkDotNet +/BenchmarkDotNet.Artifacts + +*.GeneratedMSBuildEditorConfig.editorconfig + +# Fody (pulled in by Realm) - schema file +FodyWeavers.xsd +**/FodyWeavers.xml diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings index aa8f8739c1..2ff0f4d30b 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings @@ -18,9 +18,10 @@ WARNING HINT DO_NOT_SHOW - HINT - WARNING - WARNING + WARNING + WARNING + HINT + HINT WARNING WARNING WARNING @@ -73,6 +74,7 @@ HINT WARNING HINT + DO_NOT_SHOW WARNING DO_NOT_SHOW WARNING @@ -105,8 +107,9 @@ HINT HINT WARNING + DO_NOT_SHOW + DO_NOT_SHOW WARNING - DO_NOT_SHOW WARNING WARNING WARNING @@ -120,6 +123,7 @@ WARNING WARNING HINT + HINT WARNING HINT HINT @@ -129,7 +133,7 @@ HINT WARNING WARNING - HINT + WARNING WARNING WARNING WARNING @@ -204,8 +208,10 @@ HINT WARNING WARNING - DO_NOT_SHOW + SUGGESTION DO_NOT_SHOW + + True DO_NOT_SHOW WARNING WARNING @@ -226,6 +232,7 @@ HINT DO_NOT_SHOW WARNING + WARNING WARNING WARNING WARNING @@ -298,15 +305,21 @@ True 200 CHOP_IF_LONG + UseExplicitType + UseVarWhenEvident + UseVarWhenEvident False False AABB API BPM + EF + FPS GC GL GLSL HID + HSV HTML HUD ID @@ -909,26 +922,82 @@ private void load() { $END$ }; + True True True + True True True True True + True + True + True + True + True + True + True + True True + True + True + True True + True + True + True True True + True + True + True + True + True True True + True + True True True + True + True + True + True + True + True + True + True True True + True + True + True + True + True + True + True + True True + True + True + True True True True + True + True + True + True + True + True True True - True + True + True + True + True + True + True + True + True + True diff --git a/Templates/Rulesets/ruleset-example/.editorconfig b/Templates/Rulesets/ruleset-example/.editorconfig index f3badda9b3..b01749195f 100644 --- a/Templates/Rulesets/ruleset-example/.editorconfig +++ b/Templates/Rulesets/ruleset-example/.editorconfig @@ -10,14 +10,6 @@ trim_trailing_whitespace = true #Roslyn naming styles -#PascalCase for public and protected members -dotnet_naming_style.pascalcase.capitalization = pascal_case -dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected -dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event -dotnet_naming_rule.public_members_pascalcase.severity = error -dotnet_naming_rule.public_members_pascalcase.symbols = public_members -dotnet_naming_rule.public_members_pascalcase.style = pascalcase - #camelCase for private members dotnet_naming_style.camelcase.capitalization = camel_case @@ -121,7 +113,7 @@ dotnet_style_qualification_for_event = false:warning dotnet_style_predefined_type_for_locals_parameters_members = true:warning dotnet_style_predefined_type_for_member_access = true:warning csharp_style_var_when_type_is_apparent = true:none -csharp_style_var_for_built_in_types = true:none +csharp_style_var_for_built_in_types = false:warning csharp_style_var_elsewhere = true:silent #Style - modifiers @@ -165,7 +157,7 @@ csharp_style_unused_value_assignment_preference = discard_variable:warning #Style - variable declaration csharp_style_inlined_variable_declaration = true:warning -csharp_style_deconstructed_variable_declaration = true:warning +csharp_style_deconstructed_variable_declaration = false:silent #Style - other C# 7.x features dotnet_style_prefer_inferred_tuple_names = true:warning @@ -176,8 +168,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent #Style - C# 8 features csharp_prefer_static_local_function = true:warning csharp_prefer_simple_using_statement = true:silent -csharp_style_prefer_index_operator = true:warning -csharp_style_prefer_range_operator = true:warning +csharp_style_prefer_index_operator = false:silent +csharp_style_prefer_range_operator = false:silent csharp_style_prefer_switch_expression = false:none #Supressing roslyn built-in analyzers @@ -197,4 +189,6 @@ dotnet_diagnostic.IDE0069.severity = none dotnet_diagnostic.CA2225.severity = none # Banned APIs -dotnet_diagnostic.RS0030.severity = error \ No newline at end of file +dotnet_diagnostic.RS0030.severity = error + +dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text. diff --git a/Templates/Rulesets/ruleset-example/.gitignore b/Templates/Rulesets/ruleset-example/.gitignore index 940794e60f..5b19270ab9 100644 --- a/Templates/Rulesets/ruleset-example/.gitignore +++ b/Templates/Rulesets/ruleset-example/.gitignore @@ -1,7 +1,5 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.suo @@ -17,8 +15,6 @@ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ -x64/ -x86/ bld/ [Bb]in/ [Oo]bj/ @@ -42,11 +38,10 @@ TestResult.xml [Rr]eleasePS/ dlldata.c -# .NET Core +# DNX project.lock.json project.fragment.lock.json artifacts/ -**/Properties/launchSettings.json *_i.c *_p.c @@ -113,10 +108,6 @@ _TeamCity* # DotCover is a Code Coverage Tool *.dotCover -# Visual Studio code coverage results -*.coverage -*.coveragexml - # NCrunch _NCrunch_* .*crunch*.local.xml @@ -166,7 +157,7 @@ PublishScripts/ !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config -# NuGet v3's project.json files produces more ignorable files +# NuGet v3's project.json files produces more ignoreable files *.nuget.props *.nuget.targets @@ -196,10 +187,11 @@ ClientBin/ *~ *.dbmdl *.dbproj.schemaview -*.jfm *.pfx *.publishsettings +node_modules/ orleans.codegen.cs +Resource.designer.cs # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) @@ -219,7 +211,6 @@ UpgradeLog*.htm # SQL Server files *.mdf *.ldf -*.ndf # Business Intelligence projects *.rdl.data @@ -234,10 +225,6 @@ FakesAssemblies/ # Node.js Tools for Visual Studio .ntvs_analysis.dat -node_modules/ - -# Typescript v1 declaration files -typings/ # Visual Studio 6 build log *.plg @@ -245,9 +232,6 @@ typings/ # Visual Studio 6 workspace options file *.opt -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts @@ -263,26 +247,96 @@ paket-files/ # FAKE - F# Make .fake/ -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ - # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config +# Cake # +/tools/** +/build/tools/** +/build/temp/** -# Telerik's JustMock configuration file -*.jmconfig +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +# fastlane +fastlane/report.xml + +# inspectcode +inspectcodereport.xml +inspectcode + +# BenchmarkDotNet +/BenchmarkDotNet.Artifacts + +*.GeneratedMSBuildEditorConfig.editorconfig + +# Fody (pulled in by Realm) - schema file +FodyWeavers.xsd +**/FodyWeavers.xml diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings index aa8f8739c1..2ff0f4d30b 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings @@ -18,9 +18,10 @@ WARNING HINT DO_NOT_SHOW - HINT - WARNING - WARNING + WARNING + WARNING + HINT + HINT WARNING WARNING WARNING @@ -73,6 +74,7 @@ HINT WARNING HINT + DO_NOT_SHOW WARNING DO_NOT_SHOW WARNING @@ -105,8 +107,9 @@ HINT HINT WARNING + DO_NOT_SHOW + DO_NOT_SHOW WARNING - DO_NOT_SHOW WARNING WARNING WARNING @@ -120,6 +123,7 @@ WARNING WARNING HINT + HINT WARNING HINT HINT @@ -129,7 +133,7 @@ HINT WARNING WARNING - HINT + WARNING WARNING WARNING WARNING @@ -204,8 +208,10 @@ HINT WARNING WARNING - DO_NOT_SHOW + SUGGESTION DO_NOT_SHOW + + True DO_NOT_SHOW WARNING WARNING @@ -226,6 +232,7 @@ HINT DO_NOT_SHOW WARNING + WARNING WARNING WARNING WARNING @@ -298,15 +305,21 @@ True 200 CHOP_IF_LONG + UseExplicitType + UseVarWhenEvident + UseVarWhenEvident False False AABB API BPM + EF + FPS GC GL GLSL HID + HSV HTML HUD ID @@ -909,26 +922,82 @@ private void load() { $END$ }; + True True True + True True True True True + True + True + True + True + True + True + True + True True + True + True + True True + True + True + True True True + True + True + True + True + True True True + True + True True True + True + True + True + True + True + True + True + True True True + True + True + True + True + True + True + True + True True + True + True + True True True True + True + True + True + True + True + True True True - True + True + True + True + True + True + True + True + True + True diff --git a/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig b/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig index f3badda9b3..b01749195f 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig +++ b/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig @@ -10,14 +10,6 @@ trim_trailing_whitespace = true #Roslyn naming styles -#PascalCase for public and protected members -dotnet_naming_style.pascalcase.capitalization = pascal_case -dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected -dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event -dotnet_naming_rule.public_members_pascalcase.severity = error -dotnet_naming_rule.public_members_pascalcase.symbols = public_members -dotnet_naming_rule.public_members_pascalcase.style = pascalcase - #camelCase for private members dotnet_naming_style.camelcase.capitalization = camel_case @@ -121,7 +113,7 @@ dotnet_style_qualification_for_event = false:warning dotnet_style_predefined_type_for_locals_parameters_members = true:warning dotnet_style_predefined_type_for_member_access = true:warning csharp_style_var_when_type_is_apparent = true:none -csharp_style_var_for_built_in_types = true:none +csharp_style_var_for_built_in_types = false:warning csharp_style_var_elsewhere = true:silent #Style - modifiers @@ -165,7 +157,7 @@ csharp_style_unused_value_assignment_preference = discard_variable:warning #Style - variable declaration csharp_style_inlined_variable_declaration = true:warning -csharp_style_deconstructed_variable_declaration = true:warning +csharp_style_deconstructed_variable_declaration = false:silent #Style - other C# 7.x features dotnet_style_prefer_inferred_tuple_names = true:warning @@ -176,8 +168,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent #Style - C# 8 features csharp_prefer_static_local_function = true:warning csharp_prefer_simple_using_statement = true:silent -csharp_style_prefer_index_operator = true:warning -csharp_style_prefer_range_operator = true:warning +csharp_style_prefer_index_operator = false:silent +csharp_style_prefer_range_operator = false:silent csharp_style_prefer_switch_expression = false:none #Supressing roslyn built-in analyzers @@ -197,4 +189,6 @@ dotnet_diagnostic.IDE0069.severity = none dotnet_diagnostic.CA2225.severity = none # Banned APIs -dotnet_diagnostic.RS0030.severity = error \ No newline at end of file +dotnet_diagnostic.RS0030.severity = error + +dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text. diff --git a/Templates/Rulesets/ruleset-scrolling-empty/.gitignore b/Templates/Rulesets/ruleset-scrolling-empty/.gitignore index 940794e60f..5b19270ab9 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/.gitignore +++ b/Templates/Rulesets/ruleset-scrolling-empty/.gitignore @@ -1,7 +1,5 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.suo @@ -17,8 +15,6 @@ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ -x64/ -x86/ bld/ [Bb]in/ [Oo]bj/ @@ -42,11 +38,10 @@ TestResult.xml [Rr]eleasePS/ dlldata.c -# .NET Core +# DNX project.lock.json project.fragment.lock.json artifacts/ -**/Properties/launchSettings.json *_i.c *_p.c @@ -113,10 +108,6 @@ _TeamCity* # DotCover is a Code Coverage Tool *.dotCover -# Visual Studio code coverage results -*.coverage -*.coveragexml - # NCrunch _NCrunch_* .*crunch*.local.xml @@ -166,7 +157,7 @@ PublishScripts/ !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config -# NuGet v3's project.json files produces more ignorable files +# NuGet v3's project.json files produces more ignoreable files *.nuget.props *.nuget.targets @@ -196,10 +187,11 @@ ClientBin/ *~ *.dbmdl *.dbproj.schemaview -*.jfm *.pfx *.publishsettings +node_modules/ orleans.codegen.cs +Resource.designer.cs # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) @@ -219,7 +211,6 @@ UpgradeLog*.htm # SQL Server files *.mdf *.ldf -*.ndf # Business Intelligence projects *.rdl.data @@ -234,10 +225,6 @@ FakesAssemblies/ # Node.js Tools for Visual Studio .ntvs_analysis.dat -node_modules/ - -# Typescript v1 declaration files -typings/ # Visual Studio 6 build log *.plg @@ -245,9 +232,6 @@ typings/ # Visual Studio 6 workspace options file *.opt -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts @@ -263,26 +247,96 @@ paket-files/ # FAKE - F# Make .fake/ -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ - # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config +# Cake # +/tools/** +/build/tools/** +/build/temp/** -# Telerik's JustMock configuration file -*.jmconfig +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +# fastlane +fastlane/report.xml + +# inspectcode +inspectcodereport.xml +inspectcode + +# BenchmarkDotNet +/BenchmarkDotNet.Artifacts + +*.GeneratedMSBuildEditorConfig.editorconfig + +# Fody (pulled in by Realm) - schema file +FodyWeavers.xsd +**/FodyWeavers.xml diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings index aa8f8739c1..2ff0f4d30b 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings @@ -18,9 +18,10 @@ WARNING HINT DO_NOT_SHOW - HINT - WARNING - WARNING + WARNING + WARNING + HINT + HINT WARNING WARNING WARNING @@ -73,6 +74,7 @@ HINT WARNING HINT + DO_NOT_SHOW WARNING DO_NOT_SHOW WARNING @@ -105,8 +107,9 @@ HINT HINT WARNING + DO_NOT_SHOW + DO_NOT_SHOW WARNING - DO_NOT_SHOW WARNING WARNING WARNING @@ -120,6 +123,7 @@ WARNING WARNING HINT + HINT WARNING HINT HINT @@ -129,7 +133,7 @@ HINT WARNING WARNING - HINT + WARNING WARNING WARNING WARNING @@ -204,8 +208,10 @@ HINT WARNING WARNING - DO_NOT_SHOW + SUGGESTION DO_NOT_SHOW + + True DO_NOT_SHOW WARNING WARNING @@ -226,6 +232,7 @@ HINT DO_NOT_SHOW WARNING + WARNING WARNING WARNING WARNING @@ -298,15 +305,21 @@ True 200 CHOP_IF_LONG + UseExplicitType + UseVarWhenEvident + UseVarWhenEvident False False AABB API BPM + EF + FPS GC GL GLSL HID + HSV HTML HUD ID @@ -909,26 +922,82 @@ private void load() { $END$ }; + True True True + True True True True True + True + True + True + True + True + True + True + True True + True + True + True True + True + True + True True True + True + True + True + True + True True True + True + True True True + True + True + True + True + True + True + True + True True True + True + True + True + True + True + True + True + True True + True + True + True True True True + True + True + True + True + True + True True True - True + True + True + True + True + True + True + True + True + True diff --git a/Templates/Rulesets/ruleset-scrolling-example/.editorconfig b/Templates/Rulesets/ruleset-scrolling-example/.editorconfig index f3badda9b3..b01749195f 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/.editorconfig +++ b/Templates/Rulesets/ruleset-scrolling-example/.editorconfig @@ -10,14 +10,6 @@ trim_trailing_whitespace = true #Roslyn naming styles -#PascalCase for public and protected members -dotnet_naming_style.pascalcase.capitalization = pascal_case -dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected -dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event -dotnet_naming_rule.public_members_pascalcase.severity = error -dotnet_naming_rule.public_members_pascalcase.symbols = public_members -dotnet_naming_rule.public_members_pascalcase.style = pascalcase - #camelCase for private members dotnet_naming_style.camelcase.capitalization = camel_case @@ -121,7 +113,7 @@ dotnet_style_qualification_for_event = false:warning dotnet_style_predefined_type_for_locals_parameters_members = true:warning dotnet_style_predefined_type_for_member_access = true:warning csharp_style_var_when_type_is_apparent = true:none -csharp_style_var_for_built_in_types = true:none +csharp_style_var_for_built_in_types = false:warning csharp_style_var_elsewhere = true:silent #Style - modifiers @@ -165,7 +157,7 @@ csharp_style_unused_value_assignment_preference = discard_variable:warning #Style - variable declaration csharp_style_inlined_variable_declaration = true:warning -csharp_style_deconstructed_variable_declaration = true:warning +csharp_style_deconstructed_variable_declaration = false:silent #Style - other C# 7.x features dotnet_style_prefer_inferred_tuple_names = true:warning @@ -176,8 +168,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent #Style - C# 8 features csharp_prefer_static_local_function = true:warning csharp_prefer_simple_using_statement = true:silent -csharp_style_prefer_index_operator = true:warning -csharp_style_prefer_range_operator = true:warning +csharp_style_prefer_index_operator = false:silent +csharp_style_prefer_range_operator = false:silent csharp_style_prefer_switch_expression = false:none #Supressing roslyn built-in analyzers @@ -197,4 +189,6 @@ dotnet_diagnostic.IDE0069.severity = none dotnet_diagnostic.CA2225.severity = none # Banned APIs -dotnet_diagnostic.RS0030.severity = error \ No newline at end of file +dotnet_diagnostic.RS0030.severity = error + +dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text. diff --git a/Templates/Rulesets/ruleset-scrolling-example/.gitignore b/Templates/Rulesets/ruleset-scrolling-example/.gitignore index 940794e60f..5b19270ab9 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/.gitignore +++ b/Templates/Rulesets/ruleset-scrolling-example/.gitignore @@ -1,7 +1,5 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.suo @@ -17,8 +15,6 @@ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ -x64/ -x86/ bld/ [Bb]in/ [Oo]bj/ @@ -42,11 +38,10 @@ TestResult.xml [Rr]eleasePS/ dlldata.c -# .NET Core +# DNX project.lock.json project.fragment.lock.json artifacts/ -**/Properties/launchSettings.json *_i.c *_p.c @@ -113,10 +108,6 @@ _TeamCity* # DotCover is a Code Coverage Tool *.dotCover -# Visual Studio code coverage results -*.coverage -*.coveragexml - # NCrunch _NCrunch_* .*crunch*.local.xml @@ -166,7 +157,7 @@ PublishScripts/ !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config -# NuGet v3's project.json files produces more ignorable files +# NuGet v3's project.json files produces more ignoreable files *.nuget.props *.nuget.targets @@ -196,10 +187,11 @@ ClientBin/ *~ *.dbmdl *.dbproj.schemaview -*.jfm *.pfx *.publishsettings +node_modules/ orleans.codegen.cs +Resource.designer.cs # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) @@ -219,7 +211,6 @@ UpgradeLog*.htm # SQL Server files *.mdf *.ldf -*.ndf # Business Intelligence projects *.rdl.data @@ -234,10 +225,6 @@ FakesAssemblies/ # Node.js Tools for Visual Studio .ntvs_analysis.dat -node_modules/ - -# Typescript v1 declaration files -typings/ # Visual Studio 6 build log *.plg @@ -245,9 +232,6 @@ typings/ # Visual Studio 6 workspace options file *.opt -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts @@ -263,26 +247,96 @@ paket-files/ # FAKE - F# Make .fake/ -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ - # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config +# Cake # +/tools/** +/build/tools/** +/build/temp/** -# Telerik's JustMock configuration file -*.jmconfig +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +# fastlane +fastlane/report.xml + +# inspectcode +inspectcodereport.xml +inspectcode + +# BenchmarkDotNet +/BenchmarkDotNet.Artifacts + +*.GeneratedMSBuildEditorConfig.editorconfig + +# Fody (pulled in by Realm) - schema file +FodyWeavers.xsd +**/FodyWeavers.xml diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings index aa8f8739c1..2ff0f4d30b 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings @@ -18,9 +18,10 @@ WARNING HINT DO_NOT_SHOW - HINT - WARNING - WARNING + WARNING + WARNING + HINT + HINT WARNING WARNING WARNING @@ -73,6 +74,7 @@ HINT WARNING HINT + DO_NOT_SHOW WARNING DO_NOT_SHOW WARNING @@ -105,8 +107,9 @@ HINT HINT WARNING + DO_NOT_SHOW + DO_NOT_SHOW WARNING - DO_NOT_SHOW WARNING WARNING WARNING @@ -120,6 +123,7 @@ WARNING WARNING HINT + HINT WARNING HINT HINT @@ -129,7 +133,7 @@ HINT WARNING WARNING - HINT + WARNING WARNING WARNING WARNING @@ -204,8 +208,10 @@ HINT WARNING WARNING - DO_NOT_SHOW + SUGGESTION DO_NOT_SHOW + + True DO_NOT_SHOW WARNING WARNING @@ -226,6 +232,7 @@ HINT DO_NOT_SHOW WARNING + WARNING WARNING WARNING WARNING @@ -298,15 +305,21 @@ True 200 CHOP_IF_LONG + UseExplicitType + UseVarWhenEvident + UseVarWhenEvident False False AABB API BPM + EF + FPS GC GL GLSL HID + HSV HTML HUD ID @@ -909,26 +922,82 @@ private void load() { $END$ }; + True True True + True True True True True + True + True + True + True + True + True + True + True True + True + True + True True + True + True + True True True + True + True + True + True + True True True + True + True True True + True + True + True + True + True + True + True + True True True + True + True + True + True + True + True + True + True True + True + True + True True True True + True + True + True + True + True + True True True - True + True + True + True + True + True + True + True + True + True From d06d584867e57ee6cb03cc566c1114ada0f47473 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 9 Feb 2022 19:56:32 +0800 Subject: [PATCH 0589/5199] Change assembly titles So that they match the source name specified in template.json and can get replaced --- .../osu.Game.Rulesets.EmptyFreeform.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.csproj | 2 +- .../osu.Game.Rulesets.EmptyScrolling.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj index cfe2bd1cb2..092a013614 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj @@ -1,7 +1,7 @@  netstandard2.1 - osu.Game.Rulesets.Sample + osu.Game.Rulesets.EmptyFreeform Library AnyCPU osu.Game.Rulesets.EmptyFreeform diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj index 61b859f45b..a3607343c9 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj @@ -1,7 +1,7 @@  netstandard2.1 - osu.Game.Rulesets.Sample + osu.Game.Rulesets.Pippidon Library AnyCPU osu.Game.Rulesets.Pippidon diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj index 9dce3c9a0a..2ea52429ab 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj @@ -1,7 +1,7 @@  netstandard2.1 - osu.Game.Rulesets.Sample + osu.Game.Rulesets.EmptyScrolling Library AnyCPU osu.Game.Rulesets.EmptyScrolling diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj index 61b859f45b..a3607343c9 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj @@ -1,7 +1,7 @@  netstandard2.1 - osu.Game.Rulesets.Sample + osu.Game.Rulesets.Pippidon Library AnyCPU osu.Game.Rulesets.Pippidon From 036d17d9fd215423f34ace116203af936ef7428e Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 9 Feb 2022 20:48:14 +0800 Subject: [PATCH 0590/5199] Remove licence headers --- Templates/Rulesets/ruleset-empty/.editorconfig | 2 -- .../osu.Game.Rulesets.EmptyFreeform.sln.DotSettings | 3 --- Templates/Rulesets/ruleset-example/.editorconfig | 2 -- .../ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings | 3 --- Templates/Rulesets/ruleset-scrolling-empty/.editorconfig | 2 -- .../osu.Game.Rulesets.EmptyScrolling.sln.DotSettings | 3 --- Templates/Rulesets/ruleset-scrolling-example/.editorconfig | 2 -- .../osu.Game.Rulesets.Pippidon.sln.DotSettings | 3 --- 8 files changed, 20 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/.editorconfig b/Templates/Rulesets/ruleset-empty/.editorconfig index b01749195f..9c7537de4b 100644 --- a/Templates/Rulesets/ruleset-empty/.editorconfig +++ b/Templates/Rulesets/ruleset-empty/.editorconfig @@ -190,5 +190,3 @@ dotnet_diagnostic.CA2225.severity = none # Banned APIs dotnet_diagnostic.RS0030.severity = error - -dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text. diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings index 2ff0f4d30b..9752e08599 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings @@ -730,9 +730,6 @@ </Group> </TypePattern> </Patterns> - Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. -See the LICENCE file in the repository root for full licence text. - <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> diff --git a/Templates/Rulesets/ruleset-example/.editorconfig b/Templates/Rulesets/ruleset-example/.editorconfig index b01749195f..9c7537de4b 100644 --- a/Templates/Rulesets/ruleset-example/.editorconfig +++ b/Templates/Rulesets/ruleset-example/.editorconfig @@ -190,5 +190,3 @@ dotnet_diagnostic.CA2225.severity = none # Banned APIs dotnet_diagnostic.RS0030.severity = error - -dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text. diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings index 2ff0f4d30b..9752e08599 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings @@ -730,9 +730,6 @@ </Group> </TypePattern> </Patterns> - Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. -See the LICENCE file in the repository root for full licence text. - <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> diff --git a/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig b/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig index b01749195f..9c7537de4b 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig +++ b/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig @@ -190,5 +190,3 @@ dotnet_diagnostic.CA2225.severity = none # Banned APIs dotnet_diagnostic.RS0030.severity = error - -dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text. diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings index 2ff0f4d30b..9752e08599 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings @@ -730,9 +730,6 @@ </Group> </TypePattern> </Patterns> - Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. -See the LICENCE file in the repository root for full licence text. - <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> diff --git a/Templates/Rulesets/ruleset-scrolling-example/.editorconfig b/Templates/Rulesets/ruleset-scrolling-example/.editorconfig index b01749195f..9c7537de4b 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/.editorconfig +++ b/Templates/Rulesets/ruleset-scrolling-example/.editorconfig @@ -190,5 +190,3 @@ dotnet_diagnostic.CA2225.severity = none # Banned APIs dotnet_diagnostic.RS0030.severity = error - -dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text. diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings index 2ff0f4d30b..9752e08599 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings @@ -730,9 +730,6 @@ </Group> </TypePattern> </Patterns> - Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. -See the LICENCE file in the repository root for full licence text. - <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> From f47748591a2deb2604f9556bfbe4bdad754ba96a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Feb 2022 12:11:26 +0900 Subject: [PATCH 0591/5199] Update fastlane to latest release This pulls in the fix from souyuz to allow us to bring things up-to-date again (see https://github.com/voydz/souyuz/pull/36#event-6033249116). Have tested builds locally to work as expected. --- Gemfile.lock | 71 ++++++++++++++++++------------------ fastlane/README.md | 91 +++++++++++++++++++++++++++++++--------------- 2 files changed, 96 insertions(+), 66 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 86c8baabe6..1010027af9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,17 +8,17 @@ GEM artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.551.0) - aws-sdk-core (3.125.5) + aws-partitions (1.553.0) + aws-sdk-core (3.126.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.53.0) - aws-sdk-core (~> 3, >= 3.125.0) + aws-sdk-kms (1.54.0) + aws-sdk-core (~> 3, >= 3.126.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.111.3) - aws-sdk-core (~> 3, >= 3.125.0) + aws-sdk-s3 (1.112.0) + aws-sdk-core (~> 3, >= 3.126.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) aws-sigv4 (1.4.0) @@ -27,8 +27,8 @@ GEM claide (1.1.0) colored (1.2) colored2 (3.1.2) - commander-fastlane (4.4.6) - highline (~> 1.7.2) + commander (4.6.0) + highline (~> 2.0.0) declarative (0.0.20) digest-crc (0.6.4) rake (>= 12.0.0, < 14.0.0) @@ -36,7 +36,7 @@ GEM unf (>= 0.0.5, < 1.0.0) dotenv (2.7.6) emoji_regex (3.2.3) - excon (0.90.0) + excon (0.91.0) faraday (1.9.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -66,15 +66,15 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.2.6) - fastlane (2.181.0) + fastlane (2.204.2) CFPropertyList (>= 2.3, < 4.0.0) - addressable (>= 2.3, < 3.0.0) + addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) aws-sdk-s3 (~> 1.0) babosa (>= 1.0.3, < 2.0.0) bundler (>= 1.12.0, < 3.0.0) colored - commander-fastlane (>= 4.4.6, < 5.0.0) + commander (~> 4.6) dotenv (>= 2.1.1, < 3.0.0) emoji_regex (>= 0.1, < 4.0) excon (>= 0.71.0, < 1.0.0) @@ -83,19 +83,20 @@ GEM faraday_middleware (~> 1.0) fastimage (>= 2.1.0, < 3.0.0) gh_inspector (>= 1.1.2, < 2.0.0) - google-api-client (>= 0.37.0, < 0.39.0) - google-cloud-storage (>= 1.15.0, < 2.0.0) - highline (>= 1.7.2, < 2.0.0) + google-apis-androidpublisher_v3 (~> 0.3) + google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-storage (~> 1.31) + highline (~> 2.0) json (< 3.0.0) jwt (>= 2.1.0, < 3) mini_magick (>= 4.9.4, < 5.0.0) multipart-post (~> 2.0.0) naturally (~> 2.2) + optparse (~> 0.1.1) plist (>= 3.1.0, < 4.0.0) rubyzip (>= 2.0.0, < 3.0.0) security (= 0.1.3) simctl (~> 1.6.3) - slack-notifier (>= 2.0.0, < 3.0.0) terminal-notifier (>= 2.0.0, < 3.0.0) terminal-table (>= 1.4.5, < 2.0.0) tty-screen (>= 0.6.3, < 1.0.0) @@ -105,18 +106,12 @@ GEM xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) fastlane-plugin-clean_testflight_testers (0.3.0) - fastlane-plugin-souyuz (0.9.1) - souyuz (= 0.9.1) + fastlane-plugin-souyuz (0.11.1) + souyuz (= 0.11.1) fastlane-plugin-xamarin (0.6.3) gh_inspector (1.1.3) - google-api-client (0.38.0) - addressable (~> 2.5, >= 2.5.1) - googleauth (~> 0.9) - httpclient (>= 2.8.1, < 3.0) - mini_mime (~> 1.0) - representable (~> 3.0) - retriable (>= 2.0, < 4.0) - signet (~> 0.12) + google-apis-androidpublisher_v3 (0.16.0) + google-apis-core (>= 0.4, < 2.a) google-apis-core (0.4.2) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) @@ -128,6 +123,8 @@ GEM webrick google-apis-iamcredentials_v1 (0.10.0) google-apis-core (>= 0.4, < 2.a) + google-apis-playcustomapp_v1 (0.7.0) + google-apis-core (>= 0.4, < 2.a) google-apis-storage_v1 (0.11.0) google-apis-core (>= 0.4, < 2.a) google-cloud-core (1.6.0) @@ -144,14 +141,14 @@ GEM google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (0.17.1) + googleauth (1.1.0) faraday (>= 0.17.3, < 2.0) jwt (>= 1.4, < 3.0) memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) - signet (~> 0.15) - highline (1.7.10) + signet (>= 0.16, < 2.a) + highline (2.0.3) http-cookie (1.0.4) domain_name (~> 0.5) httpclient (2.8.3) @@ -161,16 +158,19 @@ GEM memoist (0.16.2) mini_magick (4.11.0) mini_mime (1.1.2) - mini_portile2 (2.4.0) + mini_portile2 (2.7.1) multi_json (1.15.0) multipart-post (2.0.0) nanaimo (0.3.0) naturally (2.2.1) - nokogiri (1.10.10) - mini_portile2 (~> 2.4.0) + nokogiri (1.13.1) + mini_portile2 (~> 2.7.0) + racc (~> 1.4) + optparse (0.1.1) os (1.1.4) plist (3.6.0) public_suffix (4.0.6) + racc (1.6.0) rake (13.0.6) representable (3.1.1) declarative (< 0.1.0) @@ -190,10 +190,9 @@ GEM simctl (1.6.8) CFPropertyList naturally - slack-notifier (2.4.0) - souyuz (0.9.1) - fastlane (>= 1.103.0) - highline (~> 1.7) + souyuz (0.11.1) + fastlane (>= 2.182.0) + highline (~> 2.0) nokogiri (~> 1.7) terminal-notifier (2.0.0) terminal-table (1.8.0) diff --git a/fastlane/README.md b/fastlane/README.md index 8273fdaa5d..9d5e11f7cb 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -1,78 +1,109 @@ fastlane documentation -================ +---- + # Installation Make sure you have the latest version of the Xcode command line tools installed: -``` +```sh xcode-select --install ``` -Install _fastlane_ using -``` -[sudo] gem install fastlane -NV -``` -or alternatively using `brew install fastlane` +For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane) # Available Actions + ## Android + ### android beta + +```sh +[bundle exec] fastlane android beta ``` -fastlane android beta -``` + Deploy to play store + ### android build_github + +```sh +[bundle exec] fastlane android build_github ``` -fastlane android build_github -``` + Deploy to github release + ### android build + +```sh +[bundle exec] fastlane android build ``` -fastlane android build -``` + Compile the project + ### android update_version + +```sh +[bundle exec] fastlane android update_version ``` -fastlane android update_version -``` + ---- + ## iOS + ### ios beta + +```sh +[bundle exec] fastlane ios beta ``` -fastlane ios beta -``` + Deploy to testflight + ### ios build + +```sh +[bundle exec] fastlane ios build ``` -fastlane ios build -``` + Compile the project + ### ios provision + +```sh +[bundle exec] fastlane ios provision ``` -fastlane ios provision -``` + Install provisioning profiles using match + ### ios update_version + +```sh +[bundle exec] fastlane ios update_version ``` -fastlane ios update_version -``` + + ### ios testflight_prune_dry -``` -fastlane ios testflight_prune_dry + +```sh +[bundle exec] fastlane ios testflight_prune_dry ``` + + ### ios testflight_prune + +```sh +[bundle exec] fastlane ios testflight_prune ``` -fastlane ios testflight_prune -``` + ---- -This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. -More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). -The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). +This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. + +More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools). + +The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools). From a3896a8ebdc87b95c9d97a1f5112c65bafe761b9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 10 Feb 2022 14:18:29 +0900 Subject: [PATCH 0592/5199] Remove allowance of null dependency --- osu.Game/Rulesets/UI/ReplayRecorder.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index 277040b2a6..0a94b55221 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.UI public int RecordFrameRate = 60; - [Resolved(canBeNull: true)] + [Resolved] private SpectatorClient spectatorClient { get; set; } [Resolved] @@ -48,8 +48,7 @@ namespace osu.Game.Rulesets.UI base.LoadComplete(); inputManager = GetContainingInputManager(); - - spectatorClient?.BeginPlaying(gameplayState, target); + spectatorClient.BeginPlaying(gameplayState, target); } protected override void Dispose(bool isDisposing) From f7fb7825cc322c3ac08a0c310928591ed96e3142 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 10 Feb 2022 14:21:33 +0900 Subject: [PATCH 0593/5199] Simplify disposal --- osu.Game/Rulesets/UI/ReplayRecorder.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index 0a94b55221..6843beef7b 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -54,9 +55,7 @@ namespace osu.Game.Rulesets.UI protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - - if (spectatorClient != null && gameplayState != null) - spectatorClient.EndPlaying(gameplayState); + spectatorClient?.EndPlaying(gameplayState); } protected override void Update() From ebd105422fd6c2903bced01cb820d7e0d542789a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 10 Feb 2022 14:22:08 +0900 Subject: [PATCH 0594/5199] Remove unused using --- osu.Game/Rulesets/UI/ReplayRecorder.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index 6843beef7b..dcd8f12028 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; From 88bb9d4237c62d37678d8e0e110438ea247b7ae5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Feb 2022 15:50:03 +0900 Subject: [PATCH 0595/5199] Fix migration errors not outputting the call stack to logs --- .../Settings/Sections/Maintenance/MigrationRunScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs index b0b61554eb..adb347e7b8 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs @@ -88,7 +88,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance .ContinueWith(t => { if (t.IsFaulted) - Logger.Log($"Error during migration: {t.Exception?.Message}", level: LogLevel.Error); + Logger.Error(t.Exception, $"Error during migration: {t.Exception?.Message}"); Schedule(this.Exit); }); From 176bb4a4e22901a37761ab6e1812663d3ec6c7dd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Feb 2022 15:25:54 +0900 Subject: [PATCH 0596/5199] Update desktop projects to target .NET 6 --- .run/osu! (Second Client).run.xml | 8 ++++---- .../osu.Game.Rulesets.EmptyFreeform.Tests.csproj | 4 ++-- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 4 ++-- .../osu.Game.Rulesets.EmptyScrolling.Tests.csproj | 4 ++-- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 4 ++-- osu.Desktop/osu.Desktop.csproj | 2 +- osu.Game.Benchmarks/osu.Game.Benchmarks.csproj | 2 +- .../osu.Game.Rulesets.Catch.Tests.csproj | 4 ++-- .../osu.Game.Rulesets.Mania.Tests.csproj | 4 ++-- .../osu.Game.Rulesets.Osu.Tests.csproj | 4 ++-- .../osu.Game.Rulesets.Taiko.Tests.csproj | 4 ++-- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- .../osu.Game.Tournament.Tests.csproj | 4 ++-- osu.iOS.props | 2 +- 14 files changed, 26 insertions(+), 26 deletions(-) diff --git a/.run/osu! (Second Client).run.xml b/.run/osu! (Second Client).run.xml index 599b4b986b..9a471df902 100644 --- a/.run/osu! (Second Client).run.xml +++ b/.run/osu! (Second Client).run.xml @@ -1,8 +1,8 @@ - - \ No newline at end of file + diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index 3c6aaa39ca..cb922c5a58 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -20,7 +20,7 @@ WinExe - net5.0 + net6.0 osu.Game.Rulesets.EmptyFreeform.Tests - \ No newline at end of file + diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 0719dd30df..5ecd9cc675 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -20,7 +20,7 @@ WinExe - net5.0 + net6.0 osu.Game.Rulesets.Pippidon.Tests - \ No newline at end of file + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index d0db43cc81..33ad0ac4f7 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -20,7 +20,7 @@ WinExe - net5.0 + net6.0 osu.Game.Rulesets.EmptyScrolling.Tests - \ No newline at end of file + diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 0719dd30df..5ecd9cc675 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -20,7 +20,7 @@ WinExe - net5.0 + net6.0 osu.Game.Rulesets.Pippidon.Tests - \ No newline at end of file + diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 89b9ffb94b..5e203af1f2 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -1,6 +1,6 @@  - net5.0 + net6.0 WinExe true A free-to-win rhythm game. Rhythm is just a *click* away! diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index 57b914bee6..434c0e0367 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -1,7 +1,7 @@ - net5.0 + net6.0 Exe false diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 13f2e25f05..fc6d900567 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -9,9 +9,9 @@ WinExe - net5.0 + net6.0 - \ No newline at end of file + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index d51a6da4f9..ddad2adfea 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -9,9 +9,9 @@ WinExe - net5.0 + net6.0 - \ No newline at end of file + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index fea2e408f6..bd4c3d3345 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -10,9 +10,9 @@ WinExe - net5.0 + net6.0 - \ No newline at end of file + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index ad3713e047..a6b8eb8651 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -9,9 +9,9 @@ WinExe - net5.0 + net6.0 - \ No newline at end of file + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 3b115d43e5..acf1e8470b 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -12,7 +12,7 @@ WinExe - net5.0 + net6.0 tests.ruleset diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 130fcfaca1..c7314a4969 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -11,7 +11,7 @@ WinExe - net5.0 + net6.0 @@ -20,4 +20,4 @@ - \ No newline at end of file + diff --git a/osu.iOS.props b/osu.iOS.props index 5978f6d685..7e5ab37257 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -63,7 +63,7 @@ - + $(NoWarn);NU1605 From 44f2d8a4481d686aea94d6d480cfc0e17ec7351b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Feb 2022 18:48:37 +0900 Subject: [PATCH 0597/5199] Allow game folder migration to fail gracefully when cleanup cannot completely succeed --- osu.Game.Tournament/IO/TournamentStorage.cs | 4 ++- osu.Game/IO/MigratableStorage.cs | 30 +++++++++++----- osu.Game/IO/OsuStorage.cs | 7 ++-- osu.Game/OsuGameBase.cs | 9 ++--- .../Maintenance/MigrationRunScreen.cs | 36 ++++++++++++++++--- 5 files changed, 66 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 347d368a04..b4859d0c91 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tournament.IO public IEnumerable ListTournaments() => AllTournaments.GetDirectories(string.Empty); - public override void Migrate(Storage newStorage) + public override bool Migrate(Storage newStorage) { // this migration only happens once on moving to the per-tournament storage system. // listed files are those known at that point in time. @@ -94,6 +94,8 @@ namespace osu.Game.Tournament.IO ChangeTargetStorage(newStorage); storageConfig.SetValue(StorageConfig.CurrentTournament, default_tournament); storageConfig.Save(); + + return true; } private void moveFileIfExists(string file, DirectoryInfo destination) diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 1b76725b04..e478144294 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -33,7 +33,8 @@ namespace osu.Game.IO /// A general purpose migration method to move the storage to a different location. /// The target storage of the migration. /// - public virtual void Migrate(Storage newStorage) + /// Whether cleanup could complete. + public virtual bool Migrate(Storage newStorage) { var source = new DirectoryInfo(GetFullPath(".")); var destination = new DirectoryInfo(newStorage.GetFullPath(".")); @@ -57,17 +58,20 @@ namespace osu.Game.IO CopyRecursive(source, destination); ChangeTargetStorage(newStorage); - DeleteRecursive(source); + + return DeleteRecursive(source); } - protected void DeleteRecursive(DirectoryInfo target, bool topLevelExcludes = true) + protected bool DeleteRecursive(DirectoryInfo target, bool topLevelExcludes = true) { + bool allFilesDeleted = true; + foreach (System.IO.FileInfo fi in target.GetFiles()) { if (topLevelExcludes && IgnoreFiles.Contains(fi.Name)) continue; - AttemptOperation(() => fi.Delete()); + allFilesDeleted &= AttemptOperation(() => fi.Delete(), throwOnFailure: false); } foreach (DirectoryInfo dir in target.GetDirectories()) @@ -75,11 +79,13 @@ namespace osu.Game.IO if (topLevelExcludes && IgnoreDirectories.Contains(dir.Name)) continue; - AttemptOperation(() => dir.Delete(true)); + allFilesDeleted &= AttemptOperation(() => dir.Delete(true), throwOnFailure: false); } if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0) - AttemptOperation(target.Delete); + allFilesDeleted &= AttemptOperation(target.Delete, throwOnFailure: false); + + return allFilesDeleted; } protected void CopyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true) @@ -110,19 +116,25 @@ namespace osu.Game.IO /// /// The action to perform. /// The number of attempts (250ms wait between each). - protected static void AttemptOperation(Action action, int attempts = 10) + /// Whether to throw an exception on failure. If false, will silently fail. + protected static bool AttemptOperation(Action action, int attempts = 10, bool throwOnFailure = true) { while (true) { try { action(); - return; + return true; } catch (Exception) { if (attempts-- == 0) - throw; + { + if (throwOnFailure) + throw; + + return false; + } } Thread.Sleep(250); diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 802c71e363..6e7cb545e3 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -113,11 +113,14 @@ namespace osu.Game.IO } } - public override void Migrate(Storage newStorage) + public override bool Migrate(Storage newStorage) { - base.Migrate(newStorage); + bool cleanupSucceeded = base.Migrate(newStorage); + storageConfig.SetValue(StorageConfig.FullPath, newStorage.GetFullPath(".")); storageConfig.Save(); + + return cleanupSucceeded; } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 5b2eb5607a..0b2644d5ba 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -413,7 +413,7 @@ namespace osu.Game Scheduler.AddDelayed(GracefullyExit, 2000); } - public void Migrate(string path) + public bool Migrate(string path) { Logger.Log($@"Migrating osu! data from ""{Storage.GetFullPath(string.Empty)}"" to ""{path}""..."); @@ -432,14 +432,15 @@ namespace osu.Game readyToRun.Wait(); - (Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); + bool? cleanupSucceded = (Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); + + Logger.Log(@"Migration complete!"); + return cleanupSucceded != false; } finally { realmBlocker?.Dispose(); } - - Logger.Log(@"Migration complete!"); } protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager(); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs index adb347e7b8..fb7ff0dbd1 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs @@ -4,13 +4,16 @@ using System.IO; using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; +using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Notifications; using osu.Game.Screens; using osuTK; @@ -23,6 +26,15 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance [Resolved(canBeNull: true)] private OsuGame game { get; set; } + [Resolved] + private NotificationOverlay notifications { get; set; } + + [Resolved] + private Storage storage { get; set; } + + [Resolved] + private GameHost host { get; set; } + public override bool AllowBackButton => false; public override bool AllowExternalScreenChange => false; @@ -84,17 +96,33 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Beatmap.Value = Beatmap.Default; + var originalStorage = new NativeStorage(storage.GetFullPath(string.Empty), host); + migrationTask = Task.Run(PerformMigration) - .ContinueWith(t => + .ContinueWith(task => { - if (t.IsFaulted) - Logger.Error(t.Exception, $"Error during migration: {t.Exception?.Message}"); + if (task.IsFaulted) + { + Logger.Error(task.Exception, $"Error during migration: {task.Exception?.Message}"); + } + else if (!task.GetResultSafely()) + { + notifications.Post(new SimpleNotification + { + Text = "Some files couldn't be cleaned up during migration. Clicking this notification will open the folder so you can manually clean things up.", + Activated = () => + { + originalStorage.PresentExternally(); + return true; + } + }); + } Schedule(this.Exit); }); } - protected virtual void PerformMigration() => game?.Migrate(destination.FullName); + protected virtual bool PerformMigration() => game?.Migrate(destination.FullName) != false; public override void OnEntering(IScreen last) { From 19cb8cb03a100c9baa80c23b7b307ff300130df2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Feb 2022 19:21:17 +0900 Subject: [PATCH 0598/5199] Update tests --- .../Settings/TestSceneMigrationScreens.cs | 51 ++++++++++++++++--- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneMigrationScreens.cs b/osu.Game.Tests/Visual/Settings/TestSceneMigrationScreens.cs index 2883e54385..a68090504d 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneMigrationScreens.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneMigrationScreens.cs @@ -3,32 +3,69 @@ using System.IO; using System.Threading; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Screens; +using osu.Game.Overlays; using osu.Game.Overlays.Settings.Sections.Maintenance; namespace osu.Game.Tests.Visual.Settings { public class TestSceneMigrationScreens : ScreenTestScene { + [Cached] + private readonly NotificationOverlay notifications; + public TestSceneMigrationScreens() { - AddStep("Push screen", () => Stack.Push(new TestMigrationSelectScreen())); + Children = new Drawable[] + { + notifications = new NotificationOverlay + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + } + }; + } + + [Test] + public void TestDeleteSuccess() + { + AddStep("Push screen", () => Stack.Push(new TestMigrationSelectScreen(true))); + } + + [Test] + public void TestDeleteFails() + { + AddStep("Push screen", () => Stack.Push(new TestMigrationSelectScreen(false))); } private class TestMigrationSelectScreen : MigrationSelectScreen { - protected override void BeginMigration(DirectoryInfo target) => this.Push(new TestMigrationRunScreen()); + private readonly bool deleteSuccess; + + public TestMigrationSelectScreen(bool deleteSuccess) + { + this.deleteSuccess = deleteSuccess; + } + + protected override void BeginMigration(DirectoryInfo target) => this.Push(new TestMigrationRunScreen(deleteSuccess)); private class TestMigrationRunScreen : MigrationRunScreen { - protected override void PerformMigration() - { - Thread.Sleep(3000); - } + private readonly bool success; - public TestMigrationRunScreen() + public TestMigrationRunScreen(bool success) : base(null) { + this.success = success; + } + + protected override bool PerformMigration() + { + Thread.Sleep(3000); + return success; } } } From 2939bc4644b2b5079a5af5ffa578288676cf0ca2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Feb 2022 01:49:52 +0900 Subject: [PATCH 0599/5199] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 71525a7acb..147f576c55 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c8f634284b..dd10807ec2 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 5978f6d685..6fbc468586 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + From 9cd88ec2b84d8460af461bd91deec5fc355ddced Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Feb 2022 21:23:38 +0300 Subject: [PATCH 0600/5199] Update API models with score pinning changes --- osu.Game/Online/API/Requests/GetUserScoresRequest.cs | 3 ++- osu.Game/Online/API/Requests/Responses/APIUser.cs | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs index 653abf7427..5d39799f6b 100644 --- a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs @@ -39,6 +39,7 @@ namespace osu.Game.Online.API.Requests { Best, Firsts, - Recent + Recent, + Pinned } } diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index e4a432b074..2b64e5de06 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -151,6 +151,9 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"scores_recent_count")] public int ScoresRecentCount; + [JsonProperty(@"scores_pinned_count")] + public int ScoresPinnedCount; + [JsonProperty(@"beatmap_playcounts_count")] public int BeatmapPlayCountsCount; From 4f7003928acb9a14a12adc02c343cb9329181ec5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Feb 2022 21:32:07 +0300 Subject: [PATCH 0601/5199] Add score container for pinned scores in ranks section --- .../Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs | 3 +++ osu.Game/Overlays/Profile/Sections/RanksSection.cs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 5532e35cc5..5c67da1911 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -46,6 +46,9 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks case ScoreType.Recent: return user.ScoresRecentCount; + case ScoreType.Pinned: + return user.ScoresPinnedCount; + default: return 0; } diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index 00a68d5bf9..02d8bd8c52 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -18,6 +18,8 @@ namespace osu.Game.Overlays.Profile.Sections { Children = new[] { + // todo: update to use UsersStrings.ShowExtraTopRanksPinnedTitle once that exists. + new PaginatedScoreContainer(ScoreType.Pinned, User, "Pinned Scores"), new PaginatedScoreContainer(ScoreType.Best, User, UsersStrings.ShowExtraTopRanksBestTitle), new PaginatedScoreContainer(ScoreType.Firsts, User, UsersStrings.ShowExtraTopRanksFirstTitle) }; From 9574bc13820665f5e6721915b3c05367ac9dccb7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Feb 2022 04:00:40 +0300 Subject: [PATCH 0602/5199] Allow `IRulesetInfo`s of same type to be comparable At first I was planning on making `CompareTo` implemented at `IRulesetInfo` itself and shared across classes, but turns out it only implements it explicitly and not allow direct `IRulesetInfo.Equals` calls. It messed with my head enough that I decided to just let each class have its own implementation and only allow same type. --- osu.Game/Rulesets/IRulesetInfo.cs | 2 +- osu.Game/Rulesets/RulesetInfo.cs | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/IRulesetInfo.cs b/osu.Game/Rulesets/IRulesetInfo.cs index 44731a2495..60a02212fc 100644 --- a/osu.Game/Rulesets/IRulesetInfo.cs +++ b/osu.Game/Rulesets/IRulesetInfo.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets /// /// A representation of a ruleset's metadata. /// - public interface IRulesetInfo : IHasOnlineID, IEquatable, IComparable + public interface IRulesetInfo : IHasOnlineID, IEquatable, IComparable { /// /// The user-exposed name of this ruleset. diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 0a0941d1ff..88e3988431 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets { [ExcludeFromDynamicCompile] [MapTo("Ruleset")] - public class RulesetInfo : RealmObject, IEquatable, IRulesetInfo + public class RulesetInfo : RealmObject, IEquatable, IComparable, IRulesetInfo { [PrimaryKey] public string ShortName { get; set; } = string.Empty; @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets return ShortName == other.ShortName; } - public bool Equals(IRulesetInfo? other) => other is RulesetInfo b && Equals(b); + public bool Equals(IRulesetInfo? other) => other is RulesetInfo r && Equals(r); public int CompareTo(RulesetInfo other) { @@ -63,6 +63,14 @@ namespace osu.Game.Rulesets return string.Compare(ShortName, other.ShortName, StringComparison.Ordinal); } + public int CompareTo(IRulesetInfo other) + { + if (!(other is RulesetInfo ruleset)) + throw new ArgumentException($@"Object is not of type {nameof(RulesetInfo)}.", nameof(other)); + + return CompareTo(ruleset); + } + public override int GetHashCode() { // Importantly, ignore the underlying realm hash code, as it will usually not match. From 1b729e891d53e8134efcd5ef8f09d5655c5bf20f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Feb 2022 04:01:10 +0300 Subject: [PATCH 0603/5199] Update pointless `CompareTo` implementation once again --- osu.Game/Rulesets/EFRulesetInfo.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/EFRulesetInfo.cs b/osu.Game/Rulesets/EFRulesetInfo.cs index ba56adac49..4174aa773c 100644 --- a/osu.Game/Rulesets/EFRulesetInfo.cs +++ b/osu.Game/Rulesets/EFRulesetInfo.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets { [ExcludeFromDynamicCompile] [Table(@"RulesetInfo")] - public sealed class EFRulesetInfo : IEquatable, IRulesetInfo + public sealed class EFRulesetInfo : IEquatable, IComparable, IRulesetInfo { public int? ID { get; set; } @@ -42,7 +42,15 @@ namespace osu.Game.Rulesets public bool Equals(EFRulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo; - public int CompareTo(RulesetInfo other) => OnlineID.CompareTo(other.OnlineID); + public int CompareTo(EFRulesetInfo other) => OnlineID.CompareTo(other.OnlineID); + + public int CompareTo(IRulesetInfo other) + { + if (!(other is EFRulesetInfo ruleset)) + throw new ArgumentException($@"Object is not of type {nameof(EFRulesetInfo)}.", nameof(other)); + + return CompareTo(ruleset); + } public override bool Equals(object obj) => obj is EFRulesetInfo rulesetInfo && Equals(rulesetInfo); From 26839f6ad8f3c49c3d1c63016b3af47e9afd2602 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Feb 2022 04:17:24 +0300 Subject: [PATCH 0604/5199] Consider `OnlineID`s during ruleset equality if available Required for `APIBeatmap`s, which provide `Ruleset` instances with `OnlineID` available only. Also consistent with the comparer implementation. --- osu.Game/Rulesets/RulesetInfo.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 88e3988431..ba7c8d191d 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -44,6 +44,9 @@ namespace osu.Game.Rulesets if (ReferenceEquals(this, other)) return true; if (other == null) return false; + if (OnlineID >= 0 && other.OnlineID >= 0) + return OnlineID == other.OnlineID; + return ShortName == other.ShortName; } From 6f0e32826c6d90c92c1130cd7aa07894e3b7f38f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Feb 2022 04:06:28 +0300 Subject: [PATCH 0605/5199] Standardise ordering/grouping of `IRulesetInfo`/`RulesetInfo`s --- .../Beatmaps/Drawables/Cards/BeatmapCardDifficultyList.cs | 2 +- osu.Game/Beatmaps/Drawables/DifficultySpectrumDisplay.cs | 6 ++---- osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Screens/Select/Carousel/SetPanelContent.cs | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDifficultyList.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDifficultyList.cs index 7753d8480a..eeb86f4702 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDifficultyList.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDifficultyList.cs @@ -33,7 +33,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards bool firstGroup = true; - foreach (var group in beatmapSetInfo.Beatmaps.GroupBy(beatmap => beatmap.Ruleset.OnlineID).OrderBy(group => group.Key)) + foreach (var group in beatmapSetInfo.Beatmaps.GroupBy(beatmap => beatmap.Ruleset).OrderBy(group => group.Key)) { if (!firstGroup) { diff --git a/osu.Game/Beatmaps/Drawables/DifficultySpectrumDisplay.cs b/osu.Game/Beatmaps/Drawables/DifficultySpectrumDisplay.cs index 5b211084ab..5b467d67e2 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultySpectrumDisplay.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultySpectrumDisplay.cs @@ -62,10 +62,8 @@ namespace osu.Game.Beatmaps.Drawables // matching web: https://github.com/ppy/osu-web/blob/d06d8c5e735eb1f48799b1654b528e9a7afb0a35/resources/assets/lib/beatmapset-panel.tsx#L127 bool collapsed = beatmapSet.Beatmaps.Count() > 12; - foreach (var rulesetGrouping in beatmapSet.Beatmaps.GroupBy(beatmap => beatmap.Ruleset.OnlineID).OrderBy(group => group.Key)) - { - flow.Add(new RulesetDifficultyGroup(rulesetGrouping.Key, rulesetGrouping, collapsed)); - } + foreach (var rulesetGrouping in beatmapSet.Beatmaps.GroupBy(beatmap => beatmap.Ruleset).OrderBy(group => group.Key)) + flow.Add(new RulesetDifficultyGroup(rulesetGrouping.Key.OnlineID, rulesetGrouping, collapsed)); } protected override void LoadComplete() diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 5503a62ba2..2aec63fa65 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -851,7 +851,7 @@ namespace osu.Game.Screens.Edit var difficultyItems = new List(); - foreach (var rulesetBeatmaps in beatmapSet.Beatmaps.GroupBy(b => b.Ruleset.ShortName).OrderBy(group => group.Key)) + foreach (var rulesetBeatmaps in beatmapSet.Beatmaps.GroupBy(b => b.Ruleset).OrderBy(group => group.Key)) { if (difficultyItems.Count > 0) difficultyItems.Add(new EditorMenuItemSpacer()); diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index 82523c9d9d..760915b528 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -87,7 +87,7 @@ namespace osu.Game.Screens.Select.Carousel var beatmaps = carouselSet.Beatmaps.ToList(); return beatmaps.Count > maximum_difficulty_icons - ? (IEnumerable)beatmaps.GroupBy(b => b.BeatmapInfo.Ruleset.ShortName) + ? (IEnumerable)beatmaps.GroupBy(b => b.BeatmapInfo.Ruleset) .Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Last().BeatmapInfo.Ruleset)) : beatmaps.Select(b => new FilterableDifficultyIcon(b)); } From c29cc78853f9cfb5efc4e923c7d9fca6fda8fbf7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Feb 2022 04:44:54 +0300 Subject: [PATCH 0606/5199] Fix failing test case --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index b429619044..9083415a78 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -457,10 +457,12 @@ namespace osu.Game.Tests.Visual.UserInterface public override ModType Type => ModType.Conversion; } - private class TestUnimplementedModOsuRuleset : OsuRuleset + private class TestUnimplementedModOsuRuleset : OsuRuleset, ILegacyRuleset { public override string ShortName => "unimplemented"; + int ILegacyRuleset.LegacyID => -1; + public override IEnumerable GetModsFor(ModType type) { if (type == ModType.Conversion) return base.GetModsFor(type).Concat(new[] { new TestUnimplementedMod() }); From 92e22c57a77a2860e2ddc78ee85da46f2e5f43d4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Feb 2022 08:02:51 +0300 Subject: [PATCH 0607/5199] Introduce private `APIRuleset` for online ID equality comparison --- .../API/Requests/Responses/APIBeatmap.cs | 26 ++++++++++++++++++- osu.Game/Rulesets/RulesetInfo.cs | 3 --- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index ebbac0dcab..dca60e54cb 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -98,7 +98,7 @@ namespace osu.Game.Online.API.Requests.Responses public string MD5Hash => Checksum; - public IRulesetInfo Ruleset => new RulesetInfo { OnlineID = RulesetID }; + public IRulesetInfo Ruleset => new APIRuleset { OnlineID = RulesetID }; [JsonIgnore] public string Hash => throw new NotImplementedException(); @@ -106,5 +106,29 @@ namespace osu.Game.Online.API.Requests.Responses #endregion public bool Equals(IBeatmapInfo? other) => other is APIBeatmap b && this.MatchesOnlineID(b); + + private class APIRuleset : IRulesetInfo + { + public int OnlineID { get; set; } = -1; + + public string Name => $@"{nameof(APIRuleset)} (ID: {OnlineID})"; + public string ShortName => nameof(APIRuleset); + public string InstantiationInfo => string.Empty; + + public Ruleset CreateInstance() => throw new NotImplementedException(); + + public bool Equals(IRulesetInfo? other) => other is APIRuleset r && this.MatchesOnlineID(r); + + public int CompareTo(IRulesetInfo other) + { + if (!(other is APIRuleset ruleset)) + throw new ArgumentException($@"Object is not of type {nameof(APIRuleset)}.", nameof(other)); + + return OnlineID.CompareTo(ruleset.OnlineID); + } + + // ReSharper disable once NonReadonlyMemberInGetHashCode + public override int GetHashCode() => OnlineID; + } } } diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index ba7c8d191d..88e3988431 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -44,9 +44,6 @@ namespace osu.Game.Rulesets if (ReferenceEquals(this, other)) return true; if (other == null) return false; - if (OnlineID >= 0 && other.OnlineID >= 0) - return OnlineID == other.OnlineID; - return ShortName == other.ShortName; } From b06caf2bf7b3a8705d26101f5e1fba464542a701 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Feb 2022 09:12:02 +0300 Subject: [PATCH 0608/5199] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 147f576c55..1a2859c851 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index dd10807ec2..a9c0226951 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 6fbc468586..5e0b264834 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + From f049f175d5d2d3a971bce6825d8cdbd2fedea42d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Feb 2022 15:19:55 +0900 Subject: [PATCH 0609/5199] Revert "Fix failing test case" This reverts commit c29cc78853f9cfb5efc4e923c7d9fca6fda8fbf7. --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 9083415a78..b429619044 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -457,12 +457,10 @@ namespace osu.Game.Tests.Visual.UserInterface public override ModType Type => ModType.Conversion; } - private class TestUnimplementedModOsuRuleset : OsuRuleset, ILegacyRuleset + private class TestUnimplementedModOsuRuleset : OsuRuleset { public override string ShortName => "unimplemented"; - int ILegacyRuleset.LegacyID => -1; - public override IEnumerable GetModsFor(ModType type) { if (type == ModType.Conversion) return base.GetModsFor(type).Concat(new[] { new TestUnimplementedMod() }); From f012f64fd1f441c96c589035afd63010b41d0b1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 16:45:57 +0900 Subject: [PATCH 0610/5199] Add test coverage checking carousel panel visual state after ruleset filter change --- .../SongSelect/TestSceneBeatmapCarousel.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 4e46901e08..540b820250 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -41,6 +41,68 @@ namespace osu.Game.Tests.Visual.SongSelect this.rulesets = rulesets; } + [Test] + public void TestExternalRulesetChange() + { + createCarousel(new List()); + + AddStep("filter to ruleset 0", () => carousel.Filter(new FilterCriteria + { + Ruleset = rulesets.AvailableRulesets.ElementAt(0), + AllowConvertedBeatmaps = true, + }, false)); + + AddStep("add mixed ruleset beatmapset", () => + { + var testMixed = TestResources.CreateTestBeatmapSetInfo(3); + + for (int i = 0; i <= 2; i++) + { + testMixed.Beatmaps[i].Ruleset = rulesets.AvailableRulesets.ElementAt(i); + } + + carousel.UpdateBeatmapSet(testMixed); + }); + + AddUntilStep("wait for filtered difficulties", () => + { + var visibleBeatmapPanels = carousel.Items.OfType().Where(p => p.IsPresent).ToArray(); + + return visibleBeatmapPanels.Length == 1 + && visibleBeatmapPanels.Count(p => ((CarouselBeatmap)p.Item).BeatmapInfo.Ruleset.OnlineID == 0) == 1; + }); + + AddStep("filter to ruleset 1", () => carousel.Filter(new FilterCriteria + { + Ruleset = rulesets.AvailableRulesets.ElementAt(1), + AllowConvertedBeatmaps = true, + }, false)); + + AddUntilStep("wait for filtered difficulties", () => + { + var visibleBeatmapPanels = carousel.Items.OfType().Where(p => p.IsPresent).ToArray(); + + return visibleBeatmapPanels.Length == 2 + && visibleBeatmapPanels.Count(p => ((CarouselBeatmap)p.Item).BeatmapInfo.Ruleset.OnlineID == 0) == 1 + && visibleBeatmapPanels.Count(p => ((CarouselBeatmap)p.Item).BeatmapInfo.Ruleset.OnlineID == 1) == 1; + }); + + AddStep("filter to ruleset 2", () => carousel.Filter(new FilterCriteria + { + Ruleset = rulesets.AvailableRulesets.ElementAt(2), + AllowConvertedBeatmaps = true, + }, false)); + + AddUntilStep("wait for filtered difficulties", () => + { + var visibleBeatmapPanels = carousel.Items.OfType().Where(p => p.IsPresent).ToArray(); + + return visibleBeatmapPanels.Length == 2 + && visibleBeatmapPanels.Count(p => ((CarouselBeatmap)p.Item).BeatmapInfo.Ruleset.OnlineID == 0) == 1 + && visibleBeatmapPanels.Count(p => ((CarouselBeatmap)p.Item).BeatmapInfo.Ruleset.OnlineID == 2) == 1; + }); + } + [Test] public void TestScrollPositionMaintainedOnAdd() { From ccd664896185a0aff852fb8eaf6c47cb1b7bf125 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Feb 2022 09:22:09 +0300 Subject: [PATCH 0611/5199] Update pinned score container header to use localised title --- osu.Game/Overlays/Profile/Sections/RanksSection.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index 02d8bd8c52..f48e33dc12 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -18,8 +18,7 @@ namespace osu.Game.Overlays.Profile.Sections { Children = new[] { - // todo: update to use UsersStrings.ShowExtraTopRanksPinnedTitle once that exists. - new PaginatedScoreContainer(ScoreType.Pinned, User, "Pinned Scores"), + new PaginatedScoreContainer(ScoreType.Pinned, User, UsersStrings.ShowExtraTopRanksPinnedTitle), new PaginatedScoreContainer(ScoreType.Best, User, UsersStrings.ShowExtraTopRanksBestTitle), new PaginatedScoreContainer(ScoreType.Firsts, User, UsersStrings.ShowExtraTopRanksFirstTitle) }; From beb3731c0b079c6b2bdaf1d051bd51b5350899f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Feb 2022 15:53:45 +0900 Subject: [PATCH 0612/5199] Standardise and combine base implementation of score submission requests These share too much yet have very different constructor signatures and property exposure. Just a clean-up pass as I begin to look at replay submission. --- .../Online/Rooms/SubmitRoomScoreRequest.cs | 32 ++------------- osu.Game/Online/Rooms/SubmitScoreRequest.cs | 41 +++++++++++++++++++ .../Online/Solo/SubmitSoloScoreRequest.cs | 33 ++------------- osu.Game/Screens/Play/RoomSubmittingPlayer.cs | 2 +- osu.Game/Screens/Play/SoloPlayer.cs | 2 +- 5 files changed, 51 insertions(+), 59 deletions(-) create mode 100644 osu.Game/Online/Rooms/SubmitScoreRequest.cs diff --git a/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs b/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs index e24d113822..39193be1af 100644 --- a/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs +++ b/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs @@ -1,46 +1,22 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Net.Http; -using Newtonsoft.Json; -using osu.Framework.IO.Network; -using osu.Game.Online.API; -using osu.Game.Online.Solo; using osu.Game.Scoring; namespace osu.Game.Online.Rooms { - public class SubmitRoomScoreRequest : APIRequest + public class SubmitRoomScoreRequest : SubmitScoreRequest { - private readonly long scoreId; private readonly long roomId; private readonly long playlistItemId; - private readonly SubmittableScore score; - public SubmitRoomScoreRequest(long scoreId, long roomId, long playlistItemId, ScoreInfo scoreInfo) + public SubmitRoomScoreRequest(ScoreInfo scoreInfo, long scoreId, long roomId, long playlistItemId) + : base(scoreInfo, scoreId) { - this.scoreId = scoreId; this.roomId = roomId; this.playlistItemId = playlistItemId; - score = new SubmittableScore(scoreInfo); } - protected override WebRequest CreateWebRequest() - { - var req = base.CreateWebRequest(); - - req.ContentType = "application/json"; - req.Method = HttpMethod.Put; - req.Timeout = 30000; - - req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings - { - ReferenceLoopHandling = ReferenceLoopHandling.Ignore - })); - - return req; - } - - protected override string Target => $@"rooms/{roomId}/playlist/{playlistItemId}/scores/{scoreId}"; + protected override string Target => $@"rooms/{roomId}/playlist/{playlistItemId}/scores/{ScoreId}"; } } diff --git a/osu.Game/Online/Rooms/SubmitScoreRequest.cs b/osu.Game/Online/Rooms/SubmitScoreRequest.cs new file mode 100644 index 0000000000..14f858f007 --- /dev/null +++ b/osu.Game/Online/Rooms/SubmitScoreRequest.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Net.Http; +using Newtonsoft.Json; +using osu.Framework.IO.Network; +using osu.Game.Online.API; +using osu.Game.Online.Solo; +using osu.Game.Scoring; + +namespace osu.Game.Online.Rooms +{ + public abstract class SubmitScoreRequest : APIRequest + { + public readonly SubmittableScore Score; + + protected readonly long ScoreId; + + protected SubmitScoreRequest(ScoreInfo scoreInfo, long scoreId) + { + Score = new SubmittableScore(scoreInfo); + this.ScoreId = scoreId; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + + req.ContentType = "application/json"; + req.Method = HttpMethod.Put; + req.Timeout = 30000; + + req.AddRaw(JsonConvert.SerializeObject(Score, new JsonSerializerSettings + { + ReferenceLoopHandling = ReferenceLoopHandling.Ignore + })); + + return req; + } + } +} diff --git a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs index 78ebddb2e6..77fd7b813b 100644 --- a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs +++ b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs @@ -1,46 +1,21 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Net.Http; -using Newtonsoft.Json; -using osu.Framework.IO.Network; -using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Scoring; namespace osu.Game.Online.Solo { - public class SubmitSoloScoreRequest : APIRequest + public class SubmitSoloScoreRequest : SubmitScoreRequest { - public readonly SubmittableScore Score; - - private readonly long scoreId; - private readonly int beatmapId; - public SubmitSoloScoreRequest(int beatmapId, long scoreId, ScoreInfo scoreInfo) + public SubmitSoloScoreRequest(ScoreInfo scoreInfo, long scoreId, int beatmapId) + : base(scoreInfo, scoreId) { this.beatmapId = beatmapId; - this.scoreId = scoreId; - Score = new SubmittableScore(scoreInfo); } - protected override WebRequest CreateWebRequest() - { - var req = base.CreateWebRequest(); - - req.ContentType = "application/json"; - req.Method = HttpMethod.Put; - req.Timeout = 30000; - - req.AddRaw(JsonConvert.SerializeObject(Score, new JsonSerializerSettings - { - ReferenceLoopHandling = ReferenceLoopHandling.Ignore - })); - - return req; - } - - protected override string Target => $@"beatmaps/{beatmapId}/solo/scores/{scoreId}"; + protected override string Target => $@"beatmaps/{beatmapId}/solo/scores/{ScoreId}"; } } diff --git a/osu.Game/Screens/Play/RoomSubmittingPlayer.cs b/osu.Game/Screens/Play/RoomSubmittingPlayer.cs index 1002e7607f..fc96dfa965 100644 --- a/osu.Game/Screens/Play/RoomSubmittingPlayer.cs +++ b/osu.Game/Screens/Play/RoomSubmittingPlayer.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Play protected override APIRequest CreateSubmissionRequest(Score score, long token) { Debug.Assert(Room.RoomID.Value != null); - return new SubmitRoomScoreRequest(token, Room.RoomID.Value.Value, PlaylistItem.ID, score.ScoreInfo); + return new SubmitRoomScoreRequest(score.ScoreInfo, token, Room.RoomID.Value.Value, PlaylistItem.ID); } } } diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index eced2d142b..824c0072e3 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Play Debug.Assert(beatmap.OnlineID > 0); - return new SubmitSoloScoreRequest(beatmap.OnlineID, token, score.ScoreInfo); + return new SubmitSoloScoreRequest(score.ScoreInfo, token, beatmap.OnlineID); } } } From 908c31c68764370e44e9e3a6055aa881a7ce1971 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Feb 2022 16:02:25 +0900 Subject: [PATCH 0613/5199] Update stream read operations to use new helper methods --- osu.Game/Database/ImportTask.cs | 5 ++--- osu.Game/IO/Archives/ArchiveReader.cs | 14 +++----------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/osu.Game/Database/ImportTask.cs b/osu.Game/Database/ImportTask.cs index cd9e396d13..d75c1a73e6 100644 --- a/osu.Game/Database/ImportTask.cs +++ b/osu.Game/Database/ImportTask.cs @@ -4,6 +4,7 @@ #nullable enable using System.IO; +using osu.Framework.Extensions; using osu.Game.IO.Archives; using osu.Game.Stores; using osu.Game.Utils; @@ -63,9 +64,7 @@ namespace osu.Game.Database if (!(stream is MemoryStream memoryStream)) { // This isn't used in any current path. May need to reconsider for performance reasons (ie. if we don't expect the incoming stream to be copied out). - byte[] buffer = new byte[stream.Length]; - stream.Read(buffer, 0, (int)stream.Length); - memoryStream = new MemoryStream(buffer); + memoryStream = new MemoryStream(stream.ReadAllBytesToArray()); } if (ZipUtils.IsZipArchive(memoryStream)) diff --git a/osu.Game/IO/Archives/ArchiveReader.cs b/osu.Game/IO/Archives/ArchiveReader.cs index 1d8da16c72..dab70eaf70 100644 --- a/osu.Game/IO/Archives/ArchiveReader.cs +++ b/osu.Game/IO/Archives/ArchiveReader.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; +using osu.Framework.Extensions; using osu.Framework.IO.Stores; namespace osu.Game.IO.Archives @@ -35,14 +36,7 @@ namespace osu.Game.IO.Archives public virtual byte[] Get(string name) { using (Stream input = GetStream(name)) - { - if (input == null) - return null; - - byte[] buffer = new byte[input.Length]; - input.Read(buffer); - return buffer; - } + return input?.ReadAllBytesToArray(); } public async Task GetAsync(string name, CancellationToken cancellationToken = default) @@ -52,9 +46,7 @@ namespace osu.Game.IO.Archives if (input == null) return null; - byte[] buffer = new byte[input.Length]; - await input.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); - return buffer; + return await input.ReadAllBytesToArrayAsync(cancellationToken).ConfigureAwait(false); } } } From 6005daeba8bda373622123167bc533e2738704d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Feb 2022 16:02:42 +0900 Subject: [PATCH 0614/5199] Fix fire-and-forget async calls to use `WaitSafely` --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 8f6ba6375f..7661cf671b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -829,7 +829,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem { BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo.OnlineID - }))); + })).WaitSafely()); AddUntilStep("item arrived in playlist", () => client.Room?.Playlist.Count == 2); @@ -860,11 +860,11 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem { BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo.OnlineID - }))); + })).WaitSafely()); AddUntilStep("item arrived in playlist", () => client.Room?.Playlist.Count == 2); - AddStep("delete item as other user", () => client.RemoveUserPlaylistItem(1234, 2)); + AddStep("delete item as other user", () => client.RemoveUserPlaylistItem(1234, 2).WaitSafely()); AddUntilStep("item removed from playlist", () => client.Room?.Playlist.Count == 1); AddStep("exit gameplay as initial user", () => multiplayerComponents.MultiplayerScreen.MakeCurrent()); From 28bbf34b1495908b1e145f8575f6ffaaf0979bd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Feb 2022 16:21:49 +0900 Subject: [PATCH 0615/5199] Remove unnecessary `this.` prefix --- osu.Game/Online/Rooms/SubmitScoreRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Rooms/SubmitScoreRequest.cs b/osu.Game/Online/Rooms/SubmitScoreRequest.cs index 14f858f007..b263262d2b 100644 --- a/osu.Game/Online/Rooms/SubmitScoreRequest.cs +++ b/osu.Game/Online/Rooms/SubmitScoreRequest.cs @@ -19,7 +19,7 @@ namespace osu.Game.Online.Rooms protected SubmitScoreRequest(ScoreInfo scoreInfo, long scoreId) { Score = new SubmittableScore(scoreInfo); - this.ScoreId = scoreId; + ScoreId = scoreId; } protected override WebRequest CreateWebRequest() From 2ed3d5853144436f76260feb38f0d4d1c3a56e52 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sat, 12 Feb 2022 08:51:09 +0800 Subject: [PATCH 0616/5199] Ignore short spinners for relax mod --- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 5d191119b9..905d55c64e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -87,8 +87,9 @@ namespace osu.Game.Rulesets.Osu.Mods requiresHold |= slider.Ball.IsHovered || h.IsHovered; break; - case DrawableSpinner _: - requiresHold = true; + case DrawableSpinner spinner: + if (spinner.HitObject.SpinsRequired > 0) + requiresHold = true; break; } } From 053f41d755999ec120c1674c958cfadadb7898f7 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sat, 12 Feb 2022 10:06:43 +0800 Subject: [PATCH 0617/5199] Simplify code --- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 905d55c64e..10abd24e80 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -88,8 +88,7 @@ namespace osu.Game.Rulesets.Osu.Mods break; case DrawableSpinner spinner: - if (spinner.HitObject.SpinsRequired > 0) - requiresHold = true; + requiresHold = spinner.HitObject.SpinsRequired > 0; break; } } From 639d813d06fe2275b6220fc41b6133e0390de5f5 Mon Sep 17 00:00:00 2001 From: PercyDan <50285552+PercyDan54@users.noreply.github.com> Date: Sat, 12 Feb 2022 11:15:03 +0800 Subject: [PATCH 0618/5199] Don't override previous value Co-authored-by: Salman Ahmed --- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 10abd24e80..1bf63ef6d4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Mods break; case DrawableSpinner spinner: - requiresHold = spinner.HitObject.SpinsRequired > 0; + requiresHold |= spinner.HitObject.SpinsRequired > 0; break; } } From f1535b74beb185c83f76d7e7e3a54e55b6c32a81 Mon Sep 17 00:00:00 2001 From: Kaleb Date: Sun, 13 Feb 2022 02:16:06 -0500 Subject: [PATCH 0619/5199] Give Spun Out mod dynamic spin rate --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 098c639949..b900fa3274 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -45,7 +45,8 @@ namespace osu.Game.Rulesets.Osu.Mods // for that reason using ElapsedFrameTime directly leads to fewer SPM with Half Time and more SPM with Double Time. // for spinners we want the real (wall clock) elapsed time; to achieve that, unapply the clock rate locally here. double rateIndependentElapsedTime = spinner.Clock.ElapsedFrameTime / spinner.Clock.Rate; - spinner.RotationTracker.AddRotation(MathUtils.RadiansToDegrees((float)rateIndependentElapsedTime * 0.03f)); + float rotationSpeed = (float)(spinner.HitObject.SpinsRequired / spinner.HitObject.Duration / 1.01); + spinner.RotationTracker.AddRotation(MathUtils.RadiansToDegrees((float)rateIndependentElapsedTime * rotationSpeed * MathF.PI * 2.0f)); } } } From 585bd541f319ce316c8c4eef80aeb4247449b232 Mon Sep 17 00:00:00 2001 From: Kaleb Date: Sun, 13 Feb 2022 02:38:49 -0500 Subject: [PATCH 0620/5199] Add missing parentheses to RPM calculation --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index b900fa3274..4725a43a77 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Mods // for that reason using ElapsedFrameTime directly leads to fewer SPM with Half Time and more SPM with Double Time. // for spinners we want the real (wall clock) elapsed time; to achieve that, unapply the clock rate locally here. double rateIndependentElapsedTime = spinner.Clock.ElapsedFrameTime / spinner.Clock.Rate; - float rotationSpeed = (float)(spinner.HitObject.SpinsRequired / spinner.HitObject.Duration / 1.01); + float rotationSpeed = (float)(spinner.HitObject.SpinsRequired / (spinner.HitObject.Duration / 1.01)); spinner.RotationTracker.AddRotation(MathUtils.RadiansToDegrees((float)rateIndependentElapsedTime * rotationSpeed * MathF.PI * 2.0f)); } } From a2c2b2bbb3dcbd87f9c4c08582b00b98f1096e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Feb 2022 17:52:59 +0100 Subject: [PATCH 0621/5199] Add flow for copying existing difficulty content --- osu.Game/Beatmaps/BeatmapManager.cs | 34 ++++++++------ .../Screens/Edit/CreateNewDifficultyDialog.cs | 45 +++++++++++++++++++ osu.Game/Screens/Edit/Editor.cs | 20 ++++++++- osu.Game/Screens/Edit/EditorLoader.cs | 7 ++- .../Edit/NewDifficultyCreationParameters.cs | 36 +++++++++++++++ osu.Game/Tests/Visual/EditorTestScene.cs | 2 +- 6 files changed, 125 insertions(+), 19 deletions(-) create mode 100644 osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs create mode 100644 osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 633eb8f15e..bd9cdba9fb 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -20,6 +20,7 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; +using osu.Game.Screens.Edit; using osu.Game.Skinning; using osu.Game.Stores; @@ -112,29 +113,36 @@ namespace osu.Game.Beatmaps /// The new difficulty will be backed by a model /// and represented by the returned . /// - public virtual WorkingBeatmap CreateNewBlankDifficulty(BeatmapSetInfo beatmapSetInfo, RulesetInfo rulesetInfo) + public virtual WorkingBeatmap CreateNewBlankDifficulty(NewDifficultyCreationParameters creationParameters) { - // fetch one of the existing difficulties to copy timing points and metadata from, - // so that the user doesn't have to fill all of that out again. - // this silently assumes that all difficulties have the same timing points and metadata, - // but cases where this isn't true seem rather rare / pathological. - var referenceBeatmap = GetWorkingBeatmap(beatmapSetInfo.Beatmaps.First()); + var referenceBeatmap = creationParameters.ReferenceBeatmap; + var targetBeatmapSet = creationParameters.BeatmapSet; - var newBeatmapInfo = new BeatmapInfo(rulesetInfo, new BeatmapDifficulty(), referenceBeatmap.Metadata.DeepClone()); + var newBeatmapInfo = new BeatmapInfo(creationParameters.Ruleset, new BeatmapDifficulty(), referenceBeatmap.Metadata.DeepClone()); // populate circular beatmap set info <-> beatmap info references manually. // several places like `BeatmapModelManager.Save()` or `GetWorkingBeatmap()` // rely on them being freely traversable in both directions for correct operation. - beatmapSetInfo.Beatmaps.Add(newBeatmapInfo); - newBeatmapInfo.BeatmapSet = beatmapSetInfo; + targetBeatmapSet.Beatmaps.Add(newBeatmapInfo); + newBeatmapInfo.BeatmapSet = targetBeatmapSet; - var newBeatmap = new Beatmap { BeatmapInfo = newBeatmapInfo }; - foreach (var timingPoint in referenceBeatmap.Beatmap.ControlPointInfo.TimingPoints) - newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone()); + IBeatmap newBeatmap; + + if (creationParameters.ClearAllObjects) + { + newBeatmap = new Beatmap { BeatmapInfo = newBeatmapInfo }; + foreach (var timingPoint in referenceBeatmap.ControlPointInfo.TimingPoints) + newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone()); + } + else + { + newBeatmap = referenceBeatmap.Clone(); + newBeatmap.BeatmapInfo = newBeatmapInfo; + } beatmapModelManager.Save(newBeatmapInfo, newBeatmap); - workingBeatmapCache.Invalidate(beatmapSetInfo); + workingBeatmapCache.Invalidate(targetBeatmapSet); return GetWorkingBeatmap(newBeatmap.BeatmapInfo); } diff --git a/osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs b/osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs new file mode 100644 index 0000000000..472f0e8948 --- /dev/null +++ b/osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs @@ -0,0 +1,45 @@ +// 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.Sprites; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Screens.Edit +{ + public class CreateNewDifficultyDialog : PopupDialog + { + /// + /// Delegate used to create new difficulties. + /// A value of in the clearAllObjects parameter + /// indicates that the new difficulty should have its hitobjects cleared; + /// otherwise, the new difficulty should be an exact copy of an existing one. + /// + public delegate void CreateNewDifficulty(bool clearAllObjects); + + public CreateNewDifficultyDialog(CreateNewDifficulty createNewDifficulty) + { + HeaderText = "Would you like to clear all objects?"; + + Icon = FontAwesome.Regular.Clone; + + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = "Yeah, let's start from scratch!", + Action = () => createNewDifficulty.Invoke(true) + }, + new PopupDialogCancelButton + { + Text = "No, create an exact copy of this difficulty", + Action = () => createNewDifficulty.Invoke(false) + }, + new PopupDialogCancelButton + { + Text = "I changed my mind, I want to keep editing this difficulty", + Action = () => { } + } + }; + } + } +} diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 2aec63fa65..c5578287e3 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -841,7 +841,25 @@ namespace osu.Game.Screens.Edit } protected void CreateNewDifficulty(RulesetInfo rulesetInfo) - => loader?.ScheduleSwitchToNewDifficulty(editorBeatmap.BeatmapInfo.BeatmapSet, rulesetInfo, GetState()); + { + if (!rulesetInfo.Equals(editorBeatmap.BeatmapInfo.Ruleset)) + { + switchToNewDifficulty(rulesetInfo, true); + return; + } + + dialogOverlay.Push(new CreateNewDifficultyDialog(clearAllObjects => switchToNewDifficulty(rulesetInfo, clearAllObjects))); + } + + private void switchToNewDifficulty(RulesetInfo rulesetInfo, bool clearAllObjects) + => loader?.ScheduleSwitchToNewDifficulty(new NewDifficultyCreationParameters + { + BeatmapSet = editorBeatmap.BeatmapInfo.BeatmapSet, + Ruleset = rulesetInfo, + ReferenceBeatmap = playableBeatmap, + ClearAllObjects = clearAllObjects, + EditorState = GetState() + }); private EditorMenuItem createDifficultySwitchMenu() { diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index de47411fdc..169b601a94 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -11,7 +11,6 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; @@ -80,12 +79,12 @@ namespace osu.Game.Screens.Edit } } - public void ScheduleSwitchToNewDifficulty(BeatmapSetInfo beatmapSetInfo, RulesetInfo rulesetInfo, EditorState editorState) + public void ScheduleSwitchToNewDifficulty(NewDifficultyCreationParameters creationParameters) => scheduleDifficultySwitch(() => { try { - return beatmapManager.CreateNewBlankDifficulty(beatmapSetInfo, rulesetInfo); + return beatmapManager.CreateNewBlankDifficulty(creationParameters); } catch (Exception ex) { @@ -94,7 +93,7 @@ namespace osu.Game.Screens.Edit Logger.Error(ex, ex.Message); return Beatmap.Value; } - }, editorState); + }, creationParameters.EditorState); public void ScheduleSwitchToExistingDifficulty(BeatmapInfo beatmapInfo, EditorState editorState) => scheduleDifficultySwitch(() => beatmapManager.GetWorkingBeatmap(beatmapInfo), editorState); diff --git a/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs b/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs new file mode 100644 index 0000000000..dd03fd3644 --- /dev/null +++ b/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs @@ -0,0 +1,36 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Beatmaps; +using osu.Game.Rulesets; + +namespace osu.Game.Screens.Edit +{ + public class NewDifficultyCreationParameters + { + /// + /// The that should contain the newly-created difficulty. + /// + public BeatmapSetInfo BeatmapSet { get; set; } + + /// + /// The that the new difficulty should be playable for. + /// + public RulesetInfo Ruleset { get; set; } + + /// + /// A reference upon which the new difficulty should be based. + /// + public IBeatmap ReferenceBeatmap { get; set; } + + /// + /// Whether all objects should be cleared from the new difficulty. + /// + public bool ClearAllObjects { get; set; } + + /// + /// The saved state of the previous which should be restored upon opening the newly-created difficulty. + /// + public EditorState EditorState { get; set; } + } +} diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 331bf04644..ff09598eef 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual return new TestWorkingBeatmapCache(this, audioManager, resources, storage, defaultBeatmap, host); } - public override WorkingBeatmap CreateNewBlankDifficulty(BeatmapSetInfo beatmapSetInfo, RulesetInfo rulesetInfo) + public override WorkingBeatmap CreateNewBlankDifficulty(NewDifficultyCreationParameters creationParameters) { // don't actually care about properly creating a difficulty for this context. return TestBeatmap; From 0d1171b7fae94ea769598bb2b5f0dff194c6c32d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Feb 2022 17:56:51 +0100 Subject: [PATCH 0622/5199] Adjust existing test coverage to pass --- .../Editing/TestSceneEditorBeatmapCreation.cs | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index a14c9aded3..89a9307f9f 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -10,6 +10,7 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Setup; @@ -92,7 +93,7 @@ namespace osu.Game.Tests.Visual.Editing } [Test] - public void TestCreateNewDifficulty() + public void TestCreateNewDifficulty([Values] bool sameRuleset) { string firstDifficultyName = Guid.NewGuid().ToString(); string secondDifficultyName = Guid.NewGuid().ToString(); @@ -111,7 +112,14 @@ namespace osu.Game.Tests.Visual.Editing }); AddAssert("can save again", () => Editor.Save()); - AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(sameRuleset ? new OsuRuleset().RulesetInfo : new CatchRuleset().RulesetInfo)); + + if (sameRuleset) + { + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog.PerformOkAction()); + } + AddUntilStep("wait for created", () => { string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; @@ -154,7 +162,7 @@ namespace osu.Game.Tests.Visual.Editing } [Test] - public void TestCreateNewBeatmapFailsWithSameNamedDifficulties() + public void TestCreateNewBeatmapFailsWithSameNamedDifficulties([Values] bool sameRuleset) { Guid setId = Guid.Empty; const string duplicate_difficulty_name = "duplicate"; @@ -168,7 +176,14 @@ namespace osu.Game.Tests.Visual.Editing return set != null && set.PerformRead(s => s.Beatmaps.Count == 1 && s.Files.Count == 1); }); - AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(sameRuleset ? new OsuRuleset().RulesetInfo : new CatchRuleset().RulesetInfo)); + + if (sameRuleset) + { + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog.PerformOkAction()); + } + AddUntilStep("wait for created", () => { string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; From eb939547a9978e0d4e47a65f90c3b5b902b7d988 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Feb 2022 18:03:54 +0100 Subject: [PATCH 0623/5199] Add test coverage for difficulty copy flow --- .../Editing/TestSceneEditorBeatmapCreation.cs | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 89a9307f9f..91f667d8b0 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -9,13 +9,17 @@ using osu.Framework.Allocation; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Setup; using osu.Game.Storyboards; using osu.Game.Tests.Resources; +using osuTK; using SharpCompress.Archives; using SharpCompress.Archives.Zip; @@ -99,6 +103,21 @@ namespace osu.Game.Tests.Visual.Editing string secondDifficultyName = Guid.NewGuid().ToString(); AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName); + AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 })); + AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] + { + new HitCircle + { + Position = new Vector2(0), + StartTime = 0 + }, + new HitCircle + { + Position = OsuPlayfield.BASE_SIZE, + StartTime = 1000 + } + })); + AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => { @@ -126,6 +145,80 @@ namespace osu.Game.Tests.Visual.Editing return difficultyName != null && difficultyName != firstDifficultyName; }); + AddAssert("created difficulty has timing point", () => + { + var timingPoint = EditorBeatmap.ControlPointInfo.TimingPoints.Single(); + return timingPoint.Time == 0 && timingPoint.BeatLength == 1000; + }); + AddAssert("created difficulty has no objects", () => EditorBeatmap.HitObjects.Count == 0); + + AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = secondDifficultyName); + AddStep("save beatmap", () => Editor.Save()); + AddAssert("new beatmap persisted", () => + { + var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == secondDifficultyName); + var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID); + + return beatmap != null + && beatmap.DifficultyName == secondDifficultyName + && set != null + && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName)); + }); + } + + [Test] + public void TestCopyDifficulty() + { + string firstDifficultyName = Guid.NewGuid().ToString(); + string secondDifficultyName = Guid.NewGuid().ToString(); + + AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName); + AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 })); + AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] + { + new HitCircle + { + Position = new Vector2(0), + StartTime = 0 + }, + new HitCircle + { + Position = OsuPlayfield.BASE_SIZE, + StartTime = 1000 + } + })); + + AddStep("save beatmap", () => Editor.Save()); + AddAssert("new beatmap persisted", () => + { + var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == firstDifficultyName); + var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID); + + return beatmap != null + && beatmap.DifficultyName == firstDifficultyName + && set != null + && set.PerformRead(s => s.Beatmaps.Single().ID == beatmap.ID); + }); + AddAssert("can save again", () => Editor.Save()); + + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation as a copy", () => DialogOverlay.CurrentDialog.Buttons.ElementAt(1).TriggerClick()); + + AddUntilStep("wait for created", () => + { + string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName != firstDifficultyName; + }); + + AddAssert("created difficulty has timing point", () => + { + var timingPoint = EditorBeatmap.ControlPointInfo.TimingPoints.Single(); + return timingPoint.Time == 0 && timingPoint.BeatLength == 1000; + }); + AddAssert("created difficulty has objects", () => EditorBeatmap.HitObjects.Count == 2); + AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = secondDifficultyName); AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => From fd1c8c361444f1bd7077379d64a1c03e5576c0f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Feb 2022 18:26:19 +0100 Subject: [PATCH 0624/5199] Add failing test coverage for correct beatmap difficulty copy --- osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 91f667d8b0..672e643e60 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -187,6 +187,7 @@ namespace osu.Game.Tests.Visual.Editing StartTime = 1000 } })); + AddStep("set approach rate", () => EditorBeatmap.Difficulty.ApproachRate = 4); AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => @@ -218,6 +219,7 @@ namespace osu.Game.Tests.Visual.Editing return timingPoint.Time == 0 && timingPoint.BeatLength == 1000; }); AddAssert("created difficulty has objects", () => EditorBeatmap.HitObjects.Count == 2); + AddAssert("approach rate correctly copied", () => EditorBeatmap.Difficulty.ApproachRate == 4); AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = secondDifficultyName); AddStep("save beatmap", () => Editor.Save()); From 1bf5375e746f6be78a992594fda76b821b72cb88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Feb 2022 18:40:51 +0100 Subject: [PATCH 0625/5199] Fix `BeatmapInfo`-associated member not copying --- osu.Game/Beatmaps/BeatmapManager.cs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index bd9cdba9fb..c350cfc111 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -118,18 +118,12 @@ namespace osu.Game.Beatmaps var referenceBeatmap = creationParameters.ReferenceBeatmap; var targetBeatmapSet = creationParameters.BeatmapSet; - var newBeatmapInfo = new BeatmapInfo(creationParameters.Ruleset, new BeatmapDifficulty(), referenceBeatmap.Metadata.DeepClone()); - - // populate circular beatmap set info <-> beatmap info references manually. - // several places like `BeatmapModelManager.Save()` or `GetWorkingBeatmap()` - // rely on them being freely traversable in both directions for correct operation. - targetBeatmapSet.Beatmaps.Add(newBeatmapInfo); - newBeatmapInfo.BeatmapSet = targetBeatmapSet; - + BeatmapInfo newBeatmapInfo; IBeatmap newBeatmap; if (creationParameters.ClearAllObjects) { + newBeatmapInfo = new BeatmapInfo(creationParameters.Ruleset, new BeatmapDifficulty(), referenceBeatmap.Metadata.DeepClone()); newBeatmap = new Beatmap { BeatmapInfo = newBeatmapInfo }; foreach (var timingPoint in referenceBeatmap.ControlPointInfo.TimingPoints) newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone()); @@ -137,9 +131,19 @@ namespace osu.Game.Beatmaps else { newBeatmap = referenceBeatmap.Clone(); - newBeatmap.BeatmapInfo = newBeatmapInfo; + newBeatmap.BeatmapInfo = newBeatmapInfo = referenceBeatmap.BeatmapInfo.Clone(); + // assign a new ID to the clone. + newBeatmapInfo.ID = Guid.NewGuid(); + // clear difficulty name to avoid clashes on save. + newBeatmapInfo.DifficultyName = string.Empty; } + // populate circular beatmap set info <-> beatmap info references manually. + // several places like `BeatmapModelManager.Save()` or `GetWorkingBeatmap()` + // rely on them being freely traversable in both directions for correct operation. + targetBeatmapSet.Beatmaps.Add(newBeatmapInfo); + newBeatmapInfo.BeatmapSet = targetBeatmapSet; + beatmapModelManager.Save(newBeatmapInfo, newBeatmap); workingBeatmapCache.Invalidate(targetBeatmapSet); From 1292722a00e331933852f1842455a3e25108c4cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Feb 2022 18:44:12 +0100 Subject: [PATCH 0626/5199] Add failing test coverage for correct combo colour copy --- .../Editing/TestSceneEditorBeatmapCreation.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 672e643e60..90b1d3a6f9 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -6,6 +6,8 @@ using System.IO; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -188,6 +190,16 @@ namespace osu.Game.Tests.Visual.Editing } })); AddStep("set approach rate", () => EditorBeatmap.Difficulty.ApproachRate = 4); + AddStep("set combo colours", () => + { + var beatmapSkin = EditorBeatmap.BeatmapSkin.AsNonNull(); + beatmapSkin.ComboColours.Clear(); + beatmapSkin.ComboColours.AddRange(new[] + { + new Colour4(255, 0, 0, 255), + new Colour4(0, 0, 255, 255) + }); + }); AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => @@ -220,6 +232,7 @@ namespace osu.Game.Tests.Visual.Editing }); AddAssert("created difficulty has objects", () => EditorBeatmap.HitObjects.Count == 2); AddAssert("approach rate correctly copied", () => EditorBeatmap.Difficulty.ApproachRate == 4); + AddAssert("combo colours correctly copied", () => EditorBeatmap.BeatmapSkin.AsNonNull().ComboColours.Count == 2); AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = secondDifficultyName); AddStep("save beatmap", () => Editor.Save()); From a144d6f8d67b0fe6c90021b3309682f323da9322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Feb 2022 18:50:11 +0100 Subject: [PATCH 0627/5199] Fix beatmap skin properties not copying --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 16 +++++---- .../Edit/NewDifficultyCreationParameters.cs | 34 ++++++++++++++++--- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index c350cfc111..a9d0576696 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -144,7 +144,7 @@ namespace osu.Game.Beatmaps targetBeatmapSet.Beatmaps.Add(newBeatmapInfo); newBeatmapInfo.BeatmapSet = targetBeatmapSet; - beatmapModelManager.Save(newBeatmapInfo, newBeatmap); + beatmapModelManager.Save(newBeatmapInfo, newBeatmap, creationParameters.ReferenceBeatmapSkin); workingBeatmapCache.Invalidate(targetBeatmapSet); return GetWorkingBeatmap(newBeatmap.BeatmapInfo); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index c5578287e3..37d4dce60e 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -9,6 +9,7 @@ using JetBrains.Annotations; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -853,13 +854,14 @@ namespace osu.Game.Screens.Edit private void switchToNewDifficulty(RulesetInfo rulesetInfo, bool clearAllObjects) => loader?.ScheduleSwitchToNewDifficulty(new NewDifficultyCreationParameters - { - BeatmapSet = editorBeatmap.BeatmapInfo.BeatmapSet, - Ruleset = rulesetInfo, - ReferenceBeatmap = playableBeatmap, - ClearAllObjects = clearAllObjects, - EditorState = GetState() - }); + ( + editorBeatmap.BeatmapInfo.BeatmapSet.AsNonNull(), + rulesetInfo, + playableBeatmap, + editorBeatmap.BeatmapSkin, + clearAllObjects, + GetState() + )); private EditorMenuItem createDifficultySwitchMenu() { diff --git a/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs b/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs index dd03fd3644..aa7dac609b 100644 --- a/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs +++ b/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs @@ -1,8 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Skinning; namespace osu.Game.Screens.Edit { @@ -11,26 +14,47 @@ namespace osu.Game.Screens.Edit /// /// The that should contain the newly-created difficulty. /// - public BeatmapSetInfo BeatmapSet { get; set; } + public BeatmapSetInfo BeatmapSet { get; } /// /// The that the new difficulty should be playable for. /// - public RulesetInfo Ruleset { get; set; } + public RulesetInfo Ruleset { get; } /// /// A reference upon which the new difficulty should be based. /// - public IBeatmap ReferenceBeatmap { get; set; } + public IBeatmap ReferenceBeatmap { get; } + + /// + /// A reference that the new difficulty should base its own skin upon. + /// + public ISkin? ReferenceBeatmapSkin { get; } /// /// Whether all objects should be cleared from the new difficulty. /// - public bool ClearAllObjects { get; set; } + public bool ClearAllObjects { get; } /// /// The saved state of the previous which should be restored upon opening the newly-created difficulty. /// - public EditorState EditorState { get; set; } + public EditorState EditorState { get; } + + public NewDifficultyCreationParameters( + BeatmapSetInfo beatmapSet, + RulesetInfo ruleset, + IBeatmap referenceBeatmap, + ISkin? referenceBeatmapSkin, + bool clearAllObjects, + EditorState editorState) + { + BeatmapSet = beatmapSet; + Ruleset = ruleset; + ReferenceBeatmap = referenceBeatmap; + ReferenceBeatmapSkin = referenceBeatmapSkin; + ClearAllObjects = clearAllObjects; + EditorState = editorState; + } } } From 6fd663a718b30de3b3064edc4a986d4cc61dfa2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Feb 2022 19:51:02 +0100 Subject: [PATCH 0628/5199] Apply some renames to convey difference between creation options better --- osu.Game/Beatmaps/BeatmapManager.cs | 4 ++-- osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs | 2 +- osu.Game/Screens/Edit/EditorLoader.cs | 2 +- .../Screens/Edit/NewDifficultyCreationParameters.cs | 13 +++++++++---- osu.Game/Tests/Visual/EditorTestScene.cs | 2 +- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index a9d0576696..87051ef650 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -113,7 +113,7 @@ namespace osu.Game.Beatmaps /// The new difficulty will be backed by a model /// and represented by the returned . /// - public virtual WorkingBeatmap CreateNewBlankDifficulty(NewDifficultyCreationParameters creationParameters) + public virtual WorkingBeatmap CreateNewDifficulty(NewDifficultyCreationParameters creationParameters) { var referenceBeatmap = creationParameters.ReferenceBeatmap; var targetBeatmapSet = creationParameters.BeatmapSet; @@ -121,7 +121,7 @@ namespace osu.Game.Beatmaps BeatmapInfo newBeatmapInfo; IBeatmap newBeatmap; - if (creationParameters.ClearAllObjects) + if (creationParameters.CreateBlank) { newBeatmapInfo = new BeatmapInfo(creationParameters.Ruleset, new BeatmapDifficulty(), referenceBeatmap.Metadata.DeepClone()); newBeatmap = new Beatmap { BeatmapInfo = newBeatmapInfo }; diff --git a/osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs b/osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs index 472f0e8948..138e13bda1 100644 --- a/osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs +++ b/osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Edit public CreateNewDifficultyDialog(CreateNewDifficulty createNewDifficulty) { - HeaderText = "Would you like to clear all objects?"; + HeaderText = "Would you like to create a blank difficulty?"; Icon = FontAwesome.Regular.Clone; diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 169b601a94..be3e68c857 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -84,7 +84,7 @@ namespace osu.Game.Screens.Edit { try { - return beatmapManager.CreateNewBlankDifficulty(creationParameters); + return beatmapManager.CreateNewDifficulty(creationParameters); } catch (Exception ex) { diff --git a/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs b/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs index aa7dac609b..a6458a9456 100644 --- a/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs +++ b/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs @@ -32,9 +32,14 @@ namespace osu.Game.Screens.Edit public ISkin? ReferenceBeatmapSkin { get; } /// - /// Whether all objects should be cleared from the new difficulty. + /// Whether the new difficulty should be blank. /// - public bool ClearAllObjects { get; } + /// + /// A blank difficulty will have no objects, no control points other than timing points taken from + /// and will not share values with , + /// but it will share metadata and timing information with . + /// + public bool CreateBlank { get; } /// /// The saved state of the previous which should be restored upon opening the newly-created difficulty. @@ -46,14 +51,14 @@ namespace osu.Game.Screens.Edit RulesetInfo ruleset, IBeatmap referenceBeatmap, ISkin? referenceBeatmapSkin, - bool clearAllObjects, + bool createBlank, EditorState editorState) { BeatmapSet = beatmapSet; Ruleset = ruleset; ReferenceBeatmap = referenceBeatmap; ReferenceBeatmapSkin = referenceBeatmapSkin; - ClearAllObjects = clearAllObjects; + CreateBlank = createBlank; EditorState = editorState; } } diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index ff09598eef..8c8a106791 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual return new TestWorkingBeatmapCache(this, audioManager, resources, storage, defaultBeatmap, host); } - public override WorkingBeatmap CreateNewBlankDifficulty(NewDifficultyCreationParameters creationParameters) + public override WorkingBeatmap CreateNewDifficulty(NewDifficultyCreationParameters creationParameters) { // don't actually care about properly creating a difficulty for this context. return TestBeatmap; From 90c48de9f8d68d2cdafcdde863ea671320449e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Feb 2022 20:01:47 +0100 Subject: [PATCH 0629/5199] Add failing test coverage for save of copied beatmap keeping old beatmap file --- .../Editing/TestSceneEditorBeatmapCreation.cs | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 90b1d3a6f9..0025e88e62 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -12,6 +12,7 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; @@ -236,16 +237,24 @@ namespace osu.Game.Tests.Visual.Editing AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = secondDifficultyName); AddStep("save beatmap", () => Editor.Save()); + + BeatmapInfo refetchedBeatmap = null; + Live refetchedBeatmapSet = null; + + AddStep("refetch from database", () => + { + refetchedBeatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == secondDifficultyName); + refetchedBeatmapSet = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID); + }); + AddAssert("new beatmap persisted", () => { - var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == secondDifficultyName); - var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID); - - return beatmap != null - && beatmap.DifficultyName == secondDifficultyName - && set != null - && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName)); + return refetchedBeatmap != null + && refetchedBeatmap.DifficultyName == secondDifficultyName + && refetchedBeatmapSet != null + && refetchedBeatmapSet.PerformRead(s => s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName)); }); + AddAssert("old beatmap file not deleted", () => refetchedBeatmapSet.AsNonNull().PerformRead(s => s.Files.Count == 2)); } [Test] From ecd6a68c6f0814cbd9a646f4dca4abf758162276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Feb 2022 20:05:32 +0100 Subject: [PATCH 0630/5199] Clear hash when creating copy of existing difficulty --- osu.Game/Beatmaps/BeatmapManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 87051ef650..4fa07ff518 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -136,6 +136,8 @@ namespace osu.Game.Beatmaps newBeatmapInfo.ID = Guid.NewGuid(); // clear difficulty name to avoid clashes on save. newBeatmapInfo.DifficultyName = string.Empty; + // also clear the hash, as that's what is used to match .osu files with their corresponding realm beatmaps. + newBeatmapInfo.Hash = string.Empty; } // populate circular beatmap set info <-> beatmap info references manually. From 13abc392bd139305ac876df335139580ce2f4035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 13 Feb 2022 18:54:52 +0100 Subject: [PATCH 0631/5199] Add failing test coverage for not copying online properties --- .../Visual/Editing/TestSceneEditorBeatmapCreation.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 0025e88e62..b4559ac9fa 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -201,6 +201,11 @@ namespace osu.Game.Tests.Visual.Editing new Colour4(0, 0, 255, 255) }); }); + AddStep("set status & online ID", () => + { + EditorBeatmap.BeatmapInfo.OnlineID = 123456; + EditorBeatmap.BeatmapInfo.Status = BeatmapOnlineStatus.WIP; + }); AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => @@ -235,6 +240,9 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("approach rate correctly copied", () => EditorBeatmap.Difficulty.ApproachRate == 4); AddAssert("combo colours correctly copied", () => EditorBeatmap.BeatmapSkin.AsNonNull().ComboColours.Count == 2); + AddAssert("status not copied", () => EditorBeatmap.BeatmapInfo.Status == BeatmapOnlineStatus.None); + AddAssert("online ID not copied", () => EditorBeatmap.BeatmapInfo.OnlineID == -1); + AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = secondDifficultyName); AddStep("save beatmap", () => Editor.Save()); From 5dabc9282c0e1e3be5538cd4c7996d67eae6197e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 13 Feb 2022 19:04:11 +0100 Subject: [PATCH 0632/5199] Change `BeatmapInfo` copy logic to be opt-in rather than opt-out --- osu.Game/Beatmaps/BeatmapManager.cs | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 4fa07ff518..4dd0e08dab 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -131,13 +131,28 @@ namespace osu.Game.Beatmaps else { newBeatmap = referenceBeatmap.Clone(); - newBeatmap.BeatmapInfo = newBeatmapInfo = referenceBeatmap.BeatmapInfo.Clone(); - // assign a new ID to the clone. - newBeatmapInfo.ID = Guid.NewGuid(); - // clear difficulty name to avoid clashes on save. - newBeatmapInfo.DifficultyName = string.Empty; - // also clear the hash, as that's what is used to match .osu files with their corresponding realm beatmaps. - newBeatmapInfo.Hash = string.Empty; + + var referenceBeatmapInfo = referenceBeatmap.BeatmapInfo; + newBeatmap.BeatmapInfo = newBeatmapInfo = new BeatmapInfo(referenceBeatmapInfo.Ruleset, referenceBeatmapInfo.Difficulty.Clone(), referenceBeatmapInfo.Metadata.DeepClone()) + { + // Only selected appropriate properties are copied over. + // Things like database ID, online status/ID, MD5 hash, star rating, etc. are omitted + // because they should not be copied over and/or they will be recomputed on save. + AudioLeadIn = referenceBeatmapInfo.AudioLeadIn, + StackLeniency = referenceBeatmapInfo.StackLeniency, + SpecialStyle = referenceBeatmapInfo.SpecialStyle, + LetterboxInBreaks = referenceBeatmapInfo.LetterboxInBreaks, + WidescreenStoryboard = referenceBeatmapInfo.WidescreenStoryboard, + EpilepsyWarning = referenceBeatmapInfo.EpilepsyWarning, + SamplesMatchPlaybackRate = referenceBeatmapInfo.SamplesMatchPlaybackRate, + DistanceSpacing = referenceBeatmapInfo.DistanceSpacing, + BeatDivisor = referenceBeatmapInfo.BeatDivisor, + GridSize = referenceBeatmapInfo.GridSize, + TimelineZoom = referenceBeatmapInfo.TimelineZoom, + Countdown = referenceBeatmapInfo.Countdown, + CountdownOffset = referenceBeatmapInfo.CountdownOffset, + Bookmarks = (int[])referenceBeatmapInfo.Bookmarks.Clone() + }; } // populate circular beatmap set info <-> beatmap info references manually. From df9535d195700205380bc624f2a9999cfa9e228c Mon Sep 17 00:00:00 2001 From: Kaleb Date: Sun, 13 Feb 2022 14:28:40 -0500 Subject: [PATCH 0633/5199] Update RPM calculation for readability Multiply the 1.01 factor to the resulting RPM, not to the duration. --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 4725a43a77..9be0dc748a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -45,7 +45,10 @@ namespace osu.Game.Rulesets.Osu.Mods // for that reason using ElapsedFrameTime directly leads to fewer SPM with Half Time and more SPM with Double Time. // for spinners we want the real (wall clock) elapsed time; to achieve that, unapply the clock rate locally here. double rateIndependentElapsedTime = spinner.Clock.ElapsedFrameTime / spinner.Clock.Rate; - float rotationSpeed = (float)(spinner.HitObject.SpinsRequired / (spinner.HitObject.Duration / 1.01)); + + // multiply the SPM by 1.01 to ensure that the spinner is completed. if the calculation is left exact, + // some spinners may not complete due to very minor decimal loss during calculation + float rotationSpeed = (float)(1.01 * spinner.HitObject.SpinsRequired / spinner.HitObject.Duration); spinner.RotationTracker.AddRotation(MathUtils.RadiansToDegrees((float)rateIndependentElapsedTime * rotationSpeed * MathF.PI * 2.0f)); } } From c1777f20e114da75a51c6e3fc38ad5f637b4900d Mon Sep 17 00:00:00 2001 From: Kaleb Date: Mon, 14 Feb 2022 03:11:44 -0500 Subject: [PATCH 0634/5199] Fix Spun Out tests Change 'unaffected by mods' test to use dynamic RPM value instead of a fixed value --- .../Mods/TestSceneOsuModSpunOut.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs index 24e69703a6..29d7e7b4d6 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -48,7 +48,19 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods PassCondition = () => { var counter = Player.ChildrenOfType().SingleOrDefault(); - return counter != null && Precision.AlmostEquals(counter.Result.Value, 286, 1); + var spinner = Player.ChildrenOfType().FirstOrDefault(); + + if (counter == null || spinner == null) + return false; + + // ignore cases where the spinner hasn't started as these lead to false-positives + if (Precision.AlmostEquals(counter.Result.Value, 0, 1)) + return false; + + double rateIndependentElapsedTime = spinner.Clock.ElapsedFrameTime / spinner.Clock.Rate; + float rotationSpeed = (float)(1.01 * spinner.HitObject.SpinsRequired / spinner.HitObject.Duration); + + return Precision.AlmostEquals(counter.Result.Value, rotationSpeed * 1000 * 60, 1); } }); } From 95b1bffffeceff678762ccb32e329a0b9277af6a Mon Sep 17 00:00:00 2001 From: Kaleb Date: Mon, 14 Feb 2022 03:45:02 -0500 Subject: [PATCH 0635/5199] Add test to ensure spinners only complete No bonus or a non-300 judgement --- .../Mods/TestSceneOsuModSpunOut.cs | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs index 29d7e7b4d6..e71377a505 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -14,6 +14,7 @@ using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Skinning.Default; +using osu.Game.Screens.Play; using osuTK; namespace osu.Game.Rulesets.Osu.Tests.Mods @@ -57,7 +58,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods if (Precision.AlmostEquals(counter.Result.Value, 0, 1)) return false; - double rateIndependentElapsedTime = spinner.Clock.ElapsedFrameTime / spinner.Clock.Rate; float rotationSpeed = (float)(1.01 * spinner.HitObject.SpinsRequired / spinner.HitObject.Duration); return Precision.AlmostEquals(counter.Result.Value, rotationSpeed * 1000 * 60, 1); @@ -65,6 +65,27 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }); } + [Test] + public void TestSpinnerOnlyComplete() => CreateModTest(new ModTestData + { + Mod = new OsuModSpunOut(), + Autoplay = false, + Beatmap = singleSpinnerBeatmap, + PassCondition = () => + { + var spinner = Player.ChildrenOfType().SingleOrDefault(); + var gameplayClockContainer = Player.ChildrenOfType().SingleOrDefault(); + + if (spinner == null || gameplayClockContainer == null) + return false; + + if (!Precision.AlmostEquals(gameplayClockContainer.CurrentTime, spinner.HitObject.StartTime + spinner.HitObject.Duration, 200.0f)) + return false; + + return Precision.AlmostEquals(spinner.Progress, 1.0f, 0.05f) && Precision.AlmostEquals(spinner.GainedBonus.Value, 0, 1); + } + }); + private Beatmap singleSpinnerBeatmap => new Beatmap { HitObjects = new List From b9d9fc56afa5f579389201f8d06bfa41b3c3980b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Feb 2022 17:51:39 +0900 Subject: [PATCH 0636/5199] Move files to UI namespace --- .../Visual/UserInterface/TestSceneExpandingContainer.cs | 2 ++ .../Containers}/ExpandingButtonContainer.cs | 2 +- .../{Overlays => Graphics/Containers}/ExpandingContainer.cs | 3 +-- osu.Game/{Overlays => Graphics/Containers}/IExpandable.cs | 2 +- .../{Overlays => Graphics/Containers}/IExpandingContainer.cs | 3 ++- .../{Overlays => Graphics/UserInterface}/ExpandableSlider.cs | 4 ++-- osu.Game/Overlays/Settings/SettingsSidebar.cs | 1 + osu.Game/Overlays/SettingsToolboxGroup.cs | 1 + osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- 9 files changed, 12 insertions(+), 8 deletions(-) rename osu.Game/{Overlays => Graphics/Containers}/ExpandingButtonContainer.cs (94%) rename osu.Game/{Overlays => Graphics/Containers}/ExpandingContainer.cs (97%) rename osu.Game/{Overlays => Graphics/Containers}/IExpandable.cs (93%) rename osu.Game/{Overlays => Graphics/Containers}/IExpandingContainer.cs (88%) rename osu.Game/{Overlays => Graphics/UserInterface}/ExpandableSlider.cs (97%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs index f63591311f..f4920b4412 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs @@ -4,6 +4,8 @@ using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Settings.Sections; using osuTK; diff --git a/osu.Game/Overlays/ExpandingButtonContainer.cs b/osu.Game/Graphics/Containers/ExpandingButtonContainer.cs similarity index 94% rename from osu.Game/Overlays/ExpandingButtonContainer.cs rename to osu.Game/Graphics/Containers/ExpandingButtonContainer.cs index 8fb3e1b550..b79af22bd2 100644 --- a/osu.Game/Overlays/ExpandingButtonContainer.cs +++ b/osu.Game/Graphics/Containers/ExpandingButtonContainer.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -namespace osu.Game.Overlays +namespace osu.Game.Graphics.Containers { /// /// An with a long hover expansion delay. diff --git a/osu.Game/Overlays/ExpandingContainer.cs b/osu.Game/Graphics/Containers/ExpandingContainer.cs similarity index 97% rename from osu.Game/Overlays/ExpandingContainer.cs rename to osu.Game/Graphics/Containers/ExpandingContainer.cs index ea3fffcb78..b50e008362 100644 --- a/osu.Game/Overlays/ExpandingContainer.cs +++ b/osu.Game/Graphics/Containers/ExpandingContainer.cs @@ -6,9 +6,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Threading; -using osu.Game.Graphics.Containers; -namespace osu.Game.Overlays +namespace osu.Game.Graphics.Containers { /// /// Represents a with the ability to expand/contract on hover. diff --git a/osu.Game/Overlays/IExpandable.cs b/osu.Game/Graphics/Containers/IExpandable.cs similarity index 93% rename from osu.Game/Overlays/IExpandable.cs rename to osu.Game/Graphics/Containers/IExpandable.cs index 770ac97847..593564a2f9 100644 --- a/osu.Game/Overlays/IExpandable.cs +++ b/osu.Game/Graphics/Containers/IExpandable.cs @@ -4,7 +4,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; -namespace osu.Game.Overlays +namespace osu.Game.Graphics.Containers { /// /// An interface for drawables with ability to expand/contract. diff --git a/osu.Game/Overlays/IExpandingContainer.cs b/osu.Game/Graphics/Containers/IExpandingContainer.cs similarity index 88% rename from osu.Game/Overlays/IExpandingContainer.cs rename to osu.Game/Graphics/Containers/IExpandingContainer.cs index ec5f0c90f4..a82faa3cd1 100644 --- a/osu.Game/Overlays/IExpandingContainer.cs +++ b/osu.Game/Graphics/Containers/IExpandingContainer.cs @@ -3,8 +3,9 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; -namespace osu.Game.Overlays +namespace osu.Game.Graphics.Containers { /// /// A target expanding container that should be resolved by children s to propagate state changes. diff --git a/osu.Game/Overlays/ExpandableSlider.cs b/osu.Game/Graphics/UserInterface/ExpandableSlider.cs similarity index 97% rename from osu.Game/Overlays/ExpandableSlider.cs rename to osu.Game/Graphics/UserInterface/ExpandableSlider.cs index 062de98659..60e83f9c81 100644 --- a/osu.Game/Overlays/ExpandableSlider.cs +++ b/osu.Game/Graphics/UserInterface/ExpandableSlider.cs @@ -8,11 +8,11 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osuTK; -namespace osu.Game.Overlays +namespace osu.Game.Graphics.UserInterface { /// /// An implementation for the UI slider bar control. diff --git a/osu.Game/Overlays/Settings/SettingsSidebar.cs b/osu.Game/Overlays/Settings/SettingsSidebar.cs index e6ce90c33e..4e6a1eb914 100644 --- a/osu.Game/Overlays/Settings/SettingsSidebar.cs +++ b/osu.Game/Overlays/Settings/SettingsSidebar.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Settings { diff --git a/osu.Game/Overlays/SettingsToolboxGroup.cs b/osu.Game/Overlays/SettingsToolboxGroup.cs index 9e7223df9d..08321f68fe 100644 --- a/osu.Game/Overlays/SettingsToolboxGroup.cs +++ b/osu.Game/Overlays/SettingsToolboxGroup.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Layout; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osuTK; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 92ea2db338..39783cc8bb 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -13,7 +13,7 @@ using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Game.Beatmaps; -using osu.Game.Overlays; +using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; From 3aa5908de8d0bd691c5a5839130e3ce66fabe107 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Feb 2022 18:01:56 +0900 Subject: [PATCH 0637/5199] Remove unused using statement --- osu.Game/Graphics/Containers/IExpandingContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/IExpandingContainer.cs b/osu.Game/Graphics/Containers/IExpandingContainer.cs index a82faa3cd1..eb186c96a8 100644 --- a/osu.Game/Graphics/Containers/IExpandingContainer.cs +++ b/osu.Game/Graphics/Containers/IExpandingContainer.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; -using osu.Game.Overlays; namespace osu.Game.Graphics.Containers { From e324287f796128a722a4d94435b02b90f356076e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Feb 2022 18:08:16 +0900 Subject: [PATCH 0638/5199] Reduce expansion delay on `ExpandingButtonContainer` Felt too long. --- osu.Game/Graphics/Containers/ExpandingButtonContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/ExpandingButtonContainer.cs b/osu.Game/Graphics/Containers/ExpandingButtonContainer.cs index b79af22bd2..859850e771 100644 --- a/osu.Game/Graphics/Containers/ExpandingButtonContainer.cs +++ b/osu.Game/Graphics/Containers/ExpandingButtonContainer.cs @@ -16,6 +16,6 @@ namespace osu.Game.Graphics.Containers { } - protected override double HoverExpansionDelay => 750; + protected override double HoverExpansionDelay => 400; } } From e304c031dcdffdcbd66f0f896ee160f79ee7a86d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Feb 2022 21:53:56 +0900 Subject: [PATCH 0639/5199] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 1a2859c851..6b3142fbdc 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a9c0226951..847832f8ac 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 4a1dc53281..00c9653146 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - + From c5019fefb0c9ed0a212167851acc082fe5f4e618 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Feb 2022 22:35:08 +0900 Subject: [PATCH 0640/5199] Update CI runs to target net6.0 --- .github/workflows/ci.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c52802cf6..ec3816d541 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,10 +20,10 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Install .NET 5.0.x + - name: Install .NET 6.0.x uses: actions/setup-dotnet@v1 with: - dotnet-version: "5.0.x" + dotnet-version: "6.0.x" # FIXME: libavformat is not included in Ubuntu. Let's fix that. # https://github.com/ppy/osu-framework/issues/4349 @@ -65,10 +65,10 @@ jobs: run: | $VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=11.2 - - name: Install .NET 5.0.x + - name: Install .NET 6.0.x uses: actions/setup-dotnet@v1 with: - dotnet-version: "5.0.x" + dotnet-version: "6.0.x" # Contrary to seemingly any other msbuild, msbuild running on macOS/Mono # cannot accept .sln(f) files as arguments. @@ -84,10 +84,10 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Install .NET 5.0.x + - name: Install .NET 6.0.x uses: actions/setup-dotnet@v1 with: - dotnet-version: "5.0.x" + dotnet-version: "6.0.x" # Contrary to seemingly any other msbuild, msbuild running on macOS/Mono # cannot accept .sln(f) files as arguments. @@ -102,17 +102,17 @@ jobs: - name: Checkout uses: actions/checkout@v2 - # FIXME: Tools won't run in .NET 5.0 unless you install 3.1.x LTS side by side. + # FIXME: Tools won't run in .NET 6.0 unless you install 3.1.x LTS side by side. # https://itnext.io/how-to-support-multiple-net-sdks-in-github-actions-workflows-b988daa884e - name: Install .NET 3.1.x LTS uses: actions/setup-dotnet@v1 with: dotnet-version: "3.1.x" - - name: Install .NET 5.0.x + - name: Install .NET 6.0.x uses: actions/setup-dotnet@v1 with: - dotnet-version: "5.0.x" + dotnet-version: "6.0.x" - name: Restore Tools run: dotnet tool restore From 70ba6fb7dc2e48712886a6ae3ad4eb22079e4136 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Feb 2022 22:35:30 +0900 Subject: [PATCH 0641/5199] Update .NET version in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b1dfcab416..7ace47a74f 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ You can see some examples of custom rulesets by visiting the [custom ruleset dir Please make sure you have the following prerequisites: -- A desktop platform with the [.NET 5.0 SDK](https://dotnet.microsoft.com/download) installed. +- A desktop platform with the [.NET 6.0 SDK](https://dotnet.microsoft.com/download) installed. - When developing with mobile, [Xamarin](https://docs.microsoft.com/en-us/xamarin/) is required, which is shipped together with Visual Studio or [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/). - When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/). - When running on Linux, please have a system-wide FFmpeg installation available to support video decoding. From 9b7d9d42bcd01f84375cc38d5f6bf4ab74f996a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Feb 2022 22:48:04 +0900 Subject: [PATCH 0642/5199] Update reference to `NetAnalyzers` --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 894ea25c8b..6e7015fa26 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -18,7 +18,7 @@ - + $(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset From 9ad7b5d51caab9433c2eaaecbce6a1e4dc687516 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Feb 2022 22:52:30 +0900 Subject: [PATCH 0643/5199] Remove no longer required `NoWarn` spec --- Directory.Build.props | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 6e7015fa26..c1682638c2 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -32,13 +32,8 @@ NU1701: DeepEqual is not netstandard-compatible. This is fine since we run tests with .NET Framework anyway. This is required due to https://github.com/NuGet/Home/issues/5740 - - CA9998: - Microsoft.CodeAnalysis.FxCopAnalyzers has been deprecated. - The entire package will be able to be removed after migrating to .NET 5, - as analysers are shipped as part of the .NET 5 SDK anyway. --> - $(NoWarn);NU1701;CA9998 + $(NoWarn);NU1701 false From b581ca14cca29e7c8fb28bcfbb55e0aec0ae7389 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Feb 2022 02:32:38 +0900 Subject: [PATCH 0644/5199] Update usages in line with `BorderColour` type change --- osu.Game/Graphics/UserInterfaceV2/SwitchButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/SwitchButton.cs b/osu.Game/Graphics/UserInterfaceV2/SwitchButton.cs index deb2e6baf6..c6477d1781 100644 --- a/osu.Game/Graphics/UserInterfaceV2/SwitchButton.cs +++ b/osu.Game/Graphics/UserInterfaceV2/SwitchButton.cs @@ -114,7 +114,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 private class CircularBorderContainer : CircularContainer { - public void TransformBorderTo(SRGBColour colour) + public void TransformBorderTo(ColourInfo colour) => this.TransformTo(nameof(BorderColour), colour, 250, Easing.OutQuint); } } From 02f58a82fc4682df861a6069a12316d3bf474084 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Feb 2022 02:35:08 +0900 Subject: [PATCH 0645/5199] Use `WaitSafely()` in tests where it was not already being used --- .../Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs | 3 ++- .../Multiplayer/TestSceneMultiplayerPlaylist.cs | 10 +++++----- .../Multiplayer/TestSceneMultiplayerQueueList.cs | 12 ++++++------ .../Visual/Multiplayer/TestSceneTeamVersus.cs | 2 +- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index c7eeff81fe..cd14a98751 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Online.Multiplayer; @@ -71,7 +72,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers - })); + }).WaitSafely()); AddUntilStep("api room updated", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 936798e6b4..5dd9fb9fe2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -60,7 +60,7 @@ namespace osu.Game.Tests.Visual.Multiplayer importedBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0); }); - AddStep("change to all players mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); + AddStep("change to all players mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); } [Test] @@ -97,19 +97,19 @@ namespace osu.Game.Tests.Visual.Multiplayer addItemStep(); addItemStep(); - AddStep("finish current item", () => Client.FinishCurrentItem()); + AddStep("finish current item", () => Client.FinishCurrentItem().WaitSafely()); assertItemInHistoryListStep(1, 0); assertItemInQueueListStep(2, 0); assertItemInQueueListStep(3, 1); - AddStep("finish current item", () => Client.FinishCurrentItem()); + AddStep("finish current item", () => Client.FinishCurrentItem().WaitSafely()); assertItemInHistoryListStep(2, 0); assertItemInHistoryListStep(1, 1); assertItemInQueueListStep(3, 0); - AddStep("finish current item", () => Client.FinishCurrentItem()); + AddStep("finish current item", () => Client.FinishCurrentItem().WaitSafely()); assertItemInHistoryListStep(3, 0); assertItemInHistoryListStep(2, 1); @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestListsClearedWhenRoomLeft() { addItemStep(); - AddStep("finish current item", () => Client.FinishCurrentItem()); + AddStep("finish current item", () => Client.FinishCurrentItem().WaitSafely()); AddStep("leave room", () => RoomManager.PartRoom()); AddUntilStep("wait for room part", () => !RoomJoined); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index ddf794b437..80b20ed59f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -65,13 +65,13 @@ namespace osu.Game.Tests.Visual.Multiplayer importedBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0); }); - AddStep("change to all players mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); + AddStep("change to all players mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); } [Test] public void TestDeleteButtonAlwaysVisibleForHost() { - AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); + AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); addPlaylistItem(() => API.LocalUser.Value.OnlineID); @@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestDeleteButtonOnlyVisibleForItemOwnerIfNotHost() { - AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); + AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); AddStep("join other user", () => Client.AddUser(new APIUser { Id = 1234 })); @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestCurrentItemDoesNotHaveDeleteButton() { - AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); + AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); addPlaylistItem(() => API.LocalUser.Value.OnlineID); @@ -110,7 +110,7 @@ namespace osu.Game.Tests.Visual.Multiplayer assertDeleteButtonVisibility(0, false); assertDeleteButtonVisibility(1, true); - AddStep("finish current item", () => Client.FinishCurrentItem()); + AddStep("finish current item", () => Client.FinishCurrentItem().WaitSafely()); AddUntilStep("wait for next item to be selected", () => Client.Room?.Settings.PlaylistItemId == 2); AddUntilStep("wait for two items in playlist", () => playlist.ChildrenOfType().Count() == 2); @@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Multiplayer BeatmapID = importedBeatmap.OnlineID, }); - Client.AddUserPlaylistItem(userId(), item); + Client.AddUserPlaylistItem(userId(), item).WaitSafely(); itemId = item.ID; }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 781f0a1824..2837d75553 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -143,7 +143,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change match type", () => client.ChangeSettings(new MultiplayerRoomSettings { MatchType = MatchType.TeamVersus - })); + }).WaitSafely()); AddUntilStep("api room updated to team versus", () => client.APIRoom?.Type.Value == MatchType.TeamVersus); } From 8da0800d7fc70d213ab5401e97d537f308d75cf5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Feb 2022 02:47:35 +0900 Subject: [PATCH 0646/5199] Update `ChangeFocus` usage in line with framework changes --- osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 3fd56ece58..27743e709f 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -246,7 +246,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { base.LoadComplete(); - Schedule(() => GetContainingInputManager().ChangeFocus(passwordTextBox)); + ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(passwordTextBox)); passwordTextBox.OnCommit += (_, __) => performJoin(); } From 4bd58cfde160398726aaffb80e93d630a34029f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Feb 2022 18:52:19 +0100 Subject: [PATCH 0647/5199] Update one more custom transform with `BorderColour` type change --- osu.Game/Overlays/Volume/MuteButton.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Volume/MuteButton.cs b/osu.Game/Overlays/Volume/MuteButton.cs index bcc9394aba..e9d3b31207 100644 --- a/osu.Game/Overlays/Volume/MuteButton.cs +++ b/osu.Game/Overlays/Volume/MuteButton.cs @@ -79,13 +79,13 @@ namespace osu.Game.Overlays.Volume protected override bool OnHover(HoverEvent e) { - Content.TransformTo, SRGBColour>("BorderColour", hoveredColour, 500, Easing.OutQuint); + Content.TransformTo, ColourInfo>("BorderColour", hoveredColour, 500, Easing.OutQuint); return false; } protected override void OnHoverLost(HoverLostEvent e) { - Content.TransformTo, SRGBColour>("BorderColour", unhoveredColour, 500, Easing.OutQuint); + Content.TransformTo, ColourInfo>("BorderColour", unhoveredColour, 500, Easing.OutQuint); } } } From db74a226c0b3e5d59493075b124b4dc1e8fdb0a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Feb 2022 02:54:45 +0900 Subject: [PATCH 0648/5199] Fix test regression due to mouse overlapping settings overlay --- osu.Game.Tests/Visual/Menus/TestSceneSideOverlays.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneSideOverlays.cs b/osu.Game.Tests/Visual/Menus/TestSceneSideOverlays.cs index e34ec6c46a..bbab6380ba 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneSideOverlays.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneSideOverlays.cs @@ -19,6 +19,10 @@ namespace osu.Game.Tests.Visual.Menus base.SetUpSteps(); AddAssert("no screen offset applied", () => Game.ScreenOffsetContainer.X == 0f); + + // avoids mouse interacting with settings overlay. + AddStep("move mouse to centre", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre)); + AddUntilStep("wait for overlays", () => Game.Settings.IsLoaded && Game.Notifications.IsLoaded); } From 7e75fa7117ebabf0e5cb1c8ad1b5e5d79349047a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Feb 2022 19:52:08 +0100 Subject: [PATCH 0649/5199] Revert "Change `BeatmapInfo` copy logic to be opt-in rather than opt-out" This reverts commit 5dabc9282c0e1e3be5538cd4c7996d67eae6197e. --- osu.Game/Beatmaps/BeatmapManager.cs | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 4dd0e08dab..4fa07ff518 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -131,28 +131,13 @@ namespace osu.Game.Beatmaps else { newBeatmap = referenceBeatmap.Clone(); - - var referenceBeatmapInfo = referenceBeatmap.BeatmapInfo; - newBeatmap.BeatmapInfo = newBeatmapInfo = new BeatmapInfo(referenceBeatmapInfo.Ruleset, referenceBeatmapInfo.Difficulty.Clone(), referenceBeatmapInfo.Metadata.DeepClone()) - { - // Only selected appropriate properties are copied over. - // Things like database ID, online status/ID, MD5 hash, star rating, etc. are omitted - // because they should not be copied over and/or they will be recomputed on save. - AudioLeadIn = referenceBeatmapInfo.AudioLeadIn, - StackLeniency = referenceBeatmapInfo.StackLeniency, - SpecialStyle = referenceBeatmapInfo.SpecialStyle, - LetterboxInBreaks = referenceBeatmapInfo.LetterboxInBreaks, - WidescreenStoryboard = referenceBeatmapInfo.WidescreenStoryboard, - EpilepsyWarning = referenceBeatmapInfo.EpilepsyWarning, - SamplesMatchPlaybackRate = referenceBeatmapInfo.SamplesMatchPlaybackRate, - DistanceSpacing = referenceBeatmapInfo.DistanceSpacing, - BeatDivisor = referenceBeatmapInfo.BeatDivisor, - GridSize = referenceBeatmapInfo.GridSize, - TimelineZoom = referenceBeatmapInfo.TimelineZoom, - Countdown = referenceBeatmapInfo.Countdown, - CountdownOffset = referenceBeatmapInfo.CountdownOffset, - Bookmarks = (int[])referenceBeatmapInfo.Bookmarks.Clone() - }; + newBeatmap.BeatmapInfo = newBeatmapInfo = referenceBeatmap.BeatmapInfo.Clone(); + // assign a new ID to the clone. + newBeatmapInfo.ID = Guid.NewGuid(); + // clear difficulty name to avoid clashes on save. + newBeatmapInfo.DifficultyName = string.Empty; + // also clear the hash, as that's what is used to match .osu files with their corresponding realm beatmaps. + newBeatmapInfo.Hash = string.Empty; } // populate circular beatmap set info <-> beatmap info references manually. From 40cfee34211387866835612abf142b893b5498c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Feb 2022 19:54:40 +0100 Subject: [PATCH 0650/5199] Explicitly reset online ID and beatmap status on copy --- osu.Game/Beatmaps/BeatmapManager.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 4fa07ff518..884209ee56 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -136,8 +136,11 @@ namespace osu.Game.Beatmaps newBeatmapInfo.ID = Guid.NewGuid(); // clear difficulty name to avoid clashes on save. newBeatmapInfo.DifficultyName = string.Empty; - // also clear the hash, as that's what is used to match .osu files with their corresponding realm beatmaps. + // clear the hash, as that's what is used to match .osu files with their corresponding realm beatmaps. newBeatmapInfo.Hash = string.Empty; + // clear online properties. + newBeatmapInfo.OnlineID = -1; + newBeatmapInfo.Status = BeatmapOnlineStatus.None; } // populate circular beatmap set info <-> beatmap info references manually. From 1685e214d36463e225971c6ffef3fad73aae0735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Feb 2022 19:59:54 +0100 Subject: [PATCH 0651/5199] Adjust test coverage to cover desired copy naming scheme --- .../Editing/TestSceneEditorBeatmapCreation.cs | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index b4559ac9fa..0a2f622da1 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -172,10 +172,10 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestCopyDifficulty() { - string firstDifficultyName = Guid.NewGuid().ToString(); - string secondDifficultyName = Guid.NewGuid().ToString(); + string originalDifficultyName = Guid.NewGuid().ToString(); + string copyDifficultyName = $"{originalDifficultyName} (copy)"; - AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName); + AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = originalDifficultyName); AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 })); AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] { @@ -210,11 +210,11 @@ namespace osu.Game.Tests.Visual.Editing AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => { - var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == firstDifficultyName); + var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == originalDifficultyName); var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID); return beatmap != null - && beatmap.DifficultyName == firstDifficultyName + && beatmap.DifficultyName == originalDifficultyName && set != null && set.PerformRead(s => s.Beatmaps.Single().ID == beatmap.ID); }); @@ -228,9 +228,10 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("wait for created", () => { string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; - return difficultyName != null && difficultyName != firstDifficultyName; + return difficultyName != null && difficultyName != originalDifficultyName; }); + AddAssert("created difficulty has copy suffix in name", () => EditorBeatmap.BeatmapInfo.DifficultyName == copyDifficultyName); AddAssert("created difficulty has timing point", () => { var timingPoint = EditorBeatmap.ControlPointInfo.TimingPoints.Single(); @@ -243,7 +244,6 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("status not copied", () => EditorBeatmap.BeatmapInfo.Status == BeatmapOnlineStatus.None); AddAssert("online ID not copied", () => EditorBeatmap.BeatmapInfo.OnlineID == -1); - AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = secondDifficultyName); AddStep("save beatmap", () => Editor.Save()); BeatmapInfo refetchedBeatmap = null; @@ -251,16 +251,19 @@ namespace osu.Game.Tests.Visual.Editing AddStep("refetch from database", () => { - refetchedBeatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == secondDifficultyName); + refetchedBeatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == copyDifficultyName); refetchedBeatmapSet = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID); }); AddAssert("new beatmap persisted", () => { return refetchedBeatmap != null - && refetchedBeatmap.DifficultyName == secondDifficultyName + && refetchedBeatmap.DifficultyName == copyDifficultyName && refetchedBeatmapSet != null - && refetchedBeatmapSet.PerformRead(s => s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName)); + && refetchedBeatmapSet.PerformRead(s => + s.Beatmaps.Count == 2 + && s.Beatmaps.Any(b => b.DifficultyName == originalDifficultyName) + && s.Beatmaps.Any(b => b.DifficultyName == copyDifficultyName)); }); AddAssert("old beatmap file not deleted", () => refetchedBeatmapSet.AsNonNull().PerformRead(s => s.Files.Count == 2)); } From 62214471647382ff03bf1449db2c03bde8cfa9de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Feb 2022 20:19:12 +0100 Subject: [PATCH 0652/5199] Append copy suffix on creating copy of difficulty --- osu.Game/Beatmaps/BeatmapManager.cs | 4 ++-- osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Screens/Edit/EditorLoader.cs | 9 ++++++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 884209ee56..d1b8e88743 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -134,8 +134,8 @@ namespace osu.Game.Beatmaps newBeatmap.BeatmapInfo = newBeatmapInfo = referenceBeatmap.BeatmapInfo.Clone(); // assign a new ID to the clone. newBeatmapInfo.ID = Guid.NewGuid(); - // clear difficulty name to avoid clashes on save. - newBeatmapInfo.DifficultyName = string.Empty; + // add "(copy)" suffix to difficulty name to avoid clashes on save. + newBeatmapInfo.DifficultyName += " (copy)"; // clear the hash, as that's what is used to match .osu files with their corresponding realm beatmaps. newBeatmapInfo.Hash = string.Empty; // clear online properties. diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 37d4dce60e..7a3c4f2a19 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -857,7 +857,7 @@ namespace osu.Game.Screens.Edit ( editorBeatmap.BeatmapInfo.BeatmapSet.AsNonNull(), rulesetInfo, - playableBeatmap, + editorBeatmap, editorBeatmap.BeatmapSkin, clearAllObjects, GetState() diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index be3e68c857..505a57f157 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -84,7 +84,14 @@ namespace osu.Game.Screens.Edit { try { - return beatmapManager.CreateNewDifficulty(creationParameters); + var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(creationParameters.ReferenceBeatmap.BeatmapInfo); + return beatmapManager.CreateNewDifficulty(new NewDifficultyCreationParameters( + refetchedBeatmap.BeatmapSetInfo, + refetchedBeatmap.BeatmapInfo.Ruleset, + refetchedBeatmap.Beatmap, + refetchedBeatmap.Skin, + creationParameters.CreateBlank, + creationParameters.EditorState)); } catch (Exception ex) { From e45a2ae0fc9b24af51454589be31923cb80543ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Feb 2022 20:56:05 +0100 Subject: [PATCH 0653/5199] Restructure difficulty copy flow to adapt to latest changes --- osu.Game/Beatmaps/BeatmapManager.cs | 83 +++++++++++-------- .../Screens/Edit/CreateNewDifficultyDialog.cs | 12 +-- osu.Game/Screens/Edit/Editor.cs | 27 ++---- osu.Game/Screens/Edit/EditorLoader.cs | 21 ++--- .../Edit/NewDifficultyCreationParameters.cs | 65 --------------- osu.Game/Tests/Visual/EditorTestScene.cs | 8 +- 6 files changed, 83 insertions(+), 133 deletions(-) delete mode 100644 osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index d1b8e88743..777d5db2ad 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -20,7 +20,6 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; -using osu.Game.Screens.Edit; using osu.Game.Skinning; using osu.Game.Stores; @@ -109,55 +108,73 @@ namespace osu.Game.Beatmaps } /// - /// Add a new difficulty to the beatmap set represented by the provided . + /// Add a new difficulty to the provided based on the provided . /// The new difficulty will be backed by a model /// and represented by the returned . /// - public virtual WorkingBeatmap CreateNewDifficulty(NewDifficultyCreationParameters creationParameters) + /// + /// Contrary to , this method does not preserve hitobjects and beatmap-level settings from . + /// The created beatmap will have zero hitobjects and will have default settings (including difficulty settings), but will preserve metadata and existing timing points. + /// + /// The to add the new difficulty to. + /// The to use as a baseline reference when creating the new difficulty. + /// The ruleset with which the new difficulty should be created. + public virtual WorkingBeatmap CreateNewDifficulty(BeatmapSetInfo targetBeatmapSet, WorkingBeatmap referenceWorkingBeatmap, RulesetInfo rulesetInfo) { - var referenceBeatmap = creationParameters.ReferenceBeatmap; - var targetBeatmapSet = creationParameters.BeatmapSet; + var playableBeatmap = referenceWorkingBeatmap.GetPlayableBeatmap(rulesetInfo); + var newBeatmapInfo = new BeatmapInfo(rulesetInfo, new BeatmapDifficulty(), playableBeatmap.Metadata.DeepClone()); + var newBeatmap = new Beatmap { BeatmapInfo = newBeatmapInfo }; + foreach (var timingPoint in playableBeatmap.ControlPointInfo.TimingPoints) + newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone()); + + return addDifficultyToSet(targetBeatmapSet, newBeatmap, referenceWorkingBeatmap.Skin); + } + + /// + /// Add a copy of the provided to the provided . + /// The new difficulty will be backed by a model + /// and represented by the returned . + /// + /// + /// Contrary to , this method creates a nearly-exact copy of + /// (with the exception of a few key properties that cannot be copied under any circumstance, like difficulty name, beatmap hash, or online status). + /// + /// The to add the copy to. + /// The to be copied. + public virtual WorkingBeatmap CopyExistingDifficulty(BeatmapSetInfo targetBeatmapSet, WorkingBeatmap referenceWorkingBeatmap) + { + var newBeatmap = referenceWorkingBeatmap.GetPlayableBeatmap(referenceWorkingBeatmap.BeatmapInfo.Ruleset).Clone(); BeatmapInfo newBeatmapInfo; - IBeatmap newBeatmap; - if (creationParameters.CreateBlank) - { - newBeatmapInfo = new BeatmapInfo(creationParameters.Ruleset, new BeatmapDifficulty(), referenceBeatmap.Metadata.DeepClone()); - newBeatmap = new Beatmap { BeatmapInfo = newBeatmapInfo }; - foreach (var timingPoint in referenceBeatmap.ControlPointInfo.TimingPoints) - newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone()); - } - else - { - newBeatmap = referenceBeatmap.Clone(); - newBeatmap.BeatmapInfo = newBeatmapInfo = referenceBeatmap.BeatmapInfo.Clone(); - // assign a new ID to the clone. - newBeatmapInfo.ID = Guid.NewGuid(); - // add "(copy)" suffix to difficulty name to avoid clashes on save. - newBeatmapInfo.DifficultyName += " (copy)"; - // clear the hash, as that's what is used to match .osu files with their corresponding realm beatmaps. - newBeatmapInfo.Hash = string.Empty; - // clear online properties. - newBeatmapInfo.OnlineID = -1; - newBeatmapInfo.Status = BeatmapOnlineStatus.None; - } + newBeatmap.BeatmapInfo = newBeatmapInfo = referenceWorkingBeatmap.BeatmapInfo.Clone(); + // assign a new ID to the clone. + newBeatmapInfo.ID = Guid.NewGuid(); + // add "(copy)" suffix to difficulty name to avoid clashes on save. + newBeatmapInfo.DifficultyName += " (copy)"; + // clear the hash, as that's what is used to match .osu files with their corresponding realm beatmaps. + newBeatmapInfo.Hash = string.Empty; + // clear online properties. + newBeatmapInfo.OnlineID = -1; + newBeatmapInfo.Status = BeatmapOnlineStatus.None; + return addDifficultyToSet(targetBeatmapSet, newBeatmap, referenceWorkingBeatmap.Skin); + } + + private WorkingBeatmap addDifficultyToSet(BeatmapSetInfo targetBeatmapSet, IBeatmap newBeatmap, ISkin beatmapSkin) + { // populate circular beatmap set info <-> beatmap info references manually. // several places like `BeatmapModelManager.Save()` or `GetWorkingBeatmap()` // rely on them being freely traversable in both directions for correct operation. - targetBeatmapSet.Beatmaps.Add(newBeatmapInfo); - newBeatmapInfo.BeatmapSet = targetBeatmapSet; + targetBeatmapSet.Beatmaps.Add(newBeatmap.BeatmapInfo); + newBeatmap.BeatmapInfo.BeatmapSet = targetBeatmapSet; - beatmapModelManager.Save(newBeatmapInfo, newBeatmap, creationParameters.ReferenceBeatmapSkin); + beatmapModelManager.Save(newBeatmap.BeatmapInfo, newBeatmap, beatmapSkin); workingBeatmapCache.Invalidate(targetBeatmapSet); return GetWorkingBeatmap(newBeatmap.BeatmapInfo); } - // TODO: add back support for making a copy of another difficulty - // (likely via a separate `CopyDifficulty()` method). - /// /// Delete a beatmap difficulty. /// diff --git a/osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs b/osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs index 138e13bda1..aa6ca280ee 100644 --- a/osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs +++ b/osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs @@ -10,11 +10,11 @@ namespace osu.Game.Screens.Edit { /// /// Delegate used to create new difficulties. - /// A value of in the clearAllObjects parameter - /// indicates that the new difficulty should have its hitobjects cleared; - /// otherwise, the new difficulty should be an exact copy of an existing one. + /// A value of in the createCopy parameter + /// indicates that the new difficulty should be an exact copy of an existing one; + /// otherwise, the new difficulty should have its hitobjects and beatmap-level settings cleared. /// - public delegate void CreateNewDifficulty(bool clearAllObjects); + public delegate void CreateNewDifficulty(bool createCopy); public CreateNewDifficultyDialog(CreateNewDifficulty createNewDifficulty) { @@ -27,12 +27,12 @@ namespace osu.Game.Screens.Edit new PopupDialogOkButton { Text = "Yeah, let's start from scratch!", - Action = () => createNewDifficulty.Invoke(true) + Action = () => createNewDifficulty.Invoke(false) }, new PopupDialogCancelButton { Text = "No, create an exact copy of this difficulty", - Action = () => createNewDifficulty.Invoke(false) + Action = () => createNewDifficulty.Invoke(true) }, new PopupDialogCancelButton { diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7a3c4f2a19..c2775ae101 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -9,7 +9,6 @@ using JetBrains.Annotations; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -359,14 +358,14 @@ namespace osu.Game.Screens.Edit /// /// Creates an instance representing the current state of the editor. /// - /// - /// The next beatmap to be shown, in the case of difficulty switch. + /// + /// The ruleset of the next beatmap to be shown, in the case of difficulty switch. /// indicates that the beatmap will not be changing. /// - public EditorState GetState([CanBeNull] BeatmapInfo nextBeatmap = null) => new EditorState + public EditorState GetState([CanBeNull] RulesetInfo nextRuleset = null) => new EditorState { Time = clock.CurrentTimeAccurate, - ClipboardContent = nextBeatmap == null || editorBeatmap.BeatmapInfo.Ruleset.ShortName == nextBeatmap.Ruleset.ShortName ? Clipboard.Content.Value : string.Empty + ClipboardContent = nextRuleset == null || editorBeatmap.BeatmapInfo.Ruleset.ShortName == nextRuleset.ShortName ? Clipboard.Content.Value : string.Empty }; /// @@ -845,23 +844,15 @@ namespace osu.Game.Screens.Edit { if (!rulesetInfo.Equals(editorBeatmap.BeatmapInfo.Ruleset)) { - switchToNewDifficulty(rulesetInfo, true); + switchToNewDifficulty(rulesetInfo, false); return; } - dialogOverlay.Push(new CreateNewDifficultyDialog(clearAllObjects => switchToNewDifficulty(rulesetInfo, clearAllObjects))); + dialogOverlay.Push(new CreateNewDifficultyDialog(createCopy => switchToNewDifficulty(rulesetInfo, createCopy))); } - private void switchToNewDifficulty(RulesetInfo rulesetInfo, bool clearAllObjects) - => loader?.ScheduleSwitchToNewDifficulty(new NewDifficultyCreationParameters - ( - editorBeatmap.BeatmapInfo.BeatmapSet.AsNonNull(), - rulesetInfo, - editorBeatmap, - editorBeatmap.BeatmapSkin, - clearAllObjects, - GetState() - )); + private void switchToNewDifficulty(RulesetInfo rulesetInfo, bool createCopy) + => loader?.ScheduleSwitchToNewDifficulty(editorBeatmap.BeatmapInfo, rulesetInfo, createCopy, GetState(rulesetInfo)); private EditorMenuItem createDifficultySwitchMenu() { @@ -886,7 +877,7 @@ namespace osu.Game.Screens.Edit return new EditorMenuItem("Change difficulty") { Items = difficultyItems }; } - protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleSwitchToExistingDifficulty(nextBeatmap, GetState(nextBeatmap)); + protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleSwitchToExistingDifficulty(nextBeatmap, GetState(nextBeatmap.Ruleset)); private void cancelExit() { diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 505a57f157..0a2b8437fa 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -4,6 +4,7 @@ using System; using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; @@ -11,6 +12,7 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; @@ -79,19 +81,18 @@ namespace osu.Game.Screens.Edit } } - public void ScheduleSwitchToNewDifficulty(NewDifficultyCreationParameters creationParameters) + public void ScheduleSwitchToNewDifficulty(BeatmapInfo referenceBeatmapInfo, RulesetInfo rulesetInfo, bool createCopy, EditorState editorState) => scheduleDifficultySwitch(() => { try { - var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(creationParameters.ReferenceBeatmap.BeatmapInfo); - return beatmapManager.CreateNewDifficulty(new NewDifficultyCreationParameters( - refetchedBeatmap.BeatmapSetInfo, - refetchedBeatmap.BeatmapInfo.Ruleset, - refetchedBeatmap.Beatmap, - refetchedBeatmap.Skin, - creationParameters.CreateBlank, - creationParameters.EditorState)); + // fetch a fresh detached reference from database to avoid polluting model instances attached to cached working beatmaps. + var targetBeatmapSet = beatmapManager.QueryBeatmap(b => b.ID == referenceBeatmapInfo.ID).AsNonNull().BeatmapSet.AsNonNull(); + var referenceWorkingBeatmap = beatmapManager.GetWorkingBeatmap(referenceBeatmapInfo); + + return createCopy + ? beatmapManager.CopyExistingDifficulty(targetBeatmapSet, referenceWorkingBeatmap) + : beatmapManager.CreateNewDifficulty(targetBeatmapSet, referenceWorkingBeatmap, rulesetInfo); } catch (Exception ex) { @@ -100,7 +101,7 @@ namespace osu.Game.Screens.Edit Logger.Error(ex, ex.Message); return Beatmap.Value; } - }, creationParameters.EditorState); + }, editorState); public void ScheduleSwitchToExistingDifficulty(BeatmapInfo beatmapInfo, EditorState editorState) => scheduleDifficultySwitch(() => beatmapManager.GetWorkingBeatmap(beatmapInfo), editorState); diff --git a/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs b/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs deleted file mode 100644 index a6458a9456..0000000000 --- a/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable enable - -using osu.Game.Beatmaps; -using osu.Game.Rulesets; -using osu.Game.Skinning; - -namespace osu.Game.Screens.Edit -{ - public class NewDifficultyCreationParameters - { - /// - /// The that should contain the newly-created difficulty. - /// - public BeatmapSetInfo BeatmapSet { get; } - - /// - /// The that the new difficulty should be playable for. - /// - public RulesetInfo Ruleset { get; } - - /// - /// A reference upon which the new difficulty should be based. - /// - public IBeatmap ReferenceBeatmap { get; } - - /// - /// A reference that the new difficulty should base its own skin upon. - /// - public ISkin? ReferenceBeatmapSkin { get; } - - /// - /// Whether the new difficulty should be blank. - /// - /// - /// A blank difficulty will have no objects, no control points other than timing points taken from - /// and will not share values with , - /// but it will share metadata and timing information with . - /// - public bool CreateBlank { get; } - - /// - /// The saved state of the previous which should be restored upon opening the newly-created difficulty. - /// - public EditorState EditorState { get; } - - public NewDifficultyCreationParameters( - BeatmapSetInfo beatmapSet, - RulesetInfo ruleset, - IBeatmap referenceBeatmap, - ISkin? referenceBeatmapSkin, - bool createBlank, - EditorState editorState) - { - BeatmapSet = beatmapSet; - Ruleset = ruleset; - ReferenceBeatmap = referenceBeatmap; - ReferenceBeatmapSkin = referenceBeatmapSkin; - CreateBlank = createBlank; - EditorState = editorState; - } - } -} diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 8c8a106791..24015590e2 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -136,7 +136,13 @@ namespace osu.Game.Tests.Visual return new TestWorkingBeatmapCache(this, audioManager, resources, storage, defaultBeatmap, host); } - public override WorkingBeatmap CreateNewDifficulty(NewDifficultyCreationParameters creationParameters) + public override WorkingBeatmap CreateNewDifficulty(BeatmapSetInfo targetBeatmapSet, WorkingBeatmap referenceWorkingBeatmap, RulesetInfo rulesetInfo) + { + // don't actually care about properly creating a difficulty for this context. + return TestBeatmap; + } + + public override WorkingBeatmap CopyExistingDifficulty(BeatmapSetInfo targetBeatmapSet, WorkingBeatmap referenceWorkingBeatmap) { // don't actually care about properly creating a difficulty for this context. return TestBeatmap; From 19ee05c232eb86710001bdad3bef2e135f7bc97a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 17 Jan 2022 20:09:02 +0300 Subject: [PATCH 0654/5199] Add "distance spacing" multiplier for osu! ruleset While osu!catch also implements a distance snap grid, it doesn't rely on `GetBeatSnapDistanceAt` (unlike osu!), therefore it can't have the "distance spacing" multiplier yet. --- .../Edit/CatchHitObjectComposer.cs | 2 + .../Edit/ManiaHitObjectComposer.cs | 2 + .../Edit/OsuHitObjectComposer.cs | 2 + .../Edit/TaikoHitObjectComposer.cs | 2 + .../UserInterface/ExpandableSlider.cs | 9 ++ osu.Game/Rulesets/Edit/HitObjectComposer.cs | 101 +++++++++++++++++- 6 files changed, 116 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index 164f465438..19595de3b1 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Catch.Edit private readonly Bindable distanceSnapToggle = new Bindable(); + protected override bool SupportsDistanceSpacing => false; + private InputManager inputManager; public CatchHitObjectComposer(CatchRuleset ruleset) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 2baec95c94..5752f9f014 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Mania.Edit private ManiaBeatSnapGrid beatSnapGrid; private InputManager inputManager; + protected override bool SupportsDistanceSpacing => false; + public ManiaHitObjectComposer(Ruleset ruleset) : base(ruleset) { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 1e84ec80e1..e7eabdc748 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Osu.Edit { public class OsuHitObjectComposer : HitObjectComposer { + protected override bool SupportsDistanceSpacing => true; + public OsuHitObjectComposer(Ruleset ruleset) : base(ruleset) { diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs index 161799c980..af9bc6c3a3 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs @@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Taiko.Edit { public class TaikoHitObjectComposer : HitObjectComposer { + protected override bool SupportsDistanceSpacing => false; + public TaikoHitObjectComposer(TaikoRuleset ruleset) : base(ruleset) { diff --git a/osu.Game/Graphics/UserInterface/ExpandableSlider.cs b/osu.Game/Graphics/UserInterface/ExpandableSlider.cs index 60e83f9c81..a05c0cfab0 100644 --- a/osu.Game/Graphics/UserInterface/ExpandableSlider.cs +++ b/osu.Game/Graphics/UserInterface/ExpandableSlider.cs @@ -70,6 +70,15 @@ namespace osu.Game.Graphics.UserInterface set => slider.Current = value; } + /// + /// A custom step value for each key press which actuates a change on this control. + /// + public float KeyboardStep + { + get => slider.KeyboardStep; + set => slider.KeyboardStep = value; + } + public BindableBool Expanded { get; } = new BindableBool(); public override bool HandlePositionalInput => true; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 39783cc8bb..528ba2fb8b 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -14,6 +14,8 @@ using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings.Sections; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; @@ -56,6 +58,26 @@ namespace osu.Game.Rulesets.Edit [Resolved] protected IBeatSnapProvider BeatSnapProvider { get; private set; } + /// + /// Whether this composer supports a "distance spacing" multiplier for distance snap grids. + /// + /// + /// Setting this to displays a "distance spacing" slider and allows this composer to configure the value of . + /// + protected abstract bool SupportsDistanceSpacing { get; } + + private readonly BindableFloat distanceSpacing = new BindableFloat + { + Default = 1.0f, + MinValue = 0.1f, + MaxValue = 6.0f, + Precision = 0.01f, + }; + + public override IBindable DistanceSpacingMultiplier => distanceSpacing; + + private SnappingToolboxContainer snappingToolboxContainer; + protected ComposeBlueprintContainer BlueprintContainer { get; private set; } private DrawableEditorRulesetWrapper drawableRulesetWrapper; @@ -117,6 +139,8 @@ namespace osu.Game.Rulesets.Edit }, new LeftToolboxFlow { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, Children = new Drawable[] { new EditorToolboxGroup("toolbox (1-9)") @@ -132,11 +156,41 @@ namespace osu.Game.Rulesets.Edit Direction = FillDirection.Vertical, Spacing = new Vector2(0, 5), }, - } + }, } }, }; + distanceSpacing.Value = (float)EditorBeatmap.BeatmapInfo.DistanceSpacing; + + if (SupportsDistanceSpacing) + { + ExpandableSlider distanceSpacingSlider; + + AddInternal(snappingToolboxContainer = new SnappingToolboxContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Child = new EditorToolboxGroup("snapping") + { + Child = distanceSpacingSlider = new ExpandableSlider + { + Current = { BindTarget = distanceSpacing }, + KeyboardStep = 0.01f, + } + } + }); + + distanceSpacing.BindValueChanged(v => + { + distanceSpacingSlider.ContractedLabelText = $"D. S. ({v.NewValue:0.##x})"; + distanceSpacingSlider.ExpandedLabelText = $"Distance Spacing ({v.NewValue:0.##x})"; + EditorBeatmap.BeatmapInfo.DistanceSpacing = v.NewValue; + }, true); + } + else + distanceSpacing.Disabled = true; + toolboxCollection.Items = CompositionTools .Prepend(new SelectTool()) .Select(t => new RadioButton(t.Name, () => toolSelected(t), t.CreateIcon)) @@ -211,8 +265,17 @@ namespace osu.Game.Rulesets.Edit #region Tool selection logic + private bool distanceSpacingScrollActive; + protected override bool OnKeyDown(KeyDownEvent e) { + if (SupportsDistanceSpacing && e.AltPressed && e.Key == Key.D && !e.Repeat) + { + snappingToolboxContainer.Expanded.Value = true; + distanceSpacingScrollActive = true; + return true; + } + if (e.ControlPressed || e.AltPressed || e.SuperPressed) return false; @@ -242,6 +305,28 @@ namespace osu.Game.Rulesets.Edit return base.OnKeyDown(e); } + protected override void OnKeyUp(KeyUpEvent e) + { + if (distanceSpacingScrollActive && (e.Key == Key.AltLeft || e.Key == Key.AltRight || e.Key == Key.D)) + { + snappingToolboxContainer.Expanded.Value = false; + distanceSpacingScrollActive = false; + } + + base.OnKeyUp(e); + } + + protected override bool OnScroll(ScrollEvent e) + { + if (distanceSpacingScrollActive) + { + distanceSpacing.Value += e.ScrollDelta.Y * (e.ShiftPressed || e.IsPrecise ? 0.01f : 0.1f); + return true; + } + + return base.OnScroll(e); + } + private bool checkLeftToggleFromKey(Key key, out int index) { if (key < Key.Number1 || key > Key.Number9) @@ -383,7 +468,7 @@ namespace osu.Game.Rulesets.Edit public override float GetBeatSnapDistanceAt(HitObject referenceObject) { - return (float)(100 * EditorBeatmap.Difficulty.SliderMultiplier * referenceObject.DifficultyControlPoint.SliderVelocity / BeatSnapProvider.BeatDivisor); + return (float)(100 * referenceObject.DifficultyControlPoint.SliderVelocity * EditorBeatmap.Difficulty.SliderMultiplier * distanceSpacing.Value / BeatSnapProvider.BeatDivisor); } public override float DurationToDistance(HitObject referenceObject, double duration) @@ -432,6 +517,18 @@ namespace osu.Game.Rulesets.Edit FillFlow.Spacing = new Vector2(10); } } + + private class SnappingToolboxContainer : ExpandingContainer + { + public SnappingToolboxContainer() + : base(130, 250) + { + RelativeSizeAxes = Axes.Y; + Padding = new MarginPadding { Left = 10 }; + + FillFlow.Spacing = new Vector2(10); + } + } } /// From 528dc03b8c06b94d1b45c0d07e8b681ba109fe00 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 18 Jan 2022 15:25:32 +0300 Subject: [PATCH 0655/5199] Expose distance spacing in `IPositionSnapProvider` for updating distance grid Alternate method is to expose a `SnapDistancesChanged` event in `IPositionSnapProvider` instead, but I chose this way as an analogue to `IBeatSnapProvider.BeatDivisor`, which might even make sense to be exposed as `BindableBeatDivisor` instead of caching that separately. --- .../Editor/TestSceneManiaBeatSnapGrid.cs | 3 +++ .../Editor/TestSceneOsuDistanceSnapGrid.cs | 3 +++ .../Visual/Editing/TestSceneDistanceSnapGrid.cs | 3 +++ osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 ++ osu.Game/Rulesets/Edit/IPositionSnapProvider.cs | 8 ++++++++ .../Screens/Edit/Compose/Components/DistanceSnapGrid.cs | 6 ++++++ .../Screens/Edit/Compose/Components/Timeline/Timeline.cs | 2 ++ 7 files changed, 27 insertions(+) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs index 50be13c4e0..38779ab47b 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Events; using osu.Framework.Timing; @@ -98,6 +99,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor set => InternalChild = value; } + public override IBindable DistanceSpacingMultiplier => throw new System.NotImplementedException(); + public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) { throw new System.NotImplementedException(); diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs index c770e2d96f..c5829d5351 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs @@ -4,6 +4,7 @@ using System; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -186,6 +187,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0); + public IBindable DistanceSpacingMultiplier { get; } = new BindableFloat(1); + public float GetBeatSnapDistanceAt(HitObject referenceObject) => (float)beat_length; public float DurationToDistance(HitObject referenceObject, double duration) => (float)duration; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs index 0d9e06e471..cba6d7692d 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.ControlPoints; @@ -166,6 +167,8 @@ namespace osu.Game.Tests.Visual.Editing public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0); + public IBindable DistanceSpacingMultiplier { get; } = new BindableFloat(1); + public float GetBeatSnapDistanceAt(HitObject referenceObject) => 10; public float DurationToDistance(HitObject referenceObject, double duration) => (float)duration; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 528ba2fb8b..6cdc2699aa 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -563,6 +563,8 @@ namespace osu.Game.Rulesets.Edit #region IPositionSnapProvider + public abstract IBindable DistanceSpacingMultiplier { get; } + public abstract SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition); public virtual SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) => diff --git a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs index 743a2f41fc..1cfcb86e10 100644 --- a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs @@ -1,6 +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.Bindables; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; using osuTK; @@ -8,6 +10,12 @@ namespace osu.Game.Rulesets.Edit { public interface IPositionSnapProvider { + /// + /// The spacing multiplier applied to beat snap distances. + /// + /// + IBindable DistanceSpacingMultiplier { get; } + /// /// Given a position, find a valid time and position snap. /// diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 05bf405f3c..afbe83ec83 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -52,6 +53,8 @@ namespace osu.Game.Screens.Edit.Compose.Components [Resolved] private BindableBeatDivisor beatDivisor { get; set; } + private IBindable distanceSpacingMultiplier; + private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); private readonly double? endTime; @@ -82,6 +85,9 @@ namespace osu.Game.Screens.Edit.Compose.Components base.LoadComplete(); beatDivisor.BindValueChanged(_ => updateSpacing(), true); + + distanceSpacingMultiplier = SnapProvider.DistanceSpacingMultiplier.GetBoundCopy(); + distanceSpacingMultiplier.BindValueChanged(_ => updateSpacing(), true); } private void updateSpacing() diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 51cca4ceff..f3a328c6bc 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -317,6 +317,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private double getTimeFromPosition(Vector2 localPosition) => (localPosition.X / Content.DrawWidth) * track.Length; + public IBindable DistanceSpacingMultiplier => throw new NotImplementedException(); + public float GetBeatSnapDistanceAt(HitObject referenceObject) => throw new NotImplementedException(); public float DurationToDistance(HitObject referenceObject, double duration) => throw new NotImplementedException(); From 9504942cfda38af4e6c5c15450a8258f1082de8c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 18 Jan 2022 18:30:31 +0300 Subject: [PATCH 0656/5199] Add test case for distance spacing hotkeys scrolling behaviour --- .../Editing/TestSceneHitObjectComposer.cs | 24 +++++++++++++++++++ osu.Game/Overlays/SettingsToolboxGroup.cs | 7 ++++++ 2 files changed, 31 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs index 145d738f60..7ac511e761 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit; @@ -21,6 +22,7 @@ using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; using osu.Game.Screens.Edit.Compose.Components; using osuTK; +using osuTK.Input; namespace osu.Game.Tests.Visual.Editing { @@ -86,6 +88,28 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("Tool changed", () => hitObjectComposer.ChildrenOfType().First().CurrentTool is HitCircleCompositionTool); } + [Test] + public void TestDistanceSpacingHotkeys() + { + float originalSpacing = 0; + + AddStep("retrieve original spacing", () => originalSpacing = (float)editorBeatmap.BeatmapInfo.DistanceSpacing); + + AddStep("hold alt", () => InputManager.PressKey(Key.LAlt)); + AddStep("hold D", () => InputManager.PressKey(Key.D)); + + AddStep("scroll mouse 5 steps", () => InputManager.ScrollVerticalBy(5)); + AddAssert("distance spacing increased by 0.5", () => Precision.AlmostEquals(editorBeatmap.BeatmapInfo.DistanceSpacing, originalSpacing + 0.5f)); + + AddStep("hold shift", () => InputManager.PressKey(Key.LShift)); + AddStep("scroll mouse 5 steps", () => InputManager.ScrollVerticalBy(5)); + AddAssert("distance spacing increased by 0.05", () => Precision.AlmostEquals(editorBeatmap.BeatmapInfo.DistanceSpacing, originalSpacing + 0.55f)); + + AddStep("release shift", () => InputManager.ReleaseKey(Key.LShift)); + AddStep("release alt", () => InputManager.ReleaseKey(Key.LAlt)); + AddStep("release D", () => InputManager.ReleaseKey(Key.D)); + } + public class EditorBeatmapContainer : Container { private readonly IWorkingBeatmap working; diff --git a/osu.Game/Overlays/SettingsToolboxGroup.cs b/osu.Game/Overlays/SettingsToolboxGroup.cs index 08321f68fe..509410ec20 100644 --- a/osu.Game/Overlays/SettingsToolboxGroup.cs +++ b/osu.Game/Overlays/SettingsToolboxGroup.cs @@ -22,6 +22,11 @@ namespace osu.Game.Overlays { public class SettingsToolboxGroup : Container, IExpandable { + /// + /// The title of this toolbox group. + /// + public string Title { get; } + private const float transition_duration = 250; private const int container_width = 270; private const int border_thickness = 2; @@ -48,6 +53,8 @@ namespace osu.Game.Overlays /// The title to be displayed in the header of this group. public SettingsToolboxGroup(string title) { + Title = title; + AutoSizeAxes = Axes.Y; Width = container_width; Masking = true; From 500322ff214753e17a78ca5bb601412ba27e7447 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 23 Jan 2022 06:13:56 +0300 Subject: [PATCH 0657/5199] Update failing snap test scenes --- .../Editor/TestSceneObjectObjectSnap.cs | 7 +++++++ .../Editor/TestSceneSliderSnapping.cs | 10 +++++++++- .../TestSceneHitObjectComposerDistanceSnapping.cs | 1 + 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs index 7bdf131e0d..41304dcd24 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs @@ -3,9 +3,11 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Bindables; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; using osu.Game.Tests.Beatmaps; @@ -26,6 +28,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor base.SetUpSteps(); AddStep("get playfield", () => playfield = Editor.ChildrenOfType().First()); AddStep("seek to first control point", () => EditorClock.Seek(Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.First().Time)); + AddStep("set distance spacing to 1", () => + { + var distanceSpacing = (BindableFloat)Editor.ChildrenOfType().Single().DistanceSpacingMultiplier; + distanceSpacing.Value = 1; + }); } [TestCase(true)] diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs index b43b2b1461..23131668a0 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs @@ -3,12 +3,15 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Input.Events; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Input.Bindings; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; @@ -61,9 +64,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor })); AddStep("set beat divisor to 1/1", () => { - var beatDivisor = (BindableBeatDivisor)Editor.Dependencies.Get(typeof(BindableBeatDivisor)); + var beatDivisor = Editor.Dependencies.Get(); beatDivisor.Value = 1; }); + AddStep("set distance spacing to 1", () => + { + var distanceSpacing = (BindableFloat)Editor.ChildrenOfType().Single().DistanceSpacingMultiplier; + distanceSpacing.Value = 1; + }); } [Test] diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index 43f22e4e90..d9312cb7ae 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -61,6 +61,7 @@ namespace osu.Game.Tests.Editing BeatDivisor.Value = 1; composer.EditorBeatmap.Difficulty.SliderMultiplier = 1; + composer.EditorBeatmap.BeatmapInfo.DistanceSpacing = 1; composer.EditorBeatmap.ControlPointInfo.Clear(); composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }); }); From 868dcd20f56d208d41bdc769f4266b91fd355c84 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 26 Jan 2022 11:39:31 +0300 Subject: [PATCH 0658/5199] Remove `e.ShiftPressed` handling for now Broken on macOS, will be handled differently later on as discussed. --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 6cdc2699aa..948e1d770f 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -320,7 +320,7 @@ namespace osu.Game.Rulesets.Edit { if (distanceSpacingScrollActive) { - distanceSpacing.Value += e.ScrollDelta.Y * (e.ShiftPressed || e.IsPrecise ? 0.01f : 0.1f); + distanceSpacing.Value += e.ScrollDelta.Y * (e.IsPrecise ? 0.01f : 0.1f); return true; } From cbc8f7ff90e95b0d0b1876ac8f0634ac5575f643 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 15 Feb 2022 02:29:09 +0300 Subject: [PATCH 0659/5199] Remove test coverage of "shift" hotkey behaviour --- osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs index 7ac511e761..d542732e19 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs @@ -101,11 +101,6 @@ namespace osu.Game.Tests.Visual.Editing AddStep("scroll mouse 5 steps", () => InputManager.ScrollVerticalBy(5)); AddAssert("distance spacing increased by 0.5", () => Precision.AlmostEquals(editorBeatmap.BeatmapInfo.DistanceSpacing, originalSpacing + 0.5f)); - AddStep("hold shift", () => InputManager.PressKey(Key.LShift)); - AddStep("scroll mouse 5 steps", () => InputManager.ScrollVerticalBy(5)); - AddAssert("distance spacing increased by 0.05", () => Precision.AlmostEquals(editorBeatmap.BeatmapInfo.DistanceSpacing, originalSpacing + 0.55f)); - - AddStep("release shift", () => InputManager.ReleaseKey(Key.LShift)); AddStep("release alt", () => InputManager.ReleaseKey(Key.LAlt)); AddStep("release D", () => InputManager.ReleaseKey(Key.D)); } From f5d0eb41cb1041c999921ada5c271411de00513f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 15 Feb 2022 02:42:37 +0300 Subject: [PATCH 0660/5199] Update further `ChangeFocus` usages --- osu.Game/Overlays/Login/LoginPanel.cs | 3 ++- osu.Game/Overlays/LoginOverlay.cs | 2 +- osu.Game/Screens/Edit/Setup/MetadataSection.cs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Login/LoginPanel.cs b/osu.Game/Overlays/Login/LoginPanel.cs index d1e5bfe809..481abd48ab 100644 --- a/osu.Game/Overlays/Login/LoginPanel.cs +++ b/osu.Game/Overlays/Login/LoginPanel.cs @@ -183,7 +183,8 @@ namespace osu.Game.Overlays.Login break; } - if (form != null) GetContainingInputManager()?.ChangeFocus(form); + if (form != null) + ScheduleAfterChildren(() => GetContainingInputManager()?.ChangeFocus(form)); }); public override bool AcceptsFocus => true; diff --git a/osu.Game/Overlays/LoginOverlay.cs b/osu.Game/Overlays/LoginOverlay.cs index f3562aa6d9..9b2d7ca1ee 100644 --- a/osu.Game/Overlays/LoginOverlay.cs +++ b/osu.Game/Overlays/LoginOverlay.cs @@ -78,7 +78,7 @@ namespace osu.Game.Overlays panel.Bounding = true; this.FadeIn(transition_time, Easing.OutQuint); - GetContainingInputManager().ChangeFocus(panel); + ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(panel)); } protected override void PopOut() diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs index f0ca3e1bbc..571dfb3f6f 100644 --- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs +++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs @@ -71,7 +71,7 @@ namespace osu.Game.Screens.Edit.Setup base.LoadComplete(); if (string.IsNullOrEmpty(ArtistTextBox.Current.Value)) - GetContainingInputManager().ChangeFocus(ArtistTextBox); + ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(ArtistTextBox)); ArtistTextBox.Current.BindValueChanged(artist => transferIfRomanised(artist.NewValue, RomanisedArtistTextBox)); TitleTextBox.Current.BindValueChanged(title => transferIfRomanised(title.NewValue, RomanisedTitleTextBox)); From 60153bb69df75819e7e2830596884bbab8b04624 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Feb 2022 13:22:14 +0900 Subject: [PATCH 0661/5199] Update nuget packages to highest usable versions EF packages are intentionally pinned to 5.0.14 as higher versions no longer support `netstandard2.1`, which we require for xamarin projects. --- ...u.Game.Rulesets.EmptyFreeform.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- ....Game.Rulesets.EmptyScrolling.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- osu.Desktop/osu.Desktop.csproj | 9 +++++--- .../osu.Game.Benchmarks.csproj | 2 +- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- .../osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- .../osu.Game.Tournament.Tests.csproj | 2 +- osu.Game/osu.Game.csproj | 22 +++++++++---------- 13 files changed, 28 insertions(+), 25 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index cb922c5a58..c305872288 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 5ecd9cc675..ec90885cd9 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index 33ad0ac4f7..f2e143a9c5 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 5ecd9cc675..ec90885cd9 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 5e203af1f2..b1117bf796 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -26,10 +26,13 @@ - + - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index 434c0e0367..2bdb6a650c 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -9,7 +9,7 @@ - + diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index fc6d900567..a6c614f22f 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index ddad2adfea..ff49d6d4dd 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index bd4c3d3345..a2e54f5cdc 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index a6b8eb8651..debddae037 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index acf1e8470b..40969c8b29 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index c7314a4969..9fd73f2c1b 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -7,7 +7,7 @@ - + WinExe diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 847832f8ac..7b50c804ff 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -18,27 +18,27 @@ - + - - + + - - - - - - + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + From 28b45fa8999ba897fa93c0f4a4a5fc1e3700a71e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Feb 2022 13:40:58 +0900 Subject: [PATCH 0662/5199] Add assertions against null reference for connection usages --- .../Multiplayer/OnlineMultiplayerClient.cs | 29 +++++++++++++++++++ .../Online/Spectator/OnlineSpectatorClient.cs | 11 +++++++ 2 files changed, 40 insertions(+) diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 3794bec228..ad898759ff 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -4,6 +4,7 @@ #nullable enable using System.Collections.Generic; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; @@ -79,6 +80,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.FromCanceled(new CancellationToken(true)); + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.JoinRoomWithPassword), roomId, password ?? string.Empty); } @@ -87,6 +90,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.FromCanceled(new CancellationToken(true)); + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.LeaveRoom)); } @@ -95,6 +100,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.TransferHost), userId); } @@ -103,6 +110,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.KickUser), userId); } @@ -111,6 +120,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeSettings), settings); } @@ -119,6 +130,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeState), newState); } @@ -127,6 +140,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeBeatmapAvailability), newBeatmapAvailability); } @@ -135,6 +150,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeUserMods), newMods); } @@ -143,6 +160,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.SendMatchRequest), request); } @@ -151,6 +170,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.StartMatch)); } @@ -159,6 +180,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.AbortGameplay)); } @@ -167,6 +190,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.AddPlaylistItem), item); } @@ -175,6 +200,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.EditPlaylistItem), item); } @@ -183,6 +210,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.RemovePlaylistItem), playlistItemId); } diff --git a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs index 753796158e..ddde69c627 100644 --- a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs +++ b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs @@ -3,6 +3,7 @@ #nullable enable +using System.Diagnostics; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; using osu.Framework.Allocation; @@ -51,6 +52,8 @@ namespace osu.Game.Online.Spectator if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), state); } @@ -59,6 +62,8 @@ namespace osu.Game.Online.Spectator if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.SendAsync(nameof(ISpectatorServer.SendFrameData), data); } @@ -67,6 +72,8 @@ namespace osu.Game.Online.Spectator if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), state); } @@ -75,6 +82,8 @@ namespace osu.Game.Online.Spectator if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.SendAsync(nameof(ISpectatorServer.StartWatchingUser), userId); } @@ -83,6 +92,8 @@ namespace osu.Game.Online.Spectator if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.SendAsync(nameof(ISpectatorServer.EndWatchingUser), userId); } } From 8ec28dc8bcc1ab691af17e3124b046a6a27a1037 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Feb 2022 13:41:10 +0900 Subject: [PATCH 0663/5199] Update `OsuDbContext` in line with EF changes --- osu.Game/Database/OsuDbContext.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 441b090a6e..79183b6f0e 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -3,7 +3,6 @@ using System; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Logging; using osu.Framework.Logging; using osu.Framework.Statistics; @@ -12,8 +11,9 @@ using osu.Game.Configuration; using osu.Game.IO; using osu.Game.Rulesets; using osu.Game.Scoring; -using LogLevel = Microsoft.Extensions.Logging.LogLevel; using osu.Game.Skinning; +using SQLitePCL; +using LogLevel = Microsoft.Extensions.Logging.LogLevel; namespace osu.Game.Database { @@ -40,10 +40,10 @@ namespace osu.Game.Database static OsuDbContext() { // required to initialise native SQLite libraries on some platforms. - SQLitePCL.Batteries_V2.Init(); + Batteries_V2.Init(); // https://github.com/aspnet/EntityFrameworkCore/issues/9994#issuecomment-508588678 - SQLitePCL.raw.sqlite3_config(2 /*SQLITE_CONFIG_MULTITHREAD*/); + raw.sqlite3_config(2 /*SQLITE_CONFIG_MULTITHREAD*/); } /// @@ -116,7 +116,6 @@ namespace osu.Game.Database optionsBuilder // this is required for the time being due to the way we are querying in places like BeatmapStore. // if we ever move to having consumers file their own .Includes, or get eager loading support, this could be re-enabled. - .ConfigureWarnings(warnings => warnings.Ignore(CoreEventId.IncludeIgnoredWarning)) .UseSqlite(connectionString, sqliteOptions => sqliteOptions.CommandTimeout(10)) .UseLoggerFactory(logger.Value); } From 334fe1f1203ac02f507132bd1d64f00139dfb81d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Feb 2022 15:23:14 +0900 Subject: [PATCH 0664/5199] Add `AsSplitQuery` specification to avoid optimisation recommendation log messages --- osu.Game/Database/EFToRealmMigrator.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 0bb5388d55..c9deee19fe 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -215,7 +215,8 @@ namespace osu.Game.Database .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) .Include(s => s.Files).ThenInclude(f => f.FileInfo) - .Include(s => s.Metadata); + .Include(s => s.Metadata) + .AsSplitQuery(); log("Beginning beatmaps migration to realm"); @@ -344,7 +345,8 @@ namespace osu.Game.Database .Include(s => s.Ruleset) .Include(s => s.BeatmapInfo) .Include(s => s.Files) - .ThenInclude(f => f.FileInfo); + .ThenInclude(f => f.FileInfo) + .AsSplitQuery(); log("Beginning scores migration to realm"); @@ -434,6 +436,7 @@ namespace osu.Game.Database var existingSkins = db.SkinInfo .Include(s => s.Files) .ThenInclude(f => f.FileInfo) + .AsSplitQuery() .ToList(); // previous entries in EF are removed post migration. From 2c3e50a450359d76c95fc4628b391733dc354f4c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Feb 2022 15:46:25 +0900 Subject: [PATCH 0665/5199] Update package references in xamarin `props` files --- osu.Android.props | 2 +- osu.iOS.props | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 6b3142fbdc..eab2be1b72 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -56,6 +56,6 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 00c9653146..03a105673c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -79,15 +79,15 @@ - - - + + + - + - - + + From efeba30b9fe0976fd99c89841029323f88c2f243 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Feb 2022 16:01:14 +0900 Subject: [PATCH 0666/5199] Remove ruleset and mod bindables from PlaylistItem --- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 2 +- .../Visual/Multiplayer/QueueModeTestScene.cs | 2 +- .../TestSceneDrawableRoomPlaylist.cs | 41 +++++++------- .../TestSceneMatchBeatmapDetailArea.cs | 11 ++-- .../Multiplayer/TestSceneMultiplayer.cs | 49 ++++++++-------- .../TestSceneMultiplayerMatchSubScreen.cs | 11 ++-- .../Multiplayer/TestSceneMultiplayerPlayer.cs | 2 +- .../TestSceneMultiplayerPlaylist.cs | 4 +- .../TestSceneMultiplayerReadyButton.cs | 2 +- .../TestSceneMultiplayerSpectateButton.cs | 2 +- .../TestScenePlaylistsRoomSettingsPlaylist.cs | 11 ++-- .../TestScenePlaylistsSongSelect.cs | 19 ++++++- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 8 +-- .../TestScenePlaylistsResultsScreen.cs | 2 +- .../TestScenePlaylistsRoomCreation.cs | 8 +-- .../Online/Multiplayer/MultiplayerClient.cs | 34 ++++------- .../Online/Rooms/MultiplayerPlaylistItem.cs | 4 +- osu.Game/Online/Rooms/MultiplayerScore.cs | 6 +- osu.Game/Online/Rooms/PlaylistItem.cs | 56 ++----------------- .../OnlinePlay/Components/ModeTypeInfo.cs | 11 +++- .../OnlinePlay/Components/RoomManager.cs | 6 +- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 19 ++++--- .../Lounge/Components/RoomsContainer.cs | 3 +- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 4 +- .../Multiplayer/MultiplayerMatchSongSelect.cs | 5 +- .../Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- .../OnlinePlay/OnlinePlaySongSelect.cs | 27 +++++---- .../OnlinePlay/Playlists/PlaylistsPlayer.cs | 6 +- .../Playlists/PlaylistsSongSelect.cs | 24 +++----- .../Multiplayer/MultiplayerTestScene.cs | 2 +- .../Visual/OnlinePlay/TestRoomManager.cs | 2 +- 31 files changed, 178 insertions(+), 207 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 343fc7e6e0..9aa04dda92 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Online selectedItem.Value = new PlaylistItem { Beatmap = { Value = testBeatmapInfo }, - Ruleset = { Value = testBeatmapInfo.Ruleset }, + RulesetID = testBeatmapInfo.Ruleset.OnlineID, }; Child = availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index c79395b343..36d6c6a306 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -78,7 +78,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = InitialBeatmap }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } })); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 5c8c90e166..659cc22350 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -17,6 +17,7 @@ using osu.Game.Beatmaps.Drawables; using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.Models; +using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; @@ -215,25 +216,25 @@ namespace osu.Game.Tests.Visual.Multiplayer { ID = 0, Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, Expired = true, - RequiredMods = + RequiredMods = new[] { - new OsuModHardRock(), - new OsuModDoubleTime(), - new OsuModAutoplay() + new APIMod(new OsuModHardRock()), + new APIMod(new OsuModDoubleTime()), + new APIMod(new OsuModAutoplay()) } }, new PlaylistItem { ID = 1, Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, - RequiredMods = + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + RequiredMods = new[] { - new OsuModHardRock(), - new OsuModDoubleTime(), - new OsuModAutoplay() + new APIMod(new OsuModHardRock()), + new APIMod(new OsuModDoubleTime()), + new APIMod(new OsuModAutoplay()) } } } @@ -314,12 +315,12 @@ namespace osu.Game.Tests.Visual.Multiplayer BeatmapSet = new BeatmapSetInfo() } }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, - RequiredMods = + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + RequiredMods = new[] { - new OsuModHardRock(), - new OsuModDoubleTime(), - new OsuModAutoplay() + new APIMod(new OsuModHardRock()), + new APIMod(new OsuModDoubleTime()), + new APIMod(new OsuModAutoplay()) } }); } @@ -348,12 +349,12 @@ namespace osu.Game.Tests.Visual.Multiplayer ID = index++, OwnerID = 2, Beatmap = { Value = b }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, - RequiredMods = + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + RequiredMods = new[] { - new OsuModHardRock(), - new OsuModDoubleTime(), - new OsuModAutoplay() + new APIMod(new OsuModHardRock()), + new APIMod(new OsuModDoubleTime()), + new APIMod(new OsuModAutoplay()) } }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs index 1d61a5d496..6144824ba0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Graphics; +using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; @@ -35,12 +36,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { ID = SelectedRoom.Value.Playlist.Count, Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, - RequiredMods = + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + RequiredMods = new[] { - new OsuModHardRock(), - new OsuModDoubleTime(), - new OsuModAutoplay() + new APIMod(new OsuModHardRock()), + new APIMod(new OsuModDoubleTime()), + new APIMod(new OsuModAutoplay()) } }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 8f6ba6375f..41715f6cfb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -19,6 +19,7 @@ using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; @@ -99,7 +100,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } }); @@ -235,7 +236,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } }); @@ -257,7 +258,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } }, API.LocalUser.Value); @@ -287,7 +288,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } }, API.LocalUser.Value); @@ -318,7 +319,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } }); @@ -340,7 +341,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } }, API.LocalUser.Value); @@ -373,7 +374,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } }); @@ -393,7 +394,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } }); @@ -415,7 +416,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } }); @@ -454,7 +455,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } }); @@ -493,7 +494,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } }); @@ -532,7 +533,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } }); @@ -566,7 +567,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } }); @@ -606,7 +607,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } }); @@ -626,8 +627,8 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, - AllowedMods = { new OsuModHidden() } + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + AllowedMods = new[] { new APIMod(new OsuModHidden()) } } } }); @@ -666,7 +667,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } }); @@ -697,7 +698,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } }, API.LocalUser.Value); @@ -715,7 +716,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { ID = 2, Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID }); }); @@ -743,7 +744,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } }); @@ -779,7 +780,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } }); @@ -818,7 +819,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } }); @@ -849,7 +850,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } }); @@ -882,7 +883,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 869fb17317..a6151198cf 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -10,6 +10,7 @@ using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; @@ -72,7 +73,7 @@ namespace osu.Game.Tests.Visual.Multiplayer SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID }); }); @@ -89,8 +90,8 @@ namespace osu.Game.Tests.Visual.Multiplayer SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = new TestBeatmap(new TaikoRuleset().RulesetInfo).BeatmapInfo }, - Ruleset = { Value = new TaikoRuleset().RulesetInfo }, - AllowedMods = { new TaikoModSwap() } + RulesetID = new TaikoRuleset().RulesetInfo.OnlineID, + AllowedMods = new[] { new APIMod(new TaikoModSwap()) } }); }); @@ -112,7 +113,7 @@ namespace osu.Game.Tests.Visual.Multiplayer SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID }); }); @@ -127,7 +128,7 @@ namespace osu.Game.Tests.Visual.Multiplayer SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID }); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs index 73f2ed5b39..010e9dc078 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Stack.Push(player = new MultiplayerPlayer(Client.APIRoom, new PlaylistItem { Beatmap = { Value = Beatmap.Value.BeatmapInfo }, - Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset } + RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID, }, Client.Room?.Users.ToArray())); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 936798e6b4..361178bfe4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -146,12 +146,12 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo }, - Ruleset = { Value = Ruleset.Value } + RulesetID = Ruleset.Value.OnlineID, }, new PlaylistItem { Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo }, - Ruleset = { Value = Ruleset.Value }, + RulesetID = Ruleset.Value.OnlineID, Expired = true } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 9867e5225e..7834226f15 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer selectedItem.Value = new PlaylistItem { Beatmap = { Value = Beatmap.Value.BeatmapInfo }, - Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset }, + RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID }; if (button != null) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 42ae279667..70d4d9dd55 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Multiplayer selectedItem.Value = new PlaylistItem { Beatmap = { Value = Beatmap.Value.BeatmapInfo }, - Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset }, + RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID, }; Child = new FillFlowContainer diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs index e63e58824f..8bfdda29d5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs @@ -13,6 +13,7 @@ using osu.Game.Beatmaps.Drawables; using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.Models; +using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; @@ -161,12 +162,12 @@ namespace osu.Game.Tests.Visual.Multiplayer BeatmapSet = new BeatmapSetInfo() } }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, - RequiredMods = + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + RequiredMods = new[] { - new OsuModHardRock(), - new OsuModDoubleTime(), - new OsuModAutoplay() + new APIMod(new OsuModHardRock()), + new APIMod(new OsuModDoubleTime()), + new APIMod(new OsuModAutoplay()) } }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index d933491ab6..3333afc88b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -115,8 +115,17 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change mod rate", () => ((OsuModDoubleTime)SelectedMods.Value[0]).SpeedChange.Value = 2); AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem()); - AddAssert("item 1 has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)SelectedRoom.Value.Playlist.First().RequiredMods[0]).SpeedChange.Value)); - AddAssert("item 2 has rate 2", () => Precision.AlmostEquals(2, ((OsuModDoubleTime)SelectedRoom.Value.Playlist.Last().RequiredMods[0]).SpeedChange.Value)); + AddAssert("item 1 has rate 1.5", () => + { + var mod = (OsuModDoubleTime)SelectedRoom.Value.Playlist.First().RequiredMods[0].ToMod(new OsuRuleset()); + return Precision.AlmostEquals(1.5, mod.SpeedChange.Value); + }); + + AddAssert("item 2 has rate 2", () => + { + var mod = (OsuModDoubleTime)SelectedRoom.Value.Playlist.Last().RequiredMods[0].ToMod(new OsuRuleset()); + return Precision.AlmostEquals(2, mod.SpeedChange.Value); + }); } /// @@ -138,7 +147,11 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem()); AddStep("change stored mod rate", () => mod.SpeedChange.Value = 2); - AddAssert("item has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)SelectedRoom.Value.Playlist.First().RequiredMods[0]).SpeedChange.Value)); + AddAssert("item has rate 1.5", () => + { + var m = (OsuModDoubleTime)SelectedRoom.Value.Playlist.First().RequiredMods[0].ToMod(new OsuRuleset()); + return Precision.AlmostEquals(1.5, m.SpeedChange.Value); + }); } private class TestPlaylistsSongSelect : PlaylistsSongSelect diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 781f0a1824..513c1413fa 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } }); @@ -95,7 +95,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } }); @@ -133,7 +133,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } }); @@ -159,7 +159,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } }); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 11df115b1a..a05d01613c 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -168,7 +168,7 @@ namespace osu.Game.Tests.Visual.Playlists LoadScreen(resultsScreen = new TestResultsScreen(getScore?.Invoke(), 1, new PlaylistItem { Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo } + RulesetID = new OsuRuleset().RulesetInfo.OnlineID })); }); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index 68225f6d64..578ea63b4e 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual.Playlists room.Playlist.Add(new PlaylistItem { Beatmap = { Value = importedBeatmap.Beatmaps.First() }, - Ruleset = { Value = new OsuRuleset().RulesetInfo } + RulesetID = new OsuRuleset().RulesetInfo.OnlineID }); }); @@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.Playlists room.Playlist.Add(new PlaylistItem { Beatmap = { Value = importedBeatmap.Beatmaps.First() }, - Ruleset = { Value = new OsuRuleset().RulesetInfo } + RulesetID = new OsuRuleset().RulesetInfo.OnlineID }); }); @@ -109,7 +109,7 @@ namespace osu.Game.Tests.Visual.Playlists room.Playlist.Add(new PlaylistItem { Beatmap = { Value = importedBeatmap.Beatmaps.First() }, - Ruleset = { Value = new OsuRuleset().RulesetInfo } + RulesetID = new OsuRuleset().RulesetInfo.OnlineID }); }); @@ -173,7 +173,7 @@ namespace osu.Game.Tests.Visual.Playlists } } }, - Ruleset = { Value = new OsuRuleset().RulesetInfo } + RulesetID = new OsuRuleset().RulesetInfo.OnlineID }); }); diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 903aaa89e3..9d45229961 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -727,30 +727,18 @@ namespace osu.Game.Online.Multiplayer RoomUpdated?.Invoke(); } - private PlaylistItem createPlaylistItem(MultiplayerPlaylistItem item) + private PlaylistItem createPlaylistItem(MultiplayerPlaylistItem item) => new PlaylistItem { - var ruleset = Rulesets.GetRuleset(item.RulesetID); - - Debug.Assert(ruleset != null); - - var rulesetInstance = ruleset.CreateInstance(); - - var playlistItem = new PlaylistItem - { - ID = item.ID, - BeatmapID = item.BeatmapID, - OwnerID = item.OwnerID, - Ruleset = { Value = ruleset }, - Expired = item.Expired, - PlaylistOrder = item.PlaylistOrder, - PlayedAt = item.PlayedAt - }; - - playlistItem.RequiredMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance))); - playlistItem.AllowedMods.AddRange(item.AllowedMods.Select(m => m.ToMod(rulesetInstance))); - - return playlistItem; - } + ID = item.ID, + BeatmapID = item.BeatmapID, + OwnerID = item.OwnerID, + RulesetID = item.RulesetID, + Expired = item.Expired, + PlaylistOrder = item.PlaylistOrder, + PlayedAt = item.PlayedAt, + RequiredMods = item.RequiredMods.ToArray(), + AllowedMods = item.AllowedMods.ToArray() + }; /// /// Retrieves a from an online source. diff --git a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs index 8ec073ff1e..d74cdd8c34 100644 --- a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs +++ b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs @@ -66,8 +66,8 @@ namespace osu.Game.Online.Rooms BeatmapID = item.BeatmapID; BeatmapChecksum = item.Beatmap.Value?.MD5Hash ?? string.Empty; RulesetID = item.RulesetID; - RequiredMods = item.RequiredMods.Select(m => new APIMod(m)).ToArray(); - AllowedMods = item.AllowedMods.Select(m => new APIMod(m)).ToArray(); + RequiredMods = item.RequiredMods.ToArray(); + AllowedMods = item.AllowedMods.ToArray(); Expired = item.Expired; PlaylistOrder = item.PlaylistOrder ?? 0; PlayedAt = item.PlayedAt; diff --git a/osu.Game/Online/Rooms/MultiplayerScore.cs b/osu.Game/Online/Rooms/MultiplayerScore.cs index f1bb57bd9d..85327be037 100644 --- a/osu.Game/Online/Rooms/MultiplayerScore.cs +++ b/osu.Game/Online/Rooms/MultiplayerScore.cs @@ -65,7 +65,11 @@ namespace osu.Game.Online.Rooms public ScoreInfo CreateScoreInfo(RulesetStore rulesets, PlaylistItem playlistItem, [NotNull] BeatmapInfo beatmap) { - var rulesetInstance = playlistItem.Ruleset.Value.CreateInstance(); + var ruleset = rulesets.GetRuleset(playlistItem.RulesetID); + if (ruleset == null) + throw new InvalidOperationException($"Couldn't create score with unknown ruleset: {playlistItem.RulesetID}"); + + var rulesetInstance = ruleset.CreateInstance(); var scoreInfo = new ScoreInfo { diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs index 83a70c405b..c082babb01 100644 --- a/osu.Game/Online/Rooms/PlaylistItem.cs +++ b/osu.Game/Online/Rooms/PlaylistItem.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using Newtonsoft.Json; @@ -10,8 +9,6 @@ using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; namespace osu.Game.Online.Rooms { @@ -49,68 +46,25 @@ namespace osu.Game.Online.Rooms [JsonIgnore] public readonly Bindable Beatmap = new Bindable(); - [JsonIgnore] - public readonly Bindable Ruleset = new Bindable(); - - [JsonIgnore] - public readonly BindableList AllowedMods = new BindableList(); - - [JsonIgnore] - public readonly BindableList RequiredMods = new BindableList(); - [JsonProperty("beatmap")] private APIBeatmap apiBeatmap { get; set; } - private APIMod[] allowedModsBacking; - [JsonProperty("allowed_mods")] - private APIMod[] allowedMods - { - get => AllowedMods.Select(m => new APIMod(m)).ToArray(); - set => allowedModsBacking = value; - } - - private APIMod[] requiredModsBacking; + public APIMod[] AllowedMods { get; set; } = Array.Empty(); [JsonProperty("required_mods")] - private APIMod[] requiredMods - { - get => RequiredMods.Select(m => new APIMod(m)).ToArray(); - set => requiredModsBacking = value; - } + public APIMod[] RequiredMods { get; set; } = Array.Empty(); public PlaylistItem() { Beatmap.BindValueChanged(beatmap => BeatmapID = beatmap.NewValue?.OnlineID ?? -1); - Ruleset.BindValueChanged(ruleset => RulesetID = ruleset.NewValue?.OnlineID ?? 0); } public void MarkInvalid() => valid.Value = false; - public void MapObjects(IRulesetStore rulesets) + public void MapObjects() { Beatmap.Value ??= apiBeatmap; - Ruleset.Value ??= rulesets.GetRuleset(RulesetID); - - Debug.Assert(Ruleset.Value != null); - - Ruleset rulesetInstance = Ruleset.Value.CreateInstance(); - - if (allowedModsBacking != null) - { - AllowedMods.Clear(); - AllowedMods.AddRange(allowedModsBacking.Select(m => m.ToMod(rulesetInstance))); - - allowedModsBacking = null; - } - - if (requiredModsBacking != null) - { - RequiredMods.Clear(); - RequiredMods.AddRange(requiredModsBacking.Select(m => m.ToMod(rulesetInstance))); - - requiredModsBacking = null; - } } #region Newtonsoft.Json implicit ShouldSerialize() methods @@ -133,7 +87,7 @@ namespace osu.Game.Online.Rooms && BeatmapID == other.BeatmapID && RulesetID == other.RulesetID && Expired == other.Expired - && allowedMods.SequenceEqual(other.allowedMods) - && requiredMods.SequenceEqual(other.requiredMods); + && AllowedMods.SequenceEqual(other.AllowedMods) + && RequiredMods.SequenceEqual(other.RequiredMods); } } diff --git a/osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs b/osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs index 2026106c42..d534a1e374 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps.Drawables; +using osu.Game.Rulesets; using osuTK; namespace osu.Game.Screens.OnlinePlay.Components @@ -15,6 +16,9 @@ namespace osu.Game.Screens.OnlinePlay.Components private const float height = 28; private const float transition_duration = 100; + [Resolved] + private RulesetStore rulesets { get; set; } + private Container drawableRuleset; public ModeTypeInfo() @@ -56,11 +60,14 @@ namespace osu.Game.Screens.OnlinePlay.Components private void updateBeatmap() { var item = Playlist.FirstOrDefault(); + var ruleset = item == null ? null : rulesets.GetRuleset(item.RulesetID)?.CreateInstance(); - if (item?.Beatmap != null) + if (item?.Beatmap != null && ruleset != null) { + var mods = item.RequiredMods.Select(m => m.ToMod(ruleset)).ToArray(); + drawableRuleset.FadeIn(transition_duration); - drawableRuleset.Child = new DifficultyIcon(item.Beatmap.Value, item.Ruleset.Value, item.RequiredMods) { Size = new Vector2(height) }; + drawableRuleset.Child = new DifficultyIcon(item.Beatmap.Value, ruleset.RulesetInfo, mods) { Size = new Vector2(height) }; } else drawableRuleset.FadeOut(transition_duration); diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index 238aa4059d..21b64b61bb 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Online.API; using osu.Game.Online.Rooms; -using osu.Game.Rulesets; namespace osu.Game.Screens.OnlinePlay.Components { @@ -27,9 +26,6 @@ namespace osu.Game.Screens.OnlinePlay.Components protected IBindable JoinedRoom => joinedRoom; private readonly Bindable joinedRoom = new Bindable(); - [Resolved] - private IRulesetStore rulesets { get; set; } - [Resolved] private IAPIProvider api { get; set; } @@ -117,7 +113,7 @@ namespace osu.Game.Screens.OnlinePlay.Components try { foreach (var pi in room.Playlist) - pi.MapObjects(rulesets); + pi.MapObjects(); var existing = rooms.FirstOrDefault(e => e.RoomID.Value == room.RoomID.Value); if (existing == null) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index e1f7ea5e92..dcf2a5a480 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -69,8 +69,9 @@ namespace osu.Game.Screens.OnlinePlay private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty) { RelativeSizeAxes = Axes.Both }; private readonly IBindable valid = new Bindable(); private readonly Bindable beatmap = new Bindable(); - private readonly Bindable ruleset = new Bindable(); - private readonly BindableList requiredMods = new BindableList(); + + private IRulesetInfo ruleset; + private Mod[] requiredMods; private Container maskingContainer; private Container difficultyIconContainer; @@ -86,6 +87,9 @@ namespace osu.Game.Screens.OnlinePlay private PanelBackground panelBackground; private FillFlowContainer mainFillFlow; + [Resolved] + private RulesetStore rulesets { get; set; } + [Resolved] private OsuColour colours { get; set; } @@ -108,8 +112,6 @@ namespace osu.Game.Screens.OnlinePlay beatmap.BindTo(item.Beatmap); valid.BindTo(item.Valid); - ruleset.BindTo(item.Ruleset); - requiredMods.BindTo(item.RequiredMods); if (item.Expired) Colour = OsuColour.Gray(0.5f); @@ -119,6 +121,11 @@ namespace osu.Game.Screens.OnlinePlay private void load() { maskingContainer.BorderColour = colours.Yellow; + + ruleset = rulesets.GetRuleset(Item.RulesetID); + var rulesetInstance = ruleset?.CreateInstance(); + + requiredMods = Item.RequiredMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); } protected override void LoadComplete() @@ -145,9 +152,7 @@ namespace osu.Game.Screens.OnlinePlay }, true); beatmap.BindValueChanged(_ => Scheduler.AddOnce(refresh)); - ruleset.BindValueChanged(_ => Scheduler.AddOnce(refresh)); valid.BindValueChanged(_ => Scheduler.AddOnce(refresh)); - requiredMods.CollectionChanged += (_, __) => Scheduler.AddOnce(refresh); onScreenLoader.DelayedLoadStarted += _ => { @@ -276,7 +281,7 @@ namespace osu.Game.Screens.OnlinePlay } if (Item.Beatmap.Value != null) - difficultyIconContainer.Child = new DifficultyIcon(Item.Beatmap.Value, ruleset.Value, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(icon_height) }; + difficultyIconContainer.Child = new DifficultyIcon(Item.Beatmap.Value, ruleset, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(icon_height) }; else difficultyIconContainer.Clear(); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index f4d7823fcc..9f917c978c 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Extensions; using osu.Game.Graphics.Cursor; using osu.Game.Input.Bindings; using osu.Game.Online.Rooms; @@ -78,7 +77,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { bool matchingFilter = true; - matchingFilter &= r.Room.Playlist.Count == 0 || criteria.Ruleset == null || r.Room.Playlist.Any(i => i.Ruleset.Value.MatchesOnlineID(criteria.Ruleset)); + matchingFilter &= r.Room.Playlist.Count == 0 || criteria.Ruleset == null || r.Room.Playlist.Any(i => i.RulesetID == criteria.Ruleset.OnlineID); if (!string.IsNullOrEmpty(criteria.SearchString)) matchingFilter &= r.FilterTerms.Any(term => term.Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 2d5225639f..02e1b115a0 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -386,7 +386,9 @@ namespace osu.Game.Screens.OnlinePlay.Match if (SelectedItem.Value == null || !this.IsCurrentScreen()) return; - Mods.Value = UserMods.Value.Concat(SelectedItem.Value.RequiredMods).ToList(); + var rulesetInstance = rulesets.GetRuleset(SelectedItem.Value.RulesetID)?.CreateInstance(); + Debug.Assert(rulesetInstance != null); + Mods.Value = UserMods.Value.Concat(SelectedItem.Value.RequiredMods.Select(m => m.ToMod(rulesetInstance))).ToList(); } private void updateRuleset() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 073497e1ce..12caf1fde1 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -11,7 +11,6 @@ using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Rulesets; @@ -71,8 +70,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer BeatmapID = item.BeatmapID, BeatmapChecksum = item.Beatmap.Value.MD5Hash, RulesetID = item.RulesetID, - RequiredMods = item.RequiredMods.Select(m => new APIMod(m)).ToArray(), - AllowedMods = item.AllowedMods.Select(m => new APIMod(m)).ToArray() + RequiredMods = item.RequiredMods.ToArray(), + AllowedMods = item.AllowedMods.ToArray() }; Task task = itemToEdit != null ? client.EditPlaylistItem(multiplayerItem) : client.AddPlaylistItem(multiplayerItem); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index a397493bab..cb50a56052 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -247,7 +247,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer // update local mods based on room's reported status for the local user (omitting the base call implementation). // this makes the server authoritative, and avoids the local user potentially setting mods that the server is not aware of (ie. if the match was started during the selection being changed). var ruleset = Ruleset.Value.CreateInstance(); - Mods.Value = client.LocalUser.Mods.Select(m => m.ToMod(ruleset)).Concat(SelectedItem.Value.RequiredMods).ToList(); + Mods.Value = client.LocalUser.Mods.Select(m => m.ToMod(ruleset)).Concat(SelectedItem.Value.RequiredMods.Select(m => m.ToMod(ruleset))).ToList(); } [Resolved(canBeNull: true)] diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index 63957caee3..eab1f83967 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Beatmaps; +using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; @@ -37,6 +38,9 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(CanBeNull = true)] protected IBindable SelectedItem { get; private set; } + [Resolved] + private RulesetStore rulesets { get; set; } + protected override UserActivity InitialActivity => new UserActivity.InLobby(room); protected readonly Bindable> FreeMods = new Bindable>(Array.Empty()); @@ -78,10 +82,15 @@ namespace osu.Game.Screens.OnlinePlay { base.LoadComplete(); - // At this point, Mods contains both the required and allowed mods. For selection purposes, it should only contain the required mods. - // Similarly, freeMods is currently empty but should only contain the allowed mods. - Mods.Value = SelectedItem?.Value?.RequiredMods.Select(m => m.DeepClone()).ToArray() ?? Array.Empty(); - FreeMods.Value = SelectedItem?.Value?.AllowedMods.Select(m => m.DeepClone()).ToArray() ?? Array.Empty(); + var rulesetInstance = SelectedItem?.Value?.RulesetID == null ? null : rulesets.GetRuleset(SelectedItem.Value.RulesetID)?.CreateInstance(); + + if (rulesetInstance != null) + { + // At this point, Mods contains both the required and allowed mods. For selection purposes, it should only contain the required mods. + // Similarly, freeMods is currently empty but should only contain the allowed mods. + Mods.Value = SelectedItem.Value.RequiredMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); + FreeMods.Value = SelectedItem.Value.AllowedMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); + } Mods.BindValueChanged(onModsChanged); Ruleset.BindValueChanged(onRulesetChanged); @@ -110,15 +119,11 @@ namespace osu.Game.Screens.OnlinePlay { Value = Beatmap.Value.BeatmapInfo }, - Ruleset = - { - Value = Ruleset.Value - } + RulesetID = Ruleset.Value.OnlineID, + RequiredMods = Mods.Value.Select(m => new APIMod(m)).ToArray(), + AllowedMods = FreeMods.Value.Select(m => new APIMod(m)).ToArray() }; - item.RequiredMods.AddRange(Mods.Value.Select(m => m.DeepClone())); - item.AllowedMods.AddRange(FreeMods.Value.Select(m => m.DeepClone())); - SelectItem(item); return true; } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 35d417520e..2b071175d5 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Screens; using osu.Game.Extensions; +using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Scoring; @@ -36,10 +37,11 @@ namespace osu.Game.Screens.OnlinePlay.Playlists if (!Beatmap.Value.BeatmapInfo.MatchesOnlineID(PlaylistItem.Beatmap.Value)) throw new InvalidOperationException("Current Beatmap does not match PlaylistItem's Beatmap"); - if (!ruleset.Value.MatchesOnlineID(PlaylistItem.Ruleset.Value)) + if (ruleset.Value.OnlineID != PlaylistItem.RulesetID) throw new InvalidOperationException("Current Ruleset does not match PlaylistItem's Ruleset"); - if (!PlaylistItem.RequiredMods.All(m => Mods.Value.Any(m.Equals))) + var localMods = Mods.Value.Select(m => new APIMod(m)).ToArray(); + if (!PlaylistItem.RequiredMods.All(m => localMods.Any(m.Equals))) throw new InvalidOperationException("Current Mods do not match PlaylistItem's RequiredMods"); } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs index 0fd76f7e25..3ac576b18e 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs @@ -3,6 +3,7 @@ using System.Linq; using osu.Framework.Screens; +using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.Select; @@ -30,7 +31,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists break; case 1: - populateItemFromCurrent(Playlist.Single()); + Playlist.Clear(); + createNewItem(); break; } @@ -41,24 +43,14 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { PlaylistItem item = new PlaylistItem { - ID = Playlist.Count == 0 ? 0 : Playlist.Max(p => p.ID) + 1 + ID = Playlist.Count == 0 ? 0 : Playlist.Max(p => p.ID) + 1, + Beatmap = { Value = Beatmap.Value.BeatmapInfo }, + RulesetID = Ruleset.Value.OnlineID, + RequiredMods = Mods.Value.Select(m => new APIMod(m)).ToArray(), + AllowedMods = FreeMods.Value.Select(m => new APIMod(m)).ToArray() }; - populateItemFromCurrent(item); - Playlist.Add(item); } - - private void populateItemFromCurrent(PlaylistItem item) - { - item.Beatmap.Value = Beatmap.Value.BeatmapInfo; - item.Ruleset.Value = Ruleset.Value; - - item.RequiredMods.Clear(); - item.RequiredMods.AddRange(Mods.Value.Select(m => m.DeepClone())); - - item.AllowedMods.Clear(); - item.AllowedMods.AddRange(FreeMods.Value.Select(m => m.DeepClone())); - } } } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 7607122ef0..ed86d572b9 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new PlaylistItem { Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo }, - Ruleset = { Value = Ruleset.Value } + RulesetID = Ruleset.Value.OnlineID } } }; diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index 4cbc6174c9..73d0df2c36 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay { room.Playlist.Add(new PlaylistItem { - Ruleset = { Value = ruleset }, + RulesetID = ruleset.OnlineID, Beatmap = { Value = new BeatmapInfo From 5b765581d83b9d7613e507697d68392c034d8ebe Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Feb 2022 16:18:40 +0900 Subject: [PATCH 0667/5199] Fix free mod selection not showing allowed mods --- .../TestSceneMultiplayerMatchSubScreen.cs | 25 +++++++++++++++++++ .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 11 +++++--- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index a6151198cf..4dd3427bee 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -14,11 +14,14 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; +using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Taiko; using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Rulesets.UI; +using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Screens.OnlinePlay.Multiplayer.Participants; @@ -150,5 +153,27 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("match started", () => Client.Room?.State == MultiplayerRoomState.WaitingForLoad); } + + [Test] + public void TestFreeModSelectionHasAllowedMods() + { + AddStep("add playlist item with allowed mod", () => + { + SelectedRoom.Value.Playlist.Add(new PlaylistItem + { + Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + AllowedMods = new[] { new APIMod(new OsuModDoubleTime()) } + }); + }); + + ClickButtonWhenEnabled(); + + AddUntilStep("wait for join", () => RoomJoined); + + ClickButtonWhenEnabled(); + + AddAssert("mod select contains only double time mod", () => this.ChildrenOfType().Single().ChildrenOfType().Single().Mod is OsuModDoubleTime); + } } } diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 02e1b115a0..836629ada0 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -350,10 +351,12 @@ namespace osu.Game.Screens.OnlinePlay.Match if (selected == null) return; + var rulesetInstance = rulesets.GetRuleset(SelectedItem.Value.RulesetID)?.CreateInstance(); + Debug.Assert(rulesetInstance != null); + var allowedMods = SelectedItem.Value.AllowedMods.Select(m => m.ToMod(rulesetInstance)); + // Remove any user mods that are no longer allowed. - UserMods.Value = UserMods.Value - .Where(m => selected.AllowedMods.Any(a => m.GetType() == a.GetType())) - .ToList(); + UserMods.Value = UserMods.Value.Where(m => allowedMods.Any(a => m.GetType() == a.GetType())).ToList(); UpdateMods(); updateRuleset(); @@ -367,7 +370,7 @@ namespace osu.Game.Screens.OnlinePlay.Match else { UserModsSection?.Show(); - userModsSelectOverlay.IsValidMod = m => selected.AllowedMods.Any(a => a.GetType() == m.GetType()); + userModsSelectOverlay.IsValidMod = m => allowedMods.Any(a => a.GetType() == m.GetType()); } } From f03de16ee5a46deac3b5f2ca1edfba5c4c5dca7d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Feb 2022 16:27:10 +0900 Subject: [PATCH 0668/5199] Add a test EF database Created on d8a23aad4 (just before skins were migrated to realm). This contains: - 2 beatmap sets (intro and disco prince) - 1 score (set on disco prince using autopilot/DT) - 1 skin (haxwell) - 322 named files (from skin) - 5 named files (from beatmaps) - 270 total file infos --- osu.Game.Tests/Resources/client.db | Bin 0 -> 266240 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 osu.Game.Tests/Resources/client.db diff --git a/osu.Game.Tests/Resources/client.db b/osu.Game.Tests/Resources/client.db new file mode 100644 index 0000000000000000000000000000000000000000..079d5af3b7454bcd1c3acfdd199ce1a15c33567f GIT binary patch literal 266240 zcmeEv31D1R)&F~$Br|Vj-g~9crlpilQ%W1Uy?vn+NSkSiU1?IFBF4A2L(?QAlR{O* zf`Ed!FDRlY?zkX|Ac*3Eii+X_;szq%f+&ax3L^jCy>BL&NhU4G=lB1_o3!W5+~wSR zfA`#T&%Kj(rsu>BrE*k=2Zwii<$}&lbd;|m3j*^Lnb6 zd2adAEiK4R$$utgbonXyarw{Li-p%`E^qx7-y;8Qs;d8s=@%cLO}8yrz`tW%+4K9O z)sa`;?G1Gg#Dg_2b8EI1J9~?T-pKPNWywx2*50tG8uq_r}hx+Y9T9+Y6oBdN+4(LL(cCn|hZf0y{^; z(%^>33%ds8&+*Is#1~NGZY0SufM0fw|{C}ddgm~YeO_piUxwcjj=_M z;c>;7s?6YU6s{f|+_l>qCOAz*y0^4DB13)g;Nb4YB2J2iM@oa3jEPlGE``xZFdRh# zJ>|jSz5bv#98NV6wTonJ-6E+jnDLSHP1g9eo3|Fb*KMNfXmR_b{0`{e+TzyYrZvT$ zv6VY65bKvL-HyWMO@*%Fh9VaEn$Dgzon6IbMh@J$%6h5xwPyKD=-I^O)v#xk>d(Jh zRVD7Lz93px-=mtGhJC5#H|WgPBJ5iCnu@L34@gy&heheDzMj;P@r?R@RMSzpK_4pf zl ztx<)VmqPt?*5p86p8Z_b)jQBccJA>?{lrz6%AU1%50KrjT=L4WjbtyKY#%pyyHS74 zYGSyrOsCuC&*#ruLx;OQ+Pk_$Rb~8Drhc&F8P92~wbc$d)PRg&%5g$egI%KvW@9Bh z$r~;Kn;Mx=Pz--{Yr1XjT>jjX>G0Q<`Xd5dm8a{6UQK>Z10Sz+pkX)m3bsaZG>jcL zqV7-404h5;?2y)yZdK(1a+Fa zRBtKR)u2IGl5Kpt&9?b-yD9p8eZ{pKOFM?CEgm@mY7cj`VPaGLEHo@LHWOnayRn-l zN-gJ#Vx;NZV4G zMz0*~+|uvu^>HvG{&PbaD`%&A5*dzWsE(=VCXq3A?~f~?G8$~$fi<&IK~mXN4aL$d z-6fSf=?Sqr?5%#TQauPa?7?wusZ*;$RK-$kL#Ly_*eBhtG!)P|Gu4*}FjP%3Oj}U^ zUpyR*>^y0(e{^?Lxn3FR+<~J@LlN$b)Mg?=Q5DtIG(hQ)J`{&=0v;OKd$Kn?01z5h zJ?4q`IY;M0*U2RPTb8fJK;oQg>|rpaczj zp-)S#NyM0%iK&AM^%0%juhLj@b;ug21k|b0Y=d%lcO?xsY|NGD9I1|~>dM=;jYe5djb)r<|?MC zVz^2BxJ|(|3BhU2AZ|R#6+pRhH&&!mE0d9HP)=Afm5!W1-ME6;np&4szyd&vnnI^_ z<7m0MZX5P>MN73ERfF--bwlafui72koZ3#>v0)=zMF&6%os!DubWnpHTx*rdZEeE_ zyHlybq&_AF8f5F#*j83*axdTAl>jvkQbSQIo0569Ved^SmXWCA#?r_LtV}mK_0yZx zhLsIIQ7u$PKMdGir>}dUge&O&HTV#83NxR@el;g|2PgkZzFt08K3QHV&z7E+z9(HF zoh6+t>C!auLGhd7N5oNay|_64O#Y|&tMcdMH|H1S{)TGohatcaUjYn@sw%yu1(+t$w!m6qz}xl}RrrUrD6rr`9ETQ{6taj@6!g(a>^KYCEa@ zEbbJV&D3C0^toIgq$>Cw=%%1)UaBb2j!bbWAj;R_Z}VTr$q&j`$gfL&|9_h=);>dk zA;1t|2rvW~0t^9$07HNwzz|>vFa%z(2+U~Ba6Ep}d~0)S>v50A^rvY?OT%QqplA3@ zh8J3z@OlxciMlOKX*|Bqac%S+pK57JjY&F$uSgPF@Ka3|vFa#I^3;~7!Lx3T`5MT%}1Q-JK2pp78Eo>ss zDO96}@TP-lUo~qdB#LpA_Vwb!H}|R+I5iLbbGj2AlIjYF;9Q zJbEIhQwypk$@`XrTT)A^3Dw7@G8{L(HPul~CJ%w2Xo}0_QgemsLo9L=Bvk{lkn9|v znwzOU5+O<#am`XQCx1=8Kw2l^W%64O0ei;~U#EBWV9nZ)LAmLDW)yo$!=e*{kgERpV)F3&$IorGuo z!~ERbow+yWPRQ}u8?wXMCBpr}<-$oBH}kE`K0!`Dn!YL>rsuZa+4{!T6I%I}8(KzM zmNq}o{NCo1o2R92O`V%s+4NY`C!6A?dHh}coB0>_w<7e|H{I8Njt5{<7prXJX8=m);0TB;VQMVE?Ebj>v* zQ&Vi+b$lnZ^~iTLKh)IFveZabV>NOj-3S!RvK%u&A3Zm;Ayf-I&(Mnw72|4#;;F6^ zy1E-iu4cNvVVa)psgCa%u>(nlquXxen@$*_9o6xa$W%<*_5;ONiZ&Hv=)UUtjvtvZ zD(IoF`o3p5f#P_s8z~M1S)OBt2xAhO9(n2~LVg4=5hlcQjG~ncDz;H3a7`l!BFBIj zV4*mW9lBoNJ9v(X;A5F_XgR(gMWKfWgaRiD1N170iY65UsDj9F70=W3*fAo60WmRa zj&BB0i2eh5Jq$zDx3x%z5Je9Gt%G@W;!uywqCv%&wxb((EXVg<$F-5EMv-Qq1CAGX zw(ACwijf9>V1|$z1e#`hfu+O>!stY?;}>-*#*N)r^KCUYW8YL`UkwdAHbTvdP0NKW zKP2$#ii;<@pc2048fIiWwh|kPjwXv56=RsXW1EqytDfoDks2$ir>g-T3c_rJp63B( zL&cRVpS{G0~1`s4`l(xvrCX7LNhvB+j2Z-Z7G(IHB($c#YBM{8I}^5w&CfRK+6Wenr+2)9QqLg`arlAyB3L-0>x1sEpmM; z3JeXZ6!}4MITfQ@P&muPY*~@7TA>a8WG`J?OBn#8}7<)EAoOgorbRxw0%*)0G$ulL{S*J=6A}sXFw-(uzx| z7$9iscHrBt76$=3V?wty4{KSoFw>YtOsyT+kna0o=!BkSYGDw27Q*IurdwQ+49bpm z{F*>IK$lDlF=K)NPY^~%;9x)+R;X-HR;Xx^q82--80cN7Mu88Q10DS| zHK?fJhq^=78s^KiG!;{VVmAm$(;8I7hiS2(^lFHK z`Iha)ZUn8>0@yUgb4{!igfy~r*ca$$@yLo8AFB={^Aybpyf8#Fo`HUNo(e0ddss$> zX9cbv#Ia&Uum)J_y5~6_R*Pa37g8|>N&`|19>SoD#3~zxO?C#79AZH$E}cV6HX0cmx#_X%_5+s|3&%MEC*m!x%}HYx%JsVDXzUNwMXGF)+7bZ=rv- z>-jJYM&ww<1yqcN2u3iFk!^*6x)=xu&m}u-7`YUU&3BF%4q2+`zU11*(Qc2goXb8e+HeuuCX*@o*}}4_sF#76iMM z5u+iP2OHBzR85II>|3$pV5wr)MyDOvSWKabUfV#}@||LViorG#LBX*Bz;cBKx(gc- zn~FiY;dq)K=`K-P&%qo=8s-j`9g41Lp^07DE*?h37=9eu2G+I@VAa6D%wxL>VD8M& zfnCNj#tso!Dh#V>LLIyq3KnZvJ#a*fusA0X0|oX24VDagVq)KjP({(<=6IMOG^#*x zFfKR|fE|7UEFx@}5@-Zb)h`}O#b9+{TfhdPnRWyl2^1U^%Owm9Y`X@w6Aj_AVgf46 zn~#kP?uL(!#AE@)VR1GU<6>hb1I3OK#hMvmxnm>4(m@xX3-Cuw1F@@MEL6*a1tCrt zQ7PR~qR4UFwX=vA-gVUgdo4!os4;9X42R{zEzo?OctejyF3JQJ=AAjPtSw*YHl=(eQ>ii_uR^X8!FdrO22Q~5Vy{V`$%4klv9kqA}6%+Z` zmMu6CXapwTQgsb}o9Dr-z?E=S7*)KE03Eh&4YmVYxC;Y~fMK2u6J9)+iXj7qzXOv1 z6?Ijp9neutJ5?JU?eb6nu(Da@O|KrSPs@9aWb&Unoe;V6@!)L#R1$bAG(a4E>_U72%8$#VW?pT zPt1jD`@Rx6umh%P`i^O<@Hb%X^|cZa!>jNCV51zU4t!f66{{Fp;99U6@RrF&fvA3V zsA4IYu?hbO))a;i4xZ(}b4?YqNq68<*>F7IHy9dRmJqS(f*6+GaE$<+$MS#%!exi| zZ235ESV641umSL%U_6wfP#G1%d}61AF2K`rjnK1kWN`Ed8=s0E1=#4Y_rgOVLFOFt zLpDG7y&=LwdTudO=?$C&2W|#-`oM+uz-z*>1_Ls&W<9uru=T_vgWB0fXkhJYh(QQH z34KQs#WWQ|_E|XYSj@!N_hV?O4`k4&?h#`fwbZl&ju&V)9salj1 zF~m7gEm%Cb8u0HN3p8|e4=8$pf)p448|es}7Xoa|mmkBmMtN9|z%Pna44JzK`&00UHSq6Nf1qn|-W%K6+-kMjTrqnKvXG zp0B_%;=FzIzL$YI`#M0)VGv^_iT{r;{bN520fqoWfFZyTU316@w34>8FhwaO&o7g&LZqapDUmsQa_KeXi&K|V zx*wS|pU4bI@pyQ&R37Q#S~=krGY+0Cb2EC5|ND`LTvVHn|65a24tJwBQ1X^jx*t(A zp9tGcic=!W3%MrgdQN_;bUhgRVF)k;7y=9dh5$o=A;1t|2rvW~0t^9$!2c-(cBQgh z29I$F8DS%NcI5dyJCVlsL~6?vZJBBo6#HmRJ=#>4<1rgNRk%b^j#dWnrBjb?+JhOtQVnRJg z?U~e?<}5c|JS1FcZ5L06CU643@%cU_@brYnl%q{;xvJ`hWhFxs^Y7uBayN7Gs@%=V z@BgW3W}RUOFa#I^3;~7!Lx3T`5MT%}1Zoi2x4mT^hi?MIK?uj8OlBj`pP9*|(_ChD zMo6bJnM`Zy`JRvPTtA{}xTc422_YV)4@?B}2{k;%AK;m9$Iq;jYkGhE*qZiXMAgAHVQ|e&h$w=%4=$dLjv_oCj}R>uqOTx=Pk>k}h(myQ6o~1A zNGu4~VcUoVfv2^@03jw2QvgAj0uomU;cgs+2SKa>#9F~a@jBwuc($YIcv!rZPazT= z26ce=bA`o8oH~rLdtk&Hi0a2U?e*9F`kt?JCt1e_ujbb+dUUyRFvsy3E&>PO+3y%pt`K5GL3ADsKfZwo zVm^7m9)SrE=mT+C49f!+h;4>v)Jae#JPO~!H$B%8p5P6K_2bLF#{TQRbMGM;)zR_C zo{QPNXaB=%YEm5pb1|GCj1j%TCqZNIz`PzJb_@+VhLbhIW>_X-c_5MzLJqkhB5NT8 z0Sagcy@OCU2vwxTi29-d5k!*G5c&bZk`UR!CSiD*dH%T$ujq~Jtsh>hcv6#*pGPuk zhPUr{9*a-l1wr7G#m8-@vnRA=c%q(}RP*ya!6RS-;_sM<@1xo@UXmRnj28kW7>Ejj z*hfuClt3uxSU|w>)karc-AIo6%Af!7+M6FahUPrYT_H*jaxKz@ocxIVRq5x_Ez*V6 z-x?#sUNQt20t^9$07HNwzz|>vFa#I^3;~7!L*Rb~fnyK_5jR&U;a|E*It0NFd45qU zQ}-1GB4Qcx{OlAy-Y4Pn(b2njeqXAo^|@XL)ZFJl$Xj{-oTirNDr4-%1QJdp5}Kc@ zgt1ErNRTnErd0Cv2ul%9P~f*e%Xx45pjwXkV(Z(-B64I2t;HgD?b-P(!tLi?8WeRcJ4eYml6>-NI> z;`T!4w%*P7u8FP1jm1s9OA~>eMF%?DH!c9?`_nDYQe}W zca`>(Mh1tc7KSpry`joiE$&Cyn!(Y5ayWSA)cxuzjg&onl||2x7nBBev=;#1hA~(w zeOfnM3VTW~kEZO<3Es%g_Cjy*lmyL6wG&E+yg7B#8=`VK8ukaz>K<4<9C^D&rWC)i z%WT|2^*bo*Ai#81IhF-7^#gC9l7yytjX9 zTzbl0uxmp!P>KeEy^XO&k>PR0n5qoETqImQIJgU6eL`@Wh;(mhcSMHz;=#e)jYXUk z4Ud!tF&Pu9o?Hr}kzhEA271ba!+ZTfT%I?t#gCJg~XLV)hqN-hwYwOlyb#0D=);D=^t=+t}*u8EOU9yYYSD!c3 zl5ShFguk$@)^t?0r?S|qZ(AFhkZSI~Vn)X69?+Bwm$4ux&r&B%-!@PR21BUZ#7U$U zhe zr@9ru&!~AR)ay)5&T~?jx_SrFmd-t1sh_w$Q!Yq2iP+7{C9e!yPduaQjEw8WCOn~9 zlS-CZ4^~roLVCx(SEkc#^XK#Dt)VEakM^!EQ8gWZm8r*IJmWb*P+RSQ2n1wcQw|@Z z8rYpG#*G)iN%%4`Kvp9YSYr-c(T7Y|^iFGbCcb6=54x2;&gpTDqH4aWVaiXx0BG}prS==b#%*KREB z7^WtA8Cwd2;b>5vaB>(kF5OM7 zZKv5hhg~5$tD<|}XnBynZ`?|s+OY8yiT{`9ujlyd#jlD+{=@ktxzFZ=>>0wZgi|t) zW%j1ukv_C_q~+cgzxju$`}p7H?#OM-4z>1GZwCHHdDCs&fIT07N2Gv*D zEXGsjR_3UCQ&;g6TrD&z-A=rW_VGHHv-aw)(@~n8ZZpxE(4e(Wy4#IcJZyiBC50Qc zRmthtJ1gDRIhWrzzuHuF1L~{aNvg?nCN)>hsaCk;*evyw-ktaem6gtJWa@nF`fCGr zBg1ZFlEd=^yE^Wn|KGcjDYO@daJeuvviD?fc%b1?b^PRw1I3tu41x( z)^59vZ?GnuZ!2#npK2?1bv(ae&nnqucbRaseU9C#o)7-L=j;Q5R6Vxg6x&yQJ?X$d zo>7lUHJu)TrvfDVZ`cyYPGe-xukM#*PU`pA_71---L?$7aeC5jOip{%nuk{Q;BA|_ zPuy0-#}-vLqgtRZIU7!Fjiz?&YnhpDD|YbvI;xG-t5VGj*JsS0wDs5L*HlbAj{0me z-l`T6UEC)fl5Sg%ewb7AqwXH8URK+Ix>8qns@~KW>ex5q;B?zYmEY%1?$8*r)fe4e z<2|aT&D>9~s%84BFUX1}6Es;20jj1{$G%0=({0<)uM_vvuW>QN(`wYa8PBG76UFSW zkCV!F>^UgiwrT;tZ}xA|%9_F!VghW(gS2))VguQwAm zvzp$>`|r(!ij8|Su~5frR_4-es>;6>LF~pqo5hw?)$Z{DR8#Vme$;vFa#I^3;~7!Lx3T`5MT%}1Q-Ggf&V20T2egU+JeTW zH#dXkQz_8arlwR%5ZL+ue`yj}GYkQS07HNwzz|>vFa#I^3;~7!Lx3T`5coGEK+gXu zdj4;s=l`a<^ZzJ5|6jq$H_9u(*bhU1A;1t|2rvW~0t^9$07HNwzz|>vFa#I^99L|{ z*8o1>@6#a1@3s8%{UX7pF7!oszF#kpl^@6X|Ig&_|No4a?1v%15MT%}1Q-Gg0fqoW zfFZyTUMe;+45CEu6)z7Ph<#=sC@2rvW~ z0t^9$07HNwzz|>vFa#I^41xbT1jwKN;|mh;_UC1e#MBp4F z`F{qV|KG#4$hVR6{}b|0vFa#I^3;~9~ z3kv}n=ue~m|KBC&|HtLKvFa#I^3;~9~3k3mkAHhf!)b=}iUv=_yiYx|*N zIa+APp6xa*NNp4}J9KT!v{X&C70)$QJ61e1a(u&5^;ofz@Be#*eE$E4{K(YfV>t`~ zh5$o=A;1t|2rvW~0t^9$07HNwzz|>v{O2G*{_;OfzW^XbpZ{;d2mUQmkqls^e693T zsVKcsUXc2G`oiY(TTT|1G|8#m@{O%erEe3?$o-c4jBuTFWz)T_9hvvF9K=1*{GHs4 z?DwTMepUV%{??ZJvaidXnq8AxB#(+k@dx6?`7a1hw_M(IS>}Pv_WZJRr@SKf{`^Su zajDP#%ZL@5_ldj_IjUhOapd|z?0TjX21aO^wqh%muB%Gq*me|IVeI>nVaH||gm$QC zu3>AAu6g<~RElk?n&sQ>R^j1-bJ+>yhu^8c9<_%Tgm%jn&ABbR$qK%W}*BJ@nkrhD6jw78Pj#IT*HB^PYNqQOrs>(9>iC`!I}l_zy6r|jAPdoq>Uc_IDyD7YR>@Z! zD#g%!)$<)cGGmn2LtpiM&vF9A@mx1j9LTXe#|#z44o%NdF$&~I08&*|&oS&|M6rz` zfomE;5IF{<00+f^=+N~7-?0*GEHe%*2lEt#o}xN|6NLf#6hs!40+fQta23zf^w=>X z1=TQLj&BB0h|U9bJq$w?H(`+u8HyePRR=Tb#GxLUCY55^xJFZTjKg(Y8+mFJX$Gdo z@dDhGxk032m;r9=LWm6lO|!kgQes6h>)GFjNH!qdFSatrZ1^1{I3@ zUAoeW9Fg(*;nvBSfb^MwDIY5U@OE)kV zimpUP;9xWw7Nu<&o{dXcSMjk@Y#0F@rohxx!&a71DV_(#bk)ekXmzq8J&W{T^CBxy zfrz8U#LmPn^wD%f1vkJRaQ0mb7B5oNqo@?9Tc}2X4}=39oisHlrs3m$nXE6&lxb(#j-f6{Mg%heqX|n4z13paeXPJ3 zM$vZ7P! zDy#v_Y#g`>`Fz1pv0$K>fgamX1w(b_QzF}G1KSD|tQnXB&o;5n4HIo44?V=r zGM7p*{5Z4?EM^~&s)2zS$F3B>wwa*=6O46>tst;e7*W%N8hH3{2WuWH2A+ly9+pUf z*7|`4vjz1qu|Gtpq3G~pJj@J0RG=>y6ubm<51s(bA1syY-Va$B&Pw*#vbR#BOABTrhDK7S2GHuv7@IjOrVSQt} z!fHVWfEfG@(@<;`#zD0#SPbG}5lzw^C5jx!ot;Q=)d2e`M(n7u=W8$&mXD2J^L66u zV16~sssmFEcS?t$vG5;lnXrboX`8dC6!*#%FN8iO=rZ5-IR#d?2Z4CVU_4<4%nIEhajH6=TD5GU2EIOe{3G z127JTZd+=gxQ?pCravHV{O5_>cm5g4yIDD z?krf8keF=^+Yt;{5F4%oBdn=$2!{b`q#N+ceI?K>EDHD$rVrhO$%z$bdSyg#k1zwU z70^VyP<>rjG~a_01S6ne7_dj0iD4P=Y~XKL4vYzLBCwH~&OuZPtc4c`aG`uCFt)Q; zLAN68T(BjfhCMnl5BOZv$C&^N-^9nVj%nkgRUc+upGKuXPhfB@tUqjZa4j4QG<0+i zrp5~tq(CLfN3sfBA#BnR<;QU7P#)?W_;S)0cuO|i3b+M^27e?}Rh$xF(GAxK&~dB? z=pDRpxWpEYEuj^}s*5!b&j{8}$yP?Bz<9%SM;e?c*9biuX8=c!u(_$8i~Ti@vAe?m zP_fZCswxDdbflr{} zi#Z178S0~=g-n7Ac2ca|i*Ku^gI*m0wgu7xI7W#_)$i_AjdgwEb4i+{tErRE0+4%I^)y{#WVj3c=MQR zXa*KQsB^bv#ZU55Hr<+iC;bM1yYU?WKbF5Qe_6gkzE-|kzDoYE{C@cz@|)$?%NNRL z%V){w;u(RRav;A{-Y)mbo8=eDMfrGnrEJNnyi{H!x65o z>Xg4ODbf<@NNK)wnA9dsmqaNerNn=Ve-|GU9~K`J9}s^f{#?9MepLL9_zm$I@uT9E z;(Nt+iEk6%C|)Ft+}t|ZpvMk`%Lbt+=p`S%e^c2*4!nz3v>H&ugHz&26LrckUK4Ra_+?3`dl%0 zT+Yquxn;RUx%s&{xfwY*C*)Gur?XFF|D63p_JQm@*`H>Al)Wwc&FojQH)gNRemeWH z?3LNevv1B`oIO8#PIhm0B)cPfYIa+8bGAFXCc7$YXVvVI?85B4?Ck8pSuvZIhqJrI zo5dTlp0rYaNHXQ$NL!?9rSFNi%eP8DmVPE*CjAQP#(o$A3;~7!Lx3T`5P0Ds(2^3G z`011$MCmk2WlAMVMN0FO<|xflihxt7i-1$0X-ZouZK1T8hMGE)zD~kUjnLOkl=75v zgmTYN`cF!qZb@aDxPQ>sPf_|LrGKaNZEo3CmD0y3{R^d!Qu=2~AEESNO8-RZ zA1Qr^(mzo8Af>;j^mmm0meSu)`T(W(Q+gkzzoztFN`FP^J(S)}=`Sh$1*Jcy^k{RyRaQhEoaKc@6Yl>U&?A5eNbrQfIYdz9Wr>31pp4yE6w^j1p0Md>#w{RXAC zQ2KRBzeed-Dg6qiH&gm$O20(uO_Y9-(iJlzx`dYbgB; zrB_q>X-Ypu=_e`u1f^F|`f*A>M(IZ>{RpKWru0LUevr~DDg6MY@2B(%O5aE6dnvt~ z()Un$8Kv*0^j(x*O6fZ(eFvp)r}S-1!#ykkSh%J)hFoPRJvX_vvq;GRAXOdTY z$SV@VZIse7NgF2Q4D!AuM_vvRGC;^~Li!2WMab!dlnB{L$PPkcLLx#!LIOg3LOeoV zMo1qaFD2wO(nw0CG*1#vCFI3~Y^RCa$jg%nIf;-{2FJA%_t%hmb=FnN7Psi@agbr6C zFSCRQgk%UIK>$f+Y8s^?NgE<0iT_XbvT1~{ABF%!fFZyTUvFa#I^3;~7!Lx3T`5TFQ<^MAAaCr*As z{u90d@F@lhLx3T`5MT%}1Q-Gg0fqoWfFZyTU675G2pNw6hYsRqBSvi;Bgm;`nFv^l(98&; z?1qRCj-ZPupdp~J($s|rfC7nijBvMz|BG0qh@g$C2#Kg6b~EDoB8<9&h=AxP0)is+ zBSQZf7`u;H(iVawBIK=^m4C}M$@h})|GQWIE&2Tq)e_cih5$o=A;1t|2rvW~0t^9$ z07HNwzz|>v{5K;&g8r>1w;wb36uG{T5cdx(jUa@n6elEq|NnDNzF&S^epvqbe=}5U z>vFa#I^3;~7!Lx3T`5cm&7Ad_kn%3f*LU^X?IkB3J~<&mb=)WMm- zkx?Pl#_#q9N?wY^|N9S|S=MES07HNwzz|>vFa#I^3;~7!Lx3T`5MT&AM+9ol|8t*X zRaTrKzz|>vFa#I^3;~7!Lx3T`5MT%}1Q-Ggf&UN$MD&xFcXRm1ei#A_0fqoWfFZyT zUx4H+mAgL!2>;CGdvG} z=D&QMw>w%+`lg=3|++X3CjUGHzyW`swst>8sOkPLHOyr;kk^krrAXX}z`e zs@4lzPj5Y;wWBrN@=(hiEnjJQf6Km>m$s~FQCbdfNjE>-d}s5Qnm^opVRL`;wr02a z@aC4(L#ZF7u1{T&IxqFo)G?{Unx1I7qvjmzPk*Gfk%B8`9WxEH%WpTeZva`RmV`uqz?X2ZP13TvLYem6MfxEN5;7+eU zE|)MxD09TV7L-{ha6f5WW=vFbBI>S*#fHP-!C+S+DwT-(Y2z{#QSJMhP;8CB{d_{P zQW!=9W9{)td-v5B8jkwCv&IB*iJ)K9ml+@s1cSqLd<)M(nQnpmb_A5DxBMHn=Amrp=s{H1qd{B`VF#ITM9UfqQadAtGVs z9%L>N_}s`4R&g}EYeU%`0d&;JUBWKlA1I7A#mBaKvIgP??N@8BUY=G2Zu;{ z+v!NhNTo`;KR7TtviwYM&&*O%L9S#_LA!_a89Ql@XVoU7__Q4;K2PAMjg-A%I<$jg zq|6uigGSCA930t6WX_I|gsEt)R9@x}mdk^5mga}Z>J<3qYL+)NG(6}9JI811@Bl@i zWKT_;{i+iB=-Dbk9_cTI(J<*pIjLO9pYEal0)cxvDd3ag&v+TqIt1>Ym9$FH*?kbD z3sRCA4TR&pD7+L|os+VN9`}2DiOjjDL8dEk&m^_u!G0)DjewUR`#6E;CTCY#pK~gj zu!Ve5pk^RMdi#r!Sro)cnKeB)Y&*)h!kHt3ad}x%@XVd1kuvlVv#{_Km!&!YIwEbc#$NsdngAw1u^ks>v*uV7!qx_eA6Y=O2#i1KCp|Z-h?6kz0_B zE$0XI+2P=*-ye~^q%zJ0n^6u1?)It55!4UgghJ50D`B?a73^5nbHcI_46VN+!`Gk1Gu1%tc&!D{}> zB>$7u{8Q>xVeZk$M_1oi&0kZeDRbPU?blYby&xDJhPO$2e7KXye%-{(O52Cp$Ol9h zRr6QZKx8HDUOXYMQuPoM*?{QvW7!lU1F3-MlCjikOM24M8>$(VQIJZ9Xo;vdjpdQl zIVw^C>ZR4xDsHqLC28l))yy^3c2*?qyrr7aRWqXH$ODpZosd@@(K2LXL~k3*rY$W^ zT6+6fYNhzBCCGp#zoVM5W7r$oNqc@2a?ta4*5y>H&FM%oKT*xBPB*DJV=*$ZXg)Qb zNs`+aAsO!C_v=kcP#TDKdlk8DN1_an|JF#wA0F~XMo8sD7a{|5{Phvy5c=NmvS6@( zaFqHbbB{nC5V>VSUPb1N1+b*Vo2b^@Rifi-N3va)9C`yg636SX`N(t|WYSH%t&sHc z8`WM`oy$Y#CB6J+ZAPUVhaR5f-CE5fL#yP?oQu3A0{7LiKKeUIA7>txq<_0ohqePF zKXXo!{+&uXMG5J14y9w7l_0zPsavOvW>N1`4PG_qCdFR_aba1X=Jy zzCyE>(R0tNgOLX1{PegYRZCA#N?(_h)`{?G2O({qz+F8Yc_B3%2Tw!FLV^1XO{r9E zlaUA&yJk3ocQOLsb~y2350#Js75i*$MzuW=dFb8elJ*qR-t>Huc5RZT(zINX_W2}@ zYRmL&l6HNPW{`5T1f*ePHz#S>7lUYko-C$klG0yDa!e|foDGt?@$p`<(?MsU-_j1$th!g&bGdQ1lar#^7?$$5xkvJp&j(l70Q@LZZ zU(dd*Y2?|Ri2wCc{2$x!hX}uGTe5_|kS}|Ff6Kl6&)VkBt-OnRqH-A?%1D)KYql0U zdy9qM&ea==h4yN4yO=H1{45r;Le1MkcUPf%Q*Uuyacf~|qaBgm4nvCVV5*b-U zZeY3x;=w9EV!~J|+3khJbTIARUF~Dt-n6;5uxZ`?E-kd5fa{K$B&2i$LvNs5^2nVC8EQ>-#|68VrQ7Ds<1bm5ph3(!g`@gn z$pk#A>CX+2@mdE$WGlSiDaYG8SoZpRh_ES(bXI(w_Ci;24Zz>9SY2v#6sl8I0dC8d z_5yTv1D(D~+D7lJHN**1LepDM63`c6PucU4pi5krRYUssoZ-`9A zek5)l=*Nan0jVnmovcXj^2%cxQqk5*PEQ#MF@lp$gFa1EqC{B_q?3hqGM2_2sa+v! zu|lc}(N{Cj+Sf$N+Ra;w-Rm~d#k07*rXVsqFnw!_TZ^046nhG_dC*I;Mmq|dH$ek7 z6k(RuboQ+2>?$VXp0Xp=`2*s8)z_0cF`iN1foeKkT~l=++aVvs-Q~{X_Z?Ik2&1#Q zPpQG7Dxqr(8nv?Wx;J$dPr)*7Sh}5zvwf@%8U2`~_KtnLmTv1rgDWOCSf@K<0+dRl z+ctHdxUC3FH@WS)ib?zX#0BZL_4E0CE356m(yqp97_V_K`%dzPORz-adF_*iIG#7T zUeeZhiTc69(pBWQcbq*x-L`50zi&~sJ5@}` zWK1kzmka5(ZnS?~wf!39?Mv{hrgT+bplgMxdQnqmV)djiH3djtCNzN_9WgiEwylHT zck+ZDVNWGCrd}#ZtoEx?VEO^NHnp6(4pxdy>LYEjz2oJFrQ2Nea^4iZBzCDX)QY5o zo@Kuhi$>N;eSOjsn%A*sPNFljt8LfJZ(rvg+;|grp(gS0O4H+Oq$YE`Mnh#8lh@u+ znw@Slv2=uLyWqs5l$NNWmGh-%)?+i!D|l#}#u z9MZEF!-y~B_sy?1L?BvQ>Zgv{n6ug4RhztcQiHX*HI>GJtx6;fS0{n&q-QVMU-Q*k z_02EZfAiy&8aH2)-*MjFBhzgQ7xEXHWBz(IyLLzmD=k*fAT<^)NHR)g(m*YW(yGURdR*Ftvb*4ol6z`l`*3y&z)h-KJ_0GLv;aQ#FH3H_d#O z4pVLWRW+&8r~3A*MJBah&6v9VYzL0857V>SlCilbOQTx#1$I{J!1Mq7>U4pVpAs}- ziExB4SA4VZbb7n^&TMmfhj_MlW_EfuC)_Ir+1crriC1O|>4EGb;qmP9>@nFj*%xI` zlzZh(;?dbS+m}5hYYH#Hr}sf-Tl#k4VSKuOx^RYYiEx&1j&OnSHsLbi1H#9ItA*=? zFA28@-xYo&{4BdWTh6{Bm(I<|9hO^=J1VE#h<=SsOD`EYrbJYAe8 z=JHSEAI!ZfcV=#5_J-`aIWGJ6>?gAC&Auu7mF#D;AIiQn`?~BOvfmPd^y2KJ+3yS6 zgiYByv%k#VpZ;q4*7Q%(cc&jnKa{>P{g(`%$zGx$c{3XKincFjWXCBBrl=(~MNr4wKnV$%<;0nhJ z>%{AXnc_{ETg9(sF34;WFVDOrb9&~C%vqUrnR7CiWZss!Ec1cP$1~StZpfXVyCnDa z+~v|)(mB!v(k0T{q|2laNM}eNldhA#B;6u?SNf6kGwELGYAKM8kX|A_B>qKwQsSkI zBug{J2c)^u5=oO>>3C_Kv`N|~{Z2YvdRRVNzF2;X{4V(l`6KeDE$X}GdE&o8i zOTI_`jr>RaUB}ntJ+dng%FS|4K0;n1JuPeU@$x$P#j+>wlpYuF7Jnk%p5K_?mOm{Y z<#*>t^JnLa`SbH{&tIPZVE(H7HTf^(Z_ZzmUzIoW%W@yeeKL1#?xx%=x!ZDgLlMt*L7Vg8%>@8$0hUm}*oA@Mx%BJpG5)#9zwfdVVn5*gl*;vgl*yH5q2VfIAL4)xrFubhY=R=a|jFhLkWxc*@V4< zpGDXnzKyV1{7k~mIe!pgujHo@HpRYSU>kq!gg~{6E?{GgRl|qDZ+BxlL`BK!v2=9CkPwj9w+P!?yrOmbB__$$^9i^ zj}kVE`!iw3bB_=*@yMwTUxE~XCF!v+EW^q3x>=5n;gw5b?Cu}D7eZtzf z?-4eOyDeegP1tt`Tf%*tu%+Csge~K~Mc69tn}i+5eS@%Lxm%KVUr*kBEqV9VivT*fHFP2wTN{Fkx3xRelZk0m{$k-cR`j+!d5x$i0v9-*WGz{CC{tl>eT4 z59JSXmr?!)?%k9>#J!91KXR8+{wMC8lt0Y9gYrkXw^ROS?roGm%Dt8Hzi@A%{4ws$ zl>e1`6XlO{Z>0PQ?hTaxhP#CF2e{W$em{3HnQ&*_gc#D;4Y;6PVNHA zf5M$l`CZ&=DE}#U9_2sh&ZYc5ZXe~p;9lLDYT~(HoMjc^8K*&&)XNZ;fKC~MhG)B5Qf zaTjIM>6FPOk~M25eKTzbWe3HSwMLXRhlFuY1*D+t(>G6hl>PH%Bu(n0^rbZEnbQd4 zxtEZ%{HcVBFDCTN?SxJ{g=X_7Q`U46NgLTlXt|fr(H@$S+DaJr!xJg{!4}GH-%Qz+ zn<#tFM#?VVK-qiOQ}(_WQFcW)W$!~#)h7ul3uY*F@lld?+;%HE(;_9l(8 zOI6C=tWfrr6_mYoIc0BKM%mk!QudA|l)dvP%0AIS*{2p$_Webaed|cdzP^yMTaF-b zu3A88JEik!#y1L-eRCdVw;oQ}SLagp?ZYVh&Ky#H)}fTnrb*wOMcHj_lznd|WnY^? z*;fvs?9&HRcHMN!u0Dve&rGB28kw@sN|b$0r0m)}WuMPcc72wzn+3|gkfH2`G-Wro zQuf6b%5G|=>`T}Vp(9^zYEJP&j-LNBk8$!p!9e4l)`{8RbI^7nCqyhXlQ zzES?X{2BQZI9Yx`zFfXkeyjWj`E@v9o+H0p9+ijWU2=>w(8`5-wjr{yN;Y3Xm$U!*@tznAWp?!h_t4(WD0U+@j-E7BKn{=G)} zr1VkgO6k4QyKpXkqjZsUzVvG871AD@kNc$^l8+}4PLXp@l!aBeo%a$ z_-^s-;+w>a#S6q&i!T?;;()kQ^l@fAN!%j7NbC}i6&+E-`SeJ!ARa0nB1&RLY|8&5 z|2WRC59aUB-<`iJ|HJ%u^WVV9_=f!F@}J6oH2;D8d+?VNZ^~bUXCYpdKPx|+@6X5i zm*roa@6B(@pO9ajKPGSGmHbipBl3smXXU5o^ZC|1mwPhzSnf}`-{pRt`$g`~-0it< z=f0l%QttZPHMvjZKAgKE_wL-=a&O4JHg|6BmAO5+q1@@YF!$2jDY>n=4Y(^gK6iA^ z$Suz;&b8wnWoB+#E}Lu4{xkbG+=D!n{Y~~)*`H;9oc&(*TiLH>znJ}e_Ui1%vmeCG z$)(x1WM7}XAp7d<%W)4gklmT}voFb>l-+`xov!S$StqMymu8R57P5zC56Mc|Otwk* zhw!-Yi146rzi_v3m+(W}<9tK7S-3&?obV~(qqyODkMIuRO~OUOYlK(f=4V*w7h>UM z!i$AoVUuuzuv$1qumlCqdK@7fF3b|93wfbc;4)7_3E2-rfFZyTUEV>lrSvdL;gzB!yi(BFl+L2G zjnbKv&Y<)VN)M)VI;96uI*n49Qi)QL(mbU(O0$#-lx8SRQ`$;t3#E7j6MaZg+C(W& zDMu*x45k01^l3`}LFrSJK1u1{Dg7IzPf+?erGKULF-reJ>7$hXnbJoneVEceQTj(p zAENXRls-u5?0HHU=6N9nIAy_eEoQF;%hcT@UHN`FD=&nf*Gr9Y+g zE=qqw>7A6`LFtbv{Sl=2;KTp3-Y6{T!vArSuv~KSSx& zlzy7hPf_|wN4z!(5Tzfa^h!!UKDwuN8>Mfh^evRWnbJ2=`o{m)-kZQ_a#eNz_4Ho7PBN3( zl1X}c5+*~^N$pEQNG6k-WC+<4NZ3NHGtJC&58XW^0RlV?LfAnCL6Jq?mqk<-K{gja zL=aI?B8t2U2!a~|uZjZd|9kGOr>A?8@bXXbE+42NR5EqzId$vYd(S=Rcb2QJFHzSQ ztLux@^@ZyC0(E`9y56I%&r{dus_Rdv>vPog+3LEYu3dF~)D)rF}WmzPTqBo9%H?XgJVGu9JALN5E{a=Bdg%H=Y-Tq>7` z%jFWeJWMVZ%jF`u#Bzz`63Qizi!T>XF0Nc0x!7{C>TMo1wXgSbwpyfczftCX;2U-ra9B4Vva-iiv%YpxE9O&;KsqOy{8dD$q zzt)ZR#VrR~4zwI-InZ*Ta$T&~l*VK+A#uZ5&v+yK|Z`(m!laPpy05X&vL5 zl}C3cMkrfz=cS+7`^tadpQ*<_Z6?hZ7v}FeI&;y?6*teEKeJRA%v|JLVBLN9`D=f$_xklexclzM7o4>-=va<3YdN#FKWzsWIqpSnctIG)u5F)Z z#TQxD(`K%jTUwgAD7Md^xu!e>Y4`k@>kiLcZhT)D7N%v@ygA5u+y<{~#bf9869 zyqLOulboV+Gq2G!_{Hj-tLAv#;rU0ViwDaS&)7V)dP_YN@{s!3<#K!#Kk7Zv`+IzM z>f2UW1wmMbo)g%9VcULQS-$VZUS7t56<4kwrCycC92S^YMUYo^oCkgt2bmjLrzrb> z>gxvow0~L-v>a$T&~l*VK+A!a11$$y4zwI-InZ*Ta$T&~l*Vz>gXSPSpQz{r^8|W7~dD%Yl{y zEeBc-v>a$T&~l*VK+A!a11$$y4zwJoIUxT3!PfuZ&WDx*EeBc-v>a$T&~l*VK+A!a z11$$y4zwI-Iq;*+f&PA@KL77~#?<$Iw7<3ex|Rbi2U-ra9B4Vva-iiv%Yl{yEeBc- zv>a$T@Z-vX{{9hD>i-)Xeq1@wes{}(mIEyZS`M@vXgSbwpyfczftCX;2U-ra9Qg6z zfY|@$$ZL#=?@in}{>|~@W8WO>nr}9@ajX5)a-iiv%Yl{yEeBc-v>f<9&Vi=|!&@%h z(dq7rCHzWt`dbMpu0tAzts+`g`1*S;H*y*DS*2d=#$xpn#hAD$Pu1c$DMp(erb6zots~tzW(Y}4#-pM9y_);cQjqRYx;0` z*K~S(d0}ooUo4N7^ULR-^vbJw;O6Akn^#|W@&l{i-s{@Q4?HS8a=cVD?%SdUhOI&da`>#k)E^crTS&z)JN zG_$AMafY|-<%JjC_l4KZADNpk^)IhjC1BMrz2({iH{8-&UhChz?uYmD{`Kv9o@(nZ z?7VLmF6TJ)qveb z8ZNm1F7%pD48(ye)^wuREQ7FyvJAt8!&~;zjdSk58@(T%)Qj2wMK4bL>;XEl=7BxW z>0B_pW%usR^Y&_?A~UAft~%TM%lY+Nd*Ws7gM68q&@=MgJ#ljFbvI96d&?sqIlb?? zYj3*w#=YF1o)H1E{+HL^c;K47H{LdVb#mME-dk?I?!dLY=$ho(o6pxId+)zL(rX@> zPmh)}TCDV5T(_=e`H9ZaG2Onlcl%H3-R-@Xul|ShV*hnFCI_y(R`vMYncgF`?3unX z*`M5)T)Qv1X?nFmhFvwXBCkjunXo(U+k4Z#y;meZKnGSkzkY75@51Us_vyoG%l&lX z#)MHhuJ6jO8>=u|J?V>eO2FCja7;LV}nL_qEot=m3y8OM^$d; zwil;WR(W|8$3d0Fj^~7F>Di?hcySiHUY>ffAIEkUM!u6pzCXBsgQAm$c?yNf%1S#A zy{xhdrz$-Jyi*j8Re6~oJCz-WS!R_{VdcK>^ZU^Dqs)p^w}1bHq9b?8!Y}|CFK=RYg(SL6rwq8aSaB z1zd~BO})}ibBjAp>WAF70>ALRkWOWlYnMD9@TRPP|G1)KJ6RBUt{+EP4kb5_ODpoD zs;r_g&9Xd9Z8uDdw4f`QWo1QR7k*WRMUh9IUpW2A#X`pnecK5#*GgTt%EQcY_~y`x zL&t`%wi8=k892Tl2euu!PMUjhoCc-qM?vA|RgoqaDKb%Bm0=#ZR^)kQSyn+>*hN_| zC_!jhewo7aG>@V(wHO2^@?BU{WO?S;fem|-Sdp<@Cx*w46}o&_RD^DsMNU>Yb`b=2 zY1@@uMkT{-1wjz`IekpyB7~@ur>W;Aks`zVuu?mYir9^dGIsnp^L#&rCsCGqRRkq3 za>KaH{HQ2+j~%5}>02-!Ze~^zDl(p%*=ZJKrC;#__-JQY8brAjrLnLZLO~k&g=K|> zpGGzxLVF1_ZQD*GFA4NVRUyyhvG3)18F`R_7ghxAB2M!x3N(KLzbb+#%gVAyEj!Ai zvdHOGUM9XG1FQ1Vi>)+u+$!=)i{CM7QRe4mLH}XBTNDLLz$smbFz~Q8VqBSXZs{kU zBIAdV>)9@!5=U`Jqg|Gc#{flXo`!Lpmo{ITXSrWMH_shb8k4WGEH7koWr?fE#8q55 zS!h>&mHBp+*@YKYUg4ybAH>kg3SqBn#W4f!!I#+c%P7ipTp=D-i(Q0TIhnKORO%*?{9;`K$D*osVLGtiOm*%gyCEUJvZ z@PTE;lyjpfD1)$!>1mJ#3<5lte!4*vxIt+#D2b)W*m<62WeWQ;7Ks~Isa1t#8M170 z$4Y%09@Cv7^sORv+$fJ_Jvc$g;>=hx$%Tqc$zt>ZL`mqSE&~{ZFxUx$Dy)jE46F#^ zP8A29XIo4hJ95f63(DMcm`Y`qCl@F(Zoq^Kd`2rMGdn2kJYbwG=1t&+h$o+p!sfs) zs|>jTUxM6aK01}>T7m5+vx*E4#Qdx%5K;)$iiM06MXWII^Uz_ef*^KWtKwe_Ds!kp z0;SAUm-!Jm$@z*59Q55V&qBl{V$aTf=9ZJPmYsl+W)vCPuteZNxhSF{4Sc6SGzOOE zrGA{8r#~vJT>j_7>6|Y4f$K3MmTQ#=cRt8rg@%EbhIUxRR>q74>``QxcLlOF zgUz{1KYfQO>Scu+$y#H){J^mp5^k5a6PAw4&fyoHr-W{pCFd$KNMV*@X|sF~eK66> z!aNW87*=!`k%Df@GV;%hj>Z03lwrX3=^=n!?xY2x&dON(9veherDerxE})ouh0W5fJSf5;&rW9a zM)puBE#DKd73X1)TkP*FqBQhbAfBJbaGE_ZKsNU!XQ}R>LR9P}vi}rJ&eEoL zVaZl+TeW^;75u_t2+N8&Agh3B$ZnUiOITrYrXrK&aqNl&VYl)sUW9lE89te6R++MI zRZ+xJW!I+D5i*t`^yzg7hl4CirWF~sk&+3{27u%$Ji3dFsC>(-SbYvnVyYy_+CGab^3 z3066*9uyI;NKV&en7~=?Ajy~~KKll$6q6P;CuM+msl~+MyHFxvJ9+{V5gBIXj&Rh@ zlI@BNtAlNU4Z`um5*Y~>BAew>proSgv7I;`Tdgn^@s_c1p>AY!1VuA&sv_B@$i!^S z@}cY~W##xK%bkskr9&5(3+N-hXN9)tdO?5$5hYBflp9%P8Ab8_twN@Qrkk_Z^3{=D zA%hVdL55o3WUgpK470=FMhI{;E*HrY@PA(BBOpUR^v9DesynDVXe~vFhQO3%>p{2_ z?0hM*kj2MxMbSg9r6?61T4j{Q9up~a(lGNqCrdVKGAKP6{Inb&^@wdgs@VD}nLMnY z5berGm4T%!JG2F)h3AHWom+8aTa}+Br)lQo5n~yJIT~bK6}BHEIT?#8LR3MOx1f9oT|vM3Ik*eR!5v+dDsZGyz=4*(d^h&fkMH2aXD8>Ql!KCCr?96p7Cri9CBhVMD( z8ij3R4j`hv*vok<^O~uHu8rCoWEcuTj*-RUMeSkLTZyU3&~ckBia|t0ioGHY(e)#@ zWXH1UQO*vX zQKgtKp;ve;S|{=tC?){bTa^qeGO|IUakC0VNzW>#SB4fC(=mqGmbFr*h(+c}DH0Gd z7lrI*2*jM_WhZ^QH%M3nEZYui0v!%U7pd!@IIx?b&_~#A9_n-)(>-=7n3QK|G65P7 zGS*Kzij2nsM*Tt457~(-H_fnygV?L8ppd?C+eS(27Yef=7EX+kM%#ZKkEBt z-!Jy%eP?v;?YyLOWydhy-`M^)jAT)LKYItpMH-?D!T*#M&gd~$yxh(r{)I4SAMgyOIN zhmdUr!j;`94$$t{BLOh%P?^@k8K#U|plfGrFE-+v*Qh1yr^!)8hF3A^_!3l0*e04z z#Xe#o7+DDwLQg;!L^_HL(=)2XB*Lmjgfs8bDmkLapmt!!vvDyl80W6<@f|E;Y+S4! zF_6&QSRXcfiW_EZ;<;$dSinUF#^EN16&W-+_IVZu3O5Ex8liN6tDw2C=8)4CYoARr zWG||O#hAjZM08M8Cg7Ved6FUnXUmLL2OkhdqKP;byA2=_(uHA@*?_8trXzZTRWc;# z10|*mtFnxoWKNOs3uG6YksDS3L4ZD1%vR(gbVGLBz{CEk_!(O`TSRGLq#%ruohWUX z2bh$H^hZ^ECvwI@fO-W}6r+%lWD^mq*#YLjhyhE=n7l}xBFrPu3QQ7q1<^#4gNh80 zLJk_iu2Dh(HOYd%83GKm1#KRbgQbB4M7CiyVV@31BD1eyOtEReV=Xx`;<)I)DTCU&ZDY$V-s7 z*g#-jB|B-6WQq)0V`PhdXa%C$;WRqL*aJ6BQYN6ZAVErpp38}a)yuLVjILx zvDom@L1js0u7hb@p;fVh!0j;YfJ|7t^cw`gOP-*}fOTf5?lazL@>a_c1CkssZajXCem-k)^>r z1SMjvqgt_)02pjLjx#rTobC9Zw#s$drBnd(k}TBia@)F@}4I7{ds`CS-U;D`EDdRBf|%EzQw^m=r)_3U?N6%Vc5~VCgvfZxJ#bfN*e{0OphcQV#0~ zAx{dK~ zXV_wxkQD|wb}rT@rm*0DEZXE&MaJ_A8Md2=iHKeC9WkAH(UP1H3 zc*Jr|Zc=2hQ5}Ri1}d6ljA0*w(fa_Kj8DmwV--|v8-TT#@M1DKD6N>)%t^Kpt4MBC zWI*HL6&5vma?bXg3RD#_-i60IV}8$QA^4fbVuGM$BFmA1fCyL+mYdw5$ONe1fW_!T zY-(a{$2^DhcfdM>)Whg!1z`FWm|beYfp>z|v6(Wdux66$6&WNRLdA(duvj8Mw2>v+ z4Jr&@$Umq6=(0eU6(0pERmjeY)J97|;YOxkr+LFN;Tv7{43;=50b&9~h9O5tB49x^ z3btn`Bg=eLS>~jJqX4zlu~^H=wTcYDj{_jhB=)gB1@1uKk9Zlv9-+v{K!Pn| z#DG;GF3@5Dk(h!7vrcS1EMw+5QUSM?%|0FR3Q=*G0~V7bIiScOdH`Y^*1UM6Jd`{j zGm&BdMQC(T=4s@E%k*UfwXnj0M@4-qK%{Ipxk`~i3#9{$X$X|aYXUyR#vy@pMWSHC zpj9I{JygEZLXXA9#^AKkco6$8!uU$f8z5v9()0GiB67F?J*Y)y<3>MveGuwNfU%5`%FM`k2=IqI$j z=n0y^HWeVYlcXl&!3|!4pkfhwA$nh6qn06;T_hfY6Z;=ogtTMs2#f>7g((Z<3}$HU zzf8z<=nQzIW7Cb0oz3?hbVEQnCZqsj^bMJe?SML#AygSOU`Wsaq@e}6ZVx0^sP0ts z7NLNaR0yPjw8cpUHXg!IKLxsp1su#}k!1+$5}OE35{HT>2x$-``xF_RipU4lV33D^ z2iSsesRDq23M!`vx|#1GVt{a6-@$}uP;l8~xD4>TImzXU4Erh@QX%_CR`9iDAwC0i z53$pkDIO|1YYv@80HKJz8z?YmJlV*=)x!OI6>~r$W%ZZh+QZ18$H;Zihad;C0{H`g z$i--88lWYBv7%;1fe1`i5BSQ!{)a0%^c3L%pduQ%i}wi`z_#T@F`WT_bAawZS>PJ_ z1pQxPTN<)oC^#uB4x0Fu+XU-JpX2;aG}d0wMO1XxwCHWU?3I z$-{(>iKxS1V$X=dPXHb8)_8P*F$b1I8kTH^=-4>BF@j4@NabT;X8({&Nus>IrMh}^PMyLqS2=h zy>aMqLz@TxuzzCk`Gflg9-!Lb{(S2{#B8| zJHx&Pd@FVq)0hr09}z$F0XRU!jVHcIbh{Kl0nH3tUQ`sB%S?LX>-wWg0Wl?xfX-H; zldv=3Y?CFyP9#__DlP;U(z3i{U755Ku2XwVL%B$!ZunU zL|HZkrgRAvfezVlUHovM4-S?Yng?4_Z2YArga3*ZjE^03nZ*=>h+}^^0U{w|^T(or z9oQf-Ryi0Nzh$Yhsl|XcU?s+1)V)EE5=X;*&zRL3u%A#>Pb70P;oKtI)7S zaYkUW_Ry$6H*mTbf3C=2H-KP^D^UDCfa3@yaipMgv5eW8SXF!q-->xFwj-DtKrhHE zMl6mzwLU|)0? z{W{OZZgcTvc^JF6D9}_`nygVgX6y~bY_Qd!r{LQLB;>Vt+#}=PbZ<)ZM3fKI5xh%4 zTP$m=dv@-iViPBHD8+ifHbjX7=mSu~C+cLV6L{hMFgISW1`1uMk!Esbk4htq?1=s( z^m&IRi=vJxfCA{D002V)ToV?B+K57nFpY4DqaXk}39KoIfY=QvhQ^0=XBsM^SL@Ei zgi~N%u+_yGrVJAZ_Z&tKmK{4=NH_)<6si{PC_q4_4Ksi-0_9{mS+4P0nhe%35Q`XJ zxcUmw3UN8BJs#|joty9+RyKyH2rOo(M;i_>>~f}i<{H1Pzp0^vc$e;sjq)s$oG2Sa zHPZ$i8Z#C97iR=BgUyw7z|<$yCKOjDn{o#3fa)UQBF4K^XHbAKbS)$>ZdxClEo4bJ zECb9Qphi{(Xe;`Cfi=Kf7xV^n#>2Gd%Q2Mi)>ImXqgXYSW}wUC6=M`uf!>2cgrbYq zOPB^eDn=Cp3>bpl2}PTQ;)#olt%Im3yx}ZCut}Df@jl&|hF0eFnhGP-#5J&3Q)w0_ z@sOHIv!IRLHI-)Q&uw~;Ffq_Hus9IRfO^<1#9g7d0sx|Y$Lwg>)tJZVtT>7Q1$_)N zjALL-oGXR#JDLo7u}E*?Ot8d|tQgVw0t%oBuy1j%3GhOIglysXGC(dd>tQE}Ng zjGxqNi4BAu9#{nRSX3K{4ntu>wZd{G77#cH#DWb3SQ{J6!ry{!k5^LA*~0F06l=gc zBp?BW=D~%}1)54jB;XoN1yhe8kAMJBG!Aw?)PHbFX1VD4z=j?`h3p^fOsGlNJea`Z zcqghb!)Lp!7z2(g2%Uh(XfhxQ2pG1vOdQdO6Sh_Ym0~bgv^5C~!<(8`U>tb=#M@mO zFRIC4fDut9&P}!8O1v9rmcU>xf)T(PO9(OSgA9QLV(PQO1D~Mii9J&p@6q4X(6w+- zcLux)vk&JzJ3ht^F%(P))Hu*Zz#WO`0?kD;Ml=E+u#3?*aA;svP?O9vUadRR5cYmz z-5DS3BW2%bSnvcBj)j4aO@`Qx5%9#};1x$n^no>fiF$zN;5R<|8R~mxJge?ZGw}R& z-5D$lFU*PTVL>sA@l+Dr1^7!a9UC6;MHnamM*>E<5}8OeDiK2fjv$9%aK^K1GWeWS zgf1>fFnR*AAcxN%OA5${twgkI9GNJD0;q|h#U^Y6+Tx)=MSFoJL*y;q1HKl|OC>55 zfoc)^Gu=ZKBLo>e8oPk#Qkj1NaT_t(1;HCASSaeoTQzGM20-o9oxv)@=E8G|pA_4g zCC(fHa|eSLYzX@g|G$gNKE{A%{a~wr2TF(;uu7I0zfhAQ6a-TMY$`&X%aJ&EuE0sa z1OWF4*RfzRhB~1N0N;EKa{||ShKVFzOXD~7H#H1Cj&*08N?cx;Cn~a2EOp=uWu#() zmIVB!xOoUX1OP=d7Ys~G7tAXeDJeFZyPFy&iuX+0P5TD zpkx3wA=nq_BdR=eo=_z$Rd4~|N4)i*_?Wz~1LqeGaG($|O)wX6S4YOTG#N3%nJEhA z$3z1v#-Gn7gMi5k%r`J=AU1UWNcks$@{oN1+aPQ3J~O_ohP|o7E>)euW=9bO<`OSR zNX&r;Mg~w$55U6E8F64>&;nR{EF@4qq*2ITmjm4q6=fM;9MHXK7$19{>WvFZDRw@% zgg8dfzc95hnVC@psp4n>gOz;;A00L|qL~<3fsMhqf!~7I_$&QUSoeUPKzNwI4~R44bGkP`8}JHQMXU@#*@y!?`N$6<9hn*IxDqXn zN6;f223r-3fH*KrA;NgDE(7DU^>{b)*F8pa821{^TtH0lVeuxgEb#$?{1qkc0}r62R0`N5ehOTnaFUpd7(g5xA19!Q z6RZz$OR$9k>Jwdu{cF5TlL3X6phQ#>e!YJETVpHLs0UZ&TP@+y!b{w=5vPLqzAY$$FeLjXh))?w3 zas-?LKQD2R#@7@X_FhrWn0bgR@frXz0%jw7*j50Xfgy8DLzEe1>k-xhSYiWlNHh>~ z-uN5MVMH0AAW{4*P!d##24^qJm>!ly`-mEe_Z_H$NJLy-pj1S6vDdRZBM;HpUTnV8 zcyS;8T!O>!h~))3BcU|l)u3`1sCW->HIw3jXOupmVGxQAs7}fZ-Vcc|z!PK}-&VcB zpWrd`O0ohF4&ak$shDtt4x+b{T7ohr0+X;e0+t9&A*%;B17N-=b3jIh<}lbHZhM(X zh!lwu1+_+*AhecgM5w2v8-Nd>h$7&HAjSk*Dv>_~@&r+5qUJDy2+syPCq8y)0@2{K z60b%IqAflW7Cu-j6CZ~gKtBDEm?t7+K|qSmkeW4MR`iz`B48eTII^+`?kBwjl%ar) zL}ig~BH^N9m*cd+0n1vV2|3Mvx}>NyP4}uli5PKqZ@d^b`Um1MB20!w4X96WE*cwN zU!Y5e5KC+@iRS=-#08rZ!H1lTjQ`XNp`pL|CQYSbI>`lk0)v$jx+-~Ze1IU(ght}e zK)7%d3xKFLyv7pCTH*2n!xK+ENp_f!#2#42ujWDubI(|Au&=yNR$HxHIA@3 z0WGljB(DIoO`;zNR&dZz2+BbDtcbHAR1cS$WO3Z78N)8Zq{qQZLJ(`7bx$G)!E3l$ zL!7h(-yrY4NAH*bZp!x$%~|GtJVV&e2pFy59F)f#=C=8WOI%(lF)H z(Nr2nWZkN%G|aFK^++S>h#6#}OI$cXK$uk$lTTDM7`2T-f;~oP1-Ka20zp-T$KwDc zas;nt7F5PhX)>%8g2@42Feb!$0-2Nq+8ocNgpI`@--0OM-XhBv2#@Hc5{Dv64Vp`l z8-Jq7;5;P6fVhvG} zjRJ=c>G=Pb8=bk%PUFv~-fz78L>++1sfmwIbjkkz=J;F3Q}O^lJ@)*uy`%p=`p(h0 z(Vga>n=dyHn4OXLk34x~&+r$AUp;)ousQVMp}U6c!M`1R!{BX$rwn{zpgR!vf3yFs z{b~QUzEAf(zi)5n-#hR8KGy&KJ9K;mAwlFAvw|_`3E5)nAybb~9Z44@*a$t97zVUP z{wlG*63Q(aH#<0C4Uyfq|G1)qECcqzl0kANJ@F;|m$%`*be2|1B5L!h71xR#}bah@TNVY6F zvRSY}Q=-MltC2ibQsfA=0X4u#!^i~(!1`whCHhf@Om-C3ah`N_Vk&NDQoDg2Bt=dV z%_V6bq(G(tnx0tWnEqs;l28YvD`pR_Pr@ru(6CnNkIqabAg+-KpLBI{Rj869Pqgg!qoc6N)W~^9cw7{nSB7qK+_pAkq9AvkgUssfL}7qK;rqx;i~o5>g4t zR|zHd4unT`2DcpYiXl8C_SCzzIR#PEm!T=_1Qb!%cE%6!NW18fDb z1cQ($AwpP43X}XWg2GeKbOFDaYlIn)))?aGA%}(7gE;A~Ca5;jyppcYP$l{hUlC4Z zRz1Um<|D~UB-4?(O^hxM0+1mzc#>;y>w}hO1>u^&`as5#D42A0iYk+eP;YXSBn?7R zjtQ$LAQW#if|PYfA|5Gp{I1L`@&B+oWpk8h7^x~yC0(7PN*q-qS5DGZNvb9&bpSTR zrx3^l^e1K^*#dYD$N}YJ*+>aBCO1_QpCkl~fg&Ubza!(0Low;$DZ~)MyDlbByDguhoe?*_i z9zv^#!628O3}dp4*;+_MU|T1lMgoh7olLqqPnE#fMxKJCTPLbERDY7L&Q!G8jjWllWftI|L5#EJPUzJ}TL};B3U(;g6A6La9JP3^xi!fZG8hj4)6{8ZJAF`J+k+ znK+CaVSq_jl>joEx{;HvDz#){A~kX~C0(5p%%0oGLz#4SQZS2%yb70?R>?ghDMUg! zW$Qv{qbVb?86Cofi8_%gEfTiI;KEc2VgU6re{@zb=q=fKEYy&7FTaN_dL?2Ke3A$^ zsd9prfOU+ozy>j8WLe4NMG)gl)Me7uX~CF_Iqp&kG-EWWMTG#s9OaFY#DnLOe+V{| z@H*rjP7DEZaXaDl#*=ENNq0!iXJU2n)`Bw&`YVD|!ng&B z6l5M?3qLs;Oo`kWRCEZ>vjITa({LVwJ`*^EWsKjQ`UeO#1TLYp#PLh)JuXy6MM6Lb z5GJrn>MkYS0Y!(n1tQYXuSwk|`6fbvCgV^-@&w`~f&eXn_*scsC34h}j209N?N@Y&B5&m2PP#g8T1rMp)+PER0Aqj&MZ_GRl}f${<49~Dp;LrMAWi%1+`^|qx*;Jk5})qDD)vO27L<)5c3NC=ls%#61X%E;h$5AM z;WA!^{w}tFCQ$^*GZh(Py-8OfLxp{Yj4R?S5rd%T{)CmtQg4s;&x5Uj;8;FUFe`R#! z2IB(b)t%0}JM#n9)a>L}CtolA{fSRaJa_Dd@$ZcN<@m3TA2D9t|Hl5ubso}xTHhb^ z-7_}YclpTZ@JENAF&qtjtN(9nkN#hc{`T0<41RX-<%5qH92oeGfjfv8SROy8^WD)) z&3`lBHDH;Ci81)Akymxz%}f7Re-ymYl-GADtVPnVh^avA6N-ilN(2Z~5HUtN4vu&R zo#{tN4~}QT5Sf3Nr-Vr1rk5B++xQnnMpOYJFsO?q8C$60R6Zdi8Pot#%LdPWgaSjB zAiDyn0x2$pBoNhxcL}V55Y%sI=RMKHji8M?RBxK@TgE+#43RKoJy17AQlqi2$s}eA z6$1+mnczT@yu<-T2&y=WQNbj<7UPNnaD@4j3Gnx7pqi$M9nizwFiG(SJx-*@69UC> zpss_~0;&-&2fix>p@4P3%#b?-D-l#faVoYyyj93pP$HhMLgU>vnPv)x@qA5&Y8{x7 zAbL4MPdw&WTL2Xpa-^>zFi6;@U}Hsn zQ&YMAG}W1=hL-UP-I<2@YM1NIpt@1{k;-=B)+DYXBH@A*PllV_1tpX{or*X<8!`cA zl2R%zYg}rm=|tM4#yfOp8kXERsyjotEheUDNs?+x<~H68$>?QnWl{u>ZH?`XuoMcc zu|FXi$!(F65t4OeRmR5@8O9!iCm^E+Fa&Iy{~|jJj9Y?Q*-zQ{P{)|bpaf(@qh$k+ z;4P0@G#ibV>&`SRDp6=E4J%r0uBkLDy*R6>G%V42v1YF% zM7p>s5L1ABARFivX!86Ot&EThbWjR{f|FyDV0Xom6-A{;%FvS9HB-I<1%qVaQ@GY#u=nVO0O>O7e8TzcZ> zl((b)57C~IZbLCbF_0ws84*i`N48k32ev)5FU)t0cmyM*k9a4^QhwPvMUw&4MTIBA zSn_)D@}cVUkQ6DB*c@zV)FD#nBtHk>SIT}e@0r#_F_CcObtcufG|le4M0W?1tV4LUmVDxlL;;>4~+`A1lj zBPBsxM7t1Qq(TcYg$X_-C6m}A3PZ5sNUCOqVK?H3F#b#R24EQ=q{JgmAS#iI&?1lk zJcJBgs%(&bfz1UNhxeUYRyaqAm?PQ%+ZB|7*BSqz$N=|J=?Hrq9GB>&fDlX-!A?%A zl;jRbutU?pHpSnU6LChAT1nnGfsc6M>4EV!O{Qs3+B?-iHLZy$mCOmpX_(Bdhr4;W zo}POROH$3%RGJlRxJr!@Wz9e&fI*48puixAJ{7ejBNt@@I7ZNOiW^`fk=sl9CWxJ6 z@Z$SpsZnddGX74J0TIS*rEnOgF8?DmnH*8jI(7^R{F8dBlqDhDkQ@i)TJh!XBs-HjMu5oG_4l$$eK#CVuZum|9_7$^}eZNQ|C^8Ve(az z*H4a2d}!j%32Xdo;1;Hw6&9~>F@(7>GoR{z(?9k{iBL*K{yp4At1zR`J0=ZT%I z#vd8?+}HR|x=+;TN<(ug>FSbJO=H`WuC8fCD08Ds`J}6hT2bPqQILGn)m5zs)FMP5 zlR*Gj0`tKd$bi7IB`y_Zi>ztfBjB?DdTeaCMx^8?kxJ107+NZfNEL37qttv>6-F8;)Ja!W>@rDy zYZSYmbainn{OF*SAyatX=j5TV^<3A>BLp;FvXb22MaR4T9ajBqEQ@{}T zHRC)iNV>YfRbEivjD%O>iR4rYR6L3RNgO!A zfaIG}G=yjgs!n22lU#z!nmlldD-%K_DK6sv)fKKvz$7wv2yxFT^~A43!sjUrf&-ga zMSQ^Iy%WlTt}1R5LI#LH052Bv2M=_{I@Kkvnp#pxSJ$}0-;9L7jR9m$jT8_FkP5oN zxJs2EDI_V893v{C9e8K3IPr&(5kRQ7xJ|%e`3cn}$+Ey@ zgovqoe+N=eZ{U&rH1heNNq;qsew*$ zMv#C?%^`{~b#y1Ggg)iN8^gGjo%OqG;NR|Rmz zM@T_F;&Tj$LW*(|KRVIpb zqp}-GSJlljDY4loYc}bsdRHbvdUAjU)-&bH@ZeD$f+S`v0_rs5Hev3|IU+cuFlVU{ z>`|o@(3IFKLTfO0aYyTVS5g;}(n?Yw6nonukrlTD#k{157H%D)yF}+Fc7qfdLbGsV zAf|{BA&Y@^UwTRRV#Q~qaHFJ=%Ke(-gu>y1t?#M^7~=V~}(`MTcr$ zjhsVC*Hv@~lx&pYlyn_Mhq8Z-O35T$TYpzWJS6E_iVlwSMh=Xmd!eEuMHQ%x2+&LD zjD!=i(5YU}XMFfik_~5u8 zB2yB*B^VDhi~3dgbfx4io-2@G$p=SwMU0SS9WW1P6&*}}wtif-Xk#3W#aSb&8X)Qi z5*F1_N}SV0vTlI{a|+{0iW&!AP~kuo)=0YND>_Y`AxZZH3nZW^rorz z4{asKe|G40q5$47`IzCSO>P|AH}R?dv5DtSTsHol!B3CBdwhO;_t+PQy|JGgyLtE{ zW0QR!9sT&|%IL!e-edlY`SyW%bIzRU|H8ocMm|5VyZ^=!bLbyOUO94o|Euq7@wazc z4*YO9pkO&7C<)yoAda$a6m}qvnGhI4!31`cBIZ;>)qR&a~LV{yD zr)o0flMp`$N{JjIGP#mMD<$O}Df2~gJwaipf^3aM)S=g7Ba#b=bBnTN1cDOH-_f2G z07=RmBABol5pPl_m1q$nt%=^J?gpj@#ugDf0?ZR?BIFT!_<-?Q5Qi3brc`f;g(CKX zSTKN3j3cVb6Uac25853eX(2id<`TF+m1Brc1^p+xl`vW=N&@jyPN_4d$xw6=F+l#c zn4J{c!2^sGB?f?4AqpZ$AsLBBLUF>vLLL*E01*riwp@;d>zJAh4i?}6PTRoSLCrYI z$-y`ZuHg`r;&~vlD5I1OLW#vVqRt#ZKY3D8yH=uxJA;}GX{S{0hC^t~I1Lft7>F)T zb96mQxDkd!z*J7W1QsCi=M;}8U5}D`RQgsv#_uUI% z5C($VDby|nnI&|e%`Y~-s>n2jAB;cJy=jO(7=NgH)6m#AeqWJkYU>-H)V*nF>KnhO z$TYRAjo(#dni`JAM>Ls+c9HQLnoL8p$oTb|H_faNop;1&SO&w6h0yfcRKVrP+Z=m1b|l3{m5ax-$SmxW*s!QL1O_&NQ^& zjkhZ@O|$8YSE_F!Fp*%z2Y)=`shUc|BQYMKsWd#CZK0;pEK%bbioH#fIgOtc`+s`) zd^!J5SND7E@CPOzJ-K1x6B8>F7mxqT_}fQ1w$C7KQv>a$T&~l*VK+A#u_c)-pT#lk9dKhR6PcSu)$@C@Z zPYx_45|OZY%4ZT1D`(|NT?I(i=~rY(LZjR=kzQo46Scu{f|B@3d?i01 z2v|5mci0I-!}ePlcCx$c_HNg5EUdY74js(&N#lCl6Ul! zFQ7V#9Pdhf6mU8@Fazf;@%K`-&vlIdS$`BIYsn-jDd0|EFkWgxV9-$j)5M!dyf2Xz z#P)(y${|+3cD#`+UCB*^j%|EJ=|-TpB-Tp>S^)!bRs8=vjQ7c!O2hLXAE&7_OksVx zrqb|$!$;Lrh@z3)D+16-bf!)UZY)Af@cqbfU(7qgKsd~ba2hgms4qap7NABsNuFsE zhSN2bW@kU%qNy}I>+=bE!Zh^C8NaN_Gz^+BKBUPsjEyrsqsTPPEH}D}!%Zt!9oJMy z^&^FWECK9X{2gTPJDf{NK_wED5mV$ZQ`!`d36TdB&E^Oy(m+X%B}mOC-YPm%e^SF@ zIL6OwG7W1E8E??v)bRMe{WX3FxSoAfTOdK4%Bq}RKsX{%26)am&4I#8 z$Z`@#@hVbditr|~&Tyx37>AS@lsGSrf$)9ZnTAn1#(O3H-|2kg|497*|9cwUk6(|H z6$h+w9~B8h*La3rOaxI*gI+D*Eul+y@x#~KCgBwbz0sp(OqNmmzhqLfObvq+P!uI5BYP@}_0 zlddl3#Mx1eP9sgas_wDbGzmEA>bg)(k1bBRs(!NBw02U`RrQn2ra)EFRrQliITx!5 zQk8TURoraT)1{K`lNFt&QD{l`n2rZ-SX?UUF6ek*%CB%BIAMo5X-+=Db2huPN|yex^NLQ>^Qx2XQALM3nvG6VPP#`F9g4?s zJ`5EM$cG_t91M&EF;0snor{Dt@*^msN#P5nAebl{6epnoUeZ0R=rpZ& zm2{t^=rql2Pr7r84g=IE{4nVr(*0?8fKk#tsOYGm&jxXfNw-pTnx2G|bW24C;Jgu} zoOBD_pN7Z4CEZ-nX<9)m>1K+K)$kzqq?;-_1P(VkQBM5-XAR$_{r|I*e?#@Z#|+;! zxrzFJ&zsmg{v8$Zf37~)Z*0Txhem&IWO(%1qnDWfYQCLV|6L=WA9=;dwLgA#bo<3E z2U-ra9B4Vva^OL6K;idIQMAseBGVMK>I`c#4KbNcpC;3|_+L$?nZ|GYgC^6~5Ip&a z6XUg-GYv}#8J}04thN*(1ztIKg>-OAvy-4!RU?`))Hk8lEAcKN2OHa1jFiv^M+3^F zeh5i#l=h=`KE+e1G()lJf|4mxMw9bNsNxY>qne7Gb55EvRr@GQPCN{i#N`}-h;lPT z4e@VWzExaMN`~Ma(w|dKKc;2?<@iWg^PER(DpW-$OPYi3C|*NN6iOvfNuJ6hz^fHC z45?904O$A)0CP*td^*M1taJu^fEWS4Pf@{!BAboGVlZ0rw<)c{@e3S;l5#=|M>5MP zOB{hBdD&Ee#cNB+7Ea6sPA7bb^u^NERB&fXObVqmN{XvXd=#gjQih*HQaFqWq=+vD zQ0IhQ$^%ky@1MLsb;cD6eFJ~!nOg%ZZ z#2hh7xz6mQG$Zqno|6pAAy*t!Ax;zfGEKz=NFvdQ$Tkl4Az70TQ`)1cIMhex2yJzg4rjt}0v>6f_*XdG0OoMw2X%5> znR}F_l={`I1ES#McvI+b@R(*y!xMsxpHZE`{|=rMa3TRc~k@U$O zCITfy$@iu9h$koe5D^3us3ZU?mrd1FnjJ>)RP{+s59ctRBmV#AbslC+{q@wRrrtC4 zhN%}%-8EHC-7pnS?V6gJ{4SLMJ~{cW$)BHm;p82YkDoj+dBNnw#8)RiI`OuNmrOim z;t3P`CmuR6NuI!G$3HOs#_{KnD{$TTdE=YLzCHH2u@8;CdF^ONSg%-5KAlSgoc`A~Di$iH$zz(+>jI`UH^%Okgr z#3MU~|AQKX?-+jh@bTf>h95qBHfIceW9So{Iq69sDpS4t58h zFt~5f8a#7wc;KG~{&e6s2i`RByn!PFw+zGsXATVZ|3m+u^uLdqgwO7OLjS}1&*=Mh z-@SeB?0Z??T;COa)1B{jKGS(e=lPuycKkqp9wJA-^j&Ql_b!!3%6xflVSe`LLNQw% zNtX^CnLBuB`7-Cu3y#en+$m?f^zAi`KVAReq4Y?#Ja@E|XU>*$Tl%gvjnCfyGpnTV z=LxAlJNdE2VsRlqtV!Xk6HVbzI~?g7biV7#{{2WeII@{dEZ~Je`v8h zlHR#W2p^gd`uzH5=7k0M!lL@VbMTM#9Wagm@c_@%w9dl))pv<${N+iHEmY~;5&5_o z{95v^zq-E%Y9iD4toklBjW4W!=2*E1I=n1YcHxccOHAX7_xnUm=1g2oeHWX?m)1X| zK3#}R<3f_x{M(84#dN+{I6AxVlyXtM2Ir8x=HKu4iTX9CKiLCv_(Q;W2+BTdV@)ooAi=)g9{g&o!vY*<=O7uBbo0=(U~)9jqGbjQNN(jlR_4WwE=YpDM5Y__nxyr4#DC*U)Sr!&xN zN{<~|TuAdnCr0Z`aAlcf-#vNsdy^2nxO0g)wRB{zC>Nz4%lgN4`>Ei-eY;HKKlB5c zeEwo;3t0Ps&>8824?i-+nrUM?0Z8t{+(QAwqqIYNDOQoVZWpnjV}%rO-}n-m$b$ zEzjx)?>IEKw9I^D6waakXXk2j!vj&Md$EP`o-#8!iprt6<@`u`OpPM-I%O35dJWaw z{G1-(t(4&G*yd8NWqx7tXnI8bh@zTsbh+2Era#-MliBgjie|%y-$evQBOKKP%ay`v#IiFtNTr3>Vj+C;O)Ze-5I-W!F{LuqF zCtN@CS{`EVy%Di>cy9jS>`hnAF7atcY9iaOq1`o&*Yw)gNYEXRq>X<5{7G%SpI)th z`r2OeO`5WrN{`S#ecefI^-niZk+E;LX}o^5S$?#I`icyk@rKpA_2Z{sNdw~Z7gihU zkC0!Twx32;!hbu_D7UvJ+`iB>ezte}SUO*-?Pco~y5+z3T5h_ke$Uo@y5&v1mgPB_ zdQdz4a$4xYo7c7U7_pZ|c=(oHoI~-e!_@fTy0j2pfH~>;%%#U zdyJsco^<2qdJXEM>c%rEpC?28>R#i~^v-W1G?+Z_A ztADzQ>TtrM7p*oc7E$X>SoGr6yS+D2yiMNpl3qjo74oA^jwbc8)iyP@Hh1~3SM~1p z_@>@t>G!<6*SN3uo(uJRezMna#hNd=fHuD9r%r0?ebFq-Dmy zTy8&0_wskwHq_nNex`2wM6XRgt!~@AgSPWb<0Gqm%nnK)H=m*J|6ctY^&W)$=F|24 zPuBMpm$-lWcJ)nLHHT-5xvHAWj~`jSOLm4W+i1KH9Yq-%v*n$v??M`OZiN~u`tPqF znDjW>P`!Jfy@f}vG>uQ)|0DHhoVA&!P`>|Q{Zo2`<$JcB#sju#{PvpftbcpzCYsPg zKCGH%mEE&-Blnn`Z$B}SdQYFKpMI}?+7;p(PT}5JrtywNj$l=SW8((yoMRgARCns1 zZkggHQ|w)fCECdny6vLYVz*Dyz!dw{wGF-ZP0&W~-mTwf$@@+n*Z1C|@7e0!n7;RG z`ktCCr;h4-@6-1@d2Xx8J-+Ni`X2jYUd}7a;?xoS^!s&-uc(Th$lE`l?*;0fn1tN> zpuQKXdtw4|@7MLcNZk{Ak9)tN@5OR&v)Fe`x8KzFkmE;FxxYyaI%LOh>HEck>e1;~ za4eLu`Gsk%hfJgTO>5QdLb3m!+nF_{J~#E=saH?~;M%G4rp(E|oBZv`*G)cavY5PV z@~lZ?;>(-}@VbeoPTV?i!Nl`~VxpT3(RcofTa;yg1 z5dm?!1SK;Cmg{!Z-Zh%)MhP|U>^7^T<^1vdi0GkMMB62B7zK77?X#R+PUj9UoY=!o zk!aw~F7u%L1Zwhw(K|^A@K%KCf@)P?+3Sx4 ztkR#Q6FfVi_ivRbRNgnX3USQ#?1=3WSjz9#G@Y;{wn(%nuUpf2C|#Bz+A8s%>nR?t zErP8Q(n-^?6Fup@Y@0-BG9jP5zDW)Gc8R>CBa0{6PIP3Ggj06*n0wVY%^s@}g3S^g ziJ@KJUO;XiR4VQU33u%5G`HSysAu+`DUpke=w+IOepVqL^`5gqLK6AS={aWnvJ$Tw zCAyHQc80!Hv-%W?6=XK;(07%gzCq&t`0<(Qo|3~GC4`S3pQUfD{`g62e(cmgo>M=b zR!^$UWuwH&@#CHP*6NQBsvpnjyBZ>p2sU`I3k7y*VUd}%a9HtRn}npn?6cQ4)Py!j zm>JzYN4JSE-Y5}cyzwFWR{h{<690vm*sbp_FV?WuMv3R*>2vXkr0n>!YKz(|r=rl8 zJ*#(XG8^TX6CQtPuR*bQlN?&Ys7@-)KBNC?1K1>IkZ^nZn%gYcO>(3Nx3}rr?6GR$ zpC%`S(8E)+j6vq#EGQL*I2k4|ci-|s$hAG2Fn5X#{modt8^2m8qyTi=)CJUWNX z$?x;aRRCkn?=FzzJ35d5!G0%Qbmg3m&OvkP`@CokR#CJ6A~}_#bKKnUgZ--RVEyA$ zr^=uf-mXaotK+C5UaFYM{%%92F-rXMeHcBaV9w%H}KdxT7Sqh``IK}zv zyEU^nN~LoiC;Lk`s7bV0>X!4kyLxx^@mcjaXISdT)#TYMWyN`1%8)Cu-hX(u4cfHmK14l!@#@{x z$InrZKTK@cB?Zj0n74G#=I%+oQCruR?ymc`s9kD%cLpUEL+v>|vvBVTb!|s?Cyjss zKXFoH-HlCMvE9*tpS#+wr-oSWsLan>yrlR2}C(J$E9KdgI5uWI@5o!#2_|Hu1zpeC}dE7m_I$k*3DBfmeRD>i><8vnGu zZT0#~tZUXQS-)TGe!z!s-p>Q8?-v|^_kU~sGy4641n~aCUcz0+9db z6U__9#M64B3uku)CSdIT<^G?kNuALZ;D9&%>wO-Rj-1sM_<#ZX_I;Xb0@GcrKk@hd z&50*48)oa7Bt&Mq7=bdTUs?Z%av3@`owK@P5%Q)l-`_*JFJc$+#OKyOaY%V)<%#L8 zn27Y{Pu4#nZi4zXySid4GVwpN{*e>jX(1%`zd!ot#?)7*K0fs;Q$L0GKbyL2>Z~ba z@=KE+oqYS`^Czpx8zsO}-#327*uRba+1LlieqroIW5>tRv8%^C6#?+~ysG`va-iiv%Yl{yEeBc- zv>a$T&~o61&w;5GV41!hX6IDgD)Sc*%yY(yU@>tI9TCiXEyS^LMF5%I<}LU$IC18n zMqp1_5hP}pd24-FbmmPfg2C)DZ>#Us3irkpL0?u09PSYi%w?DP82zvc(A=~lc*`F1 zvGu)G0s^w^HpjVrBC=}piU2IL=G5xl`tj{604qwI2e$_~sMVJ$ATM?ynsyg27yD-hOi9 z>Ng7-v)g>4c#_ong~OXy1d5q8)7886#Jia;^v^hR2b)6|L}F=v@q^PH~`jPVtLV78gb!3Fu%)QSKvXPNsC z_F`r>u4uqz>mjDnat25s_~wQc0bF*PS3=0rv?o^tYT034CAWmuDJz1q>@p9im+6N# ztq8!f$9zP6ukQb;D}u0`V?I*du5WHy5pZRXc{4XTq51IYvmh%xdyT%W>7KR%q5|l5 zZSUqPV?a{q=XLtNbZg@ZC<=pdy}ngHxN!vpg}XQCyY;-@yaHmv5Z$tWN5h4qtd*9ve*=Ss7<|3_B85Ilr0EafR7ARIq^1q=ii-_x{* z6|8xAZvJ>L67KXB&<|RlQv1QQr=k~bcg70%2SQ=J>r`5m6Yb0u@DE_jJJ$Vpjq~)Z z74Q$B)AhfvA5bJVt$=pw^MxJ9a^ o@uR2frZwyR45@#{cK~WhaN=4QumatstQn(qxBj8_Tnokj50VjuX#fBK literal 0 HcmV?d00001 From b1cf014dc21a2dd61040522df015de5e4b6ed02a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Feb 2022 16:18:39 +0900 Subject: [PATCH 0669/5199] Add test coverage of EF to Realm migration process --- .../Navigation/TestEFToRealmMigration.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 osu.Game.Tests/Visual/Navigation/TestEFToRealmMigration.cs diff --git a/osu.Game.Tests/Visual/Navigation/TestEFToRealmMigration.cs b/osu.Game.Tests/Visual/Navigation/TestEFToRealmMigration.cs new file mode 100644 index 0000000000..00a06d420e --- /dev/null +++ b/osu.Game.Tests/Visual/Navigation/TestEFToRealmMigration.cs @@ -0,0 +1,43 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.IO; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Models; +using osu.Game.Scoring; +using osu.Game.Skinning; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Visual.Navigation +{ + public class TestEFToRealmMigration : OsuGameTestScene + { + public override void RecycleLocalStorage(bool isDisposing) + { + base.RecycleLocalStorage(isDisposing); + + if (isDisposing) + return; + + using (var outStream = LocalStorage.GetStream(DatabaseContextFactory.DATABASE_NAME, FileAccess.Write, FileMode.Create)) + using (var stream = TestResources.OpenResource(DatabaseContextFactory.DATABASE_NAME)) + stream.CopyTo(outStream); + } + + [Test] + public void TestMigration() + { + // Numbers are taken from the test database (see commit f03de16ee5a46deac3b5f2ca1edfba5c4c5dca7d). + AddAssert("Check beatmaps", () => Game.Dependencies.Get().Run(r => r.All().Count(s => !s.Protected) == 1)); + AddAssert("Check skins", () => Game.Dependencies.Get().Run(r => r.All().Count(s => !s.Protected) == 1)); + AddAssert("Check scores", () => Game.Dependencies.Get().Run(r => r.All().Count() == 1)); + + // One extra file is created during realm migration / startup due to the circles intro import. + AddAssert("Check files", () => Game.Dependencies.Get().Run(r => r.All().Count() == 271)); + } + } +} From d79845fb1df28e63545aa0f4bf9552e16965d9bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Feb 2022 17:06:33 +0900 Subject: [PATCH 0670/5199] Revert `NUnit3TestAdaptor` (again) Console output is still broken. See https://github.com/ppy/osu/runs/5196023462?check_suite_focus=true. --- osu.Game.Benchmarks/osu.Game.Benchmarks.csproj | 2 +- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index 2bdb6a650c..434c0e0367 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -9,7 +9,7 @@ - + diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index a6c614f22f..fc6d900567 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index ff49d6d4dd..ddad2adfea 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index a2e54f5cdc..bd4c3d3345 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index debddae037..a6b8eb8651 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 40969c8b29..acf1e8470b 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 9fd73f2c1b..c7314a4969 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -7,7 +7,7 @@ - + WinExe From 03106e846c6a6a70c944253a333aa922c1c2bde7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Feb 2022 17:13:31 +0900 Subject: [PATCH 0671/5199] Fix test failures due to async mod icon loads --- .../Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 4dd3427bee..7c18ed2572 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -173,7 +173,8 @@ namespace osu.Game.Tests.Visual.Multiplayer ClickButtonWhenEnabled(); - AddAssert("mod select contains only double time mod", () => this.ChildrenOfType().Single().ChildrenOfType().Single().Mod is OsuModDoubleTime); + AddUntilStep("mod select contains only double time mod", + () => this.ChildrenOfType().SingleOrDefault()?.ChildrenOfType().SingleOrDefault()?.Mod is OsuModDoubleTime); } } } From b2276baf71bf461c0ba67d2ca6eb634c1ba9299a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Feb 2022 20:55:56 +0900 Subject: [PATCH 0672/5199] Seal OnlinePlayTestScene.CreateChildDependencies() --- osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index 430aae72f8..df2b4f6192 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay }); } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + protected sealed override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { dependencies = new DelegatedDependencyContainer(base.CreateChildDependencies(parent)); return dependencies; From c48a0dc993e4b5621ba9f92625f486d93229ea94 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Feb 2022 20:56:46 +0900 Subject: [PATCH 0673/5199] Move UserLookupCache to online play test dependencies --- .../Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs | 4 ++-- .../TestSceneMultiplayerGameplayLeaderboardTeams.cs | 2 +- .../Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs | 6 ------ osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs | 1 - .../Visual/Multiplayer/MultiplayerTestSceneDependencies.cs | 4 ---- .../Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs | 5 +++++ osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs | 1 + .../Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs | 4 ++++ 8 files changed, 13 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 1322fbc96e..437f4e3308 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { base.SetUpSteps(); - AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).GetResultSafely()); + AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = UserLookupCache.GetUserAsync(1).GetResultSafely()); AddStep("create leaderboard", () => { @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestUserQuit() { foreach (int user in users) - AddStep($"mark user {user} quit", () => Client.RemoveUser(LookupCache.GetUserAsync(user).GetResultSafely().AsNonNull())); + AddStep($"mark user {user} quit", () => Client.RemoveUser(UserLookupCache.GetUserAsync(user).GetResultSafely().AsNonNull())); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs index 8a78c12042..cd3ae50dab 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { base.SetUpSteps(); - AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).GetResultSafely()); + AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = UserLookupCache.GetUserAsync(1).GetResultSafely()); AddStep("create leaderboard", () => { diff --git a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs index 204c189591..f166154103 100644 --- a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.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 osu.Game.Database; using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay; using osu.Game.Tests.Visual.OnlinePlay; @@ -24,11 +23,6 @@ namespace osu.Game.Tests.Visual.Multiplayer /// new TestMultiplayerRoomManager RoomManager { get; } - /// - /// The cached . - /// - TestUserLookupCache LookupCache { get; } - /// /// The cached . /// diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index ed86d572b9..a9b3ca4991 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -19,7 +19,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public TestMultiplayerClient Client => OnlinePlayDependencies.Client; public new TestMultiplayerRoomManager RoomManager => OnlinePlayDependencies.RoomManager; - public TestUserLookupCache LookupCache => OnlinePlayDependencies?.LookupCache; public TestSpectatorClient SpectatorClient => OnlinePlayDependencies?.SpectatorClient; protected new MultiplayerTestSceneDependencies OnlinePlayDependencies => (MultiplayerTestSceneDependencies)base.OnlinePlayDependencies; diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs index ed349a7103..d9fe77ae44 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.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 osu.Game.Database; using osu.Game.Online.Multiplayer; using osu.Game.Online.Spectator; using osu.Game.Screens.OnlinePlay; @@ -16,18 +15,15 @@ namespace osu.Game.Tests.Visual.Multiplayer public class MultiplayerTestSceneDependencies : OnlinePlayTestSceneDependencies, IMultiplayerTestSceneDependencies { public TestMultiplayerClient Client { get; } - public TestUserLookupCache LookupCache { get; } public TestSpectatorClient SpectatorClient { get; } public new TestMultiplayerRoomManager RoomManager => (TestMultiplayerRoomManager)base.RoomManager; public MultiplayerTestSceneDependencies() { Client = new TestMultiplayerClient(RoomManager); - LookupCache = new TestUserLookupCache(); SpectatorClient = CreateSpectatorClient(); CacheAs(Client); - CacheAs(LookupCache); CacheAs(SpectatorClient); } diff --git a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs index 71acefb158..feb9b55743 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs @@ -31,5 +31,10 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// The cached . /// OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker { get; } + + /// + /// The cached . + /// + TestUserLookupCache UserLookupCache { get; } } } diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index df2b4f6192..99a492cd6d 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -22,6 +22,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay public IRoomManager RoomManager => OnlinePlayDependencies?.RoomManager; public OngoingOperationTracker OngoingOperationTracker => OnlinePlayDependencies?.OngoingOperationTracker; public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker => OnlinePlayDependencies?.AvailabilityTracker; + public TestUserLookupCache UserLookupCache => OnlinePlayDependencies?.UserLookupCache; /// /// All dependencies required for online play components and screens. diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs index 24c4ff79d4..47893519c7 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Database; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay; @@ -22,6 +23,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay public OngoingOperationTracker OngoingOperationTracker { get; } public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker { get; } public TestRoomRequestsHandler RequestsHandler { get; } + public TestUserLookupCache UserLookupCache { get; } /// /// All cached dependencies which are also components. @@ -38,6 +40,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay OngoingOperationTracker = new OngoingOperationTracker(); AvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); RoomManager = CreateRoomManager(); + UserLookupCache = new TestUserLookupCache(); dependencies = new DependencyContainer(new CachedModelDependencyContainer(null) { Model = { BindTarget = SelectedRoom } }); @@ -47,6 +50,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay CacheAs(OngoingOperationTracker); CacheAs(AvailabilityTracker); CacheAs(new OverlayColourProvider(OverlayColourScheme.Plum)); + CacheAs(UserLookupCache); } public object Get(Type type) From 2675bb87ff3009322be4dcad2e3244ef33425fd3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Feb 2022 21:05:05 +0900 Subject: [PATCH 0674/5199] Add BeatmapLookupCache as another dependency --- .../Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs | 6 ++++++ osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs | 2 ++ .../Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs | 3 +++ 3 files changed, 11 insertions(+) diff --git a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs index feb9b55743..c94e288e11 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; +using osu.Game.Database; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay; @@ -36,5 +37,10 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// The cached . /// TestUserLookupCache UserLookupCache { get; } + + /// + /// The cached . + /// + BeatmapLookupCache BeatmapLookupCache { get; } } } diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index 99a492cd6d..baff7c168f 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay; @@ -23,6 +24,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay public OngoingOperationTracker OngoingOperationTracker => OnlinePlayDependencies?.OngoingOperationTracker; public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker => OnlinePlayDependencies?.AvailabilityTracker; public TestUserLookupCache UserLookupCache => OnlinePlayDependencies?.UserLookupCache; + public BeatmapLookupCache BeatmapLookupCache => OnlinePlayDependencies?.BeatmapLookupCache; /// /// All dependencies required for online play components and screens. diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs index 47893519c7..7c8bc2d535 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs @@ -24,6 +24,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker { get; } public TestRoomRequestsHandler RequestsHandler { get; } public TestUserLookupCache UserLookupCache { get; } + public BeatmapLookupCache BeatmapLookupCache { get; } /// /// All cached dependencies which are also components. @@ -41,6 +42,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay AvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); RoomManager = CreateRoomManager(); UserLookupCache = new TestUserLookupCache(); + BeatmapLookupCache = new BeatmapLookupCache(); dependencies = new DependencyContainer(new CachedModelDependencyContainer(null) { Model = { BindTarget = SelectedRoom } }); @@ -51,6 +53,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay CacheAs(AvailabilityTracker); CacheAs(new OverlayColourProvider(OverlayColourScheme.Plum)); CacheAs(UserLookupCache); + CacheAs(BeatmapLookupCache); } public object Get(Type type) From 539cbe62c6be88f6da5b6cabc1df39f845f8b58b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Feb 2022 21:08:27 +0900 Subject: [PATCH 0675/5199] Fix incorrect usages of user lookup cache in tests --- .../Visual/Gameplay/TestSceneSpectator.cs | 25 +++++++++---------- .../Visual/Multiplayer/QueueModeTestScene.cs | 4 --- .../TestSceneDrawableRoomPlaylist.cs | 5 +--- .../Multiplayer/TestSceneMultiplayer.cs | 4 --- .../TestSceneMultiplayerQueueList.cs | 4 --- .../TestScenePlaylistsRoomSettingsPlaylist.cs | 8 ++---- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 4 --- .../Visual/TestMultiplayerComponents.cs | 9 +++++++ 8 files changed, 24 insertions(+), 39 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 157c248d69..6dca256d31 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -39,7 +39,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Resolved] private OsuGameBase game { get; set; } - private TestSpectatorClient spectatorClient; + private TestSpectatorClient spectatorClient => dependenciesScreen.Client; + private DependenciesScreen dependenciesScreen; private SoloSpectator spectatorScreen; private BeatmapSetInfo importedBeatmap; @@ -48,16 +49,16 @@ namespace osu.Game.Tests.Visual.Gameplay [SetUpSteps] public void SetupSteps() { - DependenciesScreen dependenciesScreen = null; - AddStep("load dependencies", () => { - spectatorClient = new TestSpectatorClient(); + LoadScreen(dependenciesScreen = new DependenciesScreen()); - // The screen gets suspended so it stops receiving updates. - Child = spectatorClient; - - LoadScreen(dependenciesScreen = new DependenciesScreen(spectatorClient)); + // The dependencies screen gets suspended so it stops receiving updates. So its children are manually added to the test scene instead. + Children = new Drawable[] + { + dependenciesScreen.UserLookupCache, + dependenciesScreen.Client, + }; }); AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded); @@ -335,12 +336,10 @@ namespace osu.Game.Tests.Visual.Gameplay private class DependenciesScreen : OsuScreen { [Cached(typeof(SpectatorClient))] - public readonly TestSpectatorClient Client; + public readonly TestSpectatorClient Client = new TestSpectatorClient(); - public DependenciesScreen(TestSpectatorClient client) - { - Client = client; - } + [Cached(typeof(UserLookupCache))] + public readonly TestUserLookupCache UserLookupCache = new TestUserLookupCache(); } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 36d6c6a306..52801dd57a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -10,7 +10,6 @@ using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Database; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Rulesets; @@ -41,9 +40,6 @@ namespace osu.Game.Tests.Visual.Multiplayer protected TestMultiplayerClient Client => multiplayerComponents.Client; - [Cached(typeof(UserLookupCache))] - private UserLookupCache lookupCache = new TestUserLookupCache(); - [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 659cc22350..8c10a0d0d9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -30,16 +30,13 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneDrawableRoomPlaylist : OsuManualInputManagerTestScene + public class TestSceneDrawableRoomPlaylist : MultiplayerTestScene { private TestPlaylist playlist; private BeatmapManager manager; private RulesetStore rulesets; - [Cached(typeof(UserLookupCache))] - private readonly TestUserLookupCache userLookupCache = new TestUserLookupCache(); - [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 41715f6cfb..b04bf5e860 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -17,7 +17,6 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; -using osu.Game.Database; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; @@ -56,9 +55,6 @@ namespace osu.Game.Tests.Visual.Multiplayer private TestMultiplayerClient client => multiplayerComponents.Client; private TestMultiplayerRoomManager roomManager => multiplayerComponents.RoomManager; - [Cached(typeof(UserLookupCache))] - private UserLookupCache lookupCache = new TestUserLookupCache(); - [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index ddf794b437..4040707c41 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Database; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; @@ -26,9 +25,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayerQueueList : MultiplayerTestScene { - [Cached(typeof(UserLookupCache))] - private readonly TestUserLookupCache userLookupCache = new TestUserLookupCache(); - private MultiplayerQueueList playlist; private BeatmapManager beatmaps; private RulesetStore rulesets; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs index 8bfdda29d5..9adf2c0370 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs @@ -4,13 +4,11 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; -using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.Models; using osu.Game.Online.API; @@ -20,18 +18,16 @@ using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual.OnlinePlay; using osuTK; using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestScenePlaylistsRoomSettingsPlaylist : OsuManualInputManagerTestScene + public class TestScenePlaylistsRoomSettingsPlaylist : OnlinePlayTestScene { private TestPlaylist playlist; - [Cached(typeof(UserLookupCache))] - private readonly TestUserLookupCache userLookupCache = new TestUserLookupCache(); - [Test] public void TestItemRemovedOnDeletion() { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 513c1413fa..b66657728e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -10,7 +10,6 @@ using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Database; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; @@ -36,9 +35,6 @@ namespace osu.Game.Tests.Visual.Multiplayer private TestMultiplayerClient client => multiplayerComponents.Client; - [Cached(typeof(UserLookupCache))] - private UserLookupCache lookupCache = new TestUserLookupCache(); - [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { diff --git a/osu.Game.Tests/Visual/TestMultiplayerComponents.cs b/osu.Game.Tests/Visual/TestMultiplayerComponents.cs index cd7a936778..bd8fb8e58e 100644 --- a/osu.Game.Tests/Visual/TestMultiplayerComponents.cs +++ b/osu.Game.Tests/Visual/TestMultiplayerComponents.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Screens; +using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; using osu.Game.Screens; @@ -39,6 +40,12 @@ namespace osu.Game.Tests.Visual [Cached(typeof(MultiplayerClient))] public readonly TestMultiplayerClient Client; + [Cached(typeof(UserLookupCache))] + private readonly UserLookupCache userLookupCache = new TestUserLookupCache(); + + [Cached] + private readonly BeatmapLookupCache beatmapLookupCache = new BeatmapLookupCache(); + private readonly OsuScreenStack screenStack; private readonly TestMultiplayer multiplayerScreen; @@ -48,6 +55,8 @@ namespace osu.Game.Tests.Visual InternalChildren = new Drawable[] { + userLookupCache, + beatmapLookupCache, Client = new TestMultiplayerClient(RoomManager), screenStack = new OsuScreenStack { From a5183cec77caaa7a8fdb64a8d7a8a7418b7c7f42 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Feb 2022 21:53:56 +0900 Subject: [PATCH 0676/5199] Add helper to construct APIBeatmap --- osu.Game/Tests/Visual/OsuTestScene.cs | 62 ++++++++++++++++----------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index ec02655544..f287a04d71 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -225,12 +225,24 @@ namespace osu.Game.Tests.Visual protected virtual IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset); /// - /// Returns a sample API Beatmap with BeatmapSet populated. + /// Returns a sample API beatmap with a populated beatmap set. /// /// The ruleset to create the sample model using. osu! ruleset will be used if not specified. - protected APIBeatmap CreateAPIBeatmap(RulesetInfo ruleset = null) + protected APIBeatmap CreateAPIBeatmap(RulesetInfo ruleset = null) => CreateAPIBeatmap(CreateBeatmap(ruleset ?? Ruleset.Value).BeatmapInfo); + + /// + /// Constructs a sample API beatmap set containing a beatmap. + /// + /// The ruleset to create the sample model using. osu! ruleset will be used if not specified. + protected APIBeatmapSet CreateAPIBeatmapSet(RulesetInfo ruleset = null) => CreateAPIBeatmapSet(CreateBeatmap(ruleset ?? Ruleset.Value).BeatmapInfo); + + /// + /// Constructs a sample API beatmap with a populated beatmap set from a given source beatmap. + /// + /// The source beatmap. + public static APIBeatmap CreateAPIBeatmap(IBeatmapInfo original) { - var beatmapSet = CreateAPIBeatmapSet(ruleset ?? Ruleset.Value); + var beatmapSet = CreateAPIBeatmapSet(original); // Avoid circular reference. var beatmap = beatmapSet.Beatmaps.First(); @@ -243,18 +255,16 @@ namespace osu.Game.Tests.Visual } /// - /// Returns a sample API BeatmapSet with beatmaps populated. + /// Constructs a sample API beatmap set containing a beatmap from a given source beatmap. /// - /// The ruleset to create the sample model using. osu! ruleset will be used if not specified. - protected APIBeatmapSet CreateAPIBeatmapSet(RulesetInfo ruleset = null) + /// The source beatmap. + public static APIBeatmapSet CreateAPIBeatmapSet(IBeatmapInfo original) { - var beatmap = CreateBeatmap(ruleset ?? Ruleset.Value).BeatmapInfo; - - Debug.Assert(beatmap.BeatmapSet != null); + Debug.Assert(original.BeatmapSet != null); return new APIBeatmapSet { - OnlineID = ((IBeatmapSetInfo)beatmap.BeatmapSet).OnlineID, + OnlineID = original.BeatmapSet.OnlineID, Status = BeatmapOnlineStatus.Ranked, Covers = new BeatmapSetOnlineCovers { @@ -262,29 +272,29 @@ namespace osu.Game.Tests.Visual Card = "https://assets.ppy.sh/beatmaps/163112/covers/card.jpg", List = "https://assets.ppy.sh/beatmaps/163112/covers/list.jpg" }, - Title = beatmap.Metadata.Title, - TitleUnicode = beatmap.Metadata.TitleUnicode, - Artist = beatmap.Metadata.Artist, - ArtistUnicode = beatmap.Metadata.ArtistUnicode, + Title = original.Metadata.Title, + TitleUnicode = original.Metadata.TitleUnicode, + Artist = original.Metadata.Artist, + ArtistUnicode = original.Metadata.ArtistUnicode, Author = new APIUser { - Username = beatmap.Metadata.Author.Username, - Id = beatmap.Metadata.Author.OnlineID + Username = original.Metadata.Author.Username, + Id = original.Metadata.Author.OnlineID }, - Source = beatmap.Metadata.Source, - Tags = beatmap.Metadata.Tags, + Source = original.Metadata.Source, + Tags = original.Metadata.Tags, Beatmaps = new[] { new APIBeatmap { - OnlineID = ((IBeatmapInfo)beatmap).OnlineID, - OnlineBeatmapSetID = ((IBeatmapSetInfo)beatmap.BeatmapSet).OnlineID, - Status = beatmap.Status, - Checksum = beatmap.MD5Hash, - AuthorID = beatmap.Metadata.Author.OnlineID, - RulesetID = beatmap.Ruleset.OnlineID, - StarRating = beatmap.StarRating, - DifficultyName = beatmap.DifficultyName, + OnlineID = original.OnlineID, + OnlineBeatmapSetID = original.BeatmapSet.OnlineID, + Status = ((BeatmapInfo)original).Status, + Checksum = original.MD5Hash, + AuthorID = original.Metadata.Author.OnlineID, + RulesetID = original.Ruleset.OnlineID, + StarRating = original.StarRating, + DifficultyName = original.DifficultyName, } } }; From ccd265ebe7237e50ecb7241dca5e3f3703b47a86 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Feb 2022 22:02:33 +0900 Subject: [PATCH 0677/5199] Handle beatmap lookup requests in TestRoomRequestsHandler --- .../Visual/TestMultiplayerComponents.cs | 8 ++++-- .../Online/API/Requests/GetBeatmapsRequest.cs | 6 ++-- .../Visual/OnlinePlay/OnlinePlayTestScene.cs | 11 +++++--- .../OnlinePlay/TestRoomRequestsHandler.cs | 28 +++++++++++++++++-- 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/TestMultiplayerComponents.cs b/osu.Game.Tests/Visual/TestMultiplayerComponents.cs index bd8fb8e58e..2e551947b6 100644 --- a/osu.Game.Tests/Visual/TestMultiplayerComponents.cs +++ b/osu.Game.Tests/Visual/TestMultiplayerComponents.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Game.Database; +using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; using osu.Game.Screens; @@ -46,6 +47,9 @@ namespace osu.Game.Tests.Visual [Cached] private readonly BeatmapLookupCache beatmapLookupCache = new BeatmapLookupCache(); + [Resolved] + private BeatmapManager beatmapManager { get; set; } + private readonly OsuScreenStack screenStack; private readonly TestMultiplayer multiplayerScreen; @@ -69,9 +73,9 @@ namespace osu.Game.Tests.Visual } [BackgroundDependencyLoader] - private void load(IAPIProvider api, OsuGameBase game) + private void load(IAPIProvider api) { - ((DummyAPIAccess)api).HandleRequest = request => multiplayerScreen.RequestsHandler.HandleRequest(request, api.LocalUser.Value, game); + ((DummyAPIAccess)api).HandleRequest = request => multiplayerScreen.RequestsHandler.HandleRequest(request, api.LocalUser.Value, beatmapManager); } public override bool OnBackButton() => (screenStack.CurrentScreen as OsuScreen)?.OnBackButton() ?? base.OnBackButton(); diff --git a/osu.Game/Online/API/Requests/GetBeatmapsRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapsRequest.cs index 1d71e22b77..c07e5ef1c2 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapsRequest.cs +++ b/osu.Game/Online/API/Requests/GetBeatmapsRequest.cs @@ -7,7 +7,7 @@ namespace osu.Game.Online.API.Requests { public class GetBeatmapsRequest : APIRequest { - private readonly int[] beatmapIds; + public readonly int[] BeatmapIds; private const int max_ids_per_request = 50; @@ -16,9 +16,9 @@ namespace osu.Game.Online.API.Requests if (beatmapIds.Length > max_ids_per_request) throw new ArgumentException($"{nameof(GetBeatmapsRequest)} calls only support up to {max_ids_per_request} IDs at once"); - this.beatmapIds = beatmapIds; + BeatmapIds = beatmapIds; } - protected override string Target => "beatmaps/?ids[]=" + string.Join("&ids[]=", beatmapIds); + protected override string Target => "beatmaps/?ids[]=" + string.Join("&ids[]=", BeatmapIds); } } diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index baff7c168f..448ec5e3d9 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Database; +using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay; @@ -33,9 +34,6 @@ namespace osu.Game.Tests.Visual.OnlinePlay protected override Container Content => content; - [Resolved] - private OsuGameBase game { get; set; } - private readonly Container content; private readonly Container drawableDependenciesContainer; private DelegatedDependencyContainer dependencies; @@ -71,7 +69,12 @@ namespace osu.Game.Tests.Visual.OnlinePlay AddStep("setup API", () => { var handler = OnlinePlayDependencies.RequestsHandler; - ((DummyAPIAccess)API).HandleRequest = request => handler.HandleRequest(request, API.LocalUser.Value, game); + + // Resolving the BeatmapManager in the test scene will inject the game-wide BeatmapManager, while many test scenes cache their own BeatmapManager instead. + // To get around this, the BeatmapManager is looked up from the dependencies provided to the children of the test scene instead. + var beatmapManager = dependencies.Get(); + + ((DummyAPIAccess)API).HandleRequest = request => handler.HandleRequest(request, API.LocalUser.Value, beatmapManager); }); } diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index 5a0a7e71d4..8290af8f78 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -4,12 +4,16 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Game.Beatmaps; using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; +using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.OnlinePlay.Components; +using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual.OnlinePlay { @@ -33,9 +37,9 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// The API request to handle. /// The local user to store in responses where required. - /// The game base for cases where actual online requests need to be sent. + /// The beatmap manager to attempt to retrieve beatmaps from, prior to returning dummy beatmaps. /// Whether the request was successfully handled. - public bool HandleRequest(APIRequest request, APIUser localUser, OsuGameBase game) + public bool HandleRequest(APIRequest request, APIUser localUser, BeatmapManager beatmapManager) { switch (request) { @@ -128,6 +132,26 @@ namespace osu.Game.Tests.Visual.OnlinePlay Statistics = new Dictionary() }); return true; + + case GetBeatmapsRequest getBeatmapsRequest: + var result = new List(); + + foreach (int id in getBeatmapsRequest.BeatmapIds) + { + var baseBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == id); + + if (baseBeatmap == null) + { + baseBeatmap = new TestBeatmap(new RulesetInfo { OnlineID = 0 }).BeatmapInfo; + baseBeatmap.OnlineID = id; + baseBeatmap.BeatmapSet!.OnlineID = id; + } + + result.Add(OsuTestScene.CreateAPIBeatmap(baseBeatmap)); + } + + getBeatmapsRequest.TriggerSuccess(new GetBeatmapsResponse { Beatmaps = result }); + return true; } return false; From 73ce1b324e84e5eef58ddf9ef5010e69a36938c7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Feb 2022 22:44:20 +0900 Subject: [PATCH 0678/5199] Make DrawableRoom look up the online beatmap --- .../Lounge/Components/DrawableRoom.cs | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index a87f21630c..60f65fbd9f 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -12,6 +14,7 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -328,6 +331,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components [Resolved] private OsuColour colours { get; set; } + [Resolved] + private BeatmapLookupCache beatmapLookupCache { get; set; } + private SpriteText statusText; private LinkFlowContainer beatmapText; @@ -385,8 +391,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components SelectedItem.BindValueChanged(onSelectedItemChanged, true); } + private CancellationTokenSource beatmapLookupCancellation; + private void onSelectedItemChanged(ValueChangedEvent item) { + beatmapLookupCancellation?.Cancel(); beatmapText.Clear(); if (Type.Value == MatchType.Playlists) @@ -395,17 +404,25 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components return; } - if (item.NewValue?.Beatmap.Value != null) - { - statusText.Text = "Currently playing "; - beatmapText.AddLink(item.NewValue.Beatmap.Value.GetDisplayTitleRomanisable(), - LinkAction.OpenBeatmap, - item.NewValue.Beatmap.Value.OnlineID.ToString(), - creationParameters: s => - { - s.Truncate = true; - }); - } + var beatmap = item.NewValue?.Beatmap; + if (beatmap == null) + return; + + var cancellationSource = beatmapLookupCancellation = new CancellationTokenSource(); + beatmapLookupCache.GetBeatmapAsync(beatmap.Value.OnlineID, cancellationSource.Token) + .ContinueWith(task => Schedule(() => + { + if (cancellationSource.IsCancellationRequested) + return; + + var retrievedBeatmap = task.GetResultSafely(); + + statusText.Text = "Currently playing "; + beatmapText.AddLink(retrievedBeatmap.GetDisplayTitleRomanisable(), + LinkAction.OpenBeatmap, + retrievedBeatmap.OnlineID.ToString(), + creationParameters: s => s.Truncate = true); + }), cancellationSource.Token); } } From afcb7a463061756988648e60f06a0ad9173f43ba Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Feb 2022 23:11:33 +0900 Subject: [PATCH 0679/5199] Make DrawableRoomPlaylistItem look up the online beatmap --- .../OnlinePlayBeatmapAvailabilityTracker.cs | 23 ++-- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 102 ++++++++---------- 2 files changed, 57 insertions(+), 68 deletions(-) diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index c67cbade6a..51e77694a1 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -78,7 +78,7 @@ namespace osu.Game.Online.Rooms // handles changes to hash that didn't occur from the import process (ie. a user editing the beatmap in the editor, somehow). realmSubscription?.Dispose(); - realmSubscription = realm.RegisterForNotifications(r => filteredBeatmaps(), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(r => QueryBeatmapForOnlinePlay(r, SelectedItem.Value.Beatmap.Value), (items, changes, ___) => { if (changes == null) return; @@ -108,12 +108,12 @@ namespace osu.Game.Online.Rooms break; case DownloadState.LocallyAvailable: - bool hashMatches = filteredBeatmaps().Any(); + bool available = QueryBeatmapForOnlinePlay(realm.Realm, SelectedItem.Value.Beatmap.Value).Any(); - availability.Value = hashMatches ? BeatmapAvailability.LocallyAvailable() : BeatmapAvailability.NotDownloaded(); + availability.Value = available ? BeatmapAvailability.LocallyAvailable() : BeatmapAvailability.NotDownloaded(); // only display a message to the user if a download seems to have just completed. - if (!hashMatches && downloadTracker.Progress.Value == 1) + if (!available && downloadTracker.Progress.Value == 1) Logger.Log("The imported beatmap set does not match the online version.", LoggingTarget.Runtime, LogLevel.Important); break; @@ -123,14 +123,15 @@ namespace osu.Game.Online.Rooms } } - private IQueryable filteredBeatmaps() + /// + /// Performs a query for a local matching a requested one for the purpose of online play. + /// + /// The realm to query from. + /// The requested beatmap. + /// A beatmap query. + public static IQueryable QueryBeatmapForOnlinePlay(Realm realm, IBeatmapInfo beatmap) { - int onlineId = SelectedItem.Value.Beatmap.Value.OnlineID; - string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash; - - return realm.Realm - .All() - .Filter("OnlineID == $0 && MD5Hash == $1 && BeatmapSet.DeletePending == false", onlineId, checksum); + return realm.All().Filter("OnlineID == $0 && MD5Hash == $1 && BeatmapSet.DeletePending == false", beatmap.OnlineID, beatmap.MD5Hash); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index dcf2a5a480..7674fac88e 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -25,7 +24,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.Chat; -using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Overlays.BeatmapSet; using osu.Game.Rulesets; @@ -68,8 +66,8 @@ namespace osu.Game.Screens.OnlinePlay private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty) { RelativeSizeAxes = Axes.Both }; private readonly IBindable valid = new Bindable(); - private readonly Bindable beatmap = new Bindable(); + private IBeatmapInfo beatmap; private IRulesetInfo ruleset; private Mod[] requiredMods; @@ -96,13 +94,12 @@ namespace osu.Game.Screens.OnlinePlay [Resolved] private UserLookupCache userLookupCache { get; set; } - [CanBeNull] - [Resolved(CanBeNull = true)] - private MultiplayerClient multiplayerClient { get; set; } - [Resolved] private BeatmapLookupCache beatmapLookupCache { get; set; } + [Resolved] + private RealmAccess realm { get; set; } + protected override bool ShouldBeConsideredForInput(Drawable child) => AllowReordering || AllowDeletion || !AllowSelection || SelectedItem.Value == Model; public DrawableRoomPlaylistItem(PlaylistItem item) @@ -110,7 +107,6 @@ namespace osu.Game.Screens.OnlinePlay { Item = item; - beatmap.BindTo(item.Beatmap); valid.BindTo(item.Valid); if (item.Expired) @@ -151,7 +147,6 @@ namespace osu.Game.Screens.OnlinePlay maskingContainer.BorderThickness = isCurrent ? 5 : 0; }, true); - beatmap.BindValueChanged(_ => Scheduler.AddOnce(refresh)); valid.BindValueChanged(_ => Scheduler.AddOnce(refresh)); onScreenLoader.DelayedLoadStarted += _ => @@ -166,19 +161,9 @@ namespace osu.Game.Screens.OnlinePlay Schedule(() => ownerAvatar.User = foundUser); } - if (Item.Beatmap.Value == null) - { - IBeatmapInfo foundBeatmap; + beatmap = await beatmapLookupCache.GetBeatmapAsync(Item.Beatmap.Value.OnlineID).ConfigureAwait(false); - if (multiplayerClient != null) - // This call can eventually go away (and use the else case below). - // Currently required only due to the method being overridden to provide special behaviour in tests. - foundBeatmap = await multiplayerClient.GetAPIBeatmap(Item.BeatmapID).ConfigureAwait(false); - else - foundBeatmap = await beatmapLookupCache.GetBeatmapAsync(Item.BeatmapID).ConfigureAwait(false); - - Schedule(() => Item.Beatmap.Value = foundBeatmap); - } + Scheduler.AddOnce(refresh); } catch (Exception e) { @@ -280,18 +265,18 @@ namespace osu.Game.Screens.OnlinePlay maskingContainer.BorderColour = colours.Red; } - if (Item.Beatmap.Value != null) - difficultyIconContainer.Child = new DifficultyIcon(Item.Beatmap.Value, ruleset, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(icon_height) }; + if (beatmap != null) + difficultyIconContainer.Child = new DifficultyIcon(beatmap, ruleset, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(icon_height) }; else difficultyIconContainer.Clear(); - panelBackground.Beatmap.Value = Item.Beatmap.Value; + panelBackground.Beatmap.Value = beatmap; beatmapText.Clear(); - if (Item.Beatmap.Value != null) + if (beatmap != null) { - beatmapText.AddLink(Item.Beatmap.Value.GetDisplayTitleRomanisable(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineID.ToString(), null, text => + beatmapText.AddLink(beatmap.GetDisplayTitleRomanisable(), LinkAction.OpenBeatmap, beatmap.OnlineID.ToString(), null, text => { text.Truncate = true; }); @@ -299,13 +284,13 @@ namespace osu.Game.Screens.OnlinePlay authorText.Clear(); - if (!string.IsNullOrEmpty(Item.Beatmap.Value?.Metadata.Author.Username)) + if (!string.IsNullOrEmpty(beatmap?.Metadata.Author.Username)) { authorText.AddText("mapped by "); - authorText.AddUserLink(Item.Beatmap.Value.Metadata.Author); + authorText.AddUserLink(beatmap.Metadata.Author); } - bool hasExplicitContent = (Item.Beatmap.Value?.BeatmapSet as IBeatmapSetOnlineInfo)?.HasExplicitContent == true; + bool hasExplicitContent = (beatmap?.BeatmapSet as IBeatmapSetOnlineInfo)?.HasExplicitContent == true; explicitContentPill.Alpha = hasExplicitContent ? 1 : 0; modDisplay.Current.Value = requiredMods.ToArray(); @@ -448,31 +433,34 @@ namespace osu.Game.Screens.OnlinePlay }; } - private IEnumerable createButtons() => new[] + private IEnumerable createButtons() { - showResultsButton = new GrayButton(FontAwesome.Solid.ChartPie) + return new[] { - Size = new Vector2(30, 30), - Action = () => RequestResults?.Invoke(Item), - Alpha = AllowShowingResults ? 1 : 0, - TooltipText = "View results" - }, - Item.Beatmap.Value == null ? Empty() : new PlaylistDownloadButton(Item), - editButton = new PlaylistEditButton - { - Size = new Vector2(30, 30), - Alpha = AllowEditing ? 1 : 0, - Action = () => RequestEdit?.Invoke(Item), - TooltipText = "Edit" - }, - removeButton = new PlaylistRemoveButton - { - Size = new Vector2(30, 30), - Alpha = AllowDeletion ? 1 : 0, - Action = () => RequestDeletion?.Invoke(Item), - TooltipText = "Remove from playlist" - }, - }; + showResultsButton = new GrayButton(FontAwesome.Solid.ChartPie) + { + Size = new Vector2(30, 30), + Action = () => RequestResults?.Invoke(Item), + Alpha = AllowShowingResults ? 1 : 0, + TooltipText = "View results" + }, + OnlinePlayBeatmapAvailabilityTracker.QueryBeatmapForOnlinePlay(realm.Realm, beatmap).Any() ? Empty() : new PlaylistDownloadButton(beatmap), + editButton = new PlaylistEditButton + { + Size = new Vector2(30, 30), + Alpha = AllowEditing ? 1 : 0, + Action = () => RequestEdit?.Invoke(Item), + TooltipText = "Edit" + }, + removeButton = new PlaylistRemoveButton + { + Size = new Vector2(30, 30), + Alpha = AllowDeletion ? 1 : 0, + Action = () => RequestDeletion?.Invoke(Item), + TooltipText = "Remove from playlist" + }, + }; + } protected override bool OnClick(ClickEvent e) { @@ -499,7 +487,7 @@ namespace osu.Game.Screens.OnlinePlay private sealed class PlaylistDownloadButton : BeatmapDownloadButton { - private readonly PlaylistItem playlistItem; + private readonly IBeatmapInfo beatmap; [Resolved] private BeatmapManager beatmapManager { get; set; } @@ -509,10 +497,10 @@ namespace osu.Game.Screens.OnlinePlay private const float width = 50; - public PlaylistDownloadButton(PlaylistItem playlistItem) - : base(playlistItem.Beatmap.Value.BeatmapSet) + public PlaylistDownloadButton(IBeatmapInfo beatmap) + : base(beatmap.BeatmapSet) { - this.playlistItem = playlistItem; + this.beatmap = beatmap; Size = new Vector2(width, 30); Alpha = 0; @@ -532,7 +520,7 @@ namespace osu.Game.Screens.OnlinePlay { case DownloadState.LocallyAvailable: // Perform a local query of the beatmap by beatmap checksum, and reset the state if not matching. - if (beatmapManager.QueryBeatmap(b => b.MD5Hash == playlistItem.Beatmap.Value.MD5Hash) == null) + if (beatmapManager.QueryBeatmap(b => b.MD5Hash == beatmap.MD5Hash) == null) State.Value = DownloadState.NotDownloaded; else { From 94a974e1c97ea36318488ba9c4c17db1d7ea9c67 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Feb 2022 23:30:39 +0900 Subject: [PATCH 0680/5199] Make OnlinePlayBeatmapAvailabilityTracker look up the online beatmap --- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 57 ++++++++++++--- .../OnlinePlayBeatmapAvailabilityTracker.cs | 73 ++++++++++++------- 2 files changed, 93 insertions(+), 37 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 9aa04dda92..4b98385a51 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Threading; @@ -12,6 +13,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Framework.Graphics; using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Framework.Testing; @@ -21,6 +23,8 @@ using osu.Game.Database; using osu.Game.IO; using osu.Game.IO.Archives; using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Tests.Resources; @@ -53,6 +57,25 @@ namespace osu.Game.Tests.Online [SetUp] public void SetUp() => Schedule(() => { + ((DummyAPIAccess)API).HandleRequest = req => + { + switch (req) + { + case GetBeatmapsRequest beatmapsReq: + var beatmap = CreateAPIBeatmap(); + beatmap.OnlineID = testBeatmapInfo.OnlineID; + beatmap.OnlineBeatmapSetID = testBeatmapSet.OnlineID; + beatmap.Checksum = testBeatmapInfo.MD5Hash; + beatmap.BeatmapSet!.OnlineID = testBeatmapSet.OnlineID; + + beatmapsReq.TriggerSuccess(new GetBeatmapsResponse { Beatmaps = new List { beatmap } }); + return true; + + default: + return false; + } + }; + beatmaps.AllowImport = new TaskCompletionSource(); testBeatmapFile = TestResources.GetQuickTestBeatmapForImport(); @@ -69,12 +92,30 @@ namespace osu.Game.Tests.Online RulesetID = testBeatmapInfo.Ruleset.OnlineID, }; - Child = availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker - { - SelectedItem = { BindTarget = selectedItem, } - }; + recreateChildren(); }); + private void recreateChildren() + { + var beatmapLookupCache = new BeatmapLookupCache(); + + Child = new DependencyProvidingContainer + { + CachedDependencies = new[] + { + (typeof(BeatmapLookupCache), (object)beatmapLookupCache) + }, + Children = new Drawable[] + { + beatmapLookupCache, + availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker + { + SelectedItem = { BindTarget = selectedItem, } + } + } + }; + } + [Test] public void TestBeatmapDownloadingFlow() { @@ -123,10 +164,7 @@ namespace osu.Game.Tests.Online }); addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded); - AddStep("recreate tracker", () => Child = availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker - { - SelectedItem = { BindTarget = selectedItem } - }); + AddStep("recreate tracker", recreateChildren); addAvailabilityCheckStep("state not downloaded as well", BeatmapAvailability.NotDownloaded); AddStep("reimport original beatmap", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely()); @@ -167,7 +205,8 @@ namespace osu.Game.Tests.Online public Live CurrentImport { get; private set; } - public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null) + public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, + GameHost host = null, WorkingBeatmap defaultBeatmap = null) : base(storage, realm, rulesets, api, audioManager, resources, host, defaultBeatmap) { } diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index 51e77694a1..90532d88b8 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -4,14 +4,17 @@ using System; using System.Diagnostics; using System.Linq; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Online.API.Requests.Responses; using Realms; namespace osu.Game.Online.Rooms @@ -32,6 +35,9 @@ namespace osu.Game.Online.Rooms [Resolved] private RealmAccess realm { get; set; } = null!; + [Resolved] + private BeatmapLookupCache beatmapLookupCache { get; set; } = null!; + /// /// The availability state of the currently selected playlist item. /// @@ -58,39 +64,50 @@ namespace osu.Game.Online.Rooms downloadTracker?.RemoveAndDisposeImmediately(); - Debug.Assert(item.NewValue.Beatmap.Value.BeatmapSet != null); - - downloadTracker = new BeatmapDownloadTracker(item.NewValue.Beatmap.Value.BeatmapSet); - - AddInternal(downloadTracker); - - downloadTracker.State.BindValueChanged(_ => Scheduler.AddOnce(updateAvailability), true); - downloadTracker.Progress.BindValueChanged(_ => + beatmapLookupCache.GetBeatmapAsync(item.NewValue.Beatmap.Value.OnlineID).ContinueWith(task => Schedule(() => { - if (downloadTracker.State.Value != DownloadState.Downloading) - return; + var beatmap = task.GetResultSafely(); - // incoming progress changes are going to be at a very high rate. - // we don't want to flood the network with this, so rate limit how often we send progress updates. - if (progressUpdate?.Completed != false) - progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500); - }, true); - - // handles changes to hash that didn't occur from the import process (ie. a user editing the beatmap in the editor, somehow). - realmSubscription?.Dispose(); - realmSubscription = realm.RegisterForNotifications(r => QueryBeatmapForOnlinePlay(r, SelectedItem.Value.Beatmap.Value), (items, changes, ___) => - { - if (changes == null) - return; - - Scheduler.AddOnce(updateAvailability); - }); + if (SelectedItem.Value?.Beatmap.Value.OnlineID == beatmap.OnlineID) + beginTracking(beatmap); + }), TaskContinuationOptions.OnlyOnRanToCompletion); }, true); } - private void updateAvailability() + private void beginTracking(APIBeatmap beatmap) { - if (downloadTracker == null || SelectedItem.Value == null) + Debug.Assert(beatmap.BeatmapSet != null); + + downloadTracker = new BeatmapDownloadTracker(beatmap.BeatmapSet); + + AddInternal(downloadTracker); + + downloadTracker.State.BindValueChanged(_ => Scheduler.AddOnce(updateAvailability, beatmap), true); + downloadTracker.Progress.BindValueChanged(_ => + { + if (downloadTracker.State.Value != DownloadState.Downloading) + return; + + // incoming progress changes are going to be at a very high rate. + // we don't want to flood the network with this, so rate limit how often we send progress updates. + if (progressUpdate?.Completed != false) + progressUpdate = Scheduler.AddDelayed(updateAvailability, beatmap, progressUpdate == null ? 0 : 500); + }, true); + + // handles changes to hash that didn't occur from the import process (ie. a user editing the beatmap in the editor, somehow). + realmSubscription?.Dispose(); + realmSubscription = realm.RegisterForNotifications(r => QueryBeatmapForOnlinePlay(r, SelectedItem.Value.Beatmap.Value), (items, changes, ___) => + { + if (changes == null) + return; + + Scheduler.AddOnce(updateAvailability, beatmap); + }); + } + + private void updateAvailability(APIBeatmap beatmap) + { + if (downloadTracker == null) return; switch (downloadTracker.State.Value) @@ -108,7 +125,7 @@ namespace osu.Game.Online.Rooms break; case DownloadState.LocallyAvailable: - bool available = QueryBeatmapForOnlinePlay(realm.Realm, SelectedItem.Value.Beatmap.Value).Any(); + bool available = QueryBeatmapForOnlinePlay(realm.Realm, beatmap).Any(); availability.Value = available ? BeatmapAvailability.LocallyAvailable() : BeatmapAvailability.NotDownloaded(); From bdc3b76df0b58406796e2b08db13be7f2140fa7e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Feb 2022 23:33:26 +0900 Subject: [PATCH 0681/5199] Remove beatmap bindable from PlaylistItem --- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 3 +- .../OnlinePlay/PlaylistExtensionsTest.cs | 25 ++--- .../Visual/Multiplayer/QueueModeTestScene.cs | 3 +- .../Multiplayer/TestSceneDrawableRoom.cs | 68 ++++-------- .../TestSceneDrawableRoomPlaylist.cs | 37 +++---- .../Multiplayer/TestSceneHostOnlyQueueMode.cs | 8 +- .../TestSceneMatchBeatmapDetailArea.cs | 3 +- .../Multiplayer/TestSceneMultiplayer.cs | 101 +++++++----------- .../TestSceneMultiplayerMatchSubScreen.cs | 15 +-- .../Multiplayer/TestSceneMultiplayerPlayer.cs | 3 +- .../TestSceneMultiplayerPlaylist.cs | 12 +-- .../TestSceneMultiplayerQueueList.cs | 6 +- .../TestSceneMultiplayerReadyButton.cs | 3 +- .../TestSceneMultiplayerResults.cs | 7 +- .../TestSceneMultiplayerSpectateButton.cs | 3 +- .../TestSceneMultiplayerTeamResults.cs | 7 +- .../TestScenePlaylistsRoomSettingsPlaylist.cs | 38 +++---- .../TestSceneStarRatingRangeDisplay.cs | 4 +- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 16 ++- .../TestScenePlaylistsMatchSettingsOverlay.cs | 8 +- .../TestScenePlaylistsResultsScreen.cs | 3 +- .../TestScenePlaylistsRoomCreation.cs | 31 +++--- .../Online/Multiplayer/MultiplayerClient.cs | 11 +- .../Multiplayer/OnlineMultiplayerClient.cs | 10 -- .../Online/Rooms/MultiplayerPlaylistItem.cs | 4 +- .../OnlinePlayBeatmapAvailabilityTracker.cs | 6 +- osu.Game/Online/Rooms/PlaylistExtensions.cs | 2 +- osu.Game/Online/Rooms/PlaylistItem.cs | 74 +++++++++---- .../OnlinePlay/Components/BeatmapTitle.cs | 4 +- .../OnlinePlay/Components/ModeTypeInfo.cs | 2 +- .../Components/OnlinePlayBackgroundScreen.cs | 2 +- .../Components/OnlinePlayBackgroundSprite.cs | 2 +- .../Components/PlaylistItemBackground.cs | 2 +- .../OnlinePlay/Components/RoomManager.cs | 3 - .../Components/StarRatingRangeDisplay.cs | 2 +- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 2 +- .../Lounge/Components/DrawableRoom.cs | 2 +- .../OnlinePlay/Match/DrawableMatchRoom.cs | 2 +- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 +- .../Multiplayer/MultiplayerMatchSongSelect.cs | 4 +- .../Multiplayer/MultiplayerMatchSubScreen.cs | 34 +----- .../OnlinePlay/OnlinePlaySongSelect.cs | 6 +- .../OnlinePlay/Playlists/PlaylistsPlayer.cs | 2 +- .../Playlists/PlaylistsRoomSettingsOverlay.cs | 2 +- .../Playlists/PlaylistsSongSelect.cs | 3 +- .../Multiplayer/MultiplayerTestScene.cs | 3 +- .../Multiplayer/TestMultiplayerClient.cs | 23 ---- .../Visual/OnlinePlay/TestRoomManager.cs | 9 +- 48 files changed, 227 insertions(+), 395 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 4b98385a51..dd7feb6699 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -86,9 +86,8 @@ namespace osu.Game.Tests.Online Realm.Write(r => r.RemoveAll()); Realm.Write(r => r.RemoveAll()); - selectedItem.Value = new PlaylistItem + selectedItem.Value = new PlaylistItem(testBeatmapInfo) { - Beatmap = { Value = testBeatmapInfo }, RulesetID = testBeatmapInfo.Ruleset.OnlineID, }; diff --git a/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs b/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs index d33081662d..9e7ea02101 100644 --- a/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs +++ b/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs @@ -3,6 +3,7 @@ using System; using NUnit.Framework; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; namespace osu.Game.Tests.OnlinePlay @@ -29,9 +30,9 @@ namespace osu.Game.Tests.OnlinePlay { var items = new[] { - new PlaylistItem { ID = 1, BeatmapID = 1001, PlaylistOrder = 1 }, - new PlaylistItem { ID = 2, BeatmapID = 1002, PlaylistOrder = 2 }, - new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 }, + new PlaylistItem(new APIBeatmap { OnlineID = 1001 }) { ID = 1, PlaylistOrder = 1 }, + new PlaylistItem(new APIBeatmap { OnlineID = 1002 }) { ID = 2, PlaylistOrder = 2 }, + new PlaylistItem(new APIBeatmap { OnlineID = 1003 }) { ID = 3, PlaylistOrder = 3 }, }; Assert.Multiple(() => @@ -47,9 +48,9 @@ namespace osu.Game.Tests.OnlinePlay { var items = new[] { - new PlaylistItem { ID = 2, BeatmapID = 1002, PlaylistOrder = 2 }, - new PlaylistItem { ID = 1, BeatmapID = 1001, PlaylistOrder = 1 }, - new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 }, + new PlaylistItem(new APIBeatmap { OnlineID = 1002 }) { ID = 2, PlaylistOrder = 2 }, + new PlaylistItem(new APIBeatmap { OnlineID = 1001 }) { ID = 1, PlaylistOrder = 1 }, + new PlaylistItem(new APIBeatmap { OnlineID = 1003 }) { ID = 3, PlaylistOrder = 3 }, }; Assert.Multiple(() => @@ -65,9 +66,9 @@ namespace osu.Game.Tests.OnlinePlay { var items = new[] { - new PlaylistItem { ID = 1, BeatmapID = 1001, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 55, 0, TimeSpan.Zero) }, - new PlaylistItem { ID = 2, BeatmapID = 1002, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 53, 0, TimeSpan.Zero) }, - new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 }, + new PlaylistItem(new APIBeatmap { OnlineID = 1001 }) { ID = 1, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 55, 0, TimeSpan.Zero) }, + new PlaylistItem(new APIBeatmap { OnlineID = 1002 }) { ID = 2, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 53, 0, TimeSpan.Zero) }, + new PlaylistItem(new APIBeatmap { OnlineID = 1003 }) { ID = 3, PlaylistOrder = 3 }, }; Assert.Multiple(() => @@ -83,9 +84,9 @@ namespace osu.Game.Tests.OnlinePlay { var items = new[] { - new PlaylistItem { ID = 1, BeatmapID = 1001, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 55, 0, TimeSpan.Zero) }, - new PlaylistItem { ID = 2, BeatmapID = 1002, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 53, 0, TimeSpan.Zero) }, - new PlaylistItem { ID = 3, BeatmapID = 1002, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 57, 0, TimeSpan.Zero) }, + new PlaylistItem(new APIBeatmap { OnlineID = 1001 }) { ID = 1, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 55, 0, TimeSpan.Zero) }, + new PlaylistItem(new APIBeatmap { OnlineID = 1002 }) { ID = 2, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 53, 0, TimeSpan.Zero) }, + new PlaylistItem(new APIBeatmap { OnlineID = 1002 }) { ID = 3, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 57, 0, TimeSpan.Zero) }, }; Assert.Multiple(() => diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 52801dd57a..5a211e7bec 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -71,9 +71,8 @@ namespace osu.Game.Tests.Visual.Multiplayer QueueMode = { Value = Mode }, Playlist = { - new PlaylistItem + new PlaylistItem(InitialBeatmap) { - Beatmap = { Value = InitialBeatmap }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index 423822cbe4..d8ec0ad1f0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -53,19 +53,13 @@ namespace osu.Game.Tests.Visual.Multiplayer Type = { Value = MatchType.HeadToHead }, Playlist = { - new PlaylistItem + new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo) { - Beatmap = + BeatmapInfo = { - Value = new TestBeatmap(new OsuRuleset().RulesetInfo) - { - BeatmapInfo = - { - StarRating = 2.5 - } - }.BeatmapInfo, + StarRating = 2.5 } - } + }.BeatmapInfo) } }), createLoungeRoom(new Room @@ -76,26 +70,20 @@ namespace osu.Game.Tests.Visual.Multiplayer Type = { Value = MatchType.HeadToHead }, Playlist = { - new PlaylistItem + new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo) { - Beatmap = + BeatmapInfo = { - Value = new TestBeatmap(new OsuRuleset().RulesetInfo) + StarRating = 2.5, + Metadata = { - BeatmapInfo = - { - StarRating = 2.5, - Metadata = - { - Artist = "very very very very very very very very very long artist", - ArtistUnicode = "very very very very very very very very very long artist", - Title = "very very very very very very very very very very very long title", - TitleUnicode = "very very very very very very very very very very very long title", - } - } - }.BeatmapInfo, + Artist = "very very very very very very very very very long artist", + ArtistUnicode = "very very very very very very very very very long artist", + Title = "very very very very very very very very very very very long title", + TitleUnicode = "very very very very very very very very very very very long title", + } } - } + }.BeatmapInfo) } }), createLoungeRoom(new Room @@ -105,32 +93,20 @@ namespace osu.Game.Tests.Visual.Multiplayer EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, Playlist = { - new PlaylistItem + new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo) { - Beatmap = + BeatmapInfo = { - Value = new TestBeatmap(new OsuRuleset().RulesetInfo) - { - BeatmapInfo = - { - StarRating = 2.5 - } - }.BeatmapInfo, + StarRating = 2.5 } - }, - new PlaylistItem + }.BeatmapInfo), + new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo) { - Beatmap = + BeatmapInfo = { - Value = new TestBeatmap(new OsuRuleset().RulesetInfo) - { - BeatmapInfo = - { - StarRating = 4.5 - } - }.BeatmapInfo, + StarRating = 4.5 } - } + }.BeatmapInfo) } }), createLoungeRoom(new Room diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 8c10a0d0d9..bea15e6e58 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -209,10 +209,9 @@ namespace osu.Game.Tests.Visual.Multiplayer Size = new Vector2(500, 300), Items = { - new PlaylistItem + new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { ID = 0, - Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID, Expired = true, RequiredMods = new[] @@ -222,10 +221,9 @@ namespace osu.Game.Tests.Visual.Multiplayer new APIMod(new OsuModAutoplay()) } }, - new PlaylistItem + new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { ID = 1, - Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID, RequiredMods = new[] { @@ -293,25 +291,21 @@ namespace osu.Game.Tests.Visual.Multiplayer for (int i = 0; i < 20; i++) { - playlist.Items.Add(new PlaylistItem + playlist.Items.Add(new PlaylistItem(i % 2 == 1 + ? new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo + : new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Artist = "Artist", + Author = new RealmUser { Username = "Creator name here" }, + Title = "Long title used to check background colour", + }, + BeatmapSet = new BeatmapSetInfo() + }) { ID = i, OwnerID = 2, - Beatmap = - { - Value = i % 2 == 1 - ? new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo - : new BeatmapInfo - { - Metadata = new BeatmapMetadata - { - Artist = "Artist", - Author = new RealmUser { Username = "Creator name here" }, - Title = "Long title used to check background colour", - }, - BeatmapSet = new BeatmapSetInfo() - } - }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID, RequiredMods = new[] { @@ -341,11 +335,10 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach (var b in beatmaps()) { - playlist.Items.Add(new PlaylistItem + playlist.Items.Add(new PlaylistItem(b) { ID = index++, OwnerID = 2, - Beatmap = { Value = b }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID, RequiredMods = new[] { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index c7eeff81fe..7614e52218 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -57,12 +57,12 @@ namespace osu.Game.Tests.Visual.Multiplayer RunGameplay(); IBeatmapInfo firstBeatmap = null; - AddStep("get first playlist item beatmap", () => firstBeatmap = Client.APIRoom?.Playlist[0].Beatmap.Value); + AddStep("get first playlist item beatmap", () => firstBeatmap = Client.APIRoom?.Playlist[0].Beatmap); selectNewItem(() => OtherBeatmap); - AddAssert("first playlist item hasn't changed", () => Client.APIRoom?.Playlist[0].Beatmap.Value == firstBeatmap); - AddAssert("second playlist item changed", () => Client.APIRoom?.Playlist[1].Beatmap.Value != firstBeatmap); + AddAssert("first playlist item hasn't changed", () => Client.APIRoom?.Playlist[0].Beatmap == firstBeatmap); + AddAssert("second playlist item changed", () => Client.APIRoom?.Playlist[1].Beatmap != firstBeatmap); } [Test] @@ -104,7 +104,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(otherBeatmap = beatmap())); AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); - AddUntilStep("selected item is new beatmap", () => (CurrentSubScreen as MultiplayerMatchSubScreen)?.SelectedItem.Value?.BeatmapID == otherBeatmap.OnlineID); + AddUntilStep("selected item is new beatmap", () => (CurrentSubScreen as MultiplayerMatchSubScreen)?.SelectedItem.Value?.Beatmap.OnlineID == otherBeatmap.OnlineID); } private void addItem(Func beatmap) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs index 6144824ba0..6f43511e8a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs @@ -32,10 +32,9 @@ namespace osu.Game.Tests.Visual.Multiplayer private void createNewItem() { - SelectedRoom.Value.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { ID = SelectedRoom.Value.Playlist.Count, - Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID, RequiredMods = new[] { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index b04bf5e860..4d87480841 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -93,9 +93,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = { Value = "Test Room" }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } @@ -229,9 +228,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = { Value = "Test Room" }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } @@ -251,9 +249,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = { Value = "Test Room" }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } @@ -281,9 +278,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = { Value = "Test Room" }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } @@ -312,9 +308,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Password = { Value = "password" }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } @@ -334,9 +329,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Password = { Value = "password" }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } @@ -367,9 +361,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Password = { Value = "password" }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } @@ -387,9 +380,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = { Value = "Test Room" }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } @@ -409,9 +401,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = { Value = "Test Room" }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } @@ -448,9 +439,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = { Value = "Test Room" }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } @@ -487,9 +477,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = { Value = "Test Room" }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } @@ -526,9 +515,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = { Value = "Test Room" }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } @@ -560,9 +548,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = { Value = "Test Room" }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } @@ -600,9 +587,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = { Value = "Test Room" }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } @@ -620,9 +606,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = { Value = "Test Room" }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID, AllowedMods = new[] { new APIMod(new OsuModHidden()) } } @@ -660,10 +645,9 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = { Value = "Test Room" }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - RulesetID = new OsuRuleset().RulesetInfo.OnlineID + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } } }); @@ -691,10 +675,9 @@ namespace osu.Game.Tests.Visual.Multiplayer QueueMode = { Value = QueueMode.AllPlayers }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - RulesetID = new OsuRuleset().RulesetInfo.OnlineID + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } } }, API.LocalUser.Value); @@ -708,11 +691,10 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change server-side settings", () => { roomManager.ServerSideRooms[0].Name.Value = "New name"; - roomManager.ServerSideRooms[0].Playlist.Add(new PlaylistItem + roomManager.ServerSideRooms[0].Playlist.Add(new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { ID = 2, - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - RulesetID = new OsuRuleset().RulesetInfo.OnlineID + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, }); }); @@ -737,10 +719,9 @@ namespace osu.Game.Tests.Visual.Multiplayer QueueMode = { Value = QueueMode.AllPlayers }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - RulesetID = new OsuRuleset().RulesetInfo.OnlineID + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } } }); @@ -773,10 +754,9 @@ namespace osu.Game.Tests.Visual.Multiplayer QueueMode = { Value = QueueMode.AllPlayers }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - RulesetID = new OsuRuleset().RulesetInfo.OnlineID + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } } }); @@ -812,10 +792,9 @@ namespace osu.Game.Tests.Visual.Multiplayer QueueMode = { Value = QueueMode.AllPlayers }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - RulesetID = new OsuRuleset().RulesetInfo.OnlineID + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } } }); @@ -823,10 +802,11 @@ namespace osu.Game.Tests.Visual.Multiplayer enterGameplay(); AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 })); - AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem - { - BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo.OnlineID - }))); + AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem( + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + }))); AddUntilStep("item arrived in playlist", () => client.Room?.Playlist.Count == 2); @@ -843,10 +823,9 @@ namespace osu.Game.Tests.Visual.Multiplayer QueueMode = { Value = QueueMode.AllPlayers }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - RulesetID = new OsuRuleset().RulesetInfo.OnlineID + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } } }); @@ -854,10 +833,11 @@ namespace osu.Game.Tests.Visual.Multiplayer enterGameplay(); AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 })); - AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem - { - BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo.OnlineID - }))); + AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem( + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + }))); AddUntilStep("item arrived in playlist", () => client.Room?.Playlist.Count == 2); @@ -876,9 +856,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = { Value = "Test Room" }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 7c18ed2572..e137bf83e4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -73,9 +73,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item", () => { - SelectedRoom.Value.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { - Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID }); }); @@ -90,9 +89,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item", () => { - SelectedRoom.Value.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new TaikoRuleset().RulesetInfo).BeatmapInfo) { - Beatmap = { Value = new TestBeatmap(new TaikoRuleset().RulesetInfo).BeatmapInfo }, RulesetID = new TaikoRuleset().RulesetInfo.OnlineID, AllowedMods = new[] { new APIMod(new TaikoModSwap()) } }); @@ -113,9 +111,8 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("set playlist", () => { - SelectedRoom.Value.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { - Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID }); }); @@ -128,9 +125,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set playlist", () => { - SelectedRoom.Value.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Playlist.Add(new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID }); }); @@ -159,9 +155,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item with allowed mod", () => { - SelectedRoom.Value.Playlist.Add(new PlaylistItem + SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { - Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID, AllowedMods = new[] { new APIMod(new OsuModDoubleTime()) } }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs index 010e9dc078..c402aff771 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs @@ -28,9 +28,8 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("initialise gameplay", () => { - Stack.Push(player = new MultiplayerPlayer(Client.APIRoom, new PlaylistItem + Stack.Push(player = new MultiplayerPlayer(Client.APIRoom, new PlaylistItem(Beatmap.Value.BeatmapInfo) { - Beatmap = { Value = Beatmap.Value.BeatmapInfo }, RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID, }, Client.Room?.Users.ToArray())); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 361178bfe4..57f1c31589 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -143,14 +143,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = { Value = "test name" }, Playlist = { - new PlaylistItem + new PlaylistItem(new TestBeatmap(Ruleset.Value).BeatmapInfo) { - Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo }, - RulesetID = Ruleset.Value.OnlineID, + RulesetID = Ruleset.Value.OnlineID }, - new PlaylistItem + new PlaylistItem(new TestBeatmap(Ruleset.Value).BeatmapInfo) { - Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo }, RulesetID = Ruleset.Value.OnlineID, Expired = true } @@ -167,10 +165,8 @@ namespace osu.Game.Tests.Visual.Multiplayer /// /// Adds a step to create a new playlist item. /// - private void addItemStep(bool expired = false) => AddStep("add item", () => Client.AddPlaylistItem(new MultiplayerPlaylistItem(new PlaylistItem + private void addItemStep(bool expired = false) => AddStep("add item", () => Client.AddPlaylistItem(new MultiplayerPlaylistItem(new PlaylistItem(importedBeatmap) { - Beatmap = { Value = importedBeatmap }, - BeatmapID = importedBeatmap.OnlineID, Expired = expired, PlayedAt = DateTimeOffset.Now }))); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 4040707c41..02bd93ed28 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -120,11 +120,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("add playlist item", () => { - MultiplayerPlaylistItem item = new MultiplayerPlaylistItem(new PlaylistItem - { - Beatmap = { Value = importedBeatmap }, - BeatmapID = importedBeatmap.OnlineID, - }); + MultiplayerPlaylistItem item = new MultiplayerPlaylistItem(new PlaylistItem(importedBeatmap)); Client.AddUserPlaylistItem(userId(), item); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 7834226f15..d486a69061 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -54,9 +54,8 @@ namespace osu.Game.Tests.Visual.Multiplayer importedSet = beatmaps.GetAllUsableBeatmapSets().First(); Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); - selectedItem.Value = new PlaylistItem + selectedItem.Value = new PlaylistItem(Beatmap.Value.BeatmapInfo) { - Beatmap = { Value = Beatmap.Value.BeatmapInfo }, RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID }; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs index 44a1745eee..cc08135939 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs @@ -22,12 +22,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var beatmapInfo = CreateBeatmap(rulesetInfo).BeatmapInfo; var score = TestResources.CreateTestScoreInfo(beatmapInfo); - PlaylistItem playlistItem = new PlaylistItem - { - BeatmapID = beatmapInfo.OnlineID, - }; - - Stack.Push(screen = new MultiplayerResultsScreen(score, 1, playlistItem)); + Stack.Push(screen = new MultiplayerResultsScreen(score, 1, new PlaylistItem(beatmapInfo))); }); AddUntilStep("wait for loaded", () => screen.IsLoaded); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 70d4d9dd55..c8077a49dc 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -55,9 +55,8 @@ namespace osu.Game.Tests.Visual.Multiplayer importedSet = beatmaps.GetAllUsableBeatmapSets().First(); Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); - selectedItem.Value = new PlaylistItem + selectedItem.Value = new PlaylistItem(Beatmap.Value.BeatmapInfo) { - Beatmap = { Value = Beatmap.Value.BeatmapInfo }, RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID, }; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs index dfc16c44f2..bdc348b043 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs @@ -26,18 +26,13 @@ namespace osu.Game.Tests.Visual.Multiplayer var beatmapInfo = CreateBeatmap(rulesetInfo).BeatmapInfo; var score = TestResources.CreateTestScoreInfo(beatmapInfo); - PlaylistItem playlistItem = new PlaylistItem - { - BeatmapID = beatmapInfo.OnlineID, - }; - SortedDictionary teamScores = new SortedDictionary { { 0, new BindableInt(team1Score) }, { 1, new BindableInt(team2Score) } }; - Stack.Push(screen = new MultiplayerTeamResultsScreen(score, 1, playlistItem, teamScores)); + Stack.Push(screen = new MultiplayerTeamResultsScreen(score, 1, new PlaylistItem(beatmapInfo), teamScores)); }); AddUntilStep("wait for loaded", () => screen.IsLoaded); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs index 9adf2c0370..0ef8b16f68 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs @@ -107,16 +107,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]); } - [Test] - public void TestChangeBeatmapAndRemove() - { - createPlaylist(); - - AddStep("change beatmap of first item", () => playlist.Items[0].BeatmapID = 30); - moveToDeleteButton(0); - AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); - } - private void moveToItem(int index, Vector2? offset = null) => AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType().ElementAt(index), offset)); @@ -139,25 +129,21 @@ namespace osu.Game.Tests.Visual.Multiplayer for (int i = 0; i < 20; i++) { - playlist.Items.Add(new PlaylistItem + playlist.Items.Add(new PlaylistItem(i % 2 == 1 + ? new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo + : new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Artist = "Artist", + Author = new RealmUser { Username = "Creator name here" }, + Title = "Long title used to check background colour", + }, + BeatmapSet = new BeatmapSetInfo() + }) { ID = i, OwnerID = 2, - Beatmap = - { - Value = i % 2 == 1 - ? new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo - : new BeatmapInfo - { - Metadata = new BeatmapMetadata - { - Artist = "Artist", - Author = new RealmUser { Username = "Creator name here" }, - Title = "Long title used to check background colour", - }, - BeatmapSet = new BeatmapSetInfo() - } - }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID, RequiredMods = new[] { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs index 20db922122..5e4013b0f1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs @@ -31,8 +31,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { SelectedRoom.Value.Playlist.AddRange(new[] { - new PlaylistItem { Beatmap = { Value = new BeatmapInfo { StarRating = min } } }, - new PlaylistItem { Beatmap = { Value = new BeatmapInfo { StarRating = max } } }, + new PlaylistItem(new BeatmapInfo { StarRating = min }), + new PlaylistItem(new BeatmapInfo { StarRating = max }), }); }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index b66657728e..bd57c8afa5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -67,9 +67,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Type = { Value = MatchType.TeamVersus }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } @@ -88,9 +87,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Type = { Value = MatchType.TeamVersus }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID } } @@ -126,10 +124,9 @@ namespace osu.Game.Tests.Visual.Multiplayer Type = { Value = MatchType.HeadToHead }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - RulesetID = new OsuRuleset().RulesetInfo.OnlineID + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } } }); @@ -152,10 +149,9 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = { Value = "Test Room" }, Playlist = { - new PlaylistItem + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, - RulesetID = new OsuRuleset().RulesetInfo.OnlineID + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } } }); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index ca3387392a..666e32d1d0 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("set name", () => SelectedRoom.Value.Name.Value = "Room name"); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); - AddStep("set beatmap", () => SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } })); + AddStep("set beatmap", () => SelectedRoom.Value.Playlist.Add(new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo))); AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value); AddStep("clear name", () => SelectedRoom.Value.Name.Value = ""); @@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.Playlists { settings.NameField.Current.Value = expected_name; settings.DurationField.Current.Value = expectedDuration; - SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); + SelectedRoom.Value.Playlist.Add(new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)); RoomManager.CreateRequested = r => { @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.Playlists var beatmap = CreateBeatmap(Ruleset.Value).BeatmapInfo; SelectedRoom.Value.Name.Value = "Test Room"; - SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = beatmap } }); + SelectedRoom.Value.Playlist.Add(new PlaylistItem(beatmap)); errorMessage = $"{not_found_prefix} {beatmap.OnlineID}"; @@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("setup", () => { SelectedRoom.Value.Name.Value = "Test Room"; - SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); + SelectedRoom.Value.Playlist.Add(new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)); RoomManager.CreateRequested = _ => failText; }); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index a05d01613c..161624413d 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -165,9 +165,8 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("load results", () => { - LoadScreen(resultsScreen = new TestResultsScreen(getScore?.Invoke(), 1, new PlaylistItem + LoadScreen(resultsScreen = new TestResultsScreen(getScore?.Invoke(), 1, new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { - Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID })); }); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index 578ea63b4e..28b1f6eff5 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -64,9 +64,8 @@ namespace osu.Game.Tests.Visual.Playlists room.Host.Value = API.LocalUser.Value; room.RecentParticipants.Add(room.Host.Value); room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5); - room.Playlist.Add(new PlaylistItem + room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) { - Beatmap = { Value = importedBeatmap.Beatmaps.First() }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID }); }); @@ -89,9 +88,8 @@ namespace osu.Game.Tests.Visual.Playlists room.Host.Value = API.LocalUser.Value; room.RecentParticipants.Add(room.Host.Value); room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5); - room.Playlist.Add(new PlaylistItem + room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) { - Beatmap = { Value = importedBeatmap.Beatmaps.First() }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID }); }); @@ -106,9 +104,8 @@ namespace osu.Game.Tests.Visual.Playlists { room.Name.Value = "my awesome room"; room.Host.Value = API.LocalUser.Value; - room.Playlist.Add(new PlaylistItem + room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) { - Beatmap = { Value = importedBeatmap.Beatmaps.First() }, RulesetID = new OsuRuleset().RulesetInfo.OnlineID }); }); @@ -158,21 +155,17 @@ namespace osu.Game.Tests.Visual.Playlists { room.Name.Value = "my awesome room"; room.Host.Value = API.LocalUser.Value; - room.Playlist.Add(new PlaylistItem + room.Playlist.Add(new PlaylistItem(new BeatmapInfo { - Beatmap = + MD5Hash = realHash, + OnlineID = realOnlineId, + Metadata = new BeatmapMetadata(), + BeatmapSet = new BeatmapSetInfo { - Value = new BeatmapInfo - { - MD5Hash = realHash, - OnlineID = realOnlineId, - Metadata = new BeatmapMetadata(), - BeatmapSet = new BeatmapSetInfo - { - OnlineID = realOnlineSetId, - } - } - }, + OnlineID = realOnlineSetId, + } + }) + { RulesetID = new OsuRuleset().RulesetInfo.OnlineID }); }); diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 9d45229961..a56cc7f8d6 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -727,10 +727,9 @@ namespace osu.Game.Online.Multiplayer RoomUpdated?.Invoke(); } - private PlaylistItem createPlaylistItem(MultiplayerPlaylistItem item) => new PlaylistItem + private PlaylistItem createPlaylistItem(MultiplayerPlaylistItem item) => new PlaylistItem(new APIBeatmap { OnlineID = item.BeatmapID }) { ID = item.ID, - BeatmapID = item.BeatmapID, OwnerID = item.OwnerID, RulesetID = item.RulesetID, Expired = item.Expired, @@ -740,14 +739,6 @@ namespace osu.Game.Online.Multiplayer AllowedMods = item.AllowedMods.ToArray() }; - /// - /// Retrieves a from an online source. - /// - /// The beatmap ID. - /// A token to cancel the request. - /// The retrieval task. - public abstract Task GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default); - /// /// For the provided user ID, update whether the user is included in . /// diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 3794bec228..e92bcd9769 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -9,9 +9,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Game.Database; using osu.Game.Online.API; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; namespace osu.Game.Online.Multiplayer @@ -29,9 +27,6 @@ namespace osu.Game.Online.Multiplayer private HubConnection? connection => connector?.CurrentConnection; - [Resolved] - private BeatmapLookupCache beatmapLookupCache { get; set; } = null!; - public OnlineMultiplayerClient(EndpointConfiguration endpoints) { endpoint = endpoints.MultiplayerEndpointUrl; @@ -186,11 +181,6 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.RemovePlaylistItem), playlistItemId); } - public override Task GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default) - { - return beatmapLookupCache.GetBeatmapAsync(beatmapId, cancellationToken); - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs index d74cdd8c34..388a02f798 100644 --- a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs +++ b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs @@ -63,8 +63,8 @@ namespace osu.Game.Online.Rooms { ID = item.ID; OwnerID = item.OwnerID; - BeatmapID = item.BeatmapID; - BeatmapChecksum = item.Beatmap.Value?.MD5Hash ?? string.Empty; + BeatmapID = item.Beatmap.OnlineID; + BeatmapChecksum = item.Beatmap.MD5Hash; RulesetID = item.RulesetID; RequiredMods = item.RequiredMods.ToArray(); AllowedMods = item.AllowedMods.ToArray(); diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index 90532d88b8..3620e703ae 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -64,11 +64,11 @@ namespace osu.Game.Online.Rooms downloadTracker?.RemoveAndDisposeImmediately(); - beatmapLookupCache.GetBeatmapAsync(item.NewValue.Beatmap.Value.OnlineID).ContinueWith(task => Schedule(() => + beatmapLookupCache.GetBeatmapAsync(item.NewValue.Beatmap.OnlineID).ContinueWith(task => Schedule(() => { var beatmap = task.GetResultSafely(); - if (SelectedItem.Value?.Beatmap.Value.OnlineID == beatmap.OnlineID) + if (SelectedItem.Value?.Beatmap.OnlineID == beatmap.OnlineID) beginTracking(beatmap); }), TaskContinuationOptions.OnlyOnRanToCompletion); }, true); @@ -96,7 +96,7 @@ namespace osu.Game.Online.Rooms // handles changes to hash that didn't occur from the import process (ie. a user editing the beatmap in the editor, somehow). realmSubscription?.Dispose(); - realmSubscription = realm.RegisterForNotifications(r => QueryBeatmapForOnlinePlay(r, SelectedItem.Value.Beatmap.Value), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(r => QueryBeatmapForOnlinePlay(r, SelectedItem.Value.Beatmap), (items, changes, ___) => { if (changes == null) return; diff --git a/osu.Game/Online/Rooms/PlaylistExtensions.cs b/osu.Game/Online/Rooms/PlaylistExtensions.cs index e78f91f20b..34c93bd9e0 100644 --- a/osu.Game/Online/Rooms/PlaylistExtensions.cs +++ b/osu.Game/Online/Rooms/PlaylistExtensions.cs @@ -41,6 +41,6 @@ namespace osu.Game.Online.Rooms } public static string GetTotalDuration(this BindableList playlist) => - playlist.Select(p => p.Beatmap.Value.Length).Sum().Milliseconds().Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 2); + playlist.Select(p => p.Beatmap.Length).Sum().Milliseconds().Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 2); } } diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs index c082babb01..33718f050b 100644 --- a/osu.Game/Online/Rooms/PlaylistItem.cs +++ b/osu.Game/Online/Rooms/PlaylistItem.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using System.Linq; using JetBrains.Annotations; @@ -12,6 +14,7 @@ using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.Rooms { + [JsonObject(MemberSerialization.OptIn)] public class PlaylistItem : IEquatable { [JsonProperty("id")] @@ -20,9 +23,6 @@ namespace osu.Game.Online.Rooms [JsonProperty("owner_id")] public int OwnerID { get; set; } - [JsonProperty("beatmap_id")] - public int BeatmapID { get; set; } - [JsonProperty("ruleset_id")] public int RulesetID { get; set; } @@ -38,35 +38,50 @@ namespace osu.Game.Online.Rooms [JsonProperty("played_at")] public DateTimeOffset? PlayedAt { get; set; } - [JsonIgnore] - public IBindable Valid => valid; - - private readonly Bindable valid = new BindableBool(true); - - [JsonIgnore] - public readonly Bindable Beatmap = new Bindable(); - - [JsonProperty("beatmap")] - private APIBeatmap apiBeatmap { get; set; } - [JsonProperty("allowed_mods")] public APIMod[] AllowedMods { get; set; } = Array.Empty(); [JsonProperty("required_mods")] public APIMod[] RequiredMods { get; set; } = Array.Empty(); - public PlaylistItem() + /// + /// Used for deserialising from the API. + /// + [JsonProperty("beatmap")] + private APIBeatmap apiBeatmap { - Beatmap.BindValueChanged(beatmap => BeatmapID = beatmap.NewValue?.OnlineID ?? -1); + // This getter is required/used internally by JSON.NET during deserialisation to do default-value comparisons. It is never used during serialisation (see: ShouldSerializeapiBeatmap()). + // It will always return a null value on deserialisation, which JSON.NET will handle gracefully. + get => (APIBeatmap)Beatmap; + set => Beatmap = value; + } + + /// + /// Used for serialising to the API. + /// + [JsonProperty("beatmap_id")] + private int onlineBeatmapId => Beatmap.OnlineID; + + [JsonIgnore] + public IBeatmapInfo Beatmap { get; set; } = null!; + + [JsonIgnore] + public IBindable Valid => valid; + + private readonly Bindable valid = new BindableBool(true); + + [JsonConstructor] + private PlaylistItem() + { + } + + public PlaylistItem(IBeatmapInfo beatmap) + { + Beatmap = beatmap; } public void MarkInvalid() => valid.Value = false; - public void MapObjects() - { - Beatmap.Value ??= apiBeatmap; - } - #region Newtonsoft.Json implicit ShouldSerialize() methods // The properties in this region are used implicitly by Newtonsoft.Json to not serialise certain fields in some cases. @@ -82,9 +97,22 @@ namespace osu.Game.Online.Rooms #endregion - public bool Equals(PlaylistItem other) + public PlaylistItem With(IBeatmapInfo beatmap) => new PlaylistItem(beatmap) + { + ID = ID, + OwnerID = OwnerID, + RulesetID = RulesetID, + Expired = Expired, + PlaylistOrder = PlaylistOrder, + PlayedAt = PlayedAt, + AllowedMods = AllowedMods, + RequiredMods = RequiredMods, + valid = { Value = Valid.Value }, + }; + + public bool Equals(PlaylistItem? other) => ID == other?.ID - && BeatmapID == other.BeatmapID + && Beatmap.OnlineID == other.Beatmap.OnlineID && RulesetID == other.RulesetID && Expired == other.Expired && AllowedMods.SequenceEqual(other.AllowedMods) diff --git a/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs b/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs index e948c1adae..7cbe1a9017 100644 --- a/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs +++ b/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs @@ -68,14 +68,14 @@ namespace osu.Game.Screens.OnlinePlay.Components } else { - var metadataInfo = beatmap.Value.Metadata; + var metadataInfo = beatmap.Metadata; string artistUnicode = string.IsNullOrEmpty(metadataInfo.ArtistUnicode) ? metadataInfo.Artist : metadataInfo.ArtistUnicode; string titleUnicode = string.IsNullOrEmpty(metadataInfo.TitleUnicode) ? metadataInfo.Title : metadataInfo.TitleUnicode; var title = new RomanisableString($"{artistUnicode} - {titleUnicode}".Trim(), $"{metadataInfo.Artist} - {metadataInfo.Title}".Trim()); - textFlow.AddLink(title, LinkAction.OpenBeatmap, beatmap.Value.OnlineID.ToString(), "Open beatmap"); + textFlow.AddLink(title, LinkAction.OpenBeatmap, beatmap.OnlineID.ToString(), "Open beatmap"); } } } diff --git a/osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs b/osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs index d534a1e374..8402619ebc 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Components var mods = item.RequiredMods.Select(m => m.ToMod(ruleset)).ToArray(); drawableRuleset.FadeIn(transition_duration); - drawableRuleset.Child = new DifficultyIcon(item.Beatmap.Value, ruleset.RulesetInfo, mods) { Size = new Vector2(height) }; + drawableRuleset.Child = new DifficultyIcon(item.Beatmap, ruleset.RulesetInfo, mods) { Size = new Vector2(height) }; } else drawableRuleset.FadeOut(transition_duration); diff --git a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs index ffc5c07d4e..8906bebf0e 100644 --- a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs @@ -60,7 +60,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { Schedule(() => { - var beatmap = playlistItem?.Beatmap.Value; + var beatmap = playlistItem?.Beatmap; string? lastCover = (background?.Beatmap?.BeatmapSet as IBeatmapSetOnlineInfo)?.Covers.Cover; string? newCover = (beatmap?.BeatmapSet as IBeatmapSetOnlineInfo)?.Covers.Cover; diff --git a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs index d144e1e3a9..d46ff12279 100644 --- a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs +++ b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs @@ -30,7 +30,7 @@ namespace osu.Game.Screens.OnlinePlay.Components private void updateBeatmap() { - sprite.Beatmap.Value = Playlist.GetCurrentItem()?.Beatmap.Value; + sprite.Beatmap.Value = Playlist.GetCurrentItem()?.Beatmap; } protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(BeatmapSetCoverType) { RelativeSizeAxes = Axes.Both }; diff --git a/osu.Game/Screens/OnlinePlay/Components/PlaylistItemBackground.cs b/osu.Game/Screens/OnlinePlay/Components/PlaylistItemBackground.cs index f3e90aa396..7e31591389 100644 --- a/osu.Game/Screens/OnlinePlay/Components/PlaylistItemBackground.cs +++ b/osu.Game/Screens/OnlinePlay/Components/PlaylistItemBackground.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.OnlinePlay.Components public PlaylistItemBackground(PlaylistItem? playlistItem) { - Beatmap = playlistItem?.Beatmap.Value; + Beatmap = playlistItem?.Beatmap; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index 21b64b61bb..4242886e66 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -112,9 +112,6 @@ namespace osu.Game.Screens.OnlinePlay.Components try { - foreach (var pi in room.Playlist) - pi.MapObjects(); - var existing = rooms.FirstOrDefault(e => e.RoomID.Value == room.RoomID.Value); if (existing == null) rooms.Add(room); diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs index edf9c5d155..95ecadd21a 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs @@ -80,7 +80,7 @@ namespace osu.Game.Screens.OnlinePlay.Components private void updateRange(object sender, NotifyCollectionChangedEventArgs e) { - var orderedDifficulties = Playlist.Where(p => p.Beatmap.Value != null).Select(p => p.Beatmap.Value).OrderBy(b => b.StarRating).ToArray(); + var orderedDifficulties = Playlist.Select(p => p.Beatmap).OrderBy(b => b.StarRating).ToArray(); StarDifficulty minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarRating : 0, 0); StarDifficulty maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarRating : 0, 0); diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 7674fac88e..4be1927e57 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -161,7 +161,7 @@ namespace osu.Game.Screens.OnlinePlay Schedule(() => ownerAvatar.User = foundUser); } - beatmap = await beatmapLookupCache.GetBeatmapAsync(Item.Beatmap.Value.OnlineID).ConfigureAwait(false); + beatmap = await beatmapLookupCache.GetBeatmapAsync(Item.Beatmap.OnlineID).ConfigureAwait(false); Scheduler.AddOnce(refresh); } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 60f65fbd9f..a1a82c907a 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -409,7 +409,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components return; var cancellationSource = beatmapLookupCancellation = new CancellationTokenSource(); - beatmapLookupCache.GetBeatmapAsync(beatmap.Value.OnlineID, cancellationSource.Token) + beatmapLookupCache.GetBeatmapAsync(beatmap.OnlineID, cancellationSource.Token) .ContinueWith(task => Schedule(() => { if (cancellationSource.IsCancellationRequested) diff --git a/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs b/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs index a7b907c7d2..cdd2ae0c9c 100644 --- a/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs @@ -62,7 +62,7 @@ namespace osu.Game.Screens.OnlinePlay.Match if (editButton != null) host.BindValueChanged(h => editButton.Alpha = h.NewValue?.Equals(api.LocalUser.Value) == true ? 1 : 0, true); - SelectedItem.BindValueChanged(item => background.Beatmap.Value = item.NewValue?.Beatmap.Value, true); + SelectedItem.BindValueChanged(item => background.Beatmap.Value = item.NewValue?.Beatmap, true); } protected override Drawable CreateBackground() => background = new BackgroundSprite(); diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 836629ada0..e297c90491 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -376,7 +376,7 @@ namespace osu.Game.Screens.OnlinePlay.Match private void updateWorkingBeatmap() { - var beatmap = SelectedItem.Value?.Beatmap.Value; + var beatmap = SelectedItem.Value?.Beatmap; // Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info var localBeatmap = beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineID == beatmap.OnlineID); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 12caf1fde1..e30ec36e9c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -67,8 +67,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer var multiplayerItem = new MultiplayerPlaylistItem { ID = itemToEdit ?? 0, - BeatmapID = item.BeatmapID, - BeatmapChecksum = item.Beatmap.Value.MD5Hash, + BeatmapID = item.Beatmap.OnlineID, + BeatmapChecksum = item.Beatmap.MD5Hash, RulesetID = item.RulesetID, RequiredMods = item.RequiredMods.ToArray(), AllowedMods = item.AllowedMods.ToArray() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index cb50a56052..429b0ad89b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -398,38 +397,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private void updateCurrentItem() { Debug.Assert(client.Room != null); - - var expectedSelectedItem = Room.Playlist.SingleOrDefault(i => i.ID == client.Room.Settings.PlaylistItemId); - - if (expectedSelectedItem == null) - return; - - // There's no reason to renew the selected item if its content hasn't changed. - if (SelectedItem.Value?.Equals(expectedSelectedItem) == true && expectedSelectedItem.Beatmap.Value != null) - return; - - // Clear the selected item while the lookup is performed, so components like the ready button can enter their disabled states. - SelectedItem.Value = null; - - if (expectedSelectedItem.Beatmap.Value == null) - { - Task.Run(async () => - { - var beatmap = await client.GetAPIBeatmap(expectedSelectedItem.BeatmapID).ConfigureAwait(false); - - Schedule(() => - { - expectedSelectedItem.Beatmap.Value = beatmap; - - if (Room.Playlist.SingleOrDefault(i => i.ID == client.Room?.Settings.PlaylistItemId)?.Equals(expectedSelectedItem) == true) - applyCurrentItem(); - }); - }); - } - else - applyCurrentItem(); - - void applyCurrentItem() => SelectedItem.Value = expectedSelectedItem; + SelectedItem.Value = Room.Playlist.SingleOrDefault(i => i.ID == client.Room.Settings.PlaylistItemId); } private void handleRoomLost() => Schedule(() => diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index eab1f83967..7b64784316 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -113,12 +113,8 @@ namespace osu.Game.Screens.OnlinePlay { itemSelected = true; - var item = new PlaylistItem + var item = new PlaylistItem(Beatmap.Value.BeatmapInfo) { - Beatmap = - { - Value = Beatmap.Value.BeatmapInfo - }, RulesetID = Ruleset.Value.OnlineID, RequiredMods = Mods.Value.Select(m => new APIMod(m)).ToArray(), AllowedMods = FreeMods.Value.Select(m => new APIMod(m)).ToArray() diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 2b071175d5..8403e1e0fe 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private void load(IBindable ruleset) { // Sanity checks to ensure that PlaylistsPlayer matches the settings for the current PlaylistItem - if (!Beatmap.Value.BeatmapInfo.MatchesOnlineID(PlaylistItem.Beatmap.Value)) + if (!Beatmap.Value.BeatmapInfo.MatchesOnlineID(PlaylistItem.Beatmap)) throw new InvalidOperationException("Current Beatmap does not match PlaylistItem's Beatmap"); if (ruleset.Value.OnlineID != PlaylistItem.RulesetID) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 6c8ab52d22..6674a37c3c 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -392,7 +392,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists foreach (var item in Playlist) { - if (invalidBeatmapIDs.Contains(item.BeatmapID)) + if (invalidBeatmapIDs.Contains(item.Beatmap.OnlineID)) item.MarkInvalid(); } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs index 3ac576b18e..86591c1d6d 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs @@ -41,10 +41,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private void createNewItem() { - PlaylistItem item = new PlaylistItem + PlaylistItem item = new PlaylistItem(Beatmap.Value.BeatmapInfo) { ID = Playlist.Count == 0 ? 0 : Playlist.Max(p => p.ID) + 1, - Beatmap = { Value = Beatmap.Value.BeatmapInfo }, RulesetID = Ruleset.Value.OnlineID, RequiredMods = Mods.Value.Select(m => new APIMod(m)).ToArray(), AllowedMods = FreeMods.Value.Select(m => new APIMod(m)).ToArray() diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index a9b3ca4991..2fecf0a4cc 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -46,9 +46,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = { Value = "test name" }, Playlist = { - new PlaylistItem + new PlaylistItem(new TestBeatmap(Ruleset.Value).BeatmapInfo) { - Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo }, RulesetID = Ruleset.Value.OnlineID } } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 15ede6cc26..6dc5159b6f 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -7,14 +7,11 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; -using osu.Game.Beatmaps; using osu.Game.Online.API; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; using osu.Game.Online.Rooms; @@ -39,9 +36,6 @@ namespace osu.Game.Tests.Visual.Multiplayer [Resolved] private IAPIProvider api { get; set; } = null!; - [Resolved] - private BeatmapManager beatmaps { get; set; } = null!; - private readonly TestMultiplayerRoomManager roomManager; /// @@ -407,23 +401,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public override Task RemovePlaylistItem(long playlistItemId) => RemoveUserPlaylistItem(api.LocalUser.Value.OnlineID, playlistItemId); - public override Task GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default) - { - IBeatmapInfo? beatmap = roomManager.ServerSideRooms.SelectMany(r => r.Playlist) - .FirstOrDefault(p => p.BeatmapID == beatmapId)?.Beatmap.Value - ?? beatmaps.QueryBeatmap(b => b.OnlineID == beatmapId); - - if (beatmap == null) - throw new InvalidOperationException("Beatmap not found."); - - return Task.FromResult(new APIBeatmap - { - BeatmapSet = new APIBeatmapSet { OnlineID = beatmap.BeatmapSet?.OnlineID ?? -1 }, - OnlineID = beatmapId, - Checksum = beatmap.MD5Hash - }); - } - private async Task changeMatchType(MatchType type) { Debug.Assert(Room != null); diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index 73d0df2c36..8dfd969c51 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -43,16 +43,9 @@ namespace osu.Game.Tests.Visual.OnlinePlay if (ruleset != null) { - room.Playlist.Add(new PlaylistItem + room.Playlist.Add(new PlaylistItem(new BeatmapInfo { Metadata = new BeatmapMetadata() }) { RulesetID = ruleset.OnlineID, - Beatmap = - { - Value = new BeatmapInfo - { - Metadata = new BeatmapMetadata() - } - } }); } From 48573d2401d33434399f9abaf98659ac2776fa81 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Feb 2022 23:42:37 +0900 Subject: [PATCH 0682/5199] Move test request handling earlier in setup --- .../Visual/OnlinePlay/OnlinePlayTestScene.cs | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index 448ec5e3d9..b6a347a896 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -60,24 +60,16 @@ namespace osu.Game.Tests.Visual.OnlinePlay drawableDependenciesContainer.Clear(); dependencies.OnlinePlayDependencies = CreateOnlinePlayDependencies(); drawableDependenciesContainer.AddRange(OnlinePlayDependencies.DrawableComponents); + + var handler = OnlinePlayDependencies.RequestsHandler; + + // Resolving the BeatmapManager in the test scene will inject the game-wide BeatmapManager, while many test scenes cache their own BeatmapManager instead. + // To get around this, the BeatmapManager is looked up from the dependencies provided to the children of the test scene instead. + var beatmapManager = dependencies.Get(); + + ((DummyAPIAccess)API).HandleRequest = request => handler.HandleRequest(request, API.LocalUser.Value, beatmapManager); }); - public override void SetUpSteps() - { - base.SetUpSteps(); - - AddStep("setup API", () => - { - var handler = OnlinePlayDependencies.RequestsHandler; - - // Resolving the BeatmapManager in the test scene will inject the game-wide BeatmapManager, while many test scenes cache their own BeatmapManager instead. - // To get around this, the BeatmapManager is looked up from the dependencies provided to the children of the test scene instead. - var beatmapManager = dependencies.Get(); - - ((DummyAPIAccess)API).HandleRequest = request => handler.HandleRequest(request, API.LocalUser.Value, beatmapManager); - }); - } - /// /// Creates the room dependencies. Called every . /// From b1dbd4abfe6697134717439d1fbe06eb91c94cf5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Feb 2022 23:50:07 +0900 Subject: [PATCH 0683/5199] Fix incorrect playlist item <-> availability tracker logic Results in revert to some prior logic for the tracker implementation. --- .../OnlinePlayBeatmapAvailabilityTracker.cs | 19 +++++++++---------- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 5 +---- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index 3620e703ae..02bcd1f30c 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -96,7 +96,7 @@ namespace osu.Game.Online.Rooms // handles changes to hash that didn't occur from the import process (ie. a user editing the beatmap in the editor, somehow). realmSubscription?.Dispose(); - realmSubscription = realm.RegisterForNotifications(r => QueryBeatmapForOnlinePlay(r, SelectedItem.Value.Beatmap), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(r => filteredBeatmaps(beatmap), (items, changes, ___) => { if (changes == null) return; @@ -125,7 +125,7 @@ namespace osu.Game.Online.Rooms break; case DownloadState.LocallyAvailable: - bool available = QueryBeatmapForOnlinePlay(realm.Realm, beatmap).Any(); + bool available = filteredBeatmaps(beatmap).Any(); availability.Value = available ? BeatmapAvailability.LocallyAvailable() : BeatmapAvailability.NotDownloaded(); @@ -140,15 +140,14 @@ namespace osu.Game.Online.Rooms } } - /// - /// Performs a query for a local matching a requested one for the purpose of online play. - /// - /// The realm to query from. - /// The requested beatmap. - /// A beatmap query. - public static IQueryable QueryBeatmapForOnlinePlay(Realm realm, IBeatmapInfo beatmap) + private IQueryable filteredBeatmaps(APIBeatmap beatmap) { - return realm.All().Filter("OnlineID == $0 && MD5Hash == $1 && BeatmapSet.DeletePending == false", beatmap.OnlineID, beatmap.MD5Hash); + int onlineId = beatmap.OnlineID; + string checksum = beatmap.MD5Hash; + + return realm.Realm + .All() + .Filter("OnlineID == $0 && MD5Hash == $1 && BeatmapSet.DeletePending == false", onlineId, checksum); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 4be1927e57..faeb73cfa3 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -97,9 +97,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved] private BeatmapLookupCache beatmapLookupCache { get; set; } - [Resolved] - private RealmAccess realm { get; set; } - protected override bool ShouldBeConsideredForInput(Drawable child) => AllowReordering || AllowDeletion || !AllowSelection || SelectedItem.Value == Model; public DrawableRoomPlaylistItem(PlaylistItem item) @@ -444,7 +441,7 @@ namespace osu.Game.Screens.OnlinePlay Alpha = AllowShowingResults ? 1 : 0, TooltipText = "View results" }, - OnlinePlayBeatmapAvailabilityTracker.QueryBeatmapForOnlinePlay(realm.Realm, beatmap).Any() ? Empty() : new PlaylistDownloadButton(beatmap), + beatmap == null ? Empty() : new PlaylistDownloadButton(beatmap), editButton = new PlaylistEditButton { Size = new Vector2(30, 30), From f5b34e03139a7d098fe30e44c9e6fcad3cd890ca Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Feb 2022 23:51:44 +0900 Subject: [PATCH 0684/5199] Fix some test failures due to now-async lookups --- .../Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs | 4 ++-- .../Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index bea15e6e58..4f01c14659 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -168,7 +168,7 @@ namespace osu.Game.Tests.Visual.Multiplayer assertDownloadButtonVisible(false); void assertDownloadButtonVisible(bool visible) => AddUntilStep($"download button {(visible ? "shown" : "hidden")}", - () => playlist.ChildrenOfType().Single().Alpha == (visible ? 1 : 0)); + () => playlist.ChildrenOfType().SingleOrDefault()?.Alpha == (visible ? 1 : 0)); } [Test] @@ -260,7 +260,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } private void moveToItem(int index, Vector2? offset = null) - => AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType().ElementAt(index), offset)); + => AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType().ElementAt(index), offset)); private void moveToDragger(int index, Vector2? offset = null) => AddStep($"move mouse to dragger {index}", () => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs index 0ef8b16f68..98dc243ab5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics.Containers; using osu.Game.Models; using osu.Game.Online.API; @@ -108,7 +107,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } private void moveToItem(int index, Vector2? offset = null) - => AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType().ElementAt(index), offset)); + => AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType().ElementAt(index), offset)); private void moveToDeleteButton(int index, Vector2? offset = null) => AddStep($"move mouse to delete button {index}", () => { From 6e99fe04c3b71ce5a36609062ceb08bd6fb1e400 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 16 Feb 2022 00:39:43 +0900 Subject: [PATCH 0685/5199] Revert more NUnit test adapter bumps --- .../osu.Game.Rulesets.EmptyFreeform.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- .../osu.Game.Rulesets.EmptyScrolling.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index c305872288..cb922c5a58 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index ec90885cd9..5ecd9cc675 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index f2e143a9c5..33ad0ac4f7 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index ec90885cd9..5ecd9cc675 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -12,7 +12,7 @@ - + From f7edf25d4c84e2bc61a3f95efd22de52409fc66b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 16 Feb 2022 03:23:02 +0300 Subject: [PATCH 0686/5199] Remove no longer required property --- osu.Game/Overlays/SettingsToolboxGroup.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game/Overlays/SettingsToolboxGroup.cs b/osu.Game/Overlays/SettingsToolboxGroup.cs index 509410ec20..08321f68fe 100644 --- a/osu.Game/Overlays/SettingsToolboxGroup.cs +++ b/osu.Game/Overlays/SettingsToolboxGroup.cs @@ -22,11 +22,6 @@ namespace osu.Game.Overlays { public class SettingsToolboxGroup : Container, IExpandable { - /// - /// The title of this toolbox group. - /// - public string Title { get; } - private const float transition_duration = 250; private const int container_width = 270; private const int border_thickness = 2; @@ -53,8 +48,6 @@ namespace osu.Game.Overlays /// The title to be displayed in the header of this group. public SettingsToolboxGroup(string title) { - Title = title; - AutoSizeAxes = Axes.Y; Width = container_width; Masking = true; From 7654584e40e8860a20f6bfce1322d6b331da92af Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 16 Feb 2022 03:25:03 +0300 Subject: [PATCH 0687/5199] Allow using `SizeSlider` on different value types --- .../Visual/UserInterface/TestSceneExpandingContainer.cs | 4 ++-- .../Overlays/Settings/Sections/Gameplay/InputSettings.cs | 2 +- osu.Game/Overlays/Settings/Sections/SizeSlider.cs | 6 ++++-- .../Settings/Sections/UserInterface/GeneralSettings.cs | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs index f4920b4412..288c0cb140 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs @@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.UserInterface private TestExpandingContainer container; private SettingsToolboxGroup toolboxGroup; - private ExpandableSlider slider1; + private ExpandableSlider> slider1; private ExpandableSlider slider2; [SetUp] @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.UserInterface Width = 1, Children = new Drawable[] { - slider1 = new ExpandableSlider + slider1 = new ExpandableSlider> { Current = new BindableFloat { diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs index 962572ca6e..83ea655601 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { Children = new Drawable[] { - new SettingsSlider + new SettingsSlider> { LabelText = SkinSettingsStrings.GameplayCursorSize, Current = config.GetBindable(OsuSetting.GameplayCursorSize), diff --git a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs index 8aeb440be1..cc2c77adf2 100644 --- a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs +++ b/osu.Game/Overlays/Settings/Sections/SizeSlider.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 osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; @@ -9,8 +10,9 @@ namespace osu.Game.Overlays.Settings.Sections /// /// A slider intended to show a "size" multiplier number, where 1x is 1.0. /// - internal class SizeSlider : OsuSliderBar + internal class SizeSlider : OsuSliderBar + where T : struct, IEquatable, IComparable, IConvertible, IFormattable { - public override LocalisableString TooltipText => Current.Value.ToString(@"0.##x"); + public override LocalisableString TooltipText => Current.Value.ToString(@"0.##x", null); } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs index 0afbed5df5..66702744d3 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface LabelText = UserInterfaceStrings.CursorRotation, Current = config.GetBindable(OsuSetting.CursorRotation) }, - new SettingsSlider + new SettingsSlider> { LabelText = UserInterfaceStrings.MenuCursorSize, Current = config.GetBindable(OsuSetting.MenuCursorSize), From 0992bec2c849d9e5a7be97d1d9396027a1f8f4e1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 16 Feb 2022 03:28:05 +0300 Subject: [PATCH 0688/5199] Change "distance spacing" multipler type to `double` Avoids losing precision on initial load, causing an unnecessary hash change in `EditorChangeHandler`. Resolves test failures in `TestSceneEditorChangeStates` (https://github.com/ppy/osu/runs/5192493482?check_suite_focus=true). --- .../Editor/TestSceneManiaBeatSnapGrid.cs | 2 +- .../Editor/TestSceneOsuDistanceSnapGrid.cs | 2 +- .../Editing/TestSceneDistanceSnapGrid.cs | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 20 +++++++++---------- .../Rulesets/Edit/IPositionSnapProvider.cs | 2 +- .../Compose/Components/DistanceSnapGrid.cs | 2 +- .../Compose/Components/Timeline/Timeline.cs | 2 +- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs index 38779ab47b..f32a1a823e 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor set => InternalChild = value; } - public override IBindable DistanceSpacingMultiplier => throw new System.NotImplementedException(); + public override IBindable DistanceSpacingMultiplier => throw new System.NotImplementedException(); public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) { diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs index c5829d5351..38a442d48b 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs @@ -187,7 +187,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0); - public IBindable DistanceSpacingMultiplier { get; } = new BindableFloat(1); + public IBindable DistanceSpacingMultiplier { get; } = new BindableDouble(1); public float GetBeatSnapDistanceAt(HitObject referenceObject) => (float)beat_length; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs index cba6d7692d..69fd642143 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.Editing public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0); - public IBindable DistanceSpacingMultiplier { get; } = new BindableFloat(1); + public IBindable DistanceSpacingMultiplier { get; } = new BindableDouble(1); public float GetBeatSnapDistanceAt(HitObject referenceObject) => 10; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 948e1d770f..ab82fbad04 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -66,15 +66,15 @@ namespace osu.Game.Rulesets.Edit /// protected abstract bool SupportsDistanceSpacing { get; } - private readonly BindableFloat distanceSpacing = new BindableFloat + private readonly BindableDouble distanceSpacing = new BindableDouble { - Default = 1.0f, - MinValue = 0.1f, - MaxValue = 6.0f, - Precision = 0.01f, + Default = 1.0, + MinValue = 0.1, + MaxValue = 6.0, + Precision = 0.01, }; - public override IBindable DistanceSpacingMultiplier => distanceSpacing; + public override IBindable DistanceSpacingMultiplier => distanceSpacing; private SnappingToolboxContainer snappingToolboxContainer; @@ -161,11 +161,11 @@ namespace osu.Game.Rulesets.Edit }, }; - distanceSpacing.Value = (float)EditorBeatmap.BeatmapInfo.DistanceSpacing; + distanceSpacing.Value = EditorBeatmap.BeatmapInfo.DistanceSpacing; if (SupportsDistanceSpacing) { - ExpandableSlider distanceSpacingSlider; + ExpandableSlider> distanceSpacingSlider; AddInternal(snappingToolboxContainer = new SnappingToolboxContainer { @@ -173,7 +173,7 @@ namespace osu.Game.Rulesets.Edit Origin = Anchor.TopRight, Child = new EditorToolboxGroup("snapping") { - Child = distanceSpacingSlider = new ExpandableSlider + Child = distanceSpacingSlider = new ExpandableSlider> { Current = { BindTarget = distanceSpacing }, KeyboardStep = 0.01f, @@ -563,7 +563,7 @@ namespace osu.Game.Rulesets.Edit #region IPositionSnapProvider - public abstract IBindable DistanceSpacingMultiplier { get; } + public abstract IBindable DistanceSpacingMultiplier { get; } public abstract SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition); diff --git a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs index 1cfcb86e10..e4adbba47d 100644 --- a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Edit /// The spacing multiplier applied to beat snap distances. /// /// - IBindable DistanceSpacingMultiplier { get; } + IBindable DistanceSpacingMultiplier { get; } /// /// Given a position, find a valid time and position snap. diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index afbe83ec83..f9e4ef086c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Edit.Compose.Components [Resolved] private BindableBeatDivisor beatDivisor { get; set; } - private IBindable distanceSpacingMultiplier; + private IBindable distanceSpacingMultiplier; private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); private readonly double? endTime; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index f3a328c6bc..8832c4e89c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -317,7 +317,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private double getTimeFromPosition(Vector2 localPosition) => (localPosition.X / Content.DrawWidth) * track.Length; - public IBindable DistanceSpacingMultiplier => throw new NotImplementedException(); + public IBindable DistanceSpacingMultiplier => throw new NotImplementedException(); public float GetBeatSnapDistanceAt(HitObject referenceObject) => throw new NotImplementedException(); From 6a08fd57efb0012a723d658bc279d88292f3754e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Feb 2022 09:43:28 +0900 Subject: [PATCH 0689/5199] Rename "client" fields in tests to specify whether spectator or multiplayer --- .../StatefulMultiplayerClientTest.cs | 28 +-- .../Visual/Gameplay/TestSceneSpectator.cs | 6 +- .../Visual/Multiplayer/QueueModeTestScene.cs | 10 +- .../TestSceneAllPlayersQueueMode.cs | 38 ++-- .../TestSceneCreateMultiplayerMatchButton.cs | 4 +- .../Multiplayer/TestSceneHostOnlyQueueMode.cs | 26 +-- .../TestSceneMultiSpectatorLeaderboard.cs | 2 +- .../TestSceneMultiSpectatorScreen.cs | 12 +- .../Multiplayer/TestSceneMultiplayer.cs | 180 +++++++++--------- ...TestSceneMultiplayerGameplayLeaderboard.cs | 6 +- ...ceneMultiplayerGameplayLeaderboardTeams.cs | 4 +- .../TestSceneMultiplayerMatchSubScreen.cs | 10 +- .../TestSceneMultiplayerParticipantsList.cs | 98 +++++----- .../Multiplayer/TestSceneMultiplayerPlayer.cs | 6 +- .../TestSceneMultiplayerPlaylist.cs | 12 +- .../TestSceneMultiplayerQueueList.cs | 28 +-- .../TestSceneMultiplayerReadyButton.cs | 56 +++--- .../TestSceneMultiplayerSpectateButton.cs | 26 +-- .../Multiplayer/TestSceneRankRangePill.cs | 18 +- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 30 +-- .../Visual/TestMultiplayerComponents.cs | 4 +- .../IMultiplayerTestSceneDependencies.cs | 5 +- .../Multiplayer/MultiplayerTestScene.cs | 4 +- .../MultiplayerTestSceneDependencies.cs | 6 +- 24 files changed, 309 insertions(+), 310 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index 0c49a18c8f..4adb7002a0 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -21,8 +21,8 @@ namespace osu.Game.Tests.NonVisual.Multiplayer { var user = new APIUser { Id = 33 }; - AddRepeatStep("add user multiple times", () => Client.AddUser(user), 3); - AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2); + AddRepeatStep("add user multiple times", () => MultiplayerClient.AddUser(user), 3); + AddAssert("room has 2 users", () => MultiplayerClient.Room?.Users.Count == 2); } [Test] @@ -30,11 +30,11 @@ namespace osu.Game.Tests.NonVisual.Multiplayer { var user = new APIUser { Id = 44 }; - AddStep("add user", () => Client.AddUser(user)); - AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2); + AddStep("add user", () => MultiplayerClient.AddUser(user)); + AddAssert("room has 2 users", () => MultiplayerClient.Room?.Users.Count == 2); - AddRepeatStep("remove user multiple times", () => Client.RemoveUser(user), 3); - AddAssert("room has 1 user", () => Client.Room?.Users.Count == 1); + AddRepeatStep("remove user multiple times", () => MultiplayerClient.RemoveUser(user), 3); + AddAssert("room has 1 user", () => MultiplayerClient.Room?.Users.Count == 1); } [Test] @@ -42,7 +42,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer { int id = 2000; - AddRepeatStep("add some users", () => Client.AddUser(new APIUser { Id = id++ }), 5); + AddRepeatStep("add some users", () => MultiplayerClient.AddUser(new APIUser { Id = id++ }), 5); checkPlayingUserCount(0); changeState(3, MultiplayerUserState.WaitingForLoad); @@ -57,17 +57,17 @@ namespace osu.Game.Tests.NonVisual.Multiplayer changeState(6, MultiplayerUserState.WaitingForLoad); checkPlayingUserCount(6); - AddStep("another user left", () => Client.RemoveUser((Client.Room?.Users.Last().User).AsNonNull())); + AddStep("another user left", () => MultiplayerClient.RemoveUser((MultiplayerClient.Room?.Users.Last().User).AsNonNull())); checkPlayingUserCount(5); - AddStep("leave room", () => Client.LeaveRoom()); + AddStep("leave room", () => MultiplayerClient.LeaveRoom()); checkPlayingUserCount(0); } [Test] public void TestPlayingUsersUpdatedOnJoin() { - AddStep("leave room", () => Client.LeaveRoom()); + AddStep("leave room", () => MultiplayerClient.LeaveRoom()); AddUntilStep("wait for room part", () => !RoomJoined); AddStep("create room initially in gameplay", () => @@ -76,7 +76,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer newRoom.CopyFrom(SelectedRoom.Value); newRoom.RoomID.Value = null; - Client.RoomSetupAction = room => + MultiplayerClient.RoomSetupAction = room => { room.State = MultiplayerRoomState.Playing; room.Users.Add(new MultiplayerRoomUser(PLAYER_1_ID) @@ -94,15 +94,15 @@ namespace osu.Game.Tests.NonVisual.Multiplayer } private void checkPlayingUserCount(int expectedCount) - => AddAssert($"{"user".ToQuantity(expectedCount)} playing", () => Client.CurrentMatchPlayingUserIds.Count == expectedCount); + => AddAssert($"{"user".ToQuantity(expectedCount)} playing", () => MultiplayerClient.CurrentMatchPlayingUserIds.Count == expectedCount); private void changeState(int userCount, MultiplayerUserState state) => AddStep($"{"user".ToQuantity(userCount)} in {state}", () => { for (int i = 0; i < userCount; ++i) { - int userId = Client.Room?.Users[i].UserID ?? throw new AssertionException("Room cannot be null!"); - Client.ChangeUserState(userId, state); + int userId = MultiplayerClient.Room?.Users[i].UserID ?? throw new AssertionException("Room cannot be null!"); + MultiplayerClient.ChangeUserState(userId, state); } }); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 6dca256d31..d47ebf9f0d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Resolved] private OsuGameBase game { get; set; } - private TestSpectatorClient spectatorClient => dependenciesScreen.Client; + private TestSpectatorClient spectatorClient => dependenciesScreen.SpectatorClient; private DependenciesScreen dependenciesScreen; private SoloSpectator spectatorScreen; @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Gameplay Children = new Drawable[] { dependenciesScreen.UserLookupCache, - dependenciesScreen.Client, + dependenciesScreen.SpectatorClient, }; }); @@ -336,7 +336,7 @@ namespace osu.Game.Tests.Visual.Gameplay private class DependenciesScreen : OsuScreen { [Cached(typeof(SpectatorClient))] - public readonly TestSpectatorClient Client = new TestSpectatorClient(); + public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient(); [Cached(typeof(UserLookupCache))] public readonly TestUserLookupCache UserLookupCache = new TestUserLookupCache(); diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 52801dd57a..2a3b44d619 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private TestMultiplayerComponents multiplayerComponents; - protected TestMultiplayerClient Client => multiplayerComponents.Client; + protected TestMultiplayerClient MultiplayerClient => multiplayerComponents.MultiplayerClient; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -84,21 +84,21 @@ namespace osu.Game.Tests.Visual.Multiplayer ClickButtonWhenEnabled(); - AddUntilStep("wait for join", () => Client.RoomJoined); + AddUntilStep("wait for join", () => MultiplayerClient.RoomJoined); } [Test] public void TestCreatedWithCorrectMode() { - AddAssert("room created with correct mode", () => Client.APIRoom?.QueueMode.Value == Mode); + AddAssert("room created with correct mode", () => MultiplayerClient.APIRoom?.QueueMode.Value == Mode); } protected void RunGameplay() { - AddUntilStep("wait for idle", () => Client.LocalUser?.State == MultiplayerUserState.Idle); + AddUntilStep("wait for idle", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Idle); ClickButtonWhenEnabled(); - AddUntilStep("wait for ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready); + AddUntilStep("wait for ready", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready); ClickButtonWhenEnabled(); AddUntilStep("wait for player", () => multiplayerComponents.CurrentScreen is Player player && player.IsLoaded); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index ad60ac824d..0785315b26 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -31,19 +31,19 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestFirstItemSelectedByDefault() { - AddAssert("first item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID); + AddAssert("first item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[0].ID); } [Test] public void TestItemAddedToTheEndOfQueue() { addItem(() => OtherBeatmap); - AddAssert("playlist has 2 items", () => Client.APIRoom?.Playlist.Count == 2); + AddAssert("playlist has 2 items", () => MultiplayerClient.APIRoom?.Playlist.Count == 2); addItem(() => InitialBeatmap); - AddAssert("playlist has 3 items", () => Client.APIRoom?.Playlist.Count == 3); + AddAssert("playlist has 3 items", () => MultiplayerClient.APIRoom?.Playlist.Count == 3); - AddAssert("first item still selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID); + AddAssert("first item still selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[0].ID); } [Test] @@ -51,9 +51,9 @@ namespace osu.Game.Tests.Visual.Multiplayer { RunGameplay(); - AddAssert("playlist has only one item", () => Client.APIRoom?.Playlist.Count == 1); - AddAssert("playlist item is expired", () => Client.APIRoom?.Playlist[0].Expired == true); - AddAssert("last item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID); + AddAssert("playlist has only one item", () => MultiplayerClient.APIRoom?.Playlist.Count == 1); + AddAssert("playlist item is expired", () => MultiplayerClient.APIRoom?.Playlist[0].Expired == true); + AddAssert("last item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[0].ID); } [Test] @@ -64,13 +64,13 @@ namespace osu.Game.Tests.Visual.Multiplayer RunGameplay(); - AddAssert("first item expired", () => Client.APIRoom?.Playlist[0].Expired == true); - AddAssert("next item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[1].ID); + AddAssert("first item expired", () => MultiplayerClient.APIRoom?.Playlist[0].Expired == true); + AddAssert("next item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[1].ID); RunGameplay(); - AddAssert("second item expired", () => Client.APIRoom?.Playlist[1].Expired == true); - AddAssert("next item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[2].ID); + AddAssert("second item expired", () => MultiplayerClient.APIRoom?.Playlist[1].Expired == true); + AddAssert("next item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[2].ID); } [Test] @@ -82,10 +82,10 @@ namespace osu.Game.Tests.Visual.Multiplayer // Move to the "other" beatmap. RunGameplay(); - AddStep("change queue mode", () => Client.ChangeSettings(queueMode: QueueMode.HostOnly)); - AddAssert("playlist has 3 items", () => Client.APIRoom?.Playlist.Count == 3); - AddAssert("item 2 is not expired", () => Client.APIRoom?.Playlist[1].Expired == false); - AddAssert("current item is the other beatmap", () => Client.Room?.Settings.PlaylistItemId == 2); + AddStep("change queue mode", () => MultiplayerClient.ChangeSettings(queueMode: QueueMode.HostOnly)); + AddAssert("playlist has 3 items", () => MultiplayerClient.APIRoom?.Playlist.Count == 3); + AddAssert("item 2 is not expired", () => MultiplayerClient.APIRoom?.Playlist[1].Expired == false); + AddAssert("current item is the other beatmap", () => MultiplayerClient.Room?.Settings.PlaylistItemId == 2); } [Test] @@ -101,10 +101,10 @@ namespace osu.Game.Tests.Visual.Multiplayer addItem(() => OtherBeatmap, new CatchRuleset().RulesetInfo); AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID); - AddUntilStep("wait for idle", () => Client.LocalUser?.State == MultiplayerUserState.Idle); + AddUntilStep("wait for idle", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Idle); ClickButtonWhenEnabled(); - AddUntilStep("wait for ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready); + AddUntilStep("wait for ready", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready); ClickButtonWhenEnabled(); AddUntilStep("wait for player", () => CurrentScreen is Player player && player.IsLoaded); @@ -118,10 +118,10 @@ namespace osu.Game.Tests.Visual.Multiplayer addItem(() => OtherBeatmap, mods: new Mod[] { new OsuModDoubleTime() }); AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID); - AddUntilStep("wait for idle", () => Client.LocalUser?.State == MultiplayerUserState.Idle); + AddUntilStep("wait for idle", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Idle); ClickButtonWhenEnabled(); - AddUntilStep("wait for ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready); + AddUntilStep("wait for ready", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready); ClickButtonWhenEnabled(); AddUntilStep("wait for player", () => CurrentScreen is Player player && player.IsLoaded); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneCreateMultiplayerMatchButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneCreateMultiplayerMatchButton.cs index 2f0398c6ef..0674fc7a39 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneCreateMultiplayerMatchButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneCreateMultiplayerMatchButton.cs @@ -37,10 +37,10 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("end joining room", () => joiningRoomOperation.Dispose()); assertButtonEnableState(true); - AddStep("disconnect client", () => Client.Disconnect()); + AddStep("disconnect client", () => MultiplayerClient.Disconnect()); assertButtonEnableState(false); - AddStep("re-connect client", () => Client.Connect()); + AddStep("re-connect client", () => MultiplayerClient.Connect()); assertButtonEnableState(true); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index cd14a98751..a308d60cf1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestFirstItemSelectedByDefault() { - AddAssert("first item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID); + AddAssert("first item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[0].ID); } [Test] @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { selectNewItem(() => InitialBeatmap); - AddAssert("playlist item still selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID); + AddAssert("playlist item still selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[0].ID); } [Test] @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { selectNewItem(() => OtherBeatmap); - AddAssert("playlist item still selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID); + AddAssert("playlist item still selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[0].ID); } [Test] @@ -46,10 +46,10 @@ namespace osu.Game.Tests.Visual.Multiplayer { RunGameplay(); - AddAssert("playlist contains two items", () => Client.APIRoom?.Playlist.Count == 2); - AddAssert("first playlist item expired", () => Client.APIRoom?.Playlist[0].Expired == true); - AddAssert("second playlist item not expired", () => Client.APIRoom?.Playlist[1].Expired == false); - AddAssert("second playlist item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[1].ID); + AddAssert("playlist contains two items", () => MultiplayerClient.APIRoom?.Playlist.Count == 2); + AddAssert("first playlist item expired", () => MultiplayerClient.APIRoom?.Playlist[0].Expired == true); + AddAssert("second playlist item not expired", () => MultiplayerClient.APIRoom?.Playlist[1].Expired == false); + AddAssert("second playlist item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[1].ID); } [Test] @@ -58,23 +58,23 @@ namespace osu.Game.Tests.Visual.Multiplayer RunGameplay(); IBeatmapInfo firstBeatmap = null; - AddStep("get first playlist item beatmap", () => firstBeatmap = Client.APIRoom?.Playlist[0].Beatmap.Value); + AddStep("get first playlist item beatmap", () => firstBeatmap = MultiplayerClient.APIRoom?.Playlist[0].Beatmap.Value); selectNewItem(() => OtherBeatmap); - AddAssert("first playlist item hasn't changed", () => Client.APIRoom?.Playlist[0].Beatmap.Value == firstBeatmap); - AddAssert("second playlist item changed", () => Client.APIRoom?.Playlist[1].Beatmap.Value != firstBeatmap); + AddAssert("first playlist item hasn't changed", () => MultiplayerClient.APIRoom?.Playlist[0].Beatmap.Value == firstBeatmap); + AddAssert("second playlist item changed", () => MultiplayerClient.APIRoom?.Playlist[1].Beatmap.Value != firstBeatmap); } [Test] public void TestSettingsUpdatedWhenChangingQueueMode() { - AddStep("change queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings + AddStep("change queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); - AddUntilStep("api room updated", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + AddUntilStep("api room updated", () => MultiplayerClient.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); } [Test] @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { addItem(() => OtherBeatmap); - AddAssert("playlist contains two items", () => Client.APIRoom?.Playlist.Count == 2); + AddAssert("playlist contains two items", () => MultiplayerClient.APIRoom?.Playlist.Count == 2); } private void selectNewItem(Func beatmap) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 543e6a91d0..6b3573b3cb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach ((int userId, var _) in clocks) { SpectatorClient.StartPlay(userId, 0); - OnlinePlayDependencies.Client.AddUser(new APIUser { Id = userId }); + OnlinePlayDependencies.MultiplayerClient.AddUser(new APIUser { Id = userId }); } }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 56cb6036c7..7ce0c6a94d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -60,8 +60,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("start players silently", () => { - OnlinePlayDependencies.Client.AddUser(new APIUser { Id = PLAYER_1_ID }, true); - OnlinePlayDependencies.Client.AddUser(new APIUser { Id = PLAYER_2_ID }, true); + OnlinePlayDependencies.MultiplayerClient.AddUser(new APIUser { Id = PLAYER_1_ID }, true); + OnlinePlayDependencies.MultiplayerClient.AddUser(new APIUser { Id = PLAYER_2_ID }, true); playingUsers.Add(new MultiplayerRoomUser(PLAYER_1_ID)); playingUsers.Add(new MultiplayerRoomUser(PLAYER_2_ID)); @@ -121,13 +121,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("start players", () => { - var player1 = OnlinePlayDependencies.Client.AddUser(new APIUser { Id = PLAYER_1_ID }, true); + var player1 = OnlinePlayDependencies.MultiplayerClient.AddUser(new APIUser { Id = PLAYER_1_ID }, true); player1.MatchState = new TeamVersusUserState { TeamID = 0, }; - var player2 = OnlinePlayDependencies.Client.AddUser(new APIUser { Id = PLAYER_2_ID }, true); + var player2 = OnlinePlayDependencies.MultiplayerClient.AddUser(new APIUser { Id = PLAYER_2_ID }, true); player2.MatchState = new TeamVersusUserState { TeamID = 1, @@ -396,7 +396,7 @@ namespace osu.Game.Tests.Visual.Multiplayer User = new APIUser { Id = id }, }; - OnlinePlayDependencies.Client.AddUser(user.User, true); + OnlinePlayDependencies.MultiplayerClient.AddUser(user.User, true); SpectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId); playingUsers.Add(user); @@ -410,7 +410,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { var user = playingUsers.Single(u => u.UserID == userId); - OnlinePlayDependencies.Client.RemoveUser(user.User.AsNonNull()); + OnlinePlayDependencies.MultiplayerClient.RemoveUser(user.User.AsNonNull()); SpectatorClient.EndPlay(userId); playingUsers.Remove(user); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 5439b3eeea..3f9aec3a42 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private TestMultiplayerComponents multiplayerComponents; - private TestMultiplayerClient client => multiplayerComponents.Client; + private TestMultiplayerClient multiplayerClient => multiplayerComponents.MultiplayerClient; private TestMultiplayerRoomManager roomManager => multiplayerComponents.RoomManager; [BackgroundDependencyLoader] @@ -109,66 +109,66 @@ namespace osu.Game.Tests.Visual.Multiplayer // all ready AddUntilStep("all players ready", () => { - var nextUnready = client.Room?.Users.FirstOrDefault(c => c.State == MultiplayerUserState.Idle); + var nextUnready = multiplayerClient.Room?.Users.FirstOrDefault(c => c.State == MultiplayerUserState.Idle); if (nextUnready != null) - client.ChangeUserState(nextUnready.UserID, MultiplayerUserState.Ready); + multiplayerClient.ChangeUserState(nextUnready.UserID, MultiplayerUserState.Ready); - return client.Room?.Users.All(u => u.State == MultiplayerUserState.Ready) == true; + return multiplayerClient.Room?.Users.All(u => u.State == MultiplayerUserState.Ready) == true; }); AddStep("unready all players at once", () => { - Debug.Assert(client.Room != null); + Debug.Assert(multiplayerClient.Room != null); - foreach (var u in client.Room.Users) client.ChangeUserState(u.UserID, MultiplayerUserState.Idle); + foreach (var u in multiplayerClient.Room.Users) multiplayerClient.ChangeUserState(u.UserID, MultiplayerUserState.Idle); }); AddStep("ready all players at once", () => { - Debug.Assert(client.Room != null); + Debug.Assert(multiplayerClient.Room != null); - foreach (var u in client.Room.Users) client.ChangeUserState(u.UserID, MultiplayerUserState.Ready); + foreach (var u in multiplayerClient.Room.Users) multiplayerClient.ChangeUserState(u.UserID, MultiplayerUserState.Ready); }); } private void addRandomPlayer() { int randomUser = RNG.Next(200000, 500000); - client.AddUser(new APIUser { Id = randomUser, Username = $"user {randomUser}" }); + multiplayerClient.AddUser(new APIUser { Id = randomUser, Username = $"user {randomUser}" }); } private void removeLastUser() { - APIUser lastUser = client.Room?.Users.Last().User; + APIUser lastUser = multiplayerClient.Room?.Users.Last().User; - if (lastUser == null || lastUser == client.LocalUser?.User) + if (lastUser == null || lastUser == multiplayerClient.LocalUser?.User) return; - client.RemoveUser(lastUser); + multiplayerClient.RemoveUser(lastUser); } private void kickLastUser() { - APIUser lastUser = client.Room?.Users.Last().User; + APIUser lastUser = multiplayerClient.Room?.Users.Last().User; - if (lastUser == null || lastUser == client.LocalUser?.User) + if (lastUser == null || lastUser == multiplayerClient.LocalUser?.User) return; - client.KickUser(lastUser.Id); + multiplayerClient.KickUser(lastUser.Id); } private void markNextPlayerReady() { - var nextUnready = client.Room?.Users.FirstOrDefault(c => c.State == MultiplayerUserState.Idle); + var nextUnready = multiplayerClient.Room?.Users.FirstOrDefault(c => c.State == MultiplayerUserState.Idle); if (nextUnready != null) - client.ChangeUserState(nextUnready.UserID, MultiplayerUserState.Ready); + multiplayerClient.ChangeUserState(nextUnready.UserID, MultiplayerUserState.Ready); } private void markNextPlayerIdle() { - var nextUnready = client.Room?.Users.FirstOrDefault(c => c.State == MultiplayerUserState.Ready); + var nextUnready = multiplayerClient.Room?.Users.FirstOrDefault(c => c.State == MultiplayerUserState.Ready); if (nextUnready != null) - client.ChangeUserState(nextUnready.UserID, MultiplayerUserState.Idle); + multiplayerClient.ChangeUserState(nextUnready.UserID, MultiplayerUserState.Idle); } private void performRandomAction() @@ -218,7 +218,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("Press select", () => InputManager.Key(Key.Enter)); AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); - AddUntilStep("wait for join", () => client.RoomJoined); + AddUntilStep("wait for join", () => multiplayerClient.RoomJoined); } [Test] @@ -237,8 +237,8 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddAssert("Check participant count correct", () => client.APIRoom?.ParticipantCount.Value == 1); - AddAssert("Check participant list contains user", () => client.APIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); + AddAssert("Check participant count correct", () => multiplayerClient.APIRoom?.ParticipantCount.Value == 1); + AddAssert("Check participant list contains user", () => multiplayerClient.APIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); } [Test] @@ -297,10 +297,10 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("join room", () => InputManager.Key(Key.Enter)); AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); - AddUntilStep("wait for join", () => client.RoomJoined); + AddUntilStep("wait for join", () => multiplayerClient.RoomJoined); - AddAssert("Check participant count correct", () => client.APIRoom?.ParticipantCount.Value == 1); - AddAssert("Check participant list contains user", () => client.APIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); + AddAssert("Check participant count correct", () => multiplayerClient.APIRoom?.ParticipantCount.Value == 1); + AddAssert("Check participant list contains user", () => multiplayerClient.APIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); } [Test] @@ -320,7 +320,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddAssert("room has password", () => client.APIRoom?.Password.Value == "password"); + AddAssert("room has password", () => multiplayerClient.APIRoom?.Password.Value == "password"); } [Test] @@ -355,7 +355,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType().First().TriggerClick()); AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); - AddUntilStep("wait for join", () => client.RoomJoined); + AddUntilStep("wait for join", () => multiplayerClient.RoomJoined); } [Test] @@ -375,8 +375,8 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddStep("change password", () => client.ChangeSettings(password: "password2")); - AddUntilStep("local password changed", () => client.APIRoom?.Password.Value == "password2"); + AddStep("change password", () => multiplayerClient.ChangeSettings(password: "password2")); + AddUntilStep("local password changed", () => multiplayerClient.APIRoom?.Password.Value == "password2"); } [Test] @@ -398,7 +398,7 @@ namespace osu.Game.Tests.Visual.Multiplayer pressReadyButton(); AddStep("delete beatmap", () => beatmaps.Delete(importedSet)); - AddUntilStep("user state is idle", () => client.LocalUser?.State == MultiplayerUserState.Idle); + AddUntilStep("user state is idle", () => multiplayerClient.LocalUser?.State == MultiplayerUserState.Idle); } [Test] @@ -422,22 +422,22 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("Enter song select", () => { var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen; - ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(client.Room?.Settings.PlaylistItemId); + ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.Room?.Settings.PlaylistItemId); }); AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); - AddAssert("Beatmap matches current item", () => Beatmap.Value.BeatmapInfo.OnlineID == client.Room?.Playlist.First().BeatmapID); + AddAssert("Beatmap matches current item", () => Beatmap.Value.BeatmapInfo.OnlineID == multiplayerClient.Room?.Playlist.First().BeatmapID); AddStep("Select next beatmap", () => InputManager.Key(Key.Down)); - AddUntilStep("Beatmap doesn't match current item", () => Beatmap.Value.BeatmapInfo.OnlineID != client.Room?.Playlist.First().BeatmapID); + AddUntilStep("Beatmap doesn't match current item", () => Beatmap.Value.BeatmapInfo.OnlineID != multiplayerClient.Room?.Playlist.First().BeatmapID); - AddStep("start match externally", () => client.StartMatch()); + AddStep("start match externally", () => multiplayerClient.StartMatch()); AddUntilStep("play started", () => multiplayerComponents.CurrentScreen is Player); - AddAssert("Beatmap matches current item", () => Beatmap.Value.BeatmapInfo.OnlineID == client.Room?.Playlist.First().BeatmapID); + AddAssert("Beatmap matches current item", () => Beatmap.Value.BeatmapInfo.OnlineID == multiplayerClient.Room?.Playlist.First().BeatmapID); } [Test] @@ -461,22 +461,22 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("Enter song select", () => { var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen; - ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(client.Room?.Settings.PlaylistItemId); + ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.Room?.Settings.PlaylistItemId); }); AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); - AddAssert("Ruleset matches current item", () => Ruleset.Value.OnlineID == client.Room?.Playlist.First().RulesetID); + AddAssert("Ruleset matches current item", () => Ruleset.Value.OnlineID == multiplayerClient.Room?.Playlist.First().RulesetID); AddStep("Switch ruleset", () => ((MultiplayerMatchSongSelect)multiplayerComponents.MultiplayerScreen.CurrentSubScreen).Ruleset.Value = new CatchRuleset().RulesetInfo); - AddUntilStep("Ruleset doesn't match current item", () => Ruleset.Value.OnlineID != client.Room?.Playlist.First().RulesetID); + AddUntilStep("Ruleset doesn't match current item", () => Ruleset.Value.OnlineID != multiplayerClient.Room?.Playlist.First().RulesetID); - AddStep("start match externally", () => client.StartMatch()); + AddStep("start match externally", () => multiplayerClient.StartMatch()); AddUntilStep("play started", () => multiplayerComponents.CurrentScreen is Player); - AddAssert("Ruleset matches current item", () => Ruleset.Value.OnlineID == client.Room?.Playlist.First().RulesetID); + AddAssert("Ruleset matches current item", () => Ruleset.Value.OnlineID == multiplayerClient.Room?.Playlist.First().RulesetID); } [Test] @@ -500,22 +500,22 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("Enter song select", () => { var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen; - ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(client.Room?.Settings.PlaylistItemId); + ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.Room?.Settings.PlaylistItemId); }); AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); - AddAssert("Mods match current item", () => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(client.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); + AddAssert("Mods match current item", () => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(multiplayerClient.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); AddStep("Switch required mods", () => ((MultiplayerMatchSongSelect)multiplayerComponents.MultiplayerScreen.CurrentSubScreen).Mods.Value = new Mod[] { new OsuModDoubleTime() }); - AddAssert("Mods don't match current item", () => !SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(client.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); + AddAssert("Mods don't match current item", () => !SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(multiplayerClient.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); - AddStep("start match externally", () => client.StartMatch()); + AddStep("start match externally", () => multiplayerClient.StartMatch()); AddUntilStep("play started", () => multiplayerComponents.CurrentScreen is Player); - AddAssert("Mods match current item", () => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(client.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); + AddAssert("Mods match current item", () => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(multiplayerClient.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); } [Test] @@ -536,18 +536,18 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("join other user (ready, host)", () => { - client.AddUser(new APIUser { Id = MultiplayerTestScene.PLAYER_1_ID, Username = "Other" }); - client.TransferHost(MultiplayerTestScene.PLAYER_1_ID); - client.ChangeUserState(MultiplayerTestScene.PLAYER_1_ID, MultiplayerUserState.Ready); + multiplayerClient.AddUser(new APIUser { Id = MultiplayerTestScene.PLAYER_1_ID, Username = "Other" }); + multiplayerClient.TransferHost(MultiplayerTestScene.PLAYER_1_ID); + multiplayerClient.ChangeUserState(MultiplayerTestScene.PLAYER_1_ID, MultiplayerUserState.Ready); }); AddStep("delete beatmap", () => beatmaps.Delete(importedSet)); ClickButtonWhenEnabled(); - AddUntilStep("wait for spectating user state", () => client.LocalUser?.State == MultiplayerUserState.Spectating); + AddUntilStep("wait for spectating user state", () => multiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating); - AddStep("start match externally", () => client.StartMatch()); + AddStep("start match externally", () => multiplayerClient.StartMatch()); AddAssert("play not started", () => multiplayerComponents.IsCurrentScreen()); } @@ -572,16 +572,16 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("join other user (ready, host)", () => { - client.AddUser(new APIUser { Id = MultiplayerTestScene.PLAYER_1_ID, Username = "Other" }); - client.TransferHost(MultiplayerTestScene.PLAYER_1_ID); - client.ChangeUserState(MultiplayerTestScene.PLAYER_1_ID, MultiplayerUserState.Ready); + multiplayerClient.AddUser(new APIUser { Id = MultiplayerTestScene.PLAYER_1_ID, Username = "Other" }); + multiplayerClient.TransferHost(MultiplayerTestScene.PLAYER_1_ID); + multiplayerClient.ChangeUserState(MultiplayerTestScene.PLAYER_1_ID, MultiplayerUserState.Ready); }); ClickButtonWhenEnabled(); - AddUntilStep("wait for spectating user state", () => client.LocalUser?.State == MultiplayerUserState.Spectating); + AddUntilStep("wait for spectating user state", () => multiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating); - AddStep("start match externally", () => client.StartMatch()); + AddStep("start match externally", () => multiplayerClient.StartMatch()); AddStep("restore beatmap", () => { @@ -608,7 +608,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddStep("disconnect", () => client.Disconnect()); + AddStep("disconnect", () => multiplayerClient.Disconnect()); AddUntilStep("back in lounge", () => this.ChildrenOfType().FirstOrDefault()?.IsCurrentScreen() == true); } @@ -718,7 +718,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("join room", () => InputManager.Key(Key.Enter)); AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); - AddUntilStep("wait for join", () => client.RoomJoined); + AddUntilStep("wait for join", () => multiplayerClient.RoomJoined); AddAssert("local room has correct settings", () => { @@ -745,11 +745,11 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddStep("set spectating state", () => client.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating)); - AddUntilStep("state set to spectating", () => client.LocalUser?.State == MultiplayerUserState.Spectating); + AddStep("set spectating state", () => multiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating)); + AddUntilStep("state set to spectating", () => multiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating); - AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 })); - AddStep("set other user ready", () => client.ChangeUserState(1234, MultiplayerUserState.Ready)); + AddStep("join other user", () => multiplayerClient.AddUser(new APIUser { Id = 1234 })); + AddStep("set other user ready", () => multiplayerClient.ChangeUserState(1234, MultiplayerUserState.Ready)); pressReadyButton(1234); AddUntilStep("wait for gameplay", () => (multiplayerComponents.CurrentScreen as MultiSpectatorScreen)?.IsLoaded == true); @@ -761,7 +761,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddUntilStep("wait for return to match subscreen", () => multiplayerComponents.MultiplayerScreen.IsCurrentScreen()); - AddUntilStep("user state is idle", () => client.LocalUser?.State == MultiplayerUserState.Idle); + AddUntilStep("user state is idle", () => multiplayerClient.LocalUser?.State == MultiplayerUserState.Idle); } [Test] @@ -781,16 +781,16 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddStep("set spectating state", () => client.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating)); - AddUntilStep("state set to spectating", () => client.LocalUser?.State == MultiplayerUserState.Spectating); + AddStep("set spectating state", () => multiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating)); + AddUntilStep("state set to spectating", () => multiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating); - AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 })); - AddStep("set other user ready", () => client.ChangeUserState(1234, MultiplayerUserState.Ready)); + AddStep("join other user", () => multiplayerClient.AddUser(new APIUser { Id = 1234 })); + AddStep("set other user ready", () => multiplayerClient.ChangeUserState(1234, MultiplayerUserState.Ready)); pressReadyButton(1234); AddUntilStep("wait for gameplay", () => (multiplayerComponents.CurrentScreen as MultiSpectatorScreen)?.IsLoaded == true); - AddStep("set other user loaded", () => client.ChangeUserState(1234, MultiplayerUserState.Loaded)); - AddStep("set other user finished play", () => client.ChangeUserState(1234, MultiplayerUserState.FinishedPlay)); + AddStep("set other user loaded", () => multiplayerClient.ChangeUserState(1234, MultiplayerUserState.Loaded)); + AddStep("set other user finished play", () => multiplayerClient.ChangeUserState(1234, MultiplayerUserState.FinishedPlay)); AddStep("press back button and exit", () => { @@ -800,7 +800,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for return to match subscreen", () => multiplayerComponents.MultiplayerScreen.IsCurrentScreen()); AddWaitStep("wait for possible state change", 5); - AddUntilStep("user state is spectating", () => client.LocalUser?.State == MultiplayerUserState.Spectating); + AddUntilStep("user state is spectating", () => multiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating); } [Test] @@ -822,13 +822,13 @@ namespace osu.Game.Tests.Visual.Multiplayer enterGameplay(); - AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 })); - AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem + AddStep("join other user", () => multiplayerClient.AddUser(new APIUser { Id = 1234 })); + AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem { BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo.OnlineID })).WaitSafely()); - AddUntilStep("item arrived in playlist", () => client.Room?.Playlist.Count == 2); + AddUntilStep("item arrived in playlist", () => multiplayerClient.Room?.Playlist.Count == 2); AddStep("exit gameplay as initial user", () => multiplayerComponents.MultiplayerScreen.MakeCurrent()); AddUntilStep("queue contains item", () => this.ChildrenOfType().Single().Items.Single().ID == 2); @@ -853,16 +853,16 @@ namespace osu.Game.Tests.Visual.Multiplayer enterGameplay(); - AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 })); - AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem + AddStep("join other user", () => multiplayerClient.AddUser(new APIUser { Id = 1234 })); + AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem { BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo.OnlineID })).WaitSafely()); - AddUntilStep("item arrived in playlist", () => client.Room?.Playlist.Count == 2); + AddUntilStep("item arrived in playlist", () => multiplayerClient.Room?.Playlist.Count == 2); - AddStep("delete item as other user", () => client.RemoveUserPlaylistItem(1234, 2).WaitSafely()); - AddUntilStep("item removed from playlist", () => client.Room?.Playlist.Count == 1); + AddStep("delete item as other user", () => multiplayerClient.RemoveUserPlaylistItem(1234, 2).WaitSafely()); + AddUntilStep("item removed from playlist", () => multiplayerClient.Room?.Playlist.Count == 1); AddStep("exit gameplay as initial user", () => multiplayerComponents.MultiplayerScreen.MakeCurrent()); AddUntilStep("queue is empty", () => this.ChildrenOfType().Single().Items.Count == 0); @@ -886,17 +886,17 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("join other user and make host", () => { - client.AddUser(new APIUser { Id = 1234 }); - client.TransferHost(1234); + multiplayerClient.AddUser(new APIUser { Id = 1234 }); + multiplayerClient.TransferHost(1234); }); - AddStep("set local user spectating", () => client.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating)); - AddUntilStep("wait for spectating state", () => client.LocalUser?.State == MultiplayerUserState.Spectating); + AddStep("set local user spectating", () => multiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating)); + AddUntilStep("wait for spectating state", () => multiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating); runGameplay(); - AddStep("exit gameplay for other user", () => client.ChangeUserState(1234, MultiplayerUserState.Idle)); - AddUntilStep("wait for room to be idle", () => client.Room?.State == MultiplayerRoomState.Open); + AddStep("exit gameplay for other user", () => multiplayerClient.ChangeUserState(1234, MultiplayerUserState.Idle)); + AddUntilStep("wait for room to be idle", () => multiplayerClient.Room?.State == MultiplayerRoomState.Open); runGameplay(); @@ -904,13 +904,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("start match by other user", () => { - client.ChangeUserState(1234, MultiplayerUserState.Ready); - client.StartMatch(); + multiplayerClient.ChangeUserState(1234, MultiplayerUserState.Ready); + multiplayerClient.StartMatch(); }); - AddUntilStep("wait for loading", () => client.Room?.State == MultiplayerRoomState.WaitingForLoad); - AddStep("set player loaded", () => client.ChangeUserState(1234, MultiplayerUserState.Loaded)); - AddUntilStep("wait for gameplay to start", () => client.Room?.State == MultiplayerRoomState.Playing); + AddUntilStep("wait for loading", () => multiplayerClient.Room?.State == MultiplayerRoomState.WaitingForLoad); + AddStep("set player loaded", () => multiplayerClient.ChangeUserState(1234, MultiplayerUserState.Loaded)); + AddUntilStep("wait for gameplay to start", () => multiplayerClient.Room?.State == MultiplayerRoomState.Playing); AddUntilStep("wait for local user to enter spectator", () => multiplayerComponents.CurrentScreen is MultiSpectatorScreen); } } @@ -935,7 +935,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("click ready button", () => { - user = playingUserId == null ? client.LocalUser : client.Room?.Users.Single(u => u.UserID == playingUserId); + user = playingUserId == null ? multiplayerClient.LocalUser : multiplayerClient.Room?.Users.Single(u => u.UserID == playingUserId); lastState = user?.State ?? MultiplayerUserState.Idle; InputManager.MoveMouseTo(readyButton); @@ -955,7 +955,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ClickButtonWhenEnabled(); - AddUntilStep("wait for join", () => client.RoomJoined); + AddUntilStep("wait for join", () => multiplayerClient.RoomJoined); } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 437f4e3308..6605f82d5c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach (int user in users) { SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineID); - multiplayerUsers.Add(OnlinePlayDependencies.Client.AddUser(new APIUser { Id = user }, true)); + multiplayerUsers.Add(OnlinePlayDependencies.MultiplayerClient.AddUser(new APIUser { Id = user }, true)); } Children = new Drawable[] @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddUntilStep("wait for load", () => leaderboard.IsLoaded); - AddUntilStep("wait for user population", () => Client.CurrentMatchPlayingUserIds.Count > 0); + AddUntilStep("wait for user population", () => MultiplayerClient.CurrentMatchPlayingUserIds.Count > 0); } [Test] @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestUserQuit() { foreach (int user in users) - AddStep($"mark user {user} quit", () => Client.RemoveUser(UserLookupCache.GetUserAsync(user).GetResultSafely().AsNonNull())); + AddStep($"mark user {user} quit", () => MultiplayerClient.RemoveUser(UserLookupCache.GetUserAsync(user).GetResultSafely().AsNonNull())); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs index cd3ae50dab..dabc1c1e5a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach (int user in users) { SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineID); - var roomUser = OnlinePlayDependencies.Client.AddUser(new APIUser { Id = user }, true); + var roomUser = OnlinePlayDependencies.MultiplayerClient.AddUser(new APIUser { Id = user }, true); roomUser.MatchState = new TeamVersusUserState { @@ -105,7 +105,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddUntilStep("wait for load", () => leaderboard.IsLoaded); - AddUntilStep("wait for user population", () => Client.CurrentMatchPlayingUserIds.Count > 0); + AddUntilStep("wait for user population", () => MultiplayerClient.CurrentMatchPlayingUserIds.Count > 0); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 7c18ed2572..7d2ef8276d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for join", () => RoomJoined); - AddStep("select swap mod", () => Client.ChangeUserMods(API.LocalUser.Value.OnlineID, new[] { new TaikoModSwap() })); + AddStep("select swap mod", () => MultiplayerClient.ChangeUserMods(API.LocalUser.Value.OnlineID, new[] { new TaikoModSwap() })); AddUntilStep("participant panel has mod", () => this.ChildrenOfType().Any(p => p.ChildrenOfType().Any(m => m.Mod is TaikoModSwap))); } @@ -141,17 +141,17 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("join other user (ready)", () => { - Client.AddUser(new APIUser { Id = PLAYER_1_ID }); - Client.ChangeUserState(PLAYER_1_ID, MultiplayerUserState.Ready); + MultiplayerClient.AddUser(new APIUser { Id = PLAYER_1_ID }); + MultiplayerClient.ChangeUserState(PLAYER_1_ID, MultiplayerUserState.Ready); }); ClickButtonWhenEnabled(); - AddUntilStep("wait for spectating user state", () => Client.LocalUser?.State == MultiplayerUserState.Spectating); + AddUntilStep("wait for spectating user state", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating); ClickButtonWhenEnabled(); - AddUntilStep("match started", () => Client.Room?.State == MultiplayerRoomState.WaitingForLoad); + AddUntilStep("match started", () => MultiplayerClient.Room?.State == MultiplayerRoomState.WaitingForLoad); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 671b85164b..292319171d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddAssert("one unique panel", () => this.ChildrenOfType().Select(p => p.User).Distinct().Count() == 1); - AddStep("add user", () => Client.AddUser(new APIUser + AddStep("add user", () => MultiplayerClient.AddUser(new APIUser { Id = 3, Username = "Second", @@ -50,15 +50,15 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddAssert("one unique panel", () => this.ChildrenOfType().Select(p => p.User).Distinct().Count() == 1); - AddStep("add non-resolvable user", () => Client.TestAddUnresolvedUser()); - AddAssert("null user added", () => Client.Room.AsNonNull().Users.Count(u => u.User == null) == 1); + AddStep("add non-resolvable user", () => MultiplayerClient.TestAddUnresolvedUser()); + AddAssert("null user added", () => MultiplayerClient.Room.AsNonNull().Users.Count(u => u.User == null) == 1); AddUntilStep("two unique panels", () => this.ChildrenOfType().Select(p => p.User).Distinct().Count() == 2); AddStep("kick null user", () => this.ChildrenOfType().Single(p => p.User.User == null) .ChildrenOfType().Single().TriggerClick()); - AddAssert("null user kicked", () => Client.Room.AsNonNull().Users.Count == 1); + AddAssert("null user kicked", () => MultiplayerClient.Room.AsNonNull().Users.Count == 1); } [Test] @@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("add a user", () => { - Client.AddUser(secondUser = new APIUser + MultiplayerClient.AddUser(secondUser = new APIUser { Id = 3, Username = "Second", @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); }); - AddStep("remove host", () => Client.RemoveUser(API.LocalUser.Value)); + AddStep("remove host", () => MultiplayerClient.RemoveUser(API.LocalUser.Value)); AddAssert("single panel is for second user", () => this.ChildrenOfType().Single().User.User == secondUser); } @@ -84,21 +84,21 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestGameStateHasPriorityOverDownloadState() { - AddStep("set to downloading map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0))); + AddStep("set to downloading map", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0))); checkProgressBarVisibility(true); - AddStep("make user ready", () => Client.ChangeState(MultiplayerUserState.Results)); + AddStep("make user ready", () => MultiplayerClient.ChangeState(MultiplayerUserState.Results)); checkProgressBarVisibility(false); AddUntilStep("ready mark visible", () => this.ChildrenOfType().Single().IsPresent); - AddStep("make user ready", () => Client.ChangeState(MultiplayerUserState.Idle)); + AddStep("make user ready", () => MultiplayerClient.ChangeState(MultiplayerUserState.Idle)); checkProgressBarVisibility(true); } [Test] public void TestCorrectInitialState() { - AddStep("set to downloading map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0))); + AddStep("set to downloading map", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0))); createNewParticipantsList(); checkProgressBarVisibility(true); } @@ -106,23 +106,23 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestBeatmapDownloadingStates() { - AddStep("set to no map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.NotDownloaded())); - AddStep("set to downloading map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0))); + AddStep("set to no map", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.NotDownloaded())); + AddStep("set to downloading map", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0))); checkProgressBarVisibility(true); AddRepeatStep("increment progress", () => { float progress = this.ChildrenOfType().Single().User.BeatmapAvailability.DownloadProgress ?? 0; - Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(progress + RNG.NextSingle(0.1f))); + MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(progress + RNG.NextSingle(0.1f))); }, 25); AddAssert("progress bar increased", () => this.ChildrenOfType().Single().Current.Value > 0); - AddStep("set to importing map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Importing())); + AddStep("set to importing map", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.Importing())); checkProgressBarVisibility(false); - AddStep("set to available", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.LocallyAvailable())); + AddStep("set to available", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.LocallyAvailable())); } [Test] @@ -130,24 +130,24 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddAssert("ready mark invisible", () => !this.ChildrenOfType().Single().IsPresent); - AddStep("make user ready", () => Client.ChangeState(MultiplayerUserState.Ready)); + AddStep("make user ready", () => MultiplayerClient.ChangeState(MultiplayerUserState.Ready)); AddUntilStep("ready mark visible", () => this.ChildrenOfType().Single().IsPresent); - AddStep("make user idle", () => Client.ChangeState(MultiplayerUserState.Idle)); + AddStep("make user idle", () => MultiplayerClient.ChangeState(MultiplayerUserState.Idle)); AddUntilStep("ready mark invisible", () => !this.ChildrenOfType().Single().IsPresent); } [Test] public void TestToggleSpectateState() { - AddStep("make user spectating", () => Client.ChangeState(MultiplayerUserState.Spectating)); - AddStep("make user idle", () => Client.ChangeState(MultiplayerUserState.Idle)); + AddStep("make user spectating", () => MultiplayerClient.ChangeState(MultiplayerUserState.Spectating)); + AddStep("make user idle", () => MultiplayerClient.ChangeState(MultiplayerUserState.Idle)); } [Test] public void TestCrownChangesStateWhenHostTransferred() { - AddStep("add user", () => Client.AddUser(new APIUser + AddStep("add user", () => MultiplayerClient.AddUser(new APIUser { Id = 3, Username = "Second", @@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("first user crown visible", () => this.ChildrenOfType().ElementAt(0).ChildrenOfType().First().Alpha == 1); AddUntilStep("second user crown hidden", () => this.ChildrenOfType().ElementAt(1).ChildrenOfType().First().Alpha == 0); - AddStep("make second user host", () => Client.TransferHost(3)); + AddStep("make second user host", () => MultiplayerClient.TransferHost(3)); AddUntilStep("first user crown hidden", () => this.ChildrenOfType().ElementAt(0).ChildrenOfType().First().Alpha == 0); AddUntilStep("second user crown visible", () => this.ChildrenOfType().ElementAt(1).ChildrenOfType().First().Alpha == 1); @@ -166,7 +166,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestKickButtonOnlyPresentWhenHost() { - AddStep("add user", () => Client.AddUser(new APIUser + AddStep("add user", () => MultiplayerClient.AddUser(new APIUser { Id = 3, Username = "Second", @@ -175,11 +175,11 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("kick buttons visible", () => this.ChildrenOfType().Count(d => d.IsPresent) == 1); - AddStep("make second user host", () => Client.TransferHost(3)); + AddStep("make second user host", () => MultiplayerClient.TransferHost(3)); AddUntilStep("kick buttons not visible", () => this.ChildrenOfType().Count(d => d.IsPresent) == 0); - AddStep("make local user host again", () => Client.TransferHost(API.LocalUser.Value.Id)); + AddStep("make local user host again", () => MultiplayerClient.TransferHost(API.LocalUser.Value.Id)); AddUntilStep("kick buttons visible", () => this.ChildrenOfType().Count(d => d.IsPresent) == 1); } @@ -187,7 +187,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestKickButtonKicks() { - AddStep("add user", () => Client.AddUser(new APIUser + AddStep("add user", () => MultiplayerClient.AddUser(new APIUser { Id = 3, Username = "Second", @@ -196,7 +196,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("kick second user", () => this.ChildrenOfType().Single(d => d.IsPresent).TriggerClick()); - AddAssert("second user kicked", () => Client.Room?.Users.Single().UserID == API.LocalUser.Value.Id); + AddAssert("second user kicked", () => MultiplayerClient.Room?.Users.Single().UserID == API.LocalUser.Value.Id); } [Test] @@ -206,7 +206,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { for (int i = 0; i < 20; i++) { - Client.AddUser(new APIUser + MultiplayerClient.AddUser(new APIUser { Id = i, Username = $"User {i}", @@ -220,7 +220,7 @@ namespace osu.Game.Tests.Visual.Multiplayer CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }); - Client.ChangeUserState(i, (MultiplayerUserState)RNG.Next(0, (int)MultiplayerUserState.Results + 1)); + MultiplayerClient.ChangeUserState(i, (MultiplayerUserState)RNG.Next(0, (int)MultiplayerUserState.Results + 1)); if (RNG.NextBool()) { @@ -229,15 +229,15 @@ namespace osu.Game.Tests.Visual.Multiplayer switch (beatmapState) { case DownloadState.NotDownloaded: - Client.ChangeUserBeatmapAvailability(i, BeatmapAvailability.NotDownloaded()); + MultiplayerClient.ChangeUserBeatmapAvailability(i, BeatmapAvailability.NotDownloaded()); break; case DownloadState.Downloading: - Client.ChangeUserBeatmapAvailability(i, BeatmapAvailability.Downloading(RNG.NextSingle())); + MultiplayerClient.ChangeUserBeatmapAvailability(i, BeatmapAvailability.Downloading(RNG.NextSingle())); break; case DownloadState.Importing: - Client.ChangeUserBeatmapAvailability(i, BeatmapAvailability.Importing()); + MultiplayerClient.ChangeUserBeatmapAvailability(i, BeatmapAvailability.Importing()); break; } } @@ -250,7 +250,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add user", () => { - Client.AddUser(new APIUser + MultiplayerClient.AddUser(new APIUser { Id = 0, Username = "User 0", @@ -264,7 +264,7 @@ namespace osu.Game.Tests.Visual.Multiplayer CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }); - Client.ChangeUserMods(0, new Mod[] + MultiplayerClient.ChangeUserMods(0, new Mod[] { new OsuModHardRock(), new OsuModDifficultyAdjust { ApproachRate = { Value = 1 } } @@ -274,12 +274,12 @@ namespace osu.Game.Tests.Visual.Multiplayer for (var i = MultiplayerUserState.Idle; i < MultiplayerUserState.Results; i++) { var state = i; - AddStep($"set state: {state}", () => Client.ChangeUserState(0, state)); + AddStep($"set state: {state}", () => MultiplayerClient.ChangeUserState(0, state)); } - AddStep("set state: downloading", () => Client.ChangeUserBeatmapAvailability(0, BeatmapAvailability.Downloading(0))); + AddStep("set state: downloading", () => MultiplayerClient.ChangeUserBeatmapAvailability(0, BeatmapAvailability.Downloading(0))); - AddStep("set state: locally available", () => Client.ChangeUserBeatmapAvailability(0, BeatmapAvailability.LocallyAvailable())); + AddStep("set state: locally available", () => MultiplayerClient.ChangeUserBeatmapAvailability(0, BeatmapAvailability.LocallyAvailable())); } [Test] @@ -287,7 +287,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add dummy mods", () => { - Client.ChangeUserMods(new Mod[] + MultiplayerClient.ChangeUserMods(new Mod[] { new OsuModNoFail(), new OsuModDoubleTime() @@ -296,7 +296,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("add user with mods", () => { - Client.AddUser(new APIUser + MultiplayerClient.AddUser(new APIUser { Id = 0, Username = "Baka", @@ -309,34 +309,34 @@ namespace osu.Game.Tests.Visual.Multiplayer }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }); - Client.ChangeUserMods(0, new Mod[] + MultiplayerClient.ChangeUserMods(0, new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }); }); - AddStep("set 0 ready", () => Client.ChangeState(MultiplayerUserState.Ready)); + AddStep("set 0 ready", () => MultiplayerClient.ChangeState(MultiplayerUserState.Ready)); - AddStep("set 1 spectate", () => Client.ChangeUserState(0, MultiplayerUserState.Spectating)); + AddStep("set 1 spectate", () => MultiplayerClient.ChangeUserState(0, MultiplayerUserState.Spectating)); // Have to set back to idle due to status priority. AddStep("set 0 no map, 1 ready", () => { - Client.ChangeState(MultiplayerUserState.Idle); - Client.ChangeBeatmapAvailability(BeatmapAvailability.NotDownloaded()); - Client.ChangeUserState(0, MultiplayerUserState.Ready); + MultiplayerClient.ChangeState(MultiplayerUserState.Idle); + MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.NotDownloaded()); + MultiplayerClient.ChangeUserState(0, MultiplayerUserState.Ready); }); - AddStep("set 0 downloading", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0))); + AddStep("set 0 downloading", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0))); - AddStep("set 0 spectate", () => Client.ChangeUserState(0, MultiplayerUserState.Spectating)); + AddStep("set 0 spectate", () => MultiplayerClient.ChangeUserState(0, MultiplayerUserState.Spectating)); AddStep("make both default", () => { - Client.ChangeBeatmapAvailability(BeatmapAvailability.LocallyAvailable()); - Client.ChangeUserState(0, MultiplayerUserState.Idle); - Client.ChangeState(MultiplayerUserState.Idle); + MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.LocallyAvailable()); + MultiplayerClient.ChangeUserState(0, MultiplayerUserState.Idle); + MultiplayerClient.ChangeState(MultiplayerUserState.Idle); }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs index 010e9dc078..d391fbcf8f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs @@ -28,15 +28,15 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("initialise gameplay", () => { - Stack.Push(player = new MultiplayerPlayer(Client.APIRoom, new PlaylistItem + Stack.Push(player = new MultiplayerPlayer(MultiplayerClient.APIRoom, new PlaylistItem { Beatmap = { Value = Beatmap.Value.BeatmapInfo }, RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID, - }, Client.Room?.Users.ToArray())); + }, MultiplayerClient.Room?.Users.ToArray())); }); AddUntilStep("wait for player to be current", () => player.IsCurrentScreen() && player.IsLoaded); - AddStep("start gameplay", () => ((IMultiplayerClient)Client).MatchStarted()); + AddStep("start gameplay", () => ((IMultiplayerClient)MultiplayerClient).MatchStarted()); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 7bc5990c32..f84abc7443 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -60,7 +60,7 @@ namespace osu.Game.Tests.Visual.Multiplayer importedBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0); }); - AddStep("change to all players mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); + AddStep("change to all players mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); } [Test] @@ -97,19 +97,19 @@ namespace osu.Game.Tests.Visual.Multiplayer addItemStep(); addItemStep(); - AddStep("finish current item", () => Client.FinishCurrentItem().WaitSafely()); + AddStep("finish current item", () => MultiplayerClient.FinishCurrentItem().WaitSafely()); assertItemInHistoryListStep(1, 0); assertItemInQueueListStep(2, 0); assertItemInQueueListStep(3, 1); - AddStep("finish current item", () => Client.FinishCurrentItem().WaitSafely()); + AddStep("finish current item", () => MultiplayerClient.FinishCurrentItem().WaitSafely()); assertItemInHistoryListStep(2, 0); assertItemInHistoryListStep(1, 1); assertItemInQueueListStep(3, 0); - AddStep("finish current item", () => Client.FinishCurrentItem().WaitSafely()); + AddStep("finish current item", () => MultiplayerClient.FinishCurrentItem().WaitSafely()); assertItemInHistoryListStep(3, 0); assertItemInHistoryListStep(2, 1); @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestListsClearedWhenRoomLeft() { addItemStep(); - AddStep("finish current item", () => Client.FinishCurrentItem().WaitSafely()); + AddStep("finish current item", () => MultiplayerClient.FinishCurrentItem().WaitSafely()); AddStep("leave room", () => RoomManager.PartRoom()); AddUntilStep("wait for room part", () => !RoomJoined); @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.Multiplayer /// /// Adds a step to create a new playlist item. /// - private void addItemStep(bool expired = false) => AddStep("add item", () => Client.AddPlaylistItem(new MultiplayerPlaylistItem(new PlaylistItem + private void addItemStep(bool expired = false) => AddStep("add item", () => MultiplayerClient.AddPlaylistItem(new MultiplayerPlaylistItem(new PlaylistItem { Beatmap = { Value = importedBeatmap }, BeatmapID = importedBeatmap.OnlineID, diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 0b3e1ba20a..bf06b6ad73 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(500, 300), - Items = { BindTarget = Client.APIRoom!.Playlist } + Items = { BindTarget = MultiplayerClient.APIRoom!.Playlist } }; }); @@ -61,14 +61,14 @@ namespace osu.Game.Tests.Visual.Multiplayer importedBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0); }); - AddStep("change to all players mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); + AddStep("change to all players mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); } [Test] public void TestDeleteButtonAlwaysVisibleForHost() { - AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); - AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); + AddUntilStep("wait for queue mode change", () => MultiplayerClient.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); addPlaylistItem(() => API.LocalUser.Value.OnlineID); assertDeleteButtonVisibility(1, true); @@ -79,18 +79,18 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestDeleteButtonOnlyVisibleForItemOwnerIfNotHost() { - AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); - AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); + AddUntilStep("wait for queue mode change", () => MultiplayerClient.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); - AddStep("join other user", () => Client.AddUser(new APIUser { Id = 1234 })); - AddStep("set other user as host", () => Client.TransferHost(1234)); + AddStep("join other user", () => MultiplayerClient.AddUser(new APIUser { Id = 1234 })); + AddStep("set other user as host", () => MultiplayerClient.TransferHost(1234)); addPlaylistItem(() => API.LocalUser.Value.OnlineID); assertDeleteButtonVisibility(1, true); addPlaylistItem(() => 1234); assertDeleteButtonVisibility(2, false); - AddStep("set local user as host", () => Client.TransferHost(API.LocalUser.Value.OnlineID)); + AddStep("set local user as host", () => MultiplayerClient.TransferHost(API.LocalUser.Value.OnlineID)); assertDeleteButtonVisibility(1, true); assertDeleteButtonVisibility(2, true); } @@ -98,16 +98,16 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestCurrentItemDoesNotHaveDeleteButton() { - AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); - AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); + AddUntilStep("wait for queue mode change", () => MultiplayerClient.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); addPlaylistItem(() => API.LocalUser.Value.OnlineID); assertDeleteButtonVisibility(0, false); assertDeleteButtonVisibility(1, true); - AddStep("finish current item", () => Client.FinishCurrentItem().WaitSafely()); - AddUntilStep("wait for next item to be selected", () => Client.Room?.Settings.PlaylistItemId == 2); + AddStep("finish current item", () => MultiplayerClient.FinishCurrentItem().WaitSafely()); + AddUntilStep("wait for next item to be selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == 2); AddUntilStep("wait for two items in playlist", () => playlist.ChildrenOfType().Count() == 2); assertDeleteButtonVisibility(0, false); @@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual.Multiplayer BeatmapID = importedBeatmap.OnlineID, }); - Client.AddUserPlaylistItem(userId(), item).WaitSafely(); + MultiplayerClient.AddUserPlaylistItem(userId(), item).WaitSafely(); itemId = item.ID; }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 7834226f15..609693e54b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -74,13 +74,13 @@ namespace osu.Game.Tests.Visual.Multiplayer Task.Run(async () => { - if (Client.IsHost && Client.LocalUser?.State == MultiplayerUserState.Ready) + if (MultiplayerClient.IsHost && MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready) { - await Client.StartMatch(); + await MultiplayerClient.StartMatch(); return; } - await Client.ToggleReady(); + await MultiplayerClient.ToggleReady(); readyClickOperation.Dispose(); }); @@ -110,15 +110,15 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add second user as host", () => { - Client.AddUser(new APIUser { Id = 2, Username = "Another user" }); - Client.TransferHost(2); + MultiplayerClient.AddUser(new APIUser { Id = 2, Username = "Another user" }); + MultiplayerClient.TransferHost(2); }); ClickButtonWhenEnabled(); - AddUntilStep("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready); + AddUntilStep("user is ready", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Ready); ClickButtonWhenEnabled(); - AddUntilStep("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle); + AddUntilStep("user is idle", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Idle); } [TestCase(true)] @@ -127,14 +127,14 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("setup", () => { - Client.TransferHost(Client.Room?.Users[0].UserID ?? 0); + MultiplayerClient.TransferHost(MultiplayerClient.Room?.Users[0].UserID ?? 0); if (!allReady) - Client.AddUser(new APIUser { Id = 2, Username = "Another user" }); + MultiplayerClient.AddUser(new APIUser { Id = 2, Username = "Another user" }); }); ClickButtonWhenEnabled(); - AddUntilStep("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready); + AddUntilStep("user is ready", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Ready); verifyGameplayStartFlow(); } @@ -144,12 +144,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add host", () => { - Client.AddUser(new APIUser { Id = 2, Username = "Another user" }); - Client.TransferHost(2); + MultiplayerClient.AddUser(new APIUser { Id = 2, Username = "Another user" }); + MultiplayerClient.TransferHost(2); }); ClickButtonWhenEnabled(); - AddStep("make user host", () => Client.TransferHost(Client.Room?.Users[0].UserID ?? 0)); + AddStep("make user host", () => MultiplayerClient.TransferHost(MultiplayerClient.Room?.Users[0].UserID ?? 0)); verifyGameplayStartFlow(); } @@ -159,17 +159,17 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("setup", () => { - Client.TransferHost(Client.Room?.Users[0].UserID ?? 0); - Client.AddUser(new APIUser { Id = 2, Username = "Another user" }); + MultiplayerClient.TransferHost(MultiplayerClient.Room?.Users[0].UserID ?? 0); + MultiplayerClient.AddUser(new APIUser { Id = 2, Username = "Another user" }); }); ClickButtonWhenEnabled(); - AddUntilStep("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready); + AddUntilStep("user is ready", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Ready); - AddStep("transfer host", () => Client.TransferHost(Client.Room?.Users[1].UserID ?? 0)); + AddStep("transfer host", () => MultiplayerClient.TransferHost(MultiplayerClient.Room?.Users[1].UserID ?? 0)); ClickButtonWhenEnabled(); - AddUntilStep("user is idle (match not started)", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle); + AddUntilStep("user is idle (match not started)", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Idle); AddAssert("ready button enabled", () => button.ChildrenOfType().Single().Enabled.Value); } @@ -180,42 +180,42 @@ namespace osu.Game.Tests.Visual.Multiplayer const int users = 10; AddStep("setup", () => { - Client.TransferHost(Client.Room?.Users[0].UserID ?? 0); + MultiplayerClient.TransferHost(MultiplayerClient.Room?.Users[0].UserID ?? 0); for (int i = 0; i < users; i++) - Client.AddUser(new APIUser { Id = i, Username = "Another user" }); + MultiplayerClient.AddUser(new APIUser { Id = i, Username = "Another user" }); }); if (!isHost) - AddStep("transfer host", () => Client.TransferHost(2)); + AddStep("transfer host", () => MultiplayerClient.TransferHost(2)); ClickButtonWhenEnabled(); AddRepeatStep("change user ready state", () => { - Client.ChangeUserState(RNG.Next(0, users), RNG.NextBool() ? MultiplayerUserState.Ready : MultiplayerUserState.Idle); + MultiplayerClient.ChangeUserState(RNG.Next(0, users), RNG.NextBool() ? MultiplayerUserState.Ready : MultiplayerUserState.Idle); }, 20); AddRepeatStep("ready all users", () => { - var nextUnready = Client.Room?.Users.FirstOrDefault(c => c.State == MultiplayerUserState.Idle); + var nextUnready = MultiplayerClient.Room?.Users.FirstOrDefault(c => c.State == MultiplayerUserState.Idle); if (nextUnready != null) - Client.ChangeUserState(nextUnready.UserID, MultiplayerUserState.Ready); + MultiplayerClient.ChangeUserState(nextUnready.UserID, MultiplayerUserState.Ready); }, users); } private void verifyGameplayStartFlow() { - AddUntilStep("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready); + AddUntilStep("user is ready", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Ready); ClickButtonWhenEnabled(); - AddUntilStep("user waiting for load", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad); + AddUntilStep("user waiting for load", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad); AddAssert("ready button disabled", () => !button.ChildrenOfType().Single().Enabled.Value); AddStep("transitioned to gameplay", () => readyClickOperation.Dispose()); AddStep("finish gameplay", () => { - Client.ChangeUserState(Client.Room?.Users[0].UserID ?? 0, MultiplayerUserState.Loaded); - Client.ChangeUserState(Client.Room?.Users[0].UserID ?? 0, MultiplayerUserState.FinishedPlay); + MultiplayerClient.ChangeUserState(MultiplayerClient.Room?.Users[0].UserID ?? 0, MultiplayerUserState.Loaded); + MultiplayerClient.ChangeUserState(MultiplayerClient.Room?.Users[0].UserID ?? 0, MultiplayerUserState.FinishedPlay); }); AddUntilStep("ready button enabled", () => button.ChildrenOfType().Single().Enabled.Value); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 70d4d9dd55..c4ea78116a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -78,7 +78,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Task.Run(async () => { - await Client.ToggleSpectate(); + await MultiplayerClient.ToggleSpectate(); readyClickOperation.Dispose(); }); } @@ -94,13 +94,13 @@ namespace osu.Game.Tests.Visual.Multiplayer Task.Run(async () => { - if (Client.IsHost && Client.LocalUser?.State == MultiplayerUserState.Ready) + if (MultiplayerClient.IsHost && MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready) { - await Client.StartMatch(); + await MultiplayerClient.StartMatch(); return; } - await Client.ToggleReady(); + await MultiplayerClient.ToggleReady(); readyClickOperation.Dispose(); }); @@ -115,7 +115,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [TestCase(MultiplayerRoomState.Playing)] public void TestEnabledWhenRoomOpenOrInGameplay(MultiplayerRoomState roomState) { - AddStep($"change room to {roomState}", () => Client.ChangeRoomState(roomState)); + AddStep($"change room to {roomState}", () => MultiplayerClient.ChangeRoomState(roomState)); assertSpectateButtonEnablement(true); } @@ -124,16 +124,16 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestToggleWhenIdle(MultiplayerUserState initialState) { ClickButtonWhenEnabled(); - AddUntilStep("user is spectating", () => Client.Room?.Users[0].State == MultiplayerUserState.Spectating); + AddUntilStep("user is spectating", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Spectating); ClickButtonWhenEnabled(); - AddUntilStep("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle); + AddUntilStep("user is idle", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Idle); } [TestCase(MultiplayerRoomState.Closed)] public void TestDisabledWhenClosed(MultiplayerRoomState roomState) { - AddStep($"change room to {roomState}", () => Client.ChangeRoomState(roomState)); + AddStep($"change room to {roomState}", () => MultiplayerClient.ChangeRoomState(roomState)); assertSpectateButtonEnablement(false); } @@ -147,8 +147,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestReadyButtonEnabledWhenHostAndUsersReady() { - AddStep("add user", () => Client.AddUser(new APIUser { Id = PLAYER_1_ID })); - AddStep("set user ready", () => Client.ChangeUserState(PLAYER_1_ID, MultiplayerUserState.Ready)); + AddStep("add user", () => MultiplayerClient.AddUser(new APIUser { Id = PLAYER_1_ID })); + AddStep("set user ready", () => MultiplayerClient.ChangeUserState(PLAYER_1_ID, MultiplayerUserState.Ready)); ClickButtonWhenEnabled(); assertReadyButtonEnablement(true); @@ -159,11 +159,11 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add user and transfer host", () => { - Client.AddUser(new APIUser { Id = PLAYER_1_ID }); - Client.TransferHost(PLAYER_1_ID); + MultiplayerClient.AddUser(new APIUser { Id = PLAYER_1_ID }); + MultiplayerClient.TransferHost(PLAYER_1_ID); }); - AddStep("set user ready", () => Client.ChangeUserState(PLAYER_1_ID, MultiplayerUserState.Ready)); + AddStep("set user ready", () => MultiplayerClient.ChangeUserState(PLAYER_1_ID, MultiplayerUserState.Ready)); ClickButtonWhenEnabled(); assertReadyButtonEnablement(false); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneRankRangePill.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneRankRangePill.cs index 823ac07cf7..f95e73ff3c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneRankRangePill.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneRankRangePill.cs @@ -25,14 +25,14 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add user", () => { - Client.AddUser(new APIUser + MultiplayerClient.AddUser(new APIUser { Id = 2, Statistics = { GlobalRank = 1234 } }); // Remove the local user so only the one above is displayed. - Client.RemoveUser(API.LocalUser.Value); + MultiplayerClient.RemoveUser(API.LocalUser.Value); }); } @@ -41,26 +41,26 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add users", () => { - Client.AddUser(new APIUser + MultiplayerClient.AddUser(new APIUser { Id = 2, Statistics = { GlobalRank = 1234 } }); - Client.AddUser(new APIUser + MultiplayerClient.AddUser(new APIUser { Id = 3, Statistics = { GlobalRank = 3333 } }); - Client.AddUser(new APIUser + MultiplayerClient.AddUser(new APIUser { Id = 4, Statistics = { GlobalRank = 4321 } }); // Remove the local user so only the ones above are displayed. - Client.RemoveUser(API.LocalUser.Value); + MultiplayerClient.RemoveUser(API.LocalUser.Value); }); } @@ -75,20 +75,20 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add users", () => { - Client.AddUser(new APIUser + MultiplayerClient.AddUser(new APIUser { Id = 2, Statistics = { GlobalRank = min } }); - Client.AddUser(new APIUser + MultiplayerClient.AddUser(new APIUser { Id = 3, Statistics = { GlobalRank = max } }); // Remove the local user so only the ones above are displayed. - Client.RemoveUser(API.LocalUser.Value); + MultiplayerClient.RemoveUser(API.LocalUser.Value); }); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 7a5bd0b8f4..50b3f52047 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private TestMultiplayerComponents multiplayerComponents; - private TestMultiplayerClient client => multiplayerComponents.Client; + private TestMultiplayerClient multiplayerClient => multiplayerComponents.MultiplayerClient; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -75,8 +75,8 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddUntilStep("room type is team vs", () => client.Room?.Settings.MatchType == MatchType.TeamVersus); - AddAssert("user state arrived", () => client.Room?.Users.FirstOrDefault()?.MatchState is TeamVersusUserState); + AddUntilStep("room type is team vs", () => multiplayerClient.Room?.Settings.MatchType == MatchType.TeamVersus); + AddAssert("user state arrived", () => multiplayerClient.Room?.Users.FirstOrDefault()?.MatchState is TeamVersusUserState); } [Test] @@ -96,25 +96,25 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddAssert("user on team 0", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); - AddStep("add another user", () => client.AddUser(new APIUser { Username = "otheruser", Id = 44 })); + AddAssert("user on team 0", () => (multiplayerClient.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); + AddStep("add another user", () => multiplayerClient.AddUser(new APIUser { Username = "otheruser", Id = 44 })); AddStep("press own button", () => { InputManager.MoveMouseTo(multiplayerComponents.ChildrenOfType().First()); InputManager.Click(MouseButton.Left); }); - AddAssert("user on team 1", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 1); + AddAssert("user on team 1", () => (multiplayerClient.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 1); AddStep("press own button again", () => InputManager.Click(MouseButton.Left)); - AddAssert("user on team 0", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); + AddAssert("user on team 0", () => (multiplayerClient.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); AddStep("press other user's button", () => { InputManager.MoveMouseTo(multiplayerComponents.ChildrenOfType().ElementAt(1)); InputManager.Click(MouseButton.Left); }); - AddAssert("user still on team 0", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); + AddAssert("user still on team 0", () => (multiplayerClient.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); } [Test] @@ -134,14 +134,14 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddUntilStep("match type head to head", () => client.APIRoom?.Type.Value == MatchType.HeadToHead); + AddUntilStep("match type head to head", () => multiplayerClient.APIRoom?.Type.Value == MatchType.HeadToHead); - AddStep("change match type", () => client.ChangeSettings(new MultiplayerRoomSettings + AddStep("change match type", () => multiplayerClient.ChangeSettings(new MultiplayerRoomSettings { MatchType = MatchType.TeamVersus }).WaitSafely()); - AddUntilStep("api room updated to team versus", () => client.APIRoom?.Type.Value == MatchType.TeamVersus); + AddUntilStep("api room updated to team versus", () => multiplayerClient.APIRoom?.Type.Value == MatchType.TeamVersus); } [Test] @@ -160,13 +160,13 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddUntilStep("room type is head to head", () => client.Room?.Settings.MatchType == MatchType.HeadToHead); + AddUntilStep("room type is head to head", () => multiplayerClient.Room?.Settings.MatchType == MatchType.HeadToHead); AddUntilStep("team displays are not displaying teams", () => multiplayerComponents.ChildrenOfType().All(d => d.DisplayedTeam == null)); - AddStep("change to team vs", () => client.ChangeSettings(matchType: MatchType.TeamVersus)); + AddStep("change to team vs", () => multiplayerClient.ChangeSettings(matchType: MatchType.TeamVersus)); - AddUntilStep("room type is team vs", () => client.Room?.Settings.MatchType == MatchType.TeamVersus); + AddUntilStep("room type is team vs", () => multiplayerClient.Room?.Settings.MatchType == MatchType.TeamVersus); AddUntilStep("team displays are displaying teams", () => multiplayerComponents.ChildrenOfType().All(d => d.DisplayedTeam != null)); } @@ -185,7 +185,7 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.Click(MouseButton.Left); }); - AddUntilStep("wait for join", () => client.RoomJoined); + AddUntilStep("wait for join", () => multiplayerClient.RoomJoined); } } } diff --git a/osu.Game.Tests/Visual/TestMultiplayerComponents.cs b/osu.Game.Tests/Visual/TestMultiplayerComponents.cs index bd8fb8e58e..062aa33b99 100644 --- a/osu.Game.Tests/Visual/TestMultiplayerComponents.cs +++ b/osu.Game.Tests/Visual/TestMultiplayerComponents.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual public new bool IsLoaded => base.IsLoaded && MultiplayerScreen.IsLoaded; [Cached(typeof(MultiplayerClient))] - public readonly TestMultiplayerClient Client; + public readonly TestMultiplayerClient MultiplayerClient; [Cached(typeof(UserLookupCache))] private readonly UserLookupCache userLookupCache = new TestUserLookupCache(); @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual { userLookupCache, beatmapLookupCache, - Client = new TestMultiplayerClient(RoomManager), + MultiplayerClient = new TestMultiplayerClient(RoomManager), screenStack = new OsuScreenStack { Name = nameof(TestMultiplayerComponents), diff --git a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs index f166154103..62d1c9ceca 100644 --- a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.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 osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay; using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.Spectator; @@ -14,9 +13,9 @@ namespace osu.Game.Tests.Visual.Multiplayer public interface IMultiplayerTestSceneDependencies : IOnlinePlayTestSceneDependencies { /// - /// The cached . + /// The cached . /// - TestMultiplayerClient Client { get; } + TestMultiplayerClient MultiplayerClient { get; } /// /// The cached . diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index a9b3ca4991..19f9952d14 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -17,13 +17,13 @@ namespace osu.Game.Tests.Visual.Multiplayer public const int PLAYER_1_ID = 55; public const int PLAYER_2_ID = 56; - public TestMultiplayerClient Client => OnlinePlayDependencies.Client; + public TestMultiplayerClient MultiplayerClient => OnlinePlayDependencies.MultiplayerClient; public new TestMultiplayerRoomManager RoomManager => OnlinePlayDependencies.RoomManager; public TestSpectatorClient SpectatorClient => OnlinePlayDependencies?.SpectatorClient; protected new MultiplayerTestSceneDependencies OnlinePlayDependencies => (MultiplayerTestSceneDependencies)base.OnlinePlayDependencies; - public bool RoomJoined => Client.RoomJoined; + public bool RoomJoined => MultiplayerClient.RoomJoined; private readonly bool joinRoom; diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs index d9fe77ae44..6b4e01b673 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs @@ -14,16 +14,16 @@ namespace osu.Game.Tests.Visual.Multiplayer /// public class MultiplayerTestSceneDependencies : OnlinePlayTestSceneDependencies, IMultiplayerTestSceneDependencies { - public TestMultiplayerClient Client { get; } + public TestMultiplayerClient MultiplayerClient { get; } public TestSpectatorClient SpectatorClient { get; } public new TestMultiplayerRoomManager RoomManager => (TestMultiplayerRoomManager)base.RoomManager; public MultiplayerTestSceneDependencies() { - Client = new TestMultiplayerClient(RoomManager); + MultiplayerClient = new TestMultiplayerClient(RoomManager); SpectatorClient = CreateSpectatorClient(); - CacheAs(Client); + CacheAs(MultiplayerClient); CacheAs(SpectatorClient); } From 5db63a8751e38d9701b9987ae30508b9be516475 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 16 Feb 2022 11:30:03 +0900 Subject: [PATCH 0690/5199] Expose read-only list from request --- osu.Game/Online/API/Requests/GetBeatmapsRequest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/GetBeatmapsRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapsRequest.cs index c07e5ef1c2..22af022659 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapsRequest.cs +++ b/osu.Game/Online/API/Requests/GetBeatmapsRequest.cs @@ -2,12 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; namespace osu.Game.Online.API.Requests { public class GetBeatmapsRequest : APIRequest { - public readonly int[] BeatmapIds; + public readonly IReadOnlyList BeatmapIds; private const int max_ids_per_request = 50; From 215da7e933ded0423618e6fb6f58e28c65ea2339 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 16 Feb 2022 12:05:55 +0900 Subject: [PATCH 0691/5199] Reimplement as extension method on IBeatmap Implementation has changed slightly to support arbitrary levels of nested hitobjects. --- .../Difficulty/OsuDifficultyCalculator.cs | 17 +------------ osu.Game/Beatmaps/IBeatmap.cs | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index d04d0872d8..df6fd19d36 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -9,7 +9,6 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Skills; using osu.Game.Rulesets.Osu.Mods; @@ -62,21 +61,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; double drainRate = beatmap.Difficulty.DrainRate; - - int maxCombo = 0; - - void countCombo(HitObject ho) - { - if (ho.CreateJudgement().MaxResult.AffectsCombo()) - maxCombo++; - } - - foreach (HitObject ho in beatmap.HitObjects) - { - countCombo(ho); - foreach (HitObject nested in ho.NestedHitObjects) - countCombo(nested); - } + int maxCombo = beatmap.GetMaxCombo(); int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle); int sliderCount = beatmap.HitObjects.Count(h => h is Slider); diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 3f598cd1e5..dec1ef4294 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Beatmaps { @@ -70,4 +71,27 @@ namespace osu.Game.Beatmaps /// new IReadOnlyList HitObjects { get; } } + + public static class BeatmapExtensions + { + /// + /// Finds the maximum achievable combo by hitting all s in a beatmap. + /// + public static int GetMaxCombo(this IBeatmap beatmap) + { + int combo = 0; + foreach (var h in beatmap.HitObjects) + addCombo(h, ref combo); + return combo; + + static void addCombo(HitObject hitObject, ref int combo) + { + if (hitObject.CreateJudgement().MaxResult.AffectsCombo()) + combo++; + + foreach (var nested in hitObject.NestedHitObjects) + addCombo(nested, ref combo); + } + } + } } From 84e82ef5e466d1802f1273e72a389cdf1f54c6fd Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 16 Feb 2022 14:09:19 +0900 Subject: [PATCH 0692/5199] Add XMLDocs to difficulty attribute properties --- .../Difficulty/CatchDifficultyAttributes.cs | 6 +++ .../Difficulty/ManiaDifficultyAttributes.cs | 9 +++++ .../Difficulty/OsuDifficultyAttributes.cs | 38 +++++++++++++++++++ .../Difficulty/TaikoDifficultyAttributes.cs | 21 ++++++++++ .../Difficulty/DifficultyAttributes.cs | 2 +- 5 files changed, 75 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs index 39a58d336d..8e069d7d16 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs @@ -9,6 +9,12 @@ namespace osu.Game.Rulesets.Catch.Difficulty { public class CatchDifficultyAttributes : DifficultyAttributes { + /// + /// The perceived approach rate inclusive of rate-adjusting mods (DT/HT/etc). + /// + /// + /// Rate-adjusting mods don't directly affect the approach rate difficulty value, but have a perceived effect as a result of adjusting audio timing. + /// [JsonProperty("approach_rate")] public double ApproachRate { get; set; } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs index 979a04ddf8..efd5868504 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs @@ -9,9 +9,18 @@ namespace osu.Game.Rulesets.Mania.Difficulty { public class ManiaDifficultyAttributes : DifficultyAttributes { + /// + /// The perceived hit window for a GREAT hit inclusive of rate-adjusting mods (DT/HT/etc). + /// + /// + /// Rate-adjusting mods don't directly affect the hit window, but have a perceived effect as a result of adjusting audio timing. + /// [JsonProperty("great_hit_window")] public double GreatHitWindow { get; set; } + /// + /// The score multiplier applied via score-reducing mods. + /// [JsonProperty("score_multiplier")] public double ScoreMultiplier { get; set; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index 126a9b0183..3deed4ea3d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -12,30 +12,68 @@ namespace osu.Game.Rulesets.Osu.Difficulty { public class OsuDifficultyAttributes : DifficultyAttributes { + /// + /// The difficulty corresponding to the aim skill. + /// [JsonProperty("aim_difficulty")] public double AimDifficulty { get; set; } + /// + /// The difficulty corresponding to the speed skill. + /// [JsonProperty("speed_difficulty")] public double SpeedDifficulty { get; set; } + /// + /// The difficulty corresponding to the flashlight skill. + /// [JsonProperty("flashlight_difficulty")] public double FlashlightDifficulty { get; set; } + /// + /// Describes how much of is contributed to by hitcircles or sliders. + /// A value closer to 1.0 indicates most of is contributed by hitcircles. + /// A value closer to 0.0 indicates most of is contributed by sliders. + /// [JsonProperty("slider_factor")] public double SliderFactor { get; set; } + /// + /// The perceived approach rate inclusive of rate-adjusting mods (DT/HT/etc). + /// + /// + /// Rate-adjusting mods don't directly affect the approach rate difficulty value, but have a perceived effect as a result of adjusting audio timing. + /// [JsonProperty("approach_rate")] public double ApproachRate { get; set; } + /// + /// The perceived overall difficulty inclusive of rate-adjusting mods (DT/HT/etc). + /// + /// + /// Rate-adjusting mods don't directly affect the overall difficulty value, but have a perceived effect as a result of adjusting audio timing. + /// [JsonProperty("overall_difficulty")] public double OverallDifficulty { get; set; } + /// + /// The beatmap's drain rate. This doesn't scale with rate-adjusting mods. + /// public double DrainRate { get; set; } + /// + /// The number of hitcircles in the beatmap. + /// public int HitCircleCount { get; set; } + /// + /// The number of sliders in the beatmap. + /// public int SliderCount { get; set; } + /// + /// The number of spinners in the beatmap. + /// public int SpinnerCount { get; set; } public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes() diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs index 31f5a6f570..3dc5438072 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs @@ -9,18 +9,39 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { public class TaikoDifficultyAttributes : DifficultyAttributes { + /// + /// The difficulty corresponding to the stamina skill. + /// [JsonProperty("stamina_difficulty")] public double StaminaDifficulty { get; set; } + /// + /// The difficulty corresponding to the rhythm skill. + /// [JsonProperty("rhythm_difficulty")] public double RhythmDifficulty { get; set; } + /// + /// The difficulty corresponding to the colour skill. + /// [JsonProperty("colour_difficulty")] public double ColourDifficulty { get; set; } + /// + /// The perceived approach rate inclusive of rate-adjusting mods (DT/HT/etc). + /// + /// + /// Rate-adjusting mods don't directly affect the approach rate difficulty value, but have a perceived effect as a result of adjusting audio timing. + /// [JsonProperty("approach_rate")] public double ApproachRate { get; set; } + /// + /// The perceived hit window for a GREAT hit inclusive of rate-adjusting mods (DT/HT/etc). + /// + /// + /// Rate-adjusting mods don't directly affect the hit window, but have a perceived effect as a result of adjusting audio timing. + /// [JsonProperty("great_hit_window")] public double GreatHitWindow { get; set; } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs index 991b567f57..ec3d22b67a 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Difficulty public Mod[] Mods { get; set; } /// - /// The combined star rating of all skill. + /// The combined star rating of all skills. /// [JsonProperty("star_rating", Order = -3)] public double StarRating { get; set; } From 8b30c847f9ab3829b8fc8bc0e5f944a217cba8e5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 16 Feb 2022 08:39:08 +0300 Subject: [PATCH 0693/5199] Update outdated bindable casts in tests --- osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs | 2 +- osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs index 41304dcd24..aa0e35102e 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("seek to first control point", () => EditorClock.Seek(Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.First().Time)); AddStep("set distance spacing to 1", () => { - var distanceSpacing = (BindableFloat)Editor.ChildrenOfType().Single().DistanceSpacingMultiplier; + var distanceSpacing = (BindableDouble)Editor.ChildrenOfType().Single().DistanceSpacingMultiplier; distanceSpacing.Value = 1; }); } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs index 23131668a0..13ce340362 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor }); AddStep("set distance spacing to 1", () => { - var distanceSpacing = (BindableFloat)Editor.ChildrenOfType().Single().DistanceSpacingMultiplier; + var distanceSpacing = (BindableDouble)Editor.ChildrenOfType().Single().DistanceSpacingMultiplier; distanceSpacing.Value = 1; }); } From f703828e1bc17e3ec5421dbc09ea9a62ca3f8437 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Feb 2022 15:27:48 +0900 Subject: [PATCH 0694/5199] Clarify ambiguous conditionals in `LegacyStageBackground` --- .../Skinning/Legacy/LegacyStageBackground.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs index 952fc7ddd6..fdacc75c92 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs @@ -98,8 +98,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy float rightLineWidth = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.RightLineWidth, columnIndex)?.Value ?? 1; bool hasLeftLine = leftLineWidth > 0; - bool hasRightLine = rightLineWidth > 0 && skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value >= 2.4m - || isLastColumn; + bool hasRightLine = (rightLineWidth > 0 && skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value >= 2.4m) || isLastColumn; Color4 lineColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnLineColour, columnIndex)?.Value ?? Color4.White; Color4 backgroundColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour, columnIndex)?.Value ?? Color4.Black; From edd31bf3aa913a7b01306538f88a6c44d160d729 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 16 Feb 2022 15:51:18 +0900 Subject: [PATCH 0695/5199] Revert styling change --- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 49 +++++++++---------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index faeb73cfa3..8d840280b3 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -430,34 +430,31 @@ namespace osu.Game.Screens.OnlinePlay }; } - private IEnumerable createButtons() + private IEnumerable createButtons() => new[] { - return new[] + showResultsButton = new GrayButton(FontAwesome.Solid.ChartPie) { - showResultsButton = new GrayButton(FontAwesome.Solid.ChartPie) - { - Size = new Vector2(30, 30), - Action = () => RequestResults?.Invoke(Item), - Alpha = AllowShowingResults ? 1 : 0, - TooltipText = "View results" - }, - beatmap == null ? Empty() : new PlaylistDownloadButton(beatmap), - editButton = new PlaylistEditButton - { - Size = new Vector2(30, 30), - Alpha = AllowEditing ? 1 : 0, - Action = () => RequestEdit?.Invoke(Item), - TooltipText = "Edit" - }, - removeButton = new PlaylistRemoveButton - { - Size = new Vector2(30, 30), - Alpha = AllowDeletion ? 1 : 0, - Action = () => RequestDeletion?.Invoke(Item), - TooltipText = "Remove from playlist" - }, - }; - } + Size = new Vector2(30, 30), + Action = () => RequestResults?.Invoke(Item), + Alpha = AllowShowingResults ? 1 : 0, + TooltipText = "View results" + }, + beatmap == null ? Empty() : new PlaylistDownloadButton(beatmap), + editButton = new PlaylistEditButton + { + Size = new Vector2(30, 30), + Alpha = AllowEditing ? 1 : 0, + Action = () => RequestEdit?.Invoke(Item), + TooltipText = "Edit" + }, + removeButton = new PlaylistRemoveButton + { + Size = new Vector2(30, 30), + Alpha = AllowDeletion ? 1 : 0, + Action = () => RequestDeletion?.Invoke(Item), + TooltipText = "Remove from playlist" + }, + }; protected override bool OnClick(ClickEvent e) { From 55d9f0b44b507d4ff8140271e7fb3807ad2c4ef2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 16 Feb 2022 16:03:08 +0900 Subject: [PATCH 0696/5199] Store beatmap to a field instead --- .../OnlinePlayBeatmapAvailabilityTracker.cs | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index 02bcd1f30c..07506ba1f0 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -46,10 +46,9 @@ namespace osu.Game.Online.Rooms private readonly Bindable availability = new Bindable(BeatmapAvailability.NotDownloaded()); private ScheduledDelegate progressUpdate; - private BeatmapDownloadTracker downloadTracker; - private IDisposable realmSubscription; + private APIBeatmap selectedBeatmap; protected override void LoadComplete() { @@ -63,26 +62,30 @@ namespace osu.Game.Online.Rooms return; downloadTracker?.RemoveAndDisposeImmediately(); + selectedBeatmap = null; beatmapLookupCache.GetBeatmapAsync(item.NewValue.Beatmap.OnlineID).ContinueWith(task => Schedule(() => { var beatmap = task.GetResultSafely(); if (SelectedItem.Value?.Beatmap.OnlineID == beatmap.OnlineID) - beginTracking(beatmap); + { + selectedBeatmap = beatmap; + beginTracking(); + } }), TaskContinuationOptions.OnlyOnRanToCompletion); }, true); } - private void beginTracking(APIBeatmap beatmap) + private void beginTracking() { - Debug.Assert(beatmap.BeatmapSet != null); + Debug.Assert(selectedBeatmap.BeatmapSet != null); - downloadTracker = new BeatmapDownloadTracker(beatmap.BeatmapSet); + downloadTracker = new BeatmapDownloadTracker(selectedBeatmap.BeatmapSet); AddInternal(downloadTracker); - downloadTracker.State.BindValueChanged(_ => Scheduler.AddOnce(updateAvailability, beatmap), true); + downloadTracker.State.BindValueChanged(_ => Scheduler.AddOnce(updateAvailability), true); downloadTracker.Progress.BindValueChanged(_ => { if (downloadTracker.State.Value != DownloadState.Downloading) @@ -91,23 +94,23 @@ namespace osu.Game.Online.Rooms // incoming progress changes are going to be at a very high rate. // we don't want to flood the network with this, so rate limit how often we send progress updates. if (progressUpdate?.Completed != false) - progressUpdate = Scheduler.AddDelayed(updateAvailability, beatmap, progressUpdate == null ? 0 : 500); + progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500); }, true); // handles changes to hash that didn't occur from the import process (ie. a user editing the beatmap in the editor, somehow). realmSubscription?.Dispose(); - realmSubscription = realm.RegisterForNotifications(r => filteredBeatmaps(beatmap), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(r => filteredBeatmaps(), (items, changes, ___) => { if (changes == null) return; - Scheduler.AddOnce(updateAvailability, beatmap); + Scheduler.AddOnce(updateAvailability); }); } - private void updateAvailability(APIBeatmap beatmap) + private void updateAvailability() { - if (downloadTracker == null) + if (downloadTracker == null || selectedBeatmap == null) return; switch (downloadTracker.State.Value) @@ -125,7 +128,7 @@ namespace osu.Game.Online.Rooms break; case DownloadState.LocallyAvailable: - bool available = filteredBeatmaps(beatmap).Any(); + bool available = filteredBeatmaps().Any(); availability.Value = available ? BeatmapAvailability.LocallyAvailable() : BeatmapAvailability.NotDownloaded(); @@ -140,10 +143,10 @@ namespace osu.Game.Online.Rooms } } - private IQueryable filteredBeatmaps(APIBeatmap beatmap) + private IQueryable filteredBeatmaps() { - int onlineId = beatmap.OnlineID; - string checksum = beatmap.MD5Hash; + int onlineId = selectedBeatmap.OnlineID; + string checksum = selectedBeatmap.MD5Hash; return realm.Realm .All() From d4bf335fccd31e921447b999a91d722998d51417 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 16 Feb 2022 16:24:39 +0900 Subject: [PATCH 0697/5199] Use score multiplier attribute in ManiaPerformanceCalculator --- .../Difficulty/ManiaPerformanceCalculator.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index 8a8c41bb8a..beba29b8bb 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -43,14 +43,11 @@ namespace osu.Game.Rulesets.Mania.Difficulty countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss); - IEnumerable scoreIncreaseMods = Ruleset.GetModsFor(ModType.DifficultyIncrease); - - double scoreMultiplier = 1.0; - foreach (var m in mods.Where(m => !scoreIncreaseMods.Contains(m))) - scoreMultiplier *= m.ScoreMultiplier; - - // Scale score up, so it's comparable to other keymods - scaledScore *= 1.0 / scoreMultiplier; + if (Attributes.ScoreMultiplier > 0) + { + // Scale score up, so it's comparable to other keymods + scaledScore *= 1.0 / Attributes.ScoreMultiplier; + } // Arbitrary initial value for scaling pp in order to standardize distributions across game modes. // The specific number has no intrinsic meaning and can be adjusted as needed. @@ -80,6 +77,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty private double computeDifficultyValue() { + if (Attributes.ScoreMultiplier <= 0) + return 0; + double difficultyValue = Math.Pow(5 * Math.Max(1, Attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0; difficultyValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0); From 5dd9771c5fbb2ae68efe146b769375a6210b00d7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 16 Feb 2022 16:27:27 +0900 Subject: [PATCH 0698/5199] Remove mod multipliers from being applied to scores --- .../TestSceneFooterButtonMods.cs | 27 ++----------------- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 13 +-------- osu.Game/Screens/Select/FooterButtonMods.cs | 25 ----------------- 3 files changed, 3 insertions(+), 62 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs index 0631059d1a..e9014c0941 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs @@ -3,9 +3,7 @@ using System; using System.Collections.Generic; -using System.Linq; using NUnit.Framework; -using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Select; @@ -14,11 +12,11 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestSceneFooterButtonMods : OsuTestScene { - private readonly TestFooterButtonMods footerButtonMods; + private readonly FooterButtonMods footerButtonMods; public TestSceneFooterButtonMods() { - Add(footerButtonMods = new TestFooterButtonMods()); + Add(footerButtonMods = new FooterButtonMods()); } [Test] @@ -26,19 +24,15 @@ namespace osu.Game.Tests.Visual.UserInterface { var hiddenMod = new Mod[] { new OsuModHidden() }; AddStep(@"Add Hidden", () => changeMods(hiddenMod)); - AddAssert(@"Check Hidden multiplier", () => assertModsMultiplier(hiddenMod)); var hardRockMod = new Mod[] { new OsuModHardRock() }; AddStep(@"Add HardRock", () => changeMods(hardRockMod)); - AddAssert(@"Check HardRock multiplier", () => assertModsMultiplier(hardRockMod)); var doubleTimeMod = new Mod[] { new OsuModDoubleTime() }; AddStep(@"Add DoubleTime", () => changeMods(doubleTimeMod)); - AddAssert(@"Check DoubleTime multiplier", () => assertModsMultiplier(doubleTimeMod)); var multipleIncrementMods = new Mod[] { new OsuModDoubleTime(), new OsuModHidden(), new OsuModHardRock() }; AddStep(@"Add multiple Mods", () => changeMods(multipleIncrementMods)); - AddAssert(@"Check multiple mod multiplier", () => assertModsMultiplier(multipleIncrementMods)); } [Test] @@ -46,15 +40,12 @@ namespace osu.Game.Tests.Visual.UserInterface { var easyMod = new Mod[] { new OsuModEasy() }; AddStep(@"Add Easy", () => changeMods(easyMod)); - AddAssert(@"Check Easy multiplier", () => assertModsMultiplier(easyMod)); var noFailMod = new Mod[] { new OsuModNoFail() }; AddStep(@"Add NoFail", () => changeMods(noFailMod)); - AddAssert(@"Check NoFail multiplier", () => assertModsMultiplier(noFailMod)); var multipleDecrementMods = new Mod[] { new OsuModEasy(), new OsuModNoFail() }; AddStep(@"Add Multiple Mods", () => changeMods(multipleDecrementMods)); - AddAssert(@"Check multiple mod multiplier", () => assertModsMultiplier(multipleDecrementMods)); } [Test] @@ -63,25 +54,11 @@ namespace osu.Game.Tests.Visual.UserInterface var multipleMods = new Mod[] { new OsuModDoubleTime(), new OsuModFlashlight() }; AddStep(@"Add mods", () => changeMods(multipleMods)); AddStep(@"Clear selected mod", () => changeMods(Array.Empty())); - AddAssert(@"Check empty multiplier", () => assertModsMultiplier(Array.Empty())); } private void changeMods(IReadOnlyList mods) { footerButtonMods.Current.Value = mods; } - - private bool assertModsMultiplier(IEnumerable mods) - { - double multiplier = mods.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier); - string expectedValue = multiplier.Equals(1.0) ? string.Empty : $"{multiplier:N2}x"; - - return expectedValue == footerButtonMods.MultiplierText.Current.Value; - } - - private class TestFooterButtonMods : FooterButtonMods - { - public new OsuSpriteText MultiplierText => base.MultiplierText; - } } } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 79861c0ecc..ce1486b02f 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -92,8 +92,6 @@ namespace osu.Game.Rulesets.Scoring private readonly List hitEvents = new List(); private HitObject lastHitObject; - private double scoreMultiplier = 1; - public ScoreProcessor() { accuracyPortion = DefaultAccuracyPortion; @@ -111,15 +109,6 @@ namespace osu.Game.Rulesets.Scoring }; Mode.ValueChanged += _ => updateScore(); - Mods.ValueChanged += mods => - { - scoreMultiplier = 1; - - foreach (var m in mods.NewValue) - scoreMultiplier *= m.ScoreMultiplier; - - updateScore(); - }; } private readonly Dictionary scoreResultCounts = new Dictionary(); @@ -235,7 +224,7 @@ namespace osu.Game.Rulesets.Scoring case ScoringMode.Standardised: double accuracyScore = accuracyPortion * accuracyRatio; double comboScore = comboPortion * comboRatio; - return (max_score * (accuracyScore + comboScore) + getBonusScore(statistics)) * scoreMultiplier; + return (max_score * (accuracyScore + comboScore) + getBonusScore(statistics)); case ScoringMode.Classic: // This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring. diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 5bbca5ca1a..1f1aa4c4b3 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -6,14 +6,11 @@ using osu.Framework.Graphics; using osu.Game.Screens.Play.HUD; using osu.Game.Rulesets.Mods; using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osuTK; -using osuTK.Graphics; using osu.Game.Input.Bindings; namespace osu.Game.Screens.Select @@ -26,10 +23,7 @@ namespace osu.Game.Screens.Select set => modDisplay.Current = value; } - protected readonly OsuSpriteText MultiplierText; private readonly ModDisplay modDisplay; - private Color4 lowMultiplierColour; - private Color4 highMultiplierColour; public FooterButtonMods() { @@ -40,12 +34,6 @@ namespace osu.Game.Screens.Select Scale = new Vector2(0.8f), ExpansionMode = ExpansionMode.AlwaysContracted, }); - ButtonContentContainer.Add(MultiplierText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(weight: FontWeight.Bold), - }); } [BackgroundDependencyLoader] @@ -53,8 +41,6 @@ namespace osu.Game.Screens.Select { SelectedColour = colours.Yellow; DeselectedColour = SelectedColour.Opacity(0.5f); - lowMultiplierColour = colours.Red; - highMultiplierColour = colours.Green; Text = @"mods"; Hotkey = GlobalAction.ToggleModSelection; } @@ -68,17 +54,6 @@ namespace osu.Game.Screens.Select private void updateMultiplierText() { - double multiplier = Current.Value?.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier) ?? 1; - - MultiplierText.Text = multiplier.Equals(1.0) ? string.Empty : $"{multiplier:N2}x"; - - if (multiplier > 1.0) - MultiplierText.FadeColour(highMultiplierColour, 200); - else if (multiplier < 1.0) - MultiplierText.FadeColour(lowMultiplierColour, 200); - else - MultiplierText.FadeColour(Color4.White, 200); - if (Current.Value?.Count > 0) modDisplay.FadeIn(); else From b66566e96d4f22d7e4351443ea8d999fd7f9bce7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 16 Feb 2022 10:35:57 +0300 Subject: [PATCH 0699/5199] Use explicit culture info rather than `null` --- osu.Game/Overlays/Settings/Sections/SizeSlider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs index cc2c77adf2..26db1aa755 100644 --- a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs +++ b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Globalization; using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; @@ -13,6 +14,6 @@ namespace osu.Game.Overlays.Settings.Sections internal class SizeSlider : OsuSliderBar where T : struct, IEquatable, IComparable, IConvertible, IFormattable { - public override LocalisableString TooltipText => Current.Value.ToString(@"0.##x", null); + public override LocalisableString TooltipText => Current.Value.ToString(@"0.##x", NumberFormatInfo.CurrentInfo); } } From 4c1413e0c793995d5a9ea4802419d9131d9abe53 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 16 Feb 2022 16:36:02 +0900 Subject: [PATCH 0700/5199] No longer require Mod implementation --- osu.Game/Rulesets/Mods/Mod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 7136795461..5555770822 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Mods /// The score multiplier of this mod. /// [JsonIgnore] - public abstract double ScoreMultiplier { get; } + public virtual double ScoreMultiplier => 1; /// /// Returns true if this mod is implemented (and playable). From 3d3f0a89c2c1b933eff1a7f8575ba7ac6a890f52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Feb 2022 16:46:53 +0900 Subject: [PATCH 0701/5199] Remove legacy `RulesetID` property from `BeatmapInfo` --- osu.Game/Beatmaps/BeatmapInfo.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index e4bfd768b7..305b3979a0 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -152,18 +152,6 @@ namespace osu.Game.Beatmaps #region Compatibility properties - [Ignored] - public int RulesetID - { - set - { - if (!string.IsNullOrEmpty(Ruleset.InstantiationInfo)) - throw new InvalidOperationException($"Cannot set a {nameof(RulesetID)} when {nameof(Ruleset)} is already set to an actual ruleset."); - - Ruleset.OnlineID = value; - } - } - [Ignored] [Obsolete("Use BeatmapInfo.Difficulty instead.")] // can be removed 20220719 public BeatmapDifficulty BaseDifficulty From 7a69de0060c2c55d731fd89f3a62d72eeb93c424 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Feb 2022 16:57:28 +0900 Subject: [PATCH 0702/5199] Split out realm portion of `RulesetStore` --- osu.Game/OsuGameBase.cs | 7 +- osu.Game/Rulesets/RealmRulesetStore.cs | 101 +++++++++++++++++++++++++ osu.Game/Rulesets/RulesetStore.cs | 101 +++---------------------- 3 files changed, 115 insertions(+), 94 deletions(-) create mode 100644 osu.Game/Rulesets/RealmRulesetStore.cs diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 0b2644d5ba..d89e1e8193 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -22,6 +22,7 @@ using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Formats; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Graphics; @@ -109,7 +110,7 @@ namespace osu.Game protected SkinManager SkinManager { get; private set; } - protected RulesetStore RulesetStore { get; private set; } + protected RealmRulesetStore RulesetStore { get; private set; } protected RealmKeyBindingStore KeyBindingStore { get; private set; } @@ -200,9 +201,11 @@ namespace osu.Game dependencies.Cache(realm = new RealmAccess(Storage, "client", EFContextFactory)); - dependencies.Cache(RulesetStore = new RulesetStore(realm, Storage)); + dependencies.CacheAs(RulesetStore = new RealmRulesetStore(realm, Storage)); dependencies.CacheAs(RulesetStore); + Decoder.RegisterDependencies(RulesetStore); + // Backup is taken here rather than in EFToRealmMigrator to avoid recycling realm contexts // after initial usages below. It can be moved once a direction is established for handling re-subscription. // See https://github.com/ppy/osu/pull/16547 for more discussion. diff --git a/osu.Game/Rulesets/RealmRulesetStore.cs b/osu.Game/Rulesets/RealmRulesetStore.cs new file mode 100644 index 0000000000..f42bf06da4 --- /dev/null +++ b/osu.Game/Rulesets/RealmRulesetStore.cs @@ -0,0 +1,101 @@ +// 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.Extensions.ObjectExtensions; +using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Game.Database; + +#nullable enable + +namespace osu.Game.Rulesets +{ + public class RealmRulesetStore : RulesetStore + { + public override IEnumerable AvailableRulesets => availableRulesets; + + private readonly List availableRulesets = new List(); + + public RealmRulesetStore(RealmAccess realm, Storage? storage = null) + : base(storage) + { + prepareDetachedRulesets(realm); + } + + private void prepareDetachedRulesets(RealmAccess realmAccess) + { + realmAccess.Write(realm => + { + var rulesets = realm.All(); + + List instances = LoadedAssemblies.Values + .Select(r => Activator.CreateInstance(r) as Ruleset) + .Where(r => r != null) + .Select(r => r.AsNonNull()) + .ToList(); + + // add all legacy rulesets first to ensure they have exclusive choice of primary key. + foreach (var r in instances.Where(r => r is ILegacyRuleset)) + { + if (realm.All().FirstOrDefault(rr => rr.OnlineID == r.RulesetInfo.OnlineID) == null) + realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); + } + + // add any other rulesets which have assemblies present but are not yet in the database. + foreach (var r in instances.Where(r => !(r is ILegacyRuleset))) + { + if (rulesets.FirstOrDefault(ri => ri.InstantiationInfo.Equals(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null) + { + var existingSameShortName = rulesets.FirstOrDefault(ri => ri.ShortName == r.RulesetInfo.ShortName); + + if (existingSameShortName != null) + { + // even if a matching InstantiationInfo was not found, there may be an existing ruleset with the same ShortName. + // this generally means the user or ruleset provider has renamed their dll but the underlying ruleset is *likely* the same one. + // in such cases, update the instantiation info of the existing entry to point to the new one. + existingSameShortName.InstantiationInfo = r.RulesetInfo.InstantiationInfo; + } + else + realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); + } + } + + List detachedRulesets = new List(); + + // perform a consistency check and detach final rulesets from realm for cross-thread runtime usage. + foreach (var r in rulesets.OrderBy(r => r.OnlineID)) + { + try + { + var resolvedType = Type.GetType(r.InstantiationInfo) + ?? throw new RulesetLoadException(@"Type could not be resolved"); + + var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo + ?? throw new RulesetLoadException(@"Instantiation failure"); + + // If a ruleset isn't up-to-date with the API, it could cause a crash at an arbitrary point of execution. + // To eagerly handle cases of missing implementations, enumerate all types here and mark as non-available on throw. + resolvedType.Assembly.GetTypes(); + + r.Name = instanceInfo.Name; + r.ShortName = instanceInfo.ShortName; + r.InstantiationInfo = instanceInfo.InstantiationInfo; + r.Available = true; + + detachedRulesets.Add(r.Clone()); + } + catch (Exception ex) + { + r.Available = false; + Logger.Log($"Could not load ruleset {r}: {ex.Message}"); + } + } + + availableRulesets.AddRange(detachedRulesets.OrderBy(r => r)); + }); + } + } +} diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index dd25005006..6f88d97a58 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -7,34 +7,26 @@ using System.IO; using System.Linq; using System.Reflection; using osu.Framework; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Logging; using osu.Framework.Platform; -using osu.Game.Database; #nullable enable namespace osu.Game.Rulesets { - public class RulesetStore : IDisposable, IRulesetStore + public abstract class RulesetStore : IDisposable, IRulesetStore { - private readonly RealmAccess realmAccess; - private const string ruleset_library_prefix = @"osu.Game.Rulesets"; - private readonly Dictionary loadedAssemblies = new Dictionary(); + protected readonly Dictionary LoadedAssemblies = new Dictionary(); /// /// All available rulesets. /// - public IEnumerable AvailableRulesets => availableRulesets; + public abstract IEnumerable AvailableRulesets { get; } - private readonly List availableRulesets = new List(); - - public RulesetStore(RealmAccess realm, Storage? storage = null) + protected RulesetStore(Storage? storage = null) { - realmAccess = realm; - // On android in release configuration assemblies are loaded from the apk directly into memory. // We cannot read assemblies from cwd, so should check loaded assemblies instead. loadFromAppDomain(); @@ -53,8 +45,6 @@ namespace osu.Game.Rulesets var rulesetStorage = storage?.GetStorageForDirectory(@"rulesets"); if (rulesetStorage != null) loadUserRulesets(rulesetStorage); - - addMissingRulesets(); } /// @@ -95,80 +85,7 @@ namespace osu.Game.Rulesets if (domainAssembly != null) return domainAssembly; - return loadedAssemblies.Keys.FirstOrDefault(a => a.FullName == asm.FullName); - } - - private void addMissingRulesets() - { - realmAccess.Write(realm => - { - var rulesets = realm.All(); - - List instances = loadedAssemblies.Values - .Select(r => Activator.CreateInstance(r) as Ruleset) - .Where(r => r != null) - .Select(r => r.AsNonNull()) - .ToList(); - - // add all legacy rulesets first to ensure they have exclusive choice of primary key. - foreach (var r in instances.Where(r => r is ILegacyRuleset)) - { - if (realm.All().FirstOrDefault(rr => rr.OnlineID == r.RulesetInfo.OnlineID) == null) - realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); - } - - // add any other rulesets which have assemblies present but are not yet in the database. - foreach (var r in instances.Where(r => !(r is ILegacyRuleset))) - { - if (rulesets.FirstOrDefault(ri => ri.InstantiationInfo.Equals(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null) - { - var existingSameShortName = rulesets.FirstOrDefault(ri => ri.ShortName == r.RulesetInfo.ShortName); - - if (existingSameShortName != null) - { - // even if a matching InstantiationInfo was not found, there may be an existing ruleset with the same ShortName. - // this generally means the user or ruleset provider has renamed their dll but the underlying ruleset is *likely* the same one. - // in such cases, update the instantiation info of the existing entry to point to the new one. - existingSameShortName.InstantiationInfo = r.RulesetInfo.InstantiationInfo; - } - else - realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); - } - } - - List detachedRulesets = new List(); - - // perform a consistency check and detach final rulesets from realm for cross-thread runtime usage. - foreach (var r in rulesets.OrderBy(r => r.OnlineID)) - { - try - { - var resolvedType = Type.GetType(r.InstantiationInfo) - ?? throw new RulesetLoadException(@"Type could not be resolved"); - - var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo - ?? throw new RulesetLoadException(@"Instantiation failure"); - - // If a ruleset isn't up-to-date with the API, it could cause a crash at an arbitrary point of execution. - // To eagerly handle cases of missing implementations, enumerate all types here and mark as non-available on throw. - resolvedType.Assembly.GetTypes(); - - r.Name = instanceInfo.Name; - r.ShortName = instanceInfo.ShortName; - r.InstantiationInfo = instanceInfo.InstantiationInfo; - r.Available = true; - - detachedRulesets.Add(r.Clone()); - } - catch (Exception ex) - { - r.Available = false; - Logger.Log($"Could not load ruleset {r}: {ex.Message}"); - } - } - - availableRulesets.AddRange(detachedRulesets.OrderBy(r => r)); - }); + return LoadedAssemblies.Keys.FirstOrDefault(a => a.FullName == asm.FullName); } private void loadFromAppDomain() @@ -214,7 +131,7 @@ namespace osu.Game.Rulesets { string? filename = Path.GetFileNameWithoutExtension(file); - if (loadedAssemblies.Values.Any(t => Path.GetFileNameWithoutExtension(t.Assembly.Location) == filename)) + if (LoadedAssemblies.Values.Any(t => Path.GetFileNameWithoutExtension(t.Assembly.Location) == filename)) return; try @@ -229,17 +146,17 @@ namespace osu.Game.Rulesets private void addRuleset(Assembly assembly) { - if (loadedAssemblies.ContainsKey(assembly)) + if (LoadedAssemblies.ContainsKey(assembly)) return; // the same assembly may be loaded twice in the same AppDomain (currently a thing in certain Rider versions https://youtrack.jetbrains.com/issue/RIDER-48799). // as a failsafe, also compare by FullName. - if (loadedAssemblies.Any(a => a.Key.FullName == assembly.FullName)) + if (LoadedAssemblies.Any(a => a.Key.FullName == assembly.FullName)) return; try { - loadedAssemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset))); + LoadedAssemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset))); } catch (Exception e) { From 0138f22c8d12b81cc6343798c8be1661a45f8524 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Feb 2022 17:13:51 +0900 Subject: [PATCH 0703/5199] Update existing usages to point to `RealmRulesetStore` --- .../Database/BeatmapImporterTests.cs | 50 +++++++++---------- osu.Game.Tests/Database/RulesetStoreTests.cs | 8 +-- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 2 +- .../Background/TestSceneUserDimBackgrounds.cs | 2 +- .../TestSceneManageCollectionsDialog.cs | 2 +- .../Visual/Multiplayer/QueueModeTestScene.cs | 2 +- .../TestSceneDrawableRoomPlaylist.cs | 2 +- .../Multiplayer/TestSceneMultiplayer.cs | 2 +- .../TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../TestSceneMultiplayerMatchSubScreen.cs | 2 +- .../TestSceneMultiplayerPlaylist.cs | 2 +- .../TestSceneMultiplayerQueueList.cs | 2 +- .../TestSceneMultiplayerReadyButton.cs | 2 +- .../TestSceneMultiplayerSpectateButton.cs | 2 +- .../TestScenePlaylistsSongSelect.cs | 2 +- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 2 +- .../TestScenePlaylistsRoomCreation.cs | 2 +- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 2 +- .../SongSelect/TestSceneFilterControl.cs | 2 +- .../SongSelect/TestScenePlaySongSelect.cs | 2 +- .../SongSelect/TestSceneTopLocalRank.cs | 2 +- .../TestSceneDeleteLocalScore.cs | 2 +- 22 files changed, 49 insertions(+), 49 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 2c7d0211a0..9d67381b5a 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using (var importer = new BeatmapModelManager(realm, storage)) - using (new RulesetStore(realm, storage)) + using (new RealmRulesetStore(realm, storage)) { Live? beatmapSet; @@ -85,7 +85,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using (var importer = new BeatmapModelManager(realm, storage)) - using (new RulesetStore(realm, storage)) + using (new RealmRulesetStore(realm, storage)) { Live? beatmapSet; @@ -142,7 +142,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using (var importer = new BeatmapModelManager(realm, storage)) - using (new RulesetStore(realm, storage)) + using (new RealmRulesetStore(realm, storage)) { Live? imported; @@ -171,7 +171,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); await LoadOszIntoStore(importer, realm.Realm); }); @@ -183,7 +183,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -201,7 +201,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -215,7 +215,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); string? tempPath = TestResources.GetTestBeatmapForImport(); @@ -245,7 +245,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm); @@ -265,7 +265,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -314,7 +314,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -366,7 +366,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -414,7 +414,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -463,7 +463,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -496,7 +496,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); var progressNotification = new ImportProgressNotification(); @@ -532,7 +532,7 @@ namespace osu.Game.Tests.Database }; using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -582,7 +582,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -606,7 +606,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realmFactory, storage) => { using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var store = new RealmRulesetStore(realmFactory, storage); var imported = await LoadOszIntoStore(importer, realmFactory.Realm); @@ -638,7 +638,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new NonOptimisedBeatmapImporter(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -662,7 +662,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -688,7 +688,7 @@ namespace osu.Game.Tests.Database RunTestWithRealm((realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); var metadata = new BeatmapMetadata { @@ -734,7 +734,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); using (File.OpenRead(temp)) @@ -751,7 +751,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -787,7 +787,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -829,7 +829,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -880,7 +880,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); - using var store = new RulesetStore(realm, storage); + using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); await importer.Import(temp); diff --git a/osu.Game.Tests/Database/RulesetStoreTests.cs b/osu.Game.Tests/Database/RulesetStoreTests.cs index 7544142b70..f48b5cba11 100644 --- a/osu.Game.Tests/Database/RulesetStoreTests.cs +++ b/osu.Game.Tests/Database/RulesetStoreTests.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realm, storage) => { - var rulesets = new RulesetStore(realm, storage); + var rulesets = new RealmRulesetStore(realm, storage); Assert.AreEqual(4, rulesets.AvailableRulesets.Count()); Assert.AreEqual(4, realm.Realm.All().Count()); @@ -26,8 +26,8 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realm, storage) => { - var rulesets = new RulesetStore(realm, storage); - var rulesets2 = new RulesetStore(realm, storage); + var rulesets = new RealmRulesetStore(realm, storage); + var rulesets2 = new RealmRulesetStore(realm, storage); Assert.AreEqual(4, rulesets.AvailableRulesets.Count()); Assert.AreEqual(4, rulesets2.AvailableRulesets.Count()); @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realm, storage) => { - var rulesets = new RulesetStore(realm, storage); + var rulesets = new RealmRulesetStore(realm, storage); Assert.IsFalse(rulesets.AvailableRulesets.First().IsManaged); Assert.IsFalse(rulesets.GetRuleset(0)?.IsManaged); diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 9aa04dda92..c4bffbc0ab 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Online [BackgroundDependencyLoader] private void load(AudioManager audio, GameHost host) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); Dependencies.CacheAs(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API)); } diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 40e7c0a844..9f708ace70 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Background [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(new OsuConfigManager(LocalStorage)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index d4c13059da..e40dd58663 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Collections [BackgroundDependencyLoader] private void load(GameHost host) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, Audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 2a3b44d619..2c994576f3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 8c10a0d0d9..4dc7dc1c42 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 3f9aec3a42..fc8c3f7f58 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 457b53ae61..381b9b58bd 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 7d2ef8276d..1eebe781f1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index f84abc7443..af12046d76 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index bf06b6ad73..397c2d16d1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 609693e54b..6851dba721 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index c4ea78116a..0033a4ca74 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 3333afc88b..f8d62c9840 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 50b3f52047..05b97e333f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index 578ea63b4e..a6f1762cb3 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Playlists [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 667fd08084..8c3cc02c83 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.SongSelect { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Cache(rulesetStore = new RulesetStore(Realm)); + dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, Scheduler)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index b384061531..b7ec128596 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.SongSelect [BackgroundDependencyLoader] private void load(GameHost host) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, Audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index d34aff8a23..e2b50e38c2 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.SongSelect { // These DI caches are required to ensure for interactive runs this test scene doesn't nuke all user beatmaps in the local install. // At a point we have isolated interactive test runs enough, this can likely be removed. - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(Realm); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, defaultBeatmap = Beatmap.Default)); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs index 8e5f76a2eb..39680d157b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.SongSelect [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, Realm, Scheduler)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index da4cf9c6e3..a0a1feff36 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.UserInterface { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Cache(rulesetStore = new RulesetStore(Realm)); + dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, Realm, Scheduler)); Dependencies.Cache(Realm); From 13086541f0884a4baec68afd1ee874830d6c366f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Feb 2022 17:12:57 +0900 Subject: [PATCH 0704/5199] Add static `RulesetStore` to `LegacyBeatmapDecoder` --- osu.Game/Beatmaps/Formats/Decoder.cs | 10 ++++++++++ osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 9 ++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs index 845ac20db0..8eb238a184 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using osu.Game.IO; +using osu.Game.Rulesets; namespace osu.Game.Beatmaps.Formats { @@ -37,6 +38,15 @@ namespace osu.Game.Beatmaps.Formats LegacyStoryboardDecoder.Register(); } + /// + /// Register dependencies for use with static decoder classes. + /// + /// A store containing all available rulesets (used by ). + public static void RegisterDependencies(RulesetStore rulesets) + { + LegacyBeatmapDecoder.RulesetStore = rulesets; + } + /// /// Retrieves a to parse a . /// diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 07ada8ecc4..fb7882c182 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Data; using System.IO; using System.Linq; using osu.Framework.Extensions; @@ -11,12 +12,15 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Timing; using osu.Game.IO; +using osu.Game.Rulesets; using osu.Game.Rulesets.Objects.Legacy; namespace osu.Game.Beatmaps.Formats { public class LegacyBeatmapDecoder : LegacyDecoder { + protected internal static RulesetStore RulesetStore; + private Beatmap beatmap; private ConvertHitObjectParser parser; @@ -40,6 +44,9 @@ namespace osu.Game.Beatmaps.Formats public LegacyBeatmapDecoder(int version = LATEST_VERSION) : base(version) { + if (RulesetStore == null) + throw new InvalidOperationException($"Call {nameof(Decoder)}.{nameof(RegisterDependencies)} before using {nameof(LegacyBeatmapDecoder)}."); + // BeatmapVersion 4 and lower had an incorrect offset (stable has this set as 24ms off) offset = FormatVersion < 5 ? 24 : 0; } @@ -158,7 +165,7 @@ namespace osu.Game.Beatmaps.Formats case @"Mode": int rulesetID = Parsing.ParseInt(pair.Value); - beatmap.BeatmapInfo.RulesetID = rulesetID; + beatmap.BeatmapInfo.Ruleset = RulesetStore.GetRuleset(rulesetID) ?? throw new ArgumentException("Ruleset is not available locally."); switch (rulesetID) { From d0efecfc9cff723d92a7e9289842737152e6dc61 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Feb 2022 17:13:25 +0900 Subject: [PATCH 0705/5199] Add `RulesetStore` for use where realm is not present (ie. other projects) --- osu.Game/Rulesets/AssemblyRulesetStore.cs | 35 +++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 osu.Game/Rulesets/AssemblyRulesetStore.cs diff --git a/osu.Game/Rulesets/AssemblyRulesetStore.cs b/osu.Game/Rulesets/AssemblyRulesetStore.cs new file mode 100644 index 0000000000..b7378ccf61 --- /dev/null +++ b/osu.Game/Rulesets/AssemblyRulesetStore.cs @@ -0,0 +1,35 @@ +// 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.Extensions.ObjectExtensions; +using osu.Framework.Platform; + +#nullable enable + +namespace osu.Game.Rulesets +{ + public class AssemblyRulesetStore : RulesetStore + { + public override IEnumerable AvailableRulesets => availableRulesets; + + private readonly List availableRulesets = new List(); + + public AssemblyRulesetStore(Storage? storage = null) + : base(storage) + + { + List instances = LoadedAssemblies.Values + .Select(r => Activator.CreateInstance(r) as Ruleset) + .Where(r => r != null) + .Select(r => r.AsNonNull()) + .ToList(); + + // add all legacy rulesets first to ensure they have exclusive choice of primary key. + foreach (var r in instances.Where(r => r is ILegacyRuleset)) + availableRulesets.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); + } + } +} From 5477af08c5d972988df03b7834ff5ff0f039a9cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Feb 2022 17:21:06 +0900 Subject: [PATCH 0706/5199] Register an `AssemblyRulesetStore` in tests which don't use `OsuGameBase` --- osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs | 3 +++ osu.Game.Rulesets.Osu.Tests/StackingTest.cs | 6 ++++++ .../Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 7 +++++++ osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs | 7 +++++++ osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs | 7 +++++++ osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs | 3 +++ osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 1 - osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs | 6 ++++++ osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs | 6 ++++++ 9 files changed, 45 insertions(+), 1 deletion(-) diff --git a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs index 1d207d04c7..0dd66be0b4 100644 --- a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs +++ b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs @@ -8,6 +8,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.IO.Archives; +using osu.Game.Rulesets; using osu.Game.Tests.Resources; namespace osu.Game.Benchmarks @@ -18,6 +19,8 @@ namespace osu.Game.Benchmarks public override void SetUp() { + Decoder.RegisterDependencies(new AssemblyRulesetStore()); + using (var resources = new DllResourceStore(typeof(TestResources).Assembly)) using (var archive = resources.GetStream("Resources/Archives/241526 Soleily - Renatus.osz")) using (var reader = new ZipArchiveReader(archive)) diff --git a/osu.Game.Rulesets.Osu.Tests/StackingTest.cs b/osu.Game.Rulesets.Osu.Tests/StackingTest.cs index 871afdb09d..927c0c3e1e 100644 --- a/osu.Game.Rulesets.Osu.Tests/StackingTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/StackingTest.cs @@ -18,6 +18,12 @@ namespace osu.Game.Rulesets.Osu.Tests [TestFixture] public class StackingTest { + [SetUp] + public void SetUp() + { + Decoder.RegisterDependencies(new AssemblyRulesetStore()); + } + [Test] public void TestStacking() { diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 468cb7683c..ad088d298b 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -11,6 +11,7 @@ using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Timing; using osu.Game.IO; +using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Mods; @@ -29,6 +30,12 @@ namespace osu.Game.Tests.Beatmaps.Formats [TestFixture] public class LegacyBeatmapDecoderTest { + [SetUp] + public void SetUp() + { + Decoder.RegisterDependencies(new AssemblyRulesetStore()); + } + [Test] public void TestDecodeBeatmapVersion() { diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 2eb75259d9..b7989adcb8 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -10,6 +10,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.IO.Serialization; +using osu.Game.Rulesets; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; @@ -22,6 +23,12 @@ namespace osu.Game.Tests.Beatmaps.Formats [TestFixture] public class OsuJsonDecoderTest { + [SetUp] + public void SetUp() + { + Decoder.RegisterDependencies(new AssemblyRulesetStore()); + } + private const string normal = "Soleily - Renatus (Gamu) [Insane].osu"; private const string marathon = "Within Temptation - The Unforgiving (Armin) [Marathon].osu"; private const string with_sb = "Kozato snow - Rengetsu Ouka (_Kiva) [Yuki YukI].osu"; diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index 810ea5dbd0..64a52b2b01 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -9,12 +9,19 @@ using osu.Game.Tests.Resources; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.IO.Archives; +using osu.Game.Rulesets; namespace osu.Game.Tests.Beatmaps.IO { [TestFixture] public class OszArchiveReaderTest { + [SetUp] + public void SetUp() + { + Decoder.RegisterDependencies(new AssemblyRulesetStore()); + } + [Test] public void TestReadBeatmaps() { diff --git a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs index 44a908b756..6cf760723a 100644 --- a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs +++ b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs @@ -8,6 +8,7 @@ using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; +using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; @@ -28,6 +29,8 @@ namespace osu.Game.Tests.Editing [SetUp] public void Setup() { + Decoder.RegisterDependencies(new AssemblyRulesetStore()); + patcher = new LegacyEditorBeatmapPatcher(current = new EditorBeatmap(new OsuBeatmap { BeatmapInfo = diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index fb7882c182..9626f39ec3 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Data; using System.IO; using System.Linq; using osu.Framework.Extensions; diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 8d622955b7..d5b7e3152b 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -34,6 +34,12 @@ namespace osu.Game.Tests.Beatmaps protected abstract string ResourceAssembly { get; } + [SetUp] + public void Setup() + { + Decoder.RegisterDependencies(new AssemblyRulesetStore()); + } + protected void Test(string name, params Type[] mods) { var ourResult = convert(name, mods.Select(m => (Mod)Activator.CreateInstance(m)).ToArray()); diff --git a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs index 9f8811c7f9..78b2558640 100644 --- a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs +++ b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs @@ -22,6 +22,12 @@ namespace osu.Game.Tests.Beatmaps protected abstract string ResourceAssembly { get; } + [SetUp] + public void Setup() + { + Decoder.RegisterDependencies(new AssemblyRulesetStore()); + } + protected void Test(double expected, string name, params Mod[] mods) { // Platform-dependent math functions (Pow, Cbrt, Exp, etc) may result in minute differences. From 5ffd3ff82acbf76acc91accf666b76ab1255b3e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Feb 2022 17:25:16 +0900 Subject: [PATCH 0707/5199] Add xmldoc and allow constructing an `AssemblyRulesetStore` with a directory path --- osu.Game/Rulesets/AssemblyRulesetStore.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game/Rulesets/AssemblyRulesetStore.cs b/osu.Game/Rulesets/AssemblyRulesetStore.cs index b7378ccf61..7313a77aa5 100644 --- a/osu.Game/Rulesets/AssemblyRulesetStore.cs +++ b/osu.Game/Rulesets/AssemblyRulesetStore.cs @@ -11,12 +11,28 @@ using osu.Framework.Platform; namespace osu.Game.Rulesets { + /// + /// A ruleset store that populates from loaded assemblies (and optionally, assemblies in a storage). + /// public class AssemblyRulesetStore : RulesetStore { public override IEnumerable AvailableRulesets => availableRulesets; private readonly List availableRulesets = new List(); + /// + /// Create an assembly ruleset store that populates from loaded assemblies and an external location. + /// + /// An path containing ruleset DLLs. + public AssemblyRulesetStore(string path) + : this(new NativeStorage(path)) + { + } + + /// + /// Create an assembly ruleset store that populates from loaded assemblies and an optional storage source. + /// + /// An optional storage containing ruleset DLLs. public AssemblyRulesetStore(Storage? storage = null) : base(storage) From 23933fc881f07c2cc96b10589531387e6b2658bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Feb 2022 17:32:22 +0900 Subject: [PATCH 0708/5199] Update xmldoc to mention that multipliers are not applied anywhere --- osu.Game/Rulesets/Mods/Mod.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 5555770822..9a45e2458d 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -80,8 +80,11 @@ namespace osu.Game.Rulesets.Mods } /// - /// The score multiplier of this mod. + /// The (legacy) score multiplier of this mod. /// + /// + /// This is not applied for newly set scores, but may be required for display purposes when showing legacy scores. + /// [JsonIgnore] public virtual double ScoreMultiplier => 1; From 28fcad92810097c4e4fb0cce0757bea2ec43d7b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Feb 2022 17:33:55 +0900 Subject: [PATCH 0709/5199] Update failing test to not account for no-longer-existing multiplier --- osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index de795241bf..0b9db8e20a 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -147,8 +147,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("player score matching expected bonus score", () => { - // multipled by 2 to nullify the score multiplier. (autoplay mod selected) - double totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2; + double totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value; return totalScore == (int)(drawableSpinner.Result.RateAdjustedRotation / 360) * new SpinnerTick().CreateJudgement().MaxNumericResult; }); From a0ee86ddd2761e0e901235939bed9e3f6d4f1334 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 16 Feb 2022 19:50:27 +0900 Subject: [PATCH 0710/5199] Fix improperly considering rate adjustment mods --- .../Difficulty/ManiaDifficultyAttributes.cs | 4 ++-- .../Difficulty/ManiaDifficultyCalculator.cs | 13 ++++--------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs index efd5868504..5b7a460079 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs @@ -10,10 +10,10 @@ namespace osu.Game.Rulesets.Mania.Difficulty public class ManiaDifficultyAttributes : DifficultyAttributes { /// - /// The perceived hit window for a GREAT hit inclusive of rate-adjusting mods (DT/HT/etc). + /// The hit window for a GREAT hit inclusive of rate-adjusting mods (DT/HT/etc). /// /// - /// Rate-adjusting mods don't directly affect the hit window, but have a perceived effect as a result of adjusting audio timing. + /// Rate-adjusting mods do not affect the hit window at all in osu-stable. /// [JsonProperty("great_hit_window")] public double GreatHitWindow { get; set; } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 1f82eb7ccd..d200daa7fa 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty { StarRating = skills[0].DifficultyValue() * star_scaling_factor, Mods = mods, - GreatHitWindow = Math.Ceiling(getHitWindow300(mods) / clockRate), + GreatHitWindow = Math.Ceiling((int)(getHitWindow300(mods) * clockRate) / clockRate), ScoreMultiplier = getScoreMultiplier(mods), MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1), }; @@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty } } - private int getHitWindow300(Mod[] mods) + private double getHitWindow300(Mod[] mods) { if (isForCurrentRuleset) { @@ -121,19 +121,14 @@ namespace osu.Game.Rulesets.Mania.Difficulty return applyModAdjustments(47, mods); - static int applyModAdjustments(double value, Mod[] mods) + static double applyModAdjustments(double value, Mod[] mods) { if (mods.Any(m => m is ManiaModHardRock)) value /= 1.4; else if (mods.Any(m => m is ManiaModEasy)) value *= 1.4; - if (mods.Any(m => m is ManiaModDoubleTime)) - value *= 1.5; - else if (mods.Any(m => m is ManiaModHalfTime)) - value *= 0.75; - - return (int)value; + return value; } } From 0b37efc9850f57a6ddf456b0e68fc879a536a2e0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 16 Feb 2022 20:07:26 +0900 Subject: [PATCH 0711/5199] Add explanatory note --- osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index d200daa7fa..b17aa7fc4d 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -48,6 +48,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty { StarRating = skills[0].DifficultyValue() * star_scaling_factor, Mods = mods, + // In osu-stable mania, rate-adjustment mods don't affect the hit window. + // This is done the way it is to introduce fractional differences in order to match osu-stable for the time being. GreatHitWindow = Math.Ceiling((int)(getHitWindow300(mods) * clockRate) / clockRate), ScoreMultiplier = getScoreMultiplier(mods), MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1), From c124034cf3f671cc44b950851bb6a2f3900524cc Mon Sep 17 00:00:00 2001 From: dekrain Date: Wed, 16 Feb 2022 23:18:14 +0100 Subject: [PATCH 0712/5199] Add text displaying recent score time --- .../Online/Leaderboards/LeaderboardScore.cs | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index c2393a5de5..f77982535d 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.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 osu.Framework.Allocation; @@ -29,6 +30,7 @@ using osuTK; using osuTK.Graphics; using osu.Game.Online.API; using osu.Game.Utils; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Online.Leaderboards { @@ -42,6 +44,7 @@ namespace osu.Game.Online.Leaderboards private const float edge_margin = 5; private const float background_alpha = 0.25f; private const float rank_width = 35; + private const float date_width = 35; protected Container RankContainer { get; private set; } @@ -100,10 +103,18 @@ namespace osu.Game.Online.Leaderboards RelativeSizeAxes = Axes.Y, Width = rank_width, }, + new Container + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = date_width, + Child = new DateLabel(Score.Date), + }, content = new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = rank_width, }, + Padding = new MarginPadding { Left = rank_width, Right = date_width, }, Children = new Drawable[] { new Container @@ -377,6 +388,36 @@ namespace osu.Game.Online.Leaderboards public LocalisableString TooltipText { get; } } + private class DateLabel : DrawableDate + { + public DateLabel(DateTimeOffset date) + : base(date, 20) + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + protected override string Format() + { + var now = DateTime.Now; + var difference = now - Date; + + // TODO(optional): support localisation (probably via `CommonStrings.CountHours()` etc.) + // requires pluralisable string support framework-side + + if (difference.TotalMinutes < 1) + return CommonStrings.TimeNow.ToString(); + if (difference.TotalHours < 1) + return $@"{Math.Ceiling(difference.TotalMinutes)}min"; + if (difference.TotalDays < 1) + return $@"{Math.Ceiling(difference.TotalHours)}h"; + if (difference.TotalDays < 7) + return $@"{Math.Ceiling(difference.TotalDays)}d"; + + return string.Empty; + } + } + public class LeaderboardScoreStatistic { public IconUsage Icon; From 333a305af3dcabba5591b17140be49ba4c5c14f3 Mon Sep 17 00:00:00 2001 From: dekrain Date: Thu, 17 Feb 2022 00:09:17 +0100 Subject: [PATCH 0713/5199] Use floor instead of ceiling --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index f77982535d..9ac1ab8075 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -408,11 +408,11 @@ namespace osu.Game.Online.Leaderboards if (difference.TotalMinutes < 1) return CommonStrings.TimeNow.ToString(); if (difference.TotalHours < 1) - return $@"{Math.Ceiling(difference.TotalMinutes)}min"; + return $@"{Math.Floor(difference.TotalMinutes)}min"; if (difference.TotalDays < 1) - return $@"{Math.Ceiling(difference.TotalHours)}h"; + return $@"{Math.Floor(difference.TotalHours)}h"; if (difference.TotalDays < 7) - return $@"{Math.Ceiling(difference.TotalDays)}d"; + return $@"{Math.Floor(difference.TotalDays)}d"; return string.Empty; } From cb9ffc655acf4509ce174c770788b9bfeb715339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Feb 2022 22:32:42 +0100 Subject: [PATCH 0714/5199] Add tests showing expected behaviour of naming helper --- osu.Game.Tests/Utils/NamingUtilsTest.cs | 132 ++++++++++++++++++++++++ osu.Game/Utils/NamingUtils.cs | 15 +++ 2 files changed, 147 insertions(+) create mode 100644 osu.Game.Tests/Utils/NamingUtilsTest.cs create mode 100644 osu.Game/Utils/NamingUtils.cs diff --git a/osu.Game.Tests/Utils/NamingUtilsTest.cs b/osu.Game.Tests/Utils/NamingUtilsTest.cs new file mode 100644 index 0000000000..62e688db90 --- /dev/null +++ b/osu.Game.Tests/Utils/NamingUtilsTest.cs @@ -0,0 +1,132 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Game.Utils; + +namespace osu.Game.Tests.Utils +{ + [TestFixture] + public class NamingUtilsTest + { + [Test] + public void TestEmptySet() + { + string nextBestName = NamingUtils.GetNextBestName(Enumerable.Empty(), "New Difficulty"); + + Assert.AreEqual("New Difficulty", nextBestName); + } + + [Test] + public void TestNotTaken() + { + string[] existingNames = + { + "Something", + "Entirely", + "Different" + }; + + string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty"); + + Assert.AreEqual("New Difficulty", nextBestName); + } + + [Test] + public void TestNotTakenButClose() + { + string[] existingNames = + { + "New Difficulty(1)", + "New Difficulty (abcd)", + "New Difficulty but not really" + }; + + string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty"); + + Assert.AreEqual("New Difficulty", nextBestName); + } + + [Test] + public void TestAlreadyTaken() + { + string[] existingNames = + { + "New Difficulty" + }; + + string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty"); + + Assert.AreEqual("New Difficulty (1)", nextBestName); + } + + [Test] + public void TestAlreadyTakenWithDifferentCase() + { + string[] existingNames = + { + "new difficulty" + }; + + string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty"); + + Assert.AreEqual("New Difficulty (1)", nextBestName); + } + + [Test] + public void TestAlreadyTakenWithBrackets() + { + string[] existingNames = + { + "new difficulty (copy)" + }; + + string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty (copy)"); + + Assert.AreEqual("New Difficulty (copy) (1)", nextBestName); + } + + [Test] + public void TestMultipleAlreadyTaken() + { + string[] existingNames = + { + "New Difficulty", + "New difficulty (1)", + "new Difficulty (2)", + "New DIFFICULTY (3)" + }; + + string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty"); + + Assert.AreEqual("New Difficulty (4)", nextBestName); + } + + [Test] + public void TestEvenMoreAlreadyTaken() + { + string[] existingNames = Enumerable.Range(1, 30).Select(i => $"New Difficulty ({i})").Append("New Difficulty").ToArray(); + + string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty"); + + Assert.AreEqual("New Difficulty (31)", nextBestName); + } + + [Test] + public void TestMultipleAlreadyTakenWithGaps() + { + string[] existingNames = + { + "New Difficulty", + "New Difficulty (1)", + "New Difficulty (4)", + "New Difficulty (9)" + }; + + string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty"); + + Assert.AreEqual("New Difficulty (2)", nextBestName); + } + } +} diff --git a/osu.Game/Utils/NamingUtils.cs b/osu.Game/Utils/NamingUtils.cs new file mode 100644 index 0000000000..1b48f57932 --- /dev/null +++ b/osu.Game/Utils/NamingUtils.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; + +namespace osu.Game.Utils +{ + public static class NamingUtils + { + public static string GetNextBestName(IEnumerable existingNames, string desiredName) + { + return null; + } + } +} From e09570c31bf18af340d529fc59448b1b694db270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Feb 2022 23:00:45 +0100 Subject: [PATCH 0715/5199] Implement best-name-finding helper method --- osu.Game/Utils/NamingUtils.cs | 48 ++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/osu.Game/Utils/NamingUtils.cs b/osu.Game/Utils/NamingUtils.cs index 1b48f57932..482e3d0954 100644 --- a/osu.Game/Utils/NamingUtils.cs +++ b/osu.Game/Utils/NamingUtils.cs @@ -2,14 +2,60 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Text.RegularExpressions; namespace osu.Game.Utils { public static class NamingUtils { + /// + /// Given a set of and a target , + /// finds a "best" name closest to that is not in . + /// + /// + /// + /// This helper is most useful in scenarios when creating new objects in a set + /// (such as adding new difficulties to a beatmap set, or creating a clone of an existing object that needs a unique name). + /// If is already present in , + /// this method will append the lowest possible number in brackets that doesn't conflict with + /// to and return that. + /// See osu.Game.Tests.Utils.NamingUtilsTest for concrete examples of behaviour. + /// + /// + /// and are compared in a case-insensitive manner, + /// so this method is safe to use for naming files in a platform-invariant manner. + /// + /// public static string GetNextBestName(IEnumerable existingNames, string desiredName) { - return null; + string pattern = $@"^(?i){Regex.Escape(desiredName)}(?-i)( \((?[1-9][0-9]*)\))?$"; + var regex = new Regex(pattern, RegexOptions.Compiled); + var takenNumbers = new HashSet(); + + foreach (string name in existingNames) + { + var match = regex.Match(name); + if (!match.Success) + continue; + + string copyNumberString = match.Groups[@"copyNumber"].Value; + + if (string.IsNullOrEmpty(copyNumberString)) + { + takenNumbers.Add(0); + continue; + } + + takenNumbers.Add(int.Parse(copyNumberString)); + } + + int bestNumber = 0; + while (takenNumbers.Contains(bestNumber)) + bestNumber += 1; + + return bestNumber == 0 + ? desiredName + : $"{desiredName} ({bestNumber})"; } } } From 8a08bb7aaf88ec33c64c01659cd0ea9c42f93f8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Feb 2022 23:12:13 +0100 Subject: [PATCH 0716/5199] Use best-name-finding helper in new difficulty creation flow --- osu.Game/Beatmaps/BeatmapManager.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 777d5db2ad..5f7de0d762 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -22,6 +22,7 @@ using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Skinning; using osu.Game.Stores; +using osu.Game.Utils; #nullable enable @@ -123,7 +124,10 @@ namespace osu.Game.Beatmaps { var playableBeatmap = referenceWorkingBeatmap.GetPlayableBeatmap(rulesetInfo); - var newBeatmapInfo = new BeatmapInfo(rulesetInfo, new BeatmapDifficulty(), playableBeatmap.Metadata.DeepClone()); + var newBeatmapInfo = new BeatmapInfo(rulesetInfo, new BeatmapDifficulty(), playableBeatmap.Metadata.DeepClone()) + { + DifficultyName = NamingUtils.GetNextBestName(targetBeatmapSet.Beatmaps.Select(b => b.DifficultyName), "New Difficulty") + }; var newBeatmap = new Beatmap { BeatmapInfo = newBeatmapInfo }; foreach (var timingPoint in playableBeatmap.ControlPointInfo.TimingPoints) newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone()); @@ -150,8 +154,10 @@ namespace osu.Game.Beatmaps newBeatmap.BeatmapInfo = newBeatmapInfo = referenceWorkingBeatmap.BeatmapInfo.Clone(); // assign a new ID to the clone. newBeatmapInfo.ID = Guid.NewGuid(); - // add "(copy)" suffix to difficulty name to avoid clashes on save. - newBeatmapInfo.DifficultyName += " (copy)"; + // add "(copy)" suffix to difficulty name, and additionally ensure that it doesn't conflict with any other potentially pre-existing copies. + newBeatmapInfo.DifficultyName = NamingUtils.GetNextBestName( + targetBeatmapSet.Beatmaps.Select(b => b.DifficultyName), + $"{newBeatmapInfo.DifficultyName} (copy)"); // clear the hash, as that's what is used to match .osu files with their corresponding realm beatmaps. newBeatmapInfo.Hash = string.Empty; // clear online properties. From e459523afe5cd351e98a8558f41ca933ef3374b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Feb 2022 23:13:43 +0100 Subject: [PATCH 0717/5199] Adjust beatmap creation test cases to new behaviour --- .../Editing/TestSceneEditorBeatmapCreation.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 0a2f622da1..ecd4035edd 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -269,11 +269,12 @@ namespace osu.Game.Tests.Visual.Editing } [Test] - public void TestCreateNewBeatmapFailsWithBlankNamedDifficulties() + public void TestCreateMultipleNewDifficultiesSucceeds() { Guid setId = Guid.Empty; AddStep("retrieve set ID", () => setId = EditorBeatmap.BeatmapInfo.BeatmapSet!.ID); + AddStep("set difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = "New Difficulty"); AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => { @@ -282,15 +283,24 @@ namespace osu.Game.Tests.Visual.Editing }); AddStep("try to create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); - AddAssert("beatmap set unchanged", () => + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog.PerformOkAction()); + + AddUntilStep("wait for created", () => + { + string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName != "New Difficulty"; + }); + AddAssert("new difficulty has correct name", () => EditorBeatmap.BeatmapInfo.DifficultyName == "New Difficulty (1)"); + AddAssert("new difficulty persisted", () => { var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId); - return set != null && set.PerformRead(s => s.Beatmaps.Count == 1 && s.Files.Count == 1); + return set != null && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Files.Count == 2); }); } [Test] - public void TestCreateNewBeatmapFailsWithSameNamedDifficulties([Values] bool sameRuleset) + public void TestSavingBeatmapFailsWithSameNamedDifficulties([Values] bool sameRuleset) { Guid setId = Guid.Empty; const string duplicate_difficulty_name = "duplicate"; From aac1c53b060cc1885297baa3fc5709b6a5749097 Mon Sep 17 00:00:00 2001 From: Stedoss <29103029+Stedoss@users.noreply.github.com> Date: Thu, 17 Feb 2022 03:04:16 +0000 Subject: [PATCH 0718/5199] Remove creator name from playlist item panel beatmap text --- .../Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index dcf2a5a480..7533bfc8b6 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -291,10 +291,14 @@ namespace osu.Game.Screens.OnlinePlay if (Item.Beatmap.Value != null) { - beatmapText.AddLink(Item.Beatmap.Value.GetDisplayTitleRomanisable(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineID.ToString(), null, text => - { - text.Truncate = true; - }); + beatmapText.AddLink(Item.Beatmap.Value.GetDisplayTitleRomanisable(includeCreator: false), + LinkAction.OpenBeatmap, + Item.Beatmap.Value.OnlineID.ToString(), + null, + text => + { + text.Truncate = true; + }); } authorText.Clear(); From 7307e68e9c55bf1be4bde2e94d912e6a9df24f63 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Feb 2022 13:26:12 +0900 Subject: [PATCH 0719/5199] Revert "Merge pull request #16889 from smoogipoo/remove-mod-multiplier" This reverts commit 252b945d3b0cd4e6f33ac334f23bcf119a119c2a, reversing changes made to a1b39a96cfe3f4d0f02652ded5ebca09ba1d24e1. --- .../Difficulty/ManiaPerformanceCalculator.cs | 16 +++++------ .../TestSceneSpinnerRotation.cs | 3 ++- .../TestSceneFooterButtonMods.cs | 27 +++++++++++++++++-- osu.Game/Rulesets/Mods/Mod.cs | 7 ++--- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 13 ++++++++- osu.Game/Screens/Select/FooterButtonMods.cs | 25 +++++++++++++++++ 6 files changed, 74 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index beba29b8bb..8a8c41bb8a 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -43,11 +43,14 @@ namespace osu.Game.Rulesets.Mania.Difficulty countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss); - if (Attributes.ScoreMultiplier > 0) - { - // Scale score up, so it's comparable to other keymods - scaledScore *= 1.0 / Attributes.ScoreMultiplier; - } + IEnumerable scoreIncreaseMods = Ruleset.GetModsFor(ModType.DifficultyIncrease); + + double scoreMultiplier = 1.0; + foreach (var m in mods.Where(m => !scoreIncreaseMods.Contains(m))) + scoreMultiplier *= m.ScoreMultiplier; + + // Scale score up, so it's comparable to other keymods + scaledScore *= 1.0 / scoreMultiplier; // Arbitrary initial value for scaling pp in order to standardize distributions across game modes. // The specific number has no intrinsic meaning and can be adjusted as needed. @@ -77,9 +80,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty private double computeDifficultyValue() { - if (Attributes.ScoreMultiplier <= 0) - return 0; - double difficultyValue = Math.Pow(5 * Math.Max(1, Attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0; difficultyValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index 0b9db8e20a..de795241bf 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -147,7 +147,8 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("player score matching expected bonus score", () => { - double totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value; + // multipled by 2 to nullify the score multiplier. (autoplay mod selected) + double totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2; return totalScore == (int)(drawableSpinner.Result.RateAdjustedRotation / 360) * new SpinnerTick().CreateJudgement().MaxNumericResult; }); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs index e9014c0941..0631059d1a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; +using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Select; @@ -12,11 +14,11 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestSceneFooterButtonMods : OsuTestScene { - private readonly FooterButtonMods footerButtonMods; + private readonly TestFooterButtonMods footerButtonMods; public TestSceneFooterButtonMods() { - Add(footerButtonMods = new FooterButtonMods()); + Add(footerButtonMods = new TestFooterButtonMods()); } [Test] @@ -24,15 +26,19 @@ namespace osu.Game.Tests.Visual.UserInterface { var hiddenMod = new Mod[] { new OsuModHidden() }; AddStep(@"Add Hidden", () => changeMods(hiddenMod)); + AddAssert(@"Check Hidden multiplier", () => assertModsMultiplier(hiddenMod)); var hardRockMod = new Mod[] { new OsuModHardRock() }; AddStep(@"Add HardRock", () => changeMods(hardRockMod)); + AddAssert(@"Check HardRock multiplier", () => assertModsMultiplier(hardRockMod)); var doubleTimeMod = new Mod[] { new OsuModDoubleTime() }; AddStep(@"Add DoubleTime", () => changeMods(doubleTimeMod)); + AddAssert(@"Check DoubleTime multiplier", () => assertModsMultiplier(doubleTimeMod)); var multipleIncrementMods = new Mod[] { new OsuModDoubleTime(), new OsuModHidden(), new OsuModHardRock() }; AddStep(@"Add multiple Mods", () => changeMods(multipleIncrementMods)); + AddAssert(@"Check multiple mod multiplier", () => assertModsMultiplier(multipleIncrementMods)); } [Test] @@ -40,12 +46,15 @@ namespace osu.Game.Tests.Visual.UserInterface { var easyMod = new Mod[] { new OsuModEasy() }; AddStep(@"Add Easy", () => changeMods(easyMod)); + AddAssert(@"Check Easy multiplier", () => assertModsMultiplier(easyMod)); var noFailMod = new Mod[] { new OsuModNoFail() }; AddStep(@"Add NoFail", () => changeMods(noFailMod)); + AddAssert(@"Check NoFail multiplier", () => assertModsMultiplier(noFailMod)); var multipleDecrementMods = new Mod[] { new OsuModEasy(), new OsuModNoFail() }; AddStep(@"Add Multiple Mods", () => changeMods(multipleDecrementMods)); + AddAssert(@"Check multiple mod multiplier", () => assertModsMultiplier(multipleDecrementMods)); } [Test] @@ -54,11 +63,25 @@ namespace osu.Game.Tests.Visual.UserInterface var multipleMods = new Mod[] { new OsuModDoubleTime(), new OsuModFlashlight() }; AddStep(@"Add mods", () => changeMods(multipleMods)); AddStep(@"Clear selected mod", () => changeMods(Array.Empty())); + AddAssert(@"Check empty multiplier", () => assertModsMultiplier(Array.Empty())); } private void changeMods(IReadOnlyList mods) { footerButtonMods.Current.Value = mods; } + + private bool assertModsMultiplier(IEnumerable mods) + { + double multiplier = mods.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier); + string expectedValue = multiplier.Equals(1.0) ? string.Empty : $"{multiplier:N2}x"; + + return expectedValue == footerButtonMods.MultiplierText.Current.Value; + } + + private class TestFooterButtonMods : FooterButtonMods + { + public new OsuSpriteText MultiplierText => base.MultiplierText; + } } } diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 9a45e2458d..7136795461 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -80,13 +80,10 @@ namespace osu.Game.Rulesets.Mods } /// - /// The (legacy) score multiplier of this mod. + /// The score multiplier of this mod. /// - /// - /// This is not applied for newly set scores, but may be required for display purposes when showing legacy scores. - /// [JsonIgnore] - public virtual double ScoreMultiplier => 1; + public abstract double ScoreMultiplier { get; } /// /// Returns true if this mod is implemented (and playable). diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index ce1486b02f..79861c0ecc 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -92,6 +92,8 @@ namespace osu.Game.Rulesets.Scoring private readonly List hitEvents = new List(); private HitObject lastHitObject; + private double scoreMultiplier = 1; + public ScoreProcessor() { accuracyPortion = DefaultAccuracyPortion; @@ -109,6 +111,15 @@ namespace osu.Game.Rulesets.Scoring }; Mode.ValueChanged += _ => updateScore(); + Mods.ValueChanged += mods => + { + scoreMultiplier = 1; + + foreach (var m in mods.NewValue) + scoreMultiplier *= m.ScoreMultiplier; + + updateScore(); + }; } private readonly Dictionary scoreResultCounts = new Dictionary(); @@ -224,7 +235,7 @@ namespace osu.Game.Rulesets.Scoring case ScoringMode.Standardised: double accuracyScore = accuracyPortion * accuracyRatio; double comboScore = comboPortion * comboRatio; - return (max_score * (accuracyScore + comboScore) + getBonusScore(statistics)); + return (max_score * (accuracyScore + comboScore) + getBonusScore(statistics)) * scoreMultiplier; case ScoringMode.Classic: // This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring. diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 1f1aa4c4b3..5bbca5ca1a 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -6,11 +6,14 @@ using osu.Framework.Graphics; using osu.Game.Screens.Play.HUD; using osu.Game.Rulesets.Mods; using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osuTK; +using osuTK.Graphics; using osu.Game.Input.Bindings; namespace osu.Game.Screens.Select @@ -23,7 +26,10 @@ namespace osu.Game.Screens.Select set => modDisplay.Current = value; } + protected readonly OsuSpriteText MultiplierText; private readonly ModDisplay modDisplay; + private Color4 lowMultiplierColour; + private Color4 highMultiplierColour; public FooterButtonMods() { @@ -34,6 +40,12 @@ namespace osu.Game.Screens.Select Scale = new Vector2(0.8f), ExpansionMode = ExpansionMode.AlwaysContracted, }); + ButtonContentContainer.Add(MultiplierText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(weight: FontWeight.Bold), + }); } [BackgroundDependencyLoader] @@ -41,6 +53,8 @@ namespace osu.Game.Screens.Select { SelectedColour = colours.Yellow; DeselectedColour = SelectedColour.Opacity(0.5f); + lowMultiplierColour = colours.Red; + highMultiplierColour = colours.Green; Text = @"mods"; Hotkey = GlobalAction.ToggleModSelection; } @@ -54,6 +68,17 @@ namespace osu.Game.Screens.Select private void updateMultiplierText() { + double multiplier = Current.Value?.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier) ?? 1; + + MultiplierText.Text = multiplier.Equals(1.0) ? string.Empty : $"{multiplier:N2}x"; + + if (multiplier > 1.0) + MultiplierText.FadeColour(highMultiplierColour, 200); + else if (multiplier < 1.0) + MultiplierText.FadeColour(lowMultiplierColour, 200); + else + MultiplierText.FadeColour(Color4.White, 200); + if (Current.Value?.Count > 0) modDisplay.FadeIn(); else From 39a7bbdb9ad0cd23ccffc45b0760b228a078393d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Feb 2022 14:03:38 +0900 Subject: [PATCH 0720/5199] Fix mania PP calculator applying incorrect score multiplier --- .../Difficulty/ManiaPerformanceCalculator.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index 8a8c41bb8a..722cb55036 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -43,14 +43,11 @@ namespace osu.Game.Rulesets.Mania.Difficulty countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss); - IEnumerable scoreIncreaseMods = Ruleset.GetModsFor(ModType.DifficultyIncrease); - - double scoreMultiplier = 1.0; - foreach (var m in mods.Where(m => !scoreIncreaseMods.Contains(m))) - scoreMultiplier *= m.ScoreMultiplier; - - // Scale score up, so it's comparable to other keymods - scaledScore *= 1.0 / scoreMultiplier; + if (Attributes.ScoreMultiplier > 0) + { + // Scale score up, so it's comparable to other keymods + scaledScore *= 1.0 / Attributes.ScoreMultiplier; + } // Arbitrary initial value for scaling pp in order to standardize distributions across game modes. // The specific number has no intrinsic meaning and can be adjusted as needed. From dc74d17478605d1fbb03b434623a49a2f72970b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Feb 2022 16:45:20 +0900 Subject: [PATCH 0721/5199] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index eab2be1b72..24a0d20874 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 7b50c804ff..4c6f81defa 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 03a105673c..99b9de3fe2 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - + From 7bd731ae08210e58d2101aa3afef7807e15e4fa0 Mon Sep 17 00:00:00 2001 From: dekrain Date: Thu, 17 Feb 2022 10:12:35 +0100 Subject: [PATCH 0722/5199] Move the date next to the flag icon --- .../Online/Leaderboards/LeaderboardScore.cs | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 9ac1ab8075..a13d8eeffd 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -44,7 +44,6 @@ namespace osu.Game.Online.Leaderboards private const float edge_margin = 5; private const float background_alpha = 0.25f; private const float rank_width = 35; - private const float date_width = 35; protected Container RankContainer { get; private set; } @@ -59,7 +58,7 @@ namespace osu.Game.Online.Leaderboards public GlowingSpriteText ScoreText { get; private set; } - private Container flagBadgeContainer; + private FillFlowContainer flagBadgeAndDateContainer; private FillFlowContainer modsContainer; private List statisticsLabels; @@ -103,18 +102,10 @@ namespace osu.Game.Online.Leaderboards RelativeSizeAxes = Axes.Y, Width = rank_width, }, - new Container - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Width = date_width, - Child = new DateLabel(Score.Date), - }, content = new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = rank_width, Right = date_width, }, + Padding = new MarginPadding { Left = rank_width }, Children = new Drawable[] { new Container @@ -176,10 +167,12 @@ namespace osu.Game.Online.Leaderboards Spacing = new Vector2(10f, 0f), Children = new Drawable[] { - flagBadgeContainer = new Container + flagBadgeAndDateContainer = new FillFlowContainer { Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5f, 0f), Size = new Vector2(87f, 20f), Masking = true, Children = new Drawable[] @@ -189,6 +182,10 @@ namespace osu.Game.Online.Leaderboards Width = 30, RelativeSizeAxes = Axes.Y, }, + new DateLabel(Score.Date) + { + RelativeSizeAxes = Axes.Y, + }, }, }, new FillFlowContainer @@ -254,7 +251,7 @@ namespace osu.Game.Online.Leaderboards public override void Show() { - foreach (var d in new[] { avatar, nameLabel, ScoreText, scoreRank, flagBadgeContainer, modsContainer }.Concat(statisticsLabels)) + foreach (var d in new[] { avatar, nameLabel, ScoreText, scoreRank, flagBadgeAndDateContainer, modsContainer }.Concat(statisticsLabels)) d.FadeOut(); Alpha = 0; @@ -281,7 +278,7 @@ namespace osu.Game.Online.Leaderboards using (BeginDelayedSequence(50)) { - var drawables = new Drawable[] { flagBadgeContainer, modsContainer }.Concat(statisticsLabels).ToArray(); + var drawables = new Drawable[] { flagBadgeAndDateContainer, modsContainer }.Concat(statisticsLabels).ToArray(); for (int i = 0; i < drawables.Length; i++) drawables[i].FadeIn(100 + i * 50); } @@ -391,10 +388,9 @@ namespace osu.Game.Online.Leaderboards private class DateLabel : DrawableDate { public DateLabel(DateTimeOffset date) - : base(date, 20) + : base(date) { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; + Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold, italics: true); } protected override string Format() @@ -411,7 +407,7 @@ namespace osu.Game.Online.Leaderboards return $@"{Math.Floor(difference.TotalMinutes)}min"; if (difference.TotalDays < 1) return $@"{Math.Floor(difference.TotalHours)}h"; - if (difference.TotalDays < 7) + if (difference.TotalDays < 3) return $@"{Math.Floor(difference.TotalDays)}d"; return string.Empty; From f4d1e6f600343fda0ca9446297e9f8d0e5073ce6 Mon Sep 17 00:00:00 2001 From: dekrain Date: Thu, 17 Feb 2022 10:38:29 +0100 Subject: [PATCH 0723/5199] Add tests for timerefs --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 8 ++++++++ osu.Game/Online/Leaderboards/LeaderboardScore.cs | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 667fd08084..2f44633f4d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -197,6 +197,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now, Mods = new Mod[] { new OsuModHidden(), @@ -234,6 +235,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddSeconds(-30), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -254,6 +256,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddSeconds(-70), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -275,6 +278,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddMinutes(-40), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -296,6 +300,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddHours(-2), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -317,6 +322,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.9826, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddHours(-25), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -338,6 +344,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.9654, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddHours(-50), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -359,6 +366,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.6025, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddHours(-72), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index a13d8eeffd..8cfba68ea6 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -401,8 +401,10 @@ namespace osu.Game.Online.Leaderboards // TODO(optional): support localisation (probably via `CommonStrings.CountHours()` etc.) // requires pluralisable string support framework-side - if (difference.TotalMinutes < 1) + if (difference.TotalSeconds < 10) return CommonStrings.TimeNow.ToString(); + if (difference.TotalMinutes < 1) + return $@"{Math.Floor(difference.TotalSeconds)}s"; if (difference.TotalHours < 1) return $@"{Math.Floor(difference.TotalMinutes)}min"; if (difference.TotalDays < 1) From 3d5ed24e206a43990f36880464917045e8cdf2be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Feb 2022 21:04:59 +0900 Subject: [PATCH 0724/5199] Fix beatmap overlay leaderboards and links not working Completely aware that this isn't how it should be done, but would like to get this out in a hotfix release today. Maybe changes opinions on https://github.com/ppy/osu/pull/16890 structure? --- .../API/Requests/Responses/APIBeatmap.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index dca60e54cb..c53fab48ae 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -112,7 +112,27 @@ namespace osu.Game.Online.API.Requests.Responses public int OnlineID { get; set; } = -1; public string Name => $@"{nameof(APIRuleset)} (ID: {OnlineID})"; - public string ShortName => nameof(APIRuleset); + + public string ShortName + { + get + { + // TODO: this should really not exist. + switch (OnlineID) + { + case 0: return "osu"; + + case 1: return "taiko"; + + case 2: return "catch"; + + case 3: return "fruits"; + + default: throw new ArgumentOutOfRangeException(); + } + } + } + public string InstantiationInfo => string.Empty; public Ruleset CreateInstance() => throw new NotImplementedException(); From 9d0023c750a85c2cc78a6d6d5004f50231916a92 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Feb 2022 21:12:51 +0900 Subject: [PATCH 0725/5199] Fix incorrect mappings --- osu.Game/Online/API/Requests/Responses/APIBeatmap.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index c53fab48ae..f5795141c5 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -124,9 +124,9 @@ namespace osu.Game.Online.API.Requests.Responses case 1: return "taiko"; - case 2: return "catch"; + case 2: return "fruits"; - case 3: return "fruits"; + case 3: return "mania"; default: throw new ArgumentOutOfRangeException(); } From 3945cd24ebb84579fe1492c083d820155581f652 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Feb 2022 21:14:49 +0900 Subject: [PATCH 0726/5199] wip --- .../Rulesets/Difficulty/DifficultyCalculator.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 6b61dd3efb..7d6c235fc1 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Utils; namespace osu.Game.Rulesets.Difficulty { @@ -122,12 +123,17 @@ namespace osu.Game.Rulesets.Difficulty /// A collection of structures describing the difficulty of the beatmap for each mod combination. public IEnumerable CalculateAll(CancellationToken cancellationToken = default) { + var rulesetInstance = ruleset.CreateInstance(); + foreach (var combination in CreateDifficultyAdjustmentModCombinations()) { - if (combination is MultiMod multi) - yield return Calculate(multi.Mods, cancellationToken); - else - yield return Calculate(combination.Yield(), cancellationToken); + Mod classicMod = rulesetInstance.CreateAllMods().SingleOrDefault(m => m is ModClassic); + + var finalCombination = ModUtils.FlattenMod(combination); + if (classicMod != null) + finalCombination = finalCombination.Append(classicMod); + + yield return Calculate(finalCombination.ToArray(), cancellationToken); } } From 08317b4265c94a6f6b30a6629d002175a7945309 Mon Sep 17 00:00:00 2001 From: OctopuSSX Date: Thu, 17 Feb 2022 20:43:36 +0300 Subject: [PATCH 0727/5199] Update ScreenshotManager.cs --- osu.Game/Graphics/ScreenshotManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index a39d7bfb47..b0f20de685 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -112,6 +112,8 @@ namespace osu.Game.Graphics if (Interlocked.Decrement(ref screenShotTasks) == 0 && cursorVisibility.Value == false) cursorVisibility.Value = true; + host.GetClipboard()?.SetImage(image); + string filename = getFilename(); if (filename == null) return; From 1abbb9ab39675f4caeba0b9700aa9fb4fd9ca0be Mon Sep 17 00:00:00 2001 From: dekrain Date: Thu, 17 Feb 2022 21:26:59 +0100 Subject: [PATCH 0728/5199] Align the bar to be on baseline of score components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../Online/Leaderboards/LeaderboardScore.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 8cfba68ea6..4f7670f098 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -160,8 +160,8 @@ namespace osu.Game.Online.Leaderboards }, new FillFlowContainer { - Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(10f, 0f), @@ -169,29 +169,32 @@ namespace osu.Game.Online.Leaderboards { flagBadgeAndDateContainer = new FillFlowContainer { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Y, Direction = FillDirection.Horizontal, Spacing = new Vector2(5f, 0f), - Size = new Vector2(87f, 20f), + Width = 87f, Masking = true, Children = new Drawable[] { new UpdateableFlag(user.Country) { - Width = 30, - RelativeSizeAxes = Axes.Y, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(30f, 20f), }, new DateLabel(Score.Date) { - RelativeSizeAxes = Axes.Y, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, }, }, }, new FillFlowContainer { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Margin = new MarginPadding { Left = edge_margin }, From 98aaf83177ee1cb8777619936e31926b2167b399 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Feb 2022 15:57:37 +0900 Subject: [PATCH 0729/5199] Add a centralised constant for the osu URL schema protocol --- osu.Game.Tests/Chat/MessageFormatterTests.cs | 12 ++++++------ osu.Game.Tests/Visual/Online/TestSceneChatLink.cs | 4 ++-- osu.Game/Online/Chat/MessageFormatter.cs | 4 ++-- osu.Game/OsuGameBase.cs | 2 ++ 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index 8def8005f1..cea4d510c1 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -409,26 +409,26 @@ namespace osu.Game.Tests.Chat Assert.AreEqual(result.Content, result.DisplayContent); Assert.AreEqual(2, result.Links.Count); - Assert.AreEqual("osu://chan/#english", result.Links[0].Url); - Assert.AreEqual("osu://chan/#japanese", result.Links[1].Url); + Assert.AreEqual($"{OsuGameBase.OSU_PROTOCOL}chan/#english", result.Links[0].Url); + Assert.AreEqual($"{OsuGameBase.OSU_PROTOCOL}chan/#japanese", result.Links[1].Url); } [Test] public void TestOsuProtocol() { - Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a custom protocol osu://chan/#english." }); + Message result = MessageFormatter.FormatMessage(new Message { Content = $"This is a custom protocol {OsuGameBase.OSU_PROTOCOL}chan/#english." }); Assert.AreEqual(result.Content, result.DisplayContent); Assert.AreEqual(1, result.Links.Count); - Assert.AreEqual("osu://chan/#english", result.Links[0].Url); + Assert.AreEqual($"{OsuGameBase.OSU_PROTOCOL}chan/#english", result.Links[0].Url); Assert.AreEqual(26, result.Links[0].Index); Assert.AreEqual(19, result.Links[0].Length); - result = MessageFormatter.FormatMessage(new Message { Content = "This is a [custom protocol](osu://chan/#english)." }); + result = MessageFormatter.FormatMessage(new Message { Content = $"This is a [custom protocol]({OsuGameBase.OSU_PROTOCOL}chan/#english)." }); Assert.AreEqual("This is a custom protocol.", result.DisplayContent); Assert.AreEqual(1, result.Links.Count); - Assert.AreEqual("osu://chan/#english", result.Links[0].Url); + Assert.AreEqual($"{OsuGameBase.OSU_PROTOCOL}chan/#english", result.Links[0].Url); Assert.AreEqual("#english", result.Links[0].Argument); Assert.AreEqual(10, result.Links[0].Index); Assert.AreEqual(15, result.Links[0].Length); diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index 12b5f64559..d077868175 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -87,8 +87,8 @@ namespace osu.Game.Tests.Visual.Online addMessageWithChecks("likes to post this [https://dev.ppy.sh/home link].", 1, true, true, expectedActions: LinkAction.External); addMessageWithChecks("Join my multiplayer game osump://12346.", 1, expectedActions: LinkAction.JoinMultiplayerMatch); addMessageWithChecks("Join my [multiplayer game](osump://12346).", 1, expectedActions: LinkAction.JoinMultiplayerMatch); - addMessageWithChecks("Join my [#english](osu://chan/#english).", 1, expectedActions: LinkAction.OpenChannel); - addMessageWithChecks("Join my osu://chan/#english.", 1, expectedActions: LinkAction.OpenChannel); + addMessageWithChecks($"Join my [#english]({OsuGameBase.OSU_PROTOCOL}chan/#english).", 1, expectedActions: LinkAction.OpenChannel); + addMessageWithChecks($"Join my {OsuGameBase.OSU_PROTOCOL}chan/#english.", 1, expectedActions: LinkAction.OpenChannel); addMessageWithChecks("Join my #english or #japanese channels.", 2, expectedActions: new[] { LinkAction.OpenChannel, LinkAction.OpenChannel }); addMessageWithChecks("Join my #english or #nonexistent #hashtag channels.", 1, expectedActions: LinkAction.OpenChannel); diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index d7974004b1..4c477d58b6 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -269,10 +269,10 @@ namespace osu.Game.Online.Chat handleAdvanced(advanced_link_regex, result, startIndex); // handle editor times - handleMatches(time_regex, "{0}", "osu://edit/{0}", result, startIndex, LinkAction.OpenEditorTimestamp); + handleMatches(time_regex, "{0}", $"{OsuGameBase.OSU_PROTOCOL}edit/{0}", result, startIndex, LinkAction.OpenEditorTimestamp); // handle channels - handleMatches(channel_regex, "{0}", "osu://chan/{0}", result, startIndex, LinkAction.OpenChannel); + handleMatches(channel_regex, "{0}", $"{OsuGameBase.OSU_PROTOCOL}chan/{0}", result, startIndex, LinkAction.OpenChannel); string empty = ""; while (space-- > 0) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 0b2644d5ba..86390e7630 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -52,6 +52,8 @@ namespace osu.Game /// public partial class OsuGameBase : Framework.Game, ICanAcceptFiles { + public const string OSU_PROTOCOL = "osu://"; + public const string CLIENT_STREAM_NAME = @"lazer"; public const int SAMPLE_CONCURRENCY = 6; From 29c5683ba3ce2e7de362cac68949b3a46424cd12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Feb 2022 16:06:38 +0900 Subject: [PATCH 0730/5199] Add handling of beatmap links on startup --- .../TestSceneStartupBeatmapDisplay.cs | 22 ++++++++++++ .../TestSceneStartupBeatmapSetDisplay.cs | 22 ++++++++++++ osu.Game/OsuGame.cs | 36 ++++++++++++++++++- 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapDisplay.cs create mode 100644 osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapSetDisplay.cs diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapDisplay.cs b/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapDisplay.cs new file mode 100644 index 0000000000..1efa24435e --- /dev/null +++ b/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapDisplay.cs @@ -0,0 +1,22 @@ +// 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.Containers; +using osu.Framework.Testing; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.Navigation +{ + public class TestSceneStartupBeatmapDisplay : OsuGameTestScene + { + protected override TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API, new[] { "osu://b/75" }); + + [Test] + public void TestBeatmapLink() + { + AddUntilStep("Beatmap overlay displayed", () => Game.ChildrenOfType().FirstOrDefault()?.State.Value == Visibility.Visible); + } + } +} diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapSetDisplay.cs b/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapSetDisplay.cs new file mode 100644 index 0000000000..1339c514e4 --- /dev/null +++ b/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapSetDisplay.cs @@ -0,0 +1,22 @@ +// 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.Containers; +using osu.Framework.Testing; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.Navigation +{ + public class TestSceneStartupBeatmapSetDisplay : OsuGameTestScene + { + protected override TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API, new[] { "osu://s/1" }); + + [Test] + public void TestBeatmapSetLink() + { + AddUntilStep("Beatmap overlay displayed", () => Game.ChildrenOfType().FirstOrDefault()?.State.Value == Visibility.Visible); + } + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5b58dec0c3..390e96d768 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -150,6 +150,7 @@ namespace osu.Game protected SettingsOverlay Settings; private VolumeOverlay volume; + private OsuLogo osuLogo; private MainMenu menuScreen; @@ -898,8 +899,41 @@ namespace osu.Game if (args?.Length > 0) { string[] paths = args.Where(a => !a.StartsWith('-')).ToArray(); + if (paths.Length > 0) - Task.Run(() => Import(paths)); + { + string firstPath = paths.First(); + + if (firstPath.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal)) + { + handleOsuProtocolUrl(firstPath); + } + else + { + Task.Run(() => Import(paths)); + } + } + } + } + + private void handleOsuProtocolUrl(string url) + { + if (!url.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal)) + throw new ArgumentException("Invalid osu URL provided.", nameof(url)); + + string[] pieces = url.Split('/'); + + switch (pieces[2]) + { + case "s": + if (int.TryParse(pieces[3], out int beatmapSetId)) + ShowBeatmapSet(beatmapSetId); + break; + + case "b": + if (int.TryParse(pieces[3], out int beatmapId)) + ShowBeatmap(beatmapId); + break; } } From e49da2948d15d4d175d3de7ca7e47656e85e41b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Feb 2022 16:24:18 +0900 Subject: [PATCH 0731/5199] Fix storyboard background replacement logic not working for beatmaps with multiple backgrounds In the case where the background image of individual difficulties is different, querying the beatmap *set*'s metadata as we were will cause issues. I haven't added test coverage for this but can if required. Can be manually tested using https://osu.ppy.sh/beatmapsets/1595773#osu/3377474 (specifically the highest difficulty). Closes https://github.com/ppy/osu/discussions/16873. --- osu.Game/Storyboards/Storyboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index b86deeab89..c4864c0334 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -78,7 +78,7 @@ namespace osu.Game.Storyboards { get { - string backgroundPath = BeatmapInfo.BeatmapSet?.Metadata.BackgroundFile; + string backgroundPath = BeatmapInfo.Metadata.BackgroundFile; if (string.IsNullOrEmpty(backgroundPath)) return false; From 420e2c538f01b1a1f387b7401a39d828698f83aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Feb 2022 16:48:30 +0900 Subject: [PATCH 0732/5199] Automatically use an `AssemblyRulesetStore` if no custom store is registered --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 9626f39ec3..9dd2f27be5 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using osu.Framework.Extensions; using osu.Framework.Extensions.EnumExtensions; +using osu.Framework.Logging; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Timing; @@ -44,7 +45,10 @@ namespace osu.Game.Beatmaps.Formats : base(version) { if (RulesetStore == null) - throw new InvalidOperationException($"Call {nameof(Decoder)}.{nameof(RegisterDependencies)} before using {nameof(LegacyBeatmapDecoder)}."); + { + Logger.Log($"A {nameof(RulesetStore)} was not provided via {nameof(Decoder)}.{nameof(RegisterDependencies)}; falling back to default {nameof(AssemblyRulesetStore)}."); + RulesetStore = new AssemblyRulesetStore(); + } // BeatmapVersion 4 and lower had an incorrect offset (stable has this set as 24ms off) offset = FormatVersion < 5 ? 24 : 0; From cf1dd1ebd3fd4b849ed5249017363949f2112ea7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Feb 2022 16:48:42 +0900 Subject: [PATCH 0733/5199] Disallow registering a null `RulesetStore` --- osu.Game/Beatmaps/Formats/Decoder.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs index 8eb238a184..c1537d7240 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using JetBrains.Annotations; using osu.Game.IO; using osu.Game.Rulesets; @@ -42,9 +43,9 @@ namespace osu.Game.Beatmaps.Formats /// Register dependencies for use with static decoder classes. /// /// A store containing all available rulesets (used by ). - public static void RegisterDependencies(RulesetStore rulesets) + public static void RegisterDependencies([NotNull] RulesetStore rulesets) { - LegacyBeatmapDecoder.RulesetStore = rulesets; + LegacyBeatmapDecoder.RulesetStore = rulesets ?? throw new ArgumentNullException(nameof(rulesets)); } /// From 3abbf07fb3391d13e617409e1bfe16b5919243d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Feb 2022 16:52:14 +0900 Subject: [PATCH 0734/5199] Revert local registrations in test scenes --- osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs | 3 --- osu.Game.Rulesets.Osu.Tests/StackingTest.cs | 6 ------ .../Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 7 ------- osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs | 7 ------- osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs | 7 ------- osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs | 3 --- osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs | 6 ------ osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs | 6 ------ 8 files changed, 45 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs index 0dd66be0b4..1d207d04c7 100644 --- a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs +++ b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs @@ -8,7 +8,6 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.IO.Archives; -using osu.Game.Rulesets; using osu.Game.Tests.Resources; namespace osu.Game.Benchmarks @@ -19,8 +18,6 @@ namespace osu.Game.Benchmarks public override void SetUp() { - Decoder.RegisterDependencies(new AssemblyRulesetStore()); - using (var resources = new DllResourceStore(typeof(TestResources).Assembly)) using (var archive = resources.GetStream("Resources/Archives/241526 Soleily - Renatus.osz")) using (var reader = new ZipArchiveReader(archive)) diff --git a/osu.Game.Rulesets.Osu.Tests/StackingTest.cs b/osu.Game.Rulesets.Osu.Tests/StackingTest.cs index 927c0c3e1e..871afdb09d 100644 --- a/osu.Game.Rulesets.Osu.Tests/StackingTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/StackingTest.cs @@ -18,12 +18,6 @@ namespace osu.Game.Rulesets.Osu.Tests [TestFixture] public class StackingTest { - [SetUp] - public void SetUp() - { - Decoder.RegisterDependencies(new AssemblyRulesetStore()); - } - [Test] public void TestStacking() { diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index ad088d298b..468cb7683c 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -11,7 +11,6 @@ using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Timing; using osu.Game.IO; -using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Mods; @@ -30,12 +29,6 @@ namespace osu.Game.Tests.Beatmaps.Formats [TestFixture] public class LegacyBeatmapDecoderTest { - [SetUp] - public void SetUp() - { - Decoder.RegisterDependencies(new AssemblyRulesetStore()); - } - [Test] public void TestDecodeBeatmapVersion() { diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index b7989adcb8..2eb75259d9 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -10,7 +10,6 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.IO.Serialization; -using osu.Game.Rulesets; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; @@ -23,12 +22,6 @@ namespace osu.Game.Tests.Beatmaps.Formats [TestFixture] public class OsuJsonDecoderTest { - [SetUp] - public void SetUp() - { - Decoder.RegisterDependencies(new AssemblyRulesetStore()); - } - private const string normal = "Soleily - Renatus (Gamu) [Insane].osu"; private const string marathon = "Within Temptation - The Unforgiving (Armin) [Marathon].osu"; private const string with_sb = "Kozato snow - Rengetsu Ouka (_Kiva) [Yuki YukI].osu"; diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index 64a52b2b01..810ea5dbd0 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -9,19 +9,12 @@ using osu.Game.Tests.Resources; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.IO.Archives; -using osu.Game.Rulesets; namespace osu.Game.Tests.Beatmaps.IO { [TestFixture] public class OszArchiveReaderTest { - [SetUp] - public void SetUp() - { - Decoder.RegisterDependencies(new AssemblyRulesetStore()); - } - [Test] public void TestReadBeatmaps() { diff --git a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs index 6cf760723a..44a908b756 100644 --- a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs +++ b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs @@ -8,7 +8,6 @@ using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; -using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; @@ -29,8 +28,6 @@ namespace osu.Game.Tests.Editing [SetUp] public void Setup() { - Decoder.RegisterDependencies(new AssemblyRulesetStore()); - patcher = new LegacyEditorBeatmapPatcher(current = new EditorBeatmap(new OsuBeatmap { BeatmapInfo = diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index d5b7e3152b..8d622955b7 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -34,12 +34,6 @@ namespace osu.Game.Tests.Beatmaps protected abstract string ResourceAssembly { get; } - [SetUp] - public void Setup() - { - Decoder.RegisterDependencies(new AssemblyRulesetStore()); - } - protected void Test(string name, params Type[] mods) { var ourResult = convert(name, mods.Select(m => (Mod)Activator.CreateInstance(m)).ToArray()); diff --git a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs index 78b2558640..9f8811c7f9 100644 --- a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs +++ b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs @@ -22,12 +22,6 @@ namespace osu.Game.Tests.Beatmaps protected abstract string ResourceAssembly { get; } - [SetUp] - public void Setup() - { - Decoder.RegisterDependencies(new AssemblyRulesetStore()); - } - protected void Test(double expected, string name, params Mod[] mods) { // Platform-dependent math functions (Pow, Cbrt, Exp, etc) may result in minute differences. From a029e418cfef55371f956f3b4e39be6126596e99 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Feb 2022 17:06:04 +0900 Subject: [PATCH 0735/5199] Use `internal` instead of `protected internal` --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 9dd2f27be5..e2a043490f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -19,7 +19,7 @@ namespace osu.Game.Beatmaps.Formats { public class LegacyBeatmapDecoder : LegacyDecoder { - protected internal static RulesetStore RulesetStore; + internal static RulesetStore RulesetStore; private Beatmap beatmap; From c869be87d1c817af8a4ae5382d5c17e124fb40ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 19 Feb 2022 20:53:04 +0900 Subject: [PATCH 0736/5199] Update `FlatFileWorkingBeatmap` to not require a ruleset store --- osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs | 5 ++--- osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs | 9 +++------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs index 97a4c57bf0..10761bc315 100644 --- a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs +++ b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs @@ -77,10 +77,9 @@ namespace osu.Desktop.LegacyIpc case LegacyIpcDifficultyCalculationRequest req: try { - var ruleset = getLegacyRulesetFromID(req.RulesetId); - + WorkingBeatmap beatmap = new FlatFileWorkingBeatmap(req.BeatmapFile); + var ruleset = beatmap.BeatmapInfo.Ruleset.CreateInstance(); Mod[] mods = ruleset.ConvertFromLegacyMods((LegacyMods)req.Mods).ToArray(); - WorkingBeatmap beatmap = new FlatFileWorkingBeatmap(req.BeatmapFile, _ => ruleset); return new LegacyIpcDifficultyCalculationResponse { diff --git a/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs b/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs index 163da12b2e..cd8aa31ead 100644 --- a/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs @@ -7,7 +7,6 @@ using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps.Formats; using osu.Game.IO; -using osu.Game.Rulesets; using osu.Game.Skinning; namespace osu.Game.Beatmaps @@ -20,18 +19,16 @@ namespace osu.Game.Beatmaps { private readonly Beatmap beatmap; - public FlatFileWorkingBeatmap(string file, Func rulesetProvider, int? beatmapId = null) - : this(readFromFile(file), rulesetProvider, beatmapId) + public FlatFileWorkingBeatmap(string file, int? beatmapId = null) + : this(readFromFile(file), beatmapId) { } - private FlatFileWorkingBeatmap(Beatmap beatmap, Func rulesetProvider, int? beatmapId = null) + private FlatFileWorkingBeatmap(Beatmap beatmap, int? beatmapId = null) : base(beatmap.BeatmapInfo, null) { this.beatmap = beatmap; - beatmap.BeatmapInfo.Ruleset = rulesetProvider(beatmap.BeatmapInfo.Ruleset.OnlineID).RulesetInfo; - if (beatmapId.HasValue) beatmap.BeatmapInfo.OnlineID = beatmapId.Value; } From 7ef710de2205e1af671fa5f4f0af76efc16373c5 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Sat, 19 Feb 2022 18:14:56 +0100 Subject: [PATCH 0737/5199] Allow exiting/minimizing on Android when on the initial cookie screen --- osu.Game/Screens/Menu/MainMenu.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 8b1bab52b3..2391903861 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -70,12 +70,16 @@ namespace osu.Game.Screens.Menu private ParallaxContainer buttonsContainer; private SongTicker songTicker; + private readonly BindableBool allowExitingAndroid = new BindableBool(true); + [BackgroundDependencyLoader(true)] private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics) { holdDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); loginDisplayed = statics.GetBindable(Static.LoginOverlayDisplayed); + host.AllowExitingAndroid.AddSource(allowExitingAndroid); + if (host.CanExit) { AddInternal(exitConfirmOverlay = new ExitConfirmOverlay @@ -134,6 +138,8 @@ namespace osu.Game.Screens.Menu ApplyToBackground(b => b.FadeColour(OsuColour.Gray(0.8f), 500, Easing.OutSine)); break; } + + allowExitingAndroid.Value = state == ButtonSystemState.Initial; }; buttons.OnSettings = () => settings?.ToggleVisibility(); @@ -297,5 +303,11 @@ namespace osu.Game.Screens.Menu Schedule(loadSoloSongSelect); } + + protected override void Dispose(bool isDisposing) + { + host.AllowExitingAndroid.RemoveSource(allowExitingAndroid); + base.Dispose(isDisposing); + } } } From 11a11802edf98e666b25182e6740c31bc9c13981 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Sat, 19 Feb 2022 19:28:17 +0100 Subject: [PATCH 0738/5199] Ensure exiting is disallowed if we're not at the main menu --- osu.Android/OsuGameAndroid.cs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index 050bf2b787..914a6f7502 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -5,7 +5,11 @@ using System; using Android.App; using Android.OS; using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Platform; +using osu.Framework.Screens; using osu.Game; +using osu.Game.Screens.Menu; using osu.Game.Updater; using osu.Game.Utils; using Xamarin.Essentials; @@ -17,6 +21,8 @@ namespace osu.Android [Cached] private readonly OsuGameActivity gameActivity; + private readonly BindableBool allowExiting = new BindableBool(); + public OsuGameAndroid(OsuGameActivity activity) : base(null) { @@ -67,16 +73,45 @@ namespace osu.Android } } + public override void SetHost(GameHost host) + { + base.SetHost(host); + host.AllowExitingAndroid.AddSource(allowExiting); + } + protected override void LoadComplete() { base.LoadComplete(); LoadComponentAsync(new GameplayScreenRotationLocker(), Add); } + protected override void ScreenChanged(IScreen current, IScreen newScreen) + { + base.ScreenChanged(current, newScreen); + + switch (newScreen) + { + case MainMenu _: + // allow the MainMenu to (dis)allow exiting based on its ButtonSystemState. + allowExiting.Value = true; + break; + + default: + allowExiting.Value = false; + break; + } + } + protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager(); protected override BatteryInfo CreateBatteryInfo() => new AndroidBatteryInfo(); + protected override void Dispose(bool isDisposing) + { + Host.AllowExitingAndroid.RemoveSource(allowExiting); + base.Dispose(isDisposing); + } + private class AndroidBatteryInfo : BatteryInfo { public override double ChargeLevel => Battery.ChargeLevel; From 15ed9ec4fa725f08c6e49ca0192542f2b7aee6ac Mon Sep 17 00:00:00 2001 From: dekrain Date: Sat, 19 Feb 2022 20:47:02 +0100 Subject: [PATCH 0739/5199] Merge scoreboard and leaderboard implementations together --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 2 + .../Online/Leaderboards/LeaderboardScore.cs | 23 +------ .../BeatmapSet/Scores/ScoreboardTime.cs | 40 +---------- osu.Game/Utils/ScoreboardTimeUtils.cs | 66 +++++++++++++++++++ 4 files changed, 73 insertions(+), 58 deletions(-) create mode 100644 osu.Game/Utils/ScoreboardTimeUtils.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 2f44633f4d..c4178143f8 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -388,6 +388,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.5140, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddMonths(-3), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -409,6 +410,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.4222, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddYears(-2), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 4f7670f098..faec0abce6 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -30,7 +30,7 @@ using osuTK; using osuTK.Graphics; using osu.Game.Online.API; using osu.Game.Utils; -using osu.Game.Resources.Localisation.Web; +using osu.Framework.Utils; namespace osu.Game.Online.Leaderboards { @@ -398,24 +398,7 @@ namespace osu.Game.Online.Leaderboards protected override string Format() { - var now = DateTime.Now; - var difference = now - Date; - - // TODO(optional): support localisation (probably via `CommonStrings.CountHours()` etc.) - // requires pluralisable string support framework-side - - if (difference.TotalSeconds < 10) - return CommonStrings.TimeNow.ToString(); - if (difference.TotalMinutes < 1) - return $@"{Math.Floor(difference.TotalSeconds)}s"; - if (difference.TotalHours < 1) - return $@"{Math.Floor(difference.TotalMinutes)}min"; - if (difference.TotalDays < 1) - return $@"{Math.Floor(difference.TotalHours)}h"; - if (difference.TotalDays < 3) - return $@"{Math.Floor(difference.TotalDays)}d"; - - return string.Empty; + return ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromSeconds(30)); } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs index ff1d3490b4..ceb446f310 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs @@ -2,9 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; -using Humanizer; using osu.Game.Graphics; -using osu.Game.Resources.Localisation.Web; +using osu.Game.Utils; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -16,41 +15,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } protected override string Format() - { - var now = DateTime.Now; - var difference = now - Date; - - // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. - // this is intended to be a best-effort, more legible approximation of that. - // compare: - // * https://github.com/ppy/osu-web/blob/a8f5a68fb435cb19a4faa4c7c4bce08c4f096933/resources/assets/lib/scoreboard-time.tsx - // * https://momentjs.com/docs/#/customization/ (reference for the customisation format) - - // TODO: support localisation (probably via `CommonStrings.CountHours()` etc.) - // requires pluralisable string support framework-side - - if (difference.TotalHours < 1) - return CommonStrings.TimeNow.ToString(); - if (difference.TotalDays < 1) - return "hr".ToQuantity((int)difference.TotalHours); - - // this is where this gets more complicated because of how the calendar works. - // since there's no `TotalMonths` / `TotalYears`, we have to iteratively add months/years - // and test against cutoff dates to determine how many months/years to show. - - if (Date > now.AddMonths(-1)) - return difference.TotalDays < 2 ? "1dy" : $"{(int)difference.TotalDays}dys"; - - for (int months = 1; months <= 11; ++months) - { - if (Date > now.AddMonths(-(months + 1))) - return months == 1 ? "1mo" : $"{months}mos"; - } - - int years = 1; - while (Date <= now.AddYears(-(years + 1))) - years += 1; - return years == 1 ? "1yr" : $"{years}yrs"; - } + => ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromHours(1)); } } diff --git a/osu.Game/Utils/ScoreboardTimeUtils.cs b/osu.Game/Utils/ScoreboardTimeUtils.cs new file mode 100644 index 0000000000..7e84b8f2d0 --- /dev/null +++ b/osu.Game/Utils/ScoreboardTimeUtils.cs @@ -0,0 +1,66 @@ +// 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.Resources.Localisation.Web; + +#nullable enable + +namespace osu.Game.Utils +{ + public static class ScoreboardTimeUtils + { + public static string FormatQuantity(string template, int quantity) + { + if (quantity <= 1) + return $@"{quantity}{template}"; + return $@"{quantity}{template}s"; + } + + public static string FormatDate(DateTimeOffset time, TimeSpan lowerCutoff) + { + // This function fails if the passed in time is something close to an epoch + + // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. + // this is intended to be a best-effort, more legible approximation of that. + // compare: + // * https://github.com/ppy/osu-web/blob/a8f5a68fb435cb19a4faa4c7c4bce08c4f096933/resources/assets/lib/scoreboard-time.tsx + // * https://momentjs.com/docs/#/customization/ (reference for the customisation format) + + // TODO: support localisation (probably via `CommonStrings.CountHours()` etc.) + // requires pluralisable string support framework-side + + var now = DateTime.Now; + var span = now - time; + + if (span < lowerCutoff) + return CommonStrings.TimeNow.ToString(); + + if (span.TotalMinutes < 1) + return FormatQuantity("sec", (int)span.TotalSeconds); + if (span.TotalHours < 1) + return FormatQuantity("min", (int)span.TotalMinutes); + if (span.TotalDays < 1) + return FormatQuantity("hr", (int)span.TotalHours); + + // this is where this gets more complicated because of how the calendar works. + // since there's no `TotalMonths` / `TotalYears`, we have to iteratively add months/years + // and test against cutoff dates to determine how many months/years to show. + + if (time > now.AddMonths(-1)) + return FormatQuantity("dy", (int)span.TotalDays); + + for (int months = 1; months <= 11; ++months) + { + if (time > now.AddMonths(-(months + 1))) + return FormatQuantity("mo", months); + } + + int years = 1; + // DateTime causes a crash here for epoch + while (time <= now.AddYears(-(years + 1))) + years += 1; + return FormatQuantity("yr", years); + } + } +} From 0d83c5a39a02581c3eb5d702c143ec55c904f461 Mon Sep 17 00:00:00 2001 From: dekrain Date: Sat, 19 Feb 2022 20:47:30 +0100 Subject: [PATCH 0740/5199] Add colour highlighting recent scores --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index faec0abce6..217be5a024 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -390,6 +390,9 @@ namespace osu.Game.Online.Leaderboards private class DateLabel : DrawableDate { + public static readonly Colour4 COLOUR_SATURATED = Colour4.Lime; + public static readonly Colour4 COLOUR_UNSATURATED = Colour4.White; + public DateLabel(DateTimeOffset date) : base(date) { @@ -398,6 +401,15 @@ namespace osu.Game.Online.Leaderboards protected override string Format() { + var now = DateTime.Now; + var difference = now - Date; + + const double seconds_to_blank = 60*45; + const double tense_factor = 2.325; + + double tf = Math.Pow(difference.TotalSeconds / seconds_to_blank, tense_factor); + Colour = Interpolation.ValueAt(tf, COLOUR_SATURATED, COLOUR_UNSATURATED, 0, 1); + return ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromSeconds(30)); } } From 31b7ce053d24cef175f3b4b399d1a9e79b86fc32 Mon Sep 17 00:00:00 2001 From: dekrain Date: Sat, 19 Feb 2022 21:18:26 +0100 Subject: [PATCH 0741/5199] Fix CI issues --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 2 +- osu.Game/Utils/ScoreboardTimeUtils.cs | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 217be5a024..aaf889d06c 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -404,7 +404,7 @@ namespace osu.Game.Online.Leaderboards var now = DateTime.Now; var difference = now - Date; - const double seconds_to_blank = 60*45; + const double seconds_to_blank = 60 * 45; const double tense_factor = 2.325; double tf = Math.Pow(difference.TotalSeconds / seconds_to_blank, tense_factor); diff --git a/osu.Game/Utils/ScoreboardTimeUtils.cs b/osu.Game/Utils/ScoreboardTimeUtils.cs index 7e84b8f2d0..86639c1034 100644 --- a/osu.Game/Utils/ScoreboardTimeUtils.cs +++ b/osu.Game/Utils/ScoreboardTimeUtils.cs @@ -14,13 +14,12 @@ namespace osu.Game.Utils { if (quantity <= 1) return $@"{quantity}{template}"; + return $@"{quantity}{template}s"; } public static string FormatDate(DateTimeOffset time, TimeSpan lowerCutoff) { - // This function fails if the passed in time is something close to an epoch - // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. // this is intended to be a best-effort, more legible approximation of that. // compare: @@ -57,10 +56,13 @@ namespace osu.Game.Utils } int years = 1; - // DateTime causes a crash here for epoch - while (time <= now.AddYears(-(years + 1))) + // Add upper bound to prevent a crash + while (years < 20 && time <= now.AddYears(-(years + 1))) years += 1; - return FormatQuantity("yr", years); + if (years < 20) + return FormatQuantity("yr", years); + + return "never"; } } } From 262751a98a22d87ef5406203d5b38f94e65d30e0 Mon Sep 17 00:00:00 2001 From: dekrain Date: Sat, 19 Feb 2022 21:23:35 +0100 Subject: [PATCH 0742/5199] Revert highlighting recent scores --- .../Online/Leaderboards/LeaderboardScore.cs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index aaf889d06c..a8b508829d 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -390,28 +390,13 @@ namespace osu.Game.Online.Leaderboards private class DateLabel : DrawableDate { - public static readonly Colour4 COLOUR_SATURATED = Colour4.Lime; - public static readonly Colour4 COLOUR_UNSATURATED = Colour4.White; - public DateLabel(DateTimeOffset date) : base(date) { Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold, italics: true); } - protected override string Format() - { - var now = DateTime.Now; - var difference = now - Date; - - const double seconds_to_blank = 60 * 45; - const double tense_factor = 2.325; - - double tf = Math.Pow(difference.TotalSeconds / seconds_to_blank, tense_factor); - Colour = Interpolation.ValueAt(tf, COLOUR_SATURATED, COLOUR_UNSATURATED, 0, 1); - - return ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromSeconds(30)); - } + protected override string Format() => ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromSeconds(30)); } public class LeaderboardScoreStatistic From e20ae5b87100a387045471902547cd2296841519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 17 Feb 2022 23:54:29 +0100 Subject: [PATCH 0743/5199] Add all colour constants for "basic" colour theme to `OsuColour` --- osu.Game/Graphics/OsuColour.cs | 57 +++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index f63bd53549..567d665e81 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -264,32 +264,53 @@ namespace osu.Game.Graphics public readonly Color4 GrayE = Color4Extensions.FromHex(@"eee"); public readonly Color4 GrayF = Color4Extensions.FromHex(@"fff"); - /// - /// Equivalent to 's . - /// - public readonly Color4 Pink3 = Color4Extensions.FromHex(@"cc3378"); + #region "Basic" colour theme - /// - /// Equivalent to 's . - /// + // Reference: https://www.figma.com/file/VIkXMYNPMtQem2RJg9k2iQ/Asset%2FColours?node-id=1838%3A3 + + public readonly Color4 Pink0 = Color4Extensions.FromHex(@"ff99c7"); + public readonly Color4 Pink1 = Color4Extensions.FromHex(@"ff66ab"); + public readonly Color4 Pink2 = Color4Extensions.FromHex(@"eb4791"); + public readonly Color4 Pink3 = Color4Extensions.FromHex(@"cc3378"); + public readonly Color4 Pink4 = Color4Extensions.FromHex(@"6b2e49"); + + public readonly Color4 Purple0 = Color4Extensions.FromHex(@"b299ff"); + public readonly Color4 Purple1 = Color4Extensions.FromHex(@"8c66ff"); + public readonly Color4 Purple2 = Color4Extensions.FromHex(@"7047eb"); + public readonly Color4 Purple3 = Color4Extensions.FromHex(@"5933cc"); + public readonly Color4 Purple4 = Color4Extensions.FromHex(@"3d2e6b"); + + public readonly Color4 Blue0 = Color4Extensions.FromHex(@"99ddff"); + public readonly Color4 Blue1 = Color4Extensions.FromHex(@"66ccff"); + public readonly Color4 Blue2 = Color4Extensions.FromHex(@"47b4eb"); public readonly Color4 Blue3 = Color4Extensions.FromHex(@"3399cc"); + public readonly Color4 Blue4 = Color4Extensions.FromHex(@"2e576b"); + + public readonly Color4 Green0 = Color4Extensions.FromHex(@"99ffa2"); + public readonly Color4 Green1 = Color4Extensions.FromHex(@"66ff73"); + public readonly Color4 Green2 = Color4Extensions.FromHex(@"47eb55"); + public readonly Color4 Green3 = Color4Extensions.FromHex(@"33cc40"); + public readonly Color4 Green4 = Color4Extensions.FromHex(@"2e6b33"); public readonly Color4 Lime0 = Color4Extensions.FromHex(@"ccff99"); - - /// - /// Equivalent to 's . - /// public readonly Color4 Lime1 = Color4Extensions.FromHex(@"b2ff66"); - - /// - /// Equivalent to 's . - /// + public readonly Color4 Lime2 = Color4Extensions.FromHex(@"99eb47"); public readonly Color4 Lime3 = Color4Extensions.FromHex(@"7fcc33"); + public readonly Color4 Lime4 = Color4Extensions.FromHex(@"4c6b2e"); - /// - /// Equivalent to 's . - /// + public readonly Color4 Orange0 = Color4Extensions.FromHex(@"ffe699"); public readonly Color4 Orange1 = Color4Extensions.FromHex(@"ffd966"); + public readonly Color4 Orange2 = Color4Extensions.FromHex(@"ebc247"); + public readonly Color4 Orange3 = Color4Extensions.FromHex(@"cca633"); + public readonly Color4 Orange4 = Color4Extensions.FromHex(@"6b5c2e"); + + public readonly Color4 Red0 = Color4Extensions.FromHex(@"ff9b9b"); + public readonly Color4 Red1 = Color4Extensions.FromHex(@"ff6666"); + public readonly Color4 Red2 = Color4Extensions.FromHex(@"eb4747"); + public readonly Color4 Red3 = Color4Extensions.FromHex(@"cc3333"); + public readonly Color4 Red4 = Color4Extensions.FromHex(@"6b2e2e"); + + #endregion // Content Background public readonly Color4 B5 = Color4Extensions.FromHex(@"222a28"); From 2592f0900d384e958cfbb6b99a638cb8089be134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Feb 2022 20:40:08 +0100 Subject: [PATCH 0744/5199] Add comments about `OverlayColourProvider` vs `OsuColour` distinction --- osu.Game/Graphics/OsuColour.cs | 5 +++++ osu.Game/Overlays/OverlayColourProvider.cs | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 567d665e81..886ba7ef92 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -268,6 +268,11 @@ namespace osu.Game.Graphics // Reference: https://www.figma.com/file/VIkXMYNPMtQem2RJg9k2iQ/Asset%2FColours?node-id=1838%3A3 + // Note that the colours in this region are also defined in `OverlayColourProvider` as `Colour{0,1,2,3,4}`. + // The difference as to which should be used where comes down to context. + // If the colour in question is supposed to always match the view in which it is displayed theme-wise, use `OverlayColourProvider`. + // If the colour usage is special and in general differs from the surrounding view in choice of hue, use the `OsuColour` constants. + public readonly Color4 Pink0 = Color4Extensions.FromHex(@"ff99c7"); public readonly Color4 Pink1 = Color4Extensions.FromHex(@"ff66ab"); public readonly Color4 Pink2 = Color4Extensions.FromHex(@"eb4791"); diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index e7b3e6d873..730ca09411 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -25,6 +25,10 @@ namespace osu.Game.Overlays this.colourScheme = colourScheme; } + // Note that the following five colours are also defined in `OsuColour` as `{colourScheme}{0,1,2,3,4}`. + // The difference as to which should be used where comes down to context. + // If the colour in question is supposed to always match the view in which it is displayed theme-wise, use `OverlayColourProvider`. + // If the colour usage is special and in general differs from the surrounding view in choice of hue, use the `OsuColour` constants. public Color4 Colour1 => getColour(1, 0.7f); public Color4 Colour2 => getColour(0.8f, 0.6f); public Color4 Colour3 => getColour(0.6f, 0.5f); From 79ba37bbabc1fdd1acffcef16c4caac092066665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Feb 2022 20:40:39 +0100 Subject: [PATCH 0745/5199] Add `Colour0` to `OverlayColourProvider` --- osu.Game/Overlays/OverlayColourProvider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index 730ca09411..c2ceef57e8 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -29,6 +29,7 @@ namespace osu.Game.Overlays // The difference as to which should be used where comes down to context. // If the colour in question is supposed to always match the view in which it is displayed theme-wise, use `OverlayColourProvider`. // If the colour usage is special and in general differs from the surrounding view in choice of hue, use the `OsuColour` constants. + public Color4 Colour0 => getColour(1, 0.8f); public Color4 Colour1 => getColour(1, 0.7f); public Color4 Colour2 => getColour(0.8f, 0.6f); public Color4 Colour3 => getColour(0.6f, 0.5f); From ce0db9d4dbd7a0b030558002331dea4312be728a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Feb 2022 20:46:12 +0100 Subject: [PATCH 0746/5199] Remove all references to static `OverlayColourProvider`s --- .../BeatmapListing/BeatmapSearchGeneralFilterRow.cs | 7 ++++++- osu.Game/Overlays/BeatmapListing/FilterTabItem.cs | 7 +++++++ osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs | 2 +- osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs | 2 +- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs index fb9e1c0420..51dad100c2 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs @@ -1,6 +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.Allocation; +using osu.Game.Graphics; using osu.Game.Resources.Localisation.Web; using osuTK.Graphics; @@ -33,7 +35,10 @@ namespace osu.Game.Overlays.BeatmapListing { } - protected override Color4 GetStateColour() => OverlayColourProvider.Orange.Colour1; + [Resolved] + private OsuColour colours { get; set; } + + protected override Color4 GetStateColour() => colours.Orange1; } } } diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 9274cf20aa..52dfcad2cc 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -44,7 +44,14 @@ namespace osu.Game.Overlays.BeatmapListing }); Enabled.Value = true; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + updateState(); + FinishTransforms(true); } protected override bool OnHover(HoverEvent e) diff --git a/osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs b/osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs index ba78592ed2..21d1d1172c 100644 --- a/osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs +++ b/osu.Game/Overlays/BeatmapSet/ExplicitContentBeatmapPill.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.BeatmapSet Margin = new MarginPadding { Horizontal = 10f, Vertical = 2f }, Text = BeatmapsetsStrings.NsfwBadgeLabel.ToUpper(), Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), - Colour = OverlayColourProvider.Orange.Colour2, + Colour = colours.Orange2 } } }; diff --git a/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs b/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs index fdee0799ff..1be987cde2 100644 --- a/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs +++ b/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.BeatmapSet Margin = new MarginPadding { Horizontal = 10f, Vertical = 2f }, Text = BeatmapsetsStrings.FeaturedArtistBadgeLabel.ToUpper(), Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), - Colour = OverlayColourProvider.Blue.Colour1, + Colour = colours.Blue1 } } }; From 36a00c1ee238b13cfe857882110e1917cef5c49d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Feb 2022 20:46:33 +0100 Subject: [PATCH 0747/5199] Remove static `OverlayColourProvider`s --- osu.Game/Overlays/OverlayColourProvider.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index c2ceef57e8..7bddb924a0 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -11,15 +11,6 @@ namespace osu.Game.Overlays { private readonly OverlayColourScheme colourScheme; - public static OverlayColourProvider Red { get; } = new OverlayColourProvider(OverlayColourScheme.Red); - public static OverlayColourProvider Pink { get; } = new OverlayColourProvider(OverlayColourScheme.Pink); - public static OverlayColourProvider Orange { get; } = new OverlayColourProvider(OverlayColourScheme.Orange); - public static OverlayColourProvider Lime { get; } = new OverlayColourProvider(OverlayColourScheme.Lime); - public static OverlayColourProvider Green { get; } = new OverlayColourProvider(OverlayColourScheme.Green); - public static OverlayColourProvider Purple { get; } = new OverlayColourProvider(OverlayColourScheme.Purple); - public static OverlayColourProvider Blue { get; } = new OverlayColourProvider(OverlayColourScheme.Blue); - public static OverlayColourProvider Plum { get; } = new OverlayColourProvider(OverlayColourScheme.Plum); - public OverlayColourProvider(OverlayColourScheme colourScheme) { this.colourScheme = colourScheme; From 2ded7d281b4b36e8d92ec12d72f1ba19ee33f935 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 13:17:19 +0900 Subject: [PATCH 0748/5199] Remove unused using statement --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index a8b508829d..0e9968f27f 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -30,7 +30,6 @@ using osuTK; using osuTK.Graphics; using osu.Game.Online.API; using osu.Game.Utils; -using osu.Framework.Utils; namespace osu.Game.Online.Leaderboards { From 79408f6afcfd778e84c59ae329c73faf253dfa56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 13:30:58 +0900 Subject: [PATCH 0749/5199] Add xmldoc and clean up `ScoreboardTimeUtils` extension methods a touch --- .../Online/Leaderboards/LeaderboardScore.cs | 2 +- .../BeatmapSet/Scores/ScoreboardTime.cs | 2 +- osu.Game/Utils/ScoreboardTimeUtils.cs | 36 +++++++++++-------- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 0e9968f27f..a9f03e5c9f 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -395,7 +395,7 @@ namespace osu.Game.Online.Leaderboards Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold, italics: true); } - protected override string Format() => ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromSeconds(30)); + protected override string Format() => ScoreboardTimeUtils.FormatRelativeTime(Date, TimeSpan.FromSeconds(30)); } public class LeaderboardScoreStatistic diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs index ceb446f310..1927e66edb 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs @@ -15,6 +15,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } protected override string Format() - => ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromHours(1)); + => ScoreboardTimeUtils.FormatRelativeTime(Date, TimeSpan.FromHours(1)); } } diff --git a/osu.Game/Utils/ScoreboardTimeUtils.cs b/osu.Game/Utils/ScoreboardTimeUtils.cs index 86639c1034..4e079ff39f 100644 --- a/osu.Game/Utils/ScoreboardTimeUtils.cs +++ b/osu.Game/Utils/ScoreboardTimeUtils.cs @@ -10,15 +10,13 @@ namespace osu.Game.Utils { public static class ScoreboardTimeUtils { - public static string FormatQuantity(string template, int quantity) - { - if (quantity <= 1) - return $@"{quantity}{template}"; - - return $@"{quantity}{template}s"; - } - - public static string FormatDate(DateTimeOffset time, TimeSpan lowerCutoff) + /// + /// Formats a provided date to a short relative string version for compact display. + /// + /// The time to be displayed. + /// A timespan denoting the time length beneath which "now" should be displayed. + /// A short relative string representing the input time. + public static string FormatRelativeTime(DateTimeOffset time, TimeSpan lowerCutoff) { // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. // this is intended to be a best-effort, more legible approximation of that. @@ -36,23 +34,23 @@ namespace osu.Game.Utils return CommonStrings.TimeNow.ToString(); if (span.TotalMinutes < 1) - return FormatQuantity("sec", (int)span.TotalSeconds); + return formatQuantity("sec", (int)span.TotalSeconds); if (span.TotalHours < 1) - return FormatQuantity("min", (int)span.TotalMinutes); + return formatQuantity("min", (int)span.TotalMinutes); if (span.TotalDays < 1) - return FormatQuantity("hr", (int)span.TotalHours); + return formatQuantity("hr", (int)span.TotalHours); // this is where this gets more complicated because of how the calendar works. // since there's no `TotalMonths` / `TotalYears`, we have to iteratively add months/years // and test against cutoff dates to determine how many months/years to show. if (time > now.AddMonths(-1)) - return FormatQuantity("dy", (int)span.TotalDays); + return formatQuantity("dy", (int)span.TotalDays); for (int months = 1; months <= 11; ++months) { if (time > now.AddMonths(-(months + 1))) - return FormatQuantity("mo", months); + return formatQuantity("mo", months); } int years = 1; @@ -60,9 +58,17 @@ namespace osu.Game.Utils while (years < 20 && time <= now.AddYears(-(years + 1))) years += 1; if (years < 20) - return FormatQuantity("yr", years); + return formatQuantity("yr", years); return "never"; } + + private static string formatQuantity(string template, int quantity) + { + if (quantity <= 1) + return $@"{quantity}{template}"; + + return $@"{quantity}{template}s"; + } } } From c3b365cf6b79c8cfd618982fb163e6b7e72ed21e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 21 Feb 2022 13:31:02 +0900 Subject: [PATCH 0750/5199] Scale classic score by hitobject count --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 15 ++++++++------- osu.Game/Scoring/ScoreManager.cs | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 79861c0ecc..42ab30d4d0 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Scoring private double getScore(ScoringMode mode) { - return GetScore(mode, maxAchievableCombo, + return GetScore(mode, calculateAccuracyRatio(baseScore), calculateComboRatio(HighestCombo.Value), scoreResultCounts); @@ -222,12 +222,11 @@ namespace osu.Game.Rulesets.Scoring /// Computes the total score. /// /// The to compute the total score in. - /// The maximum combo achievable in the beatmap. /// The accuracy percentage achieved by the player. - /// The proportion of achieved by the player. + /// The proportion of the max combo achieved by the player. /// Any statistics to be factored in. /// The total score. - public double GetScore(ScoringMode mode, int maxCombo, double accuracyRatio, double comboRatio, Dictionary statistics) + public double GetScore(ScoringMode mode, double accuracyRatio, double comboRatio, Dictionary statistics) { switch (mode) { @@ -238,10 +237,12 @@ namespace osu.Game.Rulesets.Scoring return (max_score * (accuracyScore + comboScore) + getBonusScore(statistics)) * scoreMultiplier; case ScoringMode.Classic: + int totalHitObjects = statistics.Where(k => k.Key >= HitResult.Miss && k.Key <= HitResult.Perfect).Sum(k => k.Value); + // This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring. // The invariant is important to ensure that scores don't get re-ordered on leaderboards between the two scoring modes. - double scaledStandardised = GetScore(ScoringMode.Standardised, maxCombo, accuracyRatio, comboRatio, statistics) / max_score; - return Math.Pow(scaledStandardised * (maxCombo + 1), 2) * 18; + double scaledStandardised = GetScore(ScoringMode.Standardised, accuracyRatio, comboRatio, statistics) / max_score; + return Math.Pow(scaledStandardised * totalHitObjects, 2) * 36; } } @@ -265,7 +266,7 @@ namespace osu.Game.Rulesets.Scoring computedBaseScore += Judgement.ToNumericResult(pair.Key) * pair.Value; } - return GetScore(mode, maxAchievableCombo, calculateAccuracyRatio(computedBaseScore), calculateComboRatio(maxCombo), statistics); + return GetScore(mode, calculateAccuracyRatio(computedBaseScore), calculateComboRatio(maxCombo), statistics); } /// diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 532c6b42a3..963c4a77ca 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -184,7 +184,7 @@ namespace osu.Game.Scoring var scoreProcessor = ruleset.CreateScoreProcessor(); scoreProcessor.Mods.Value = score.Mods; - return (long)Math.Round(scoreProcessor.GetScore(mode, beatmapMaxCombo, accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics)); + return (long)Math.Round(scoreProcessor.GetScore(mode, accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics)); } /// From fc1877b6faea3ad3bb9a476fc744d85dd6a463c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 13:42:26 +0900 Subject: [PATCH 0751/5199] Move to extension method and revert logic to match previous implementation --- osu.Game/Extensions/TimeDisplayExtensions.cs | 51 ++++++++++++++++++ .../Online/Leaderboards/LeaderboardScore.cs | 3 +- .../BeatmapSet/Scores/ScoreboardTime.cs | 4 +- osu.Game/Utils/ScoreboardTimeUtils.cs | 52 ------------------- 4 files changed, 55 insertions(+), 55 deletions(-) diff --git a/osu.Game/Extensions/TimeDisplayExtensions.cs b/osu.Game/Extensions/TimeDisplayExtensions.cs index dc05482a05..ee6f458398 100644 --- a/osu.Game/Extensions/TimeDisplayExtensions.cs +++ b/osu.Game/Extensions/TimeDisplayExtensions.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; +using Humanizer; using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Extensions { @@ -47,5 +49,54 @@ namespace osu.Game.Extensions return new LocalisableFormattableString(timeSpan, @"mm\:ss"); } + + /// + /// Formats a provided date to a short relative string version for compact display. + /// + /// The time to be displayed. + /// A timespan denoting the time length beneath which "now" should be displayed. + /// A short relative string representing the input time. + public static string ToShortRelativeTime(this DateTimeOffset time, TimeSpan lowerCutoff) + { + var now = DateTime.Now; + var difference = now - time; + + // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. + // this is intended to be a best-effort, more legible approximation of that. + // compare: + // * https://github.com/ppy/osu-web/blob/a8f5a68fb435cb19a4faa4c7c4bce08c4f096933/resources/assets/lib/scoreboard-time.tsx + // * https://momentjs.com/docs/#/customization/ (reference for the customisation format) + + // TODO: support localisation (probably via `CommonStrings.CountHours()` etc.) + // requires pluralisable string support framework-side + + if (difference < lowerCutoff) + return CommonStrings.TimeNow.ToString(); + + if (difference.TotalMinutes < 1) + return "sec".ToQuantity((int)difference.TotalSeconds); + if (difference.TotalHours < 1) + return "min".ToQuantity((int)difference.TotalMinutes); + if (difference.TotalDays < 1) + return "hr".ToQuantity((int)difference.TotalHours); + + // this is where this gets more complicated because of how the calendar works. + // since there's no `TotalMonths` / `TotalYears`, we have to iteratively add months/years + // and test against cutoff dates to determine how many months/years to show. + + if (time > now.AddMonths(-1)) + return difference.TotalDays < 2 ? "1dy" : $"{(int)difference.TotalDays}dys"; + + for (int months = 1; months <= 11; ++months) + { + if (time > now.AddMonths(-(months + 1))) + return months == 1 ? "1mo" : $"{months}mos"; + } + + int years = 1; + while (time <= now.AddYears(-(years + 1))) + years += 1; + return years == 1 ? "1yr" : $"{years}yrs"; + } } } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index a9f03e5c9f..ddd9d9a2b2 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -17,6 +17,7 @@ using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Game.Database; +using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -395,7 +396,7 @@ namespace osu.Game.Online.Leaderboards Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold, italics: true); } - protected override string Format() => ScoreboardTimeUtils.FormatRelativeTime(Date, TimeSpan.FromSeconds(30)); + protected override string Format() => Date.ToShortRelativeTime(TimeSpan.FromSeconds(30)); } public class LeaderboardScoreStatistic diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs index 1927e66edb..5018fb8c70 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs @@ -2,8 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Game.Extensions; using osu.Game.Graphics; -using osu.Game.Utils; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -15,6 +15,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } protected override string Format() - => ScoreboardTimeUtils.FormatRelativeTime(Date, TimeSpan.FromHours(1)); + => Date.ToShortRelativeTime(TimeSpan.FromHours(1)); } } diff --git a/osu.Game/Utils/ScoreboardTimeUtils.cs b/osu.Game/Utils/ScoreboardTimeUtils.cs index 4e079ff39f..ec222de018 100644 --- a/osu.Game/Utils/ScoreboardTimeUtils.cs +++ b/osu.Game/Utils/ScoreboardTimeUtils.cs @@ -10,58 +10,6 @@ namespace osu.Game.Utils { public static class ScoreboardTimeUtils { - /// - /// Formats a provided date to a short relative string version for compact display. - /// - /// The time to be displayed. - /// A timespan denoting the time length beneath which "now" should be displayed. - /// A short relative string representing the input time. - public static string FormatRelativeTime(DateTimeOffset time, TimeSpan lowerCutoff) - { - // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. - // this is intended to be a best-effort, more legible approximation of that. - // compare: - // * https://github.com/ppy/osu-web/blob/a8f5a68fb435cb19a4faa4c7c4bce08c4f096933/resources/assets/lib/scoreboard-time.tsx - // * https://momentjs.com/docs/#/customization/ (reference for the customisation format) - - // TODO: support localisation (probably via `CommonStrings.CountHours()` etc.) - // requires pluralisable string support framework-side - - var now = DateTime.Now; - var span = now - time; - - if (span < lowerCutoff) - return CommonStrings.TimeNow.ToString(); - - if (span.TotalMinutes < 1) - return formatQuantity("sec", (int)span.TotalSeconds); - if (span.TotalHours < 1) - return formatQuantity("min", (int)span.TotalMinutes); - if (span.TotalDays < 1) - return formatQuantity("hr", (int)span.TotalHours); - - // this is where this gets more complicated because of how the calendar works. - // since there's no `TotalMonths` / `TotalYears`, we have to iteratively add months/years - // and test against cutoff dates to determine how many months/years to show. - - if (time > now.AddMonths(-1)) - return formatQuantity("dy", (int)span.TotalDays); - - for (int months = 1; months <= 11; ++months) - { - if (time > now.AddMonths(-(months + 1))) - return formatQuantity("mo", months); - } - - int years = 1; - // Add upper bound to prevent a crash - while (years < 20 && time <= now.AddYears(-(years + 1))) - years += 1; - if (years < 20) - return formatQuantity("yr", years); - - return "never"; - } private static string formatQuantity(string template, int quantity) { From 3d0caa44c84f908a013eeac29bc126cfa20bacf8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 13:43:30 +0900 Subject: [PATCH 0752/5199] Remove unused utils class --- osu.Game/Utils/ScoreboardTimeUtils.cs | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 osu.Game/Utils/ScoreboardTimeUtils.cs diff --git a/osu.Game/Utils/ScoreboardTimeUtils.cs b/osu.Game/Utils/ScoreboardTimeUtils.cs deleted file mode 100644 index ec222de018..0000000000 --- a/osu.Game/Utils/ScoreboardTimeUtils.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Game.Resources.Localisation.Web; - -#nullable enable - -namespace osu.Game.Utils -{ - public static class ScoreboardTimeUtils - { - - private static string formatQuantity(string template, int quantity) - { - if (quantity <= 1) - return $@"{quantity}{template}"; - - return $@"{quantity}{template}s"; - } - } -} From 656c58567dbe8068d7d4f7dfabcdf2b0818fbb87 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 16:21:36 +0900 Subject: [PATCH 0753/5199] Add safeties to skip attempted import of the intro beatmap when osu! ruleset not present MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In general running this import will not cause any critical failures, but the import itself *will* fail – and more loudly with the upcoming changes to `RulesetStore` (https://github.com/ppy/osu/pull/16890). Due to it being a loud failure, it will cause the notification overlay to display a parsing error, which can interrupt the flow of some tests. See test failure at https://github.com/ppy/osu/runs/5268848949?check_suite_focus=true as an example (video coverage at https://github.com/ppy/osu/pull/16890#issuecomment-1046542243). --- osu.Game/Screens/Menu/IntroScreen.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 98c4b15f7f..afe75c5ef7 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -19,6 +19,7 @@ using osu.Game.Database; using osu.Game.IO.Archives; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osu.Game.Rulesets; using osu.Game.Screens.Backgrounds; using osuTK; using osuTK.Graphics; @@ -71,6 +72,9 @@ namespace osu.Game.Screens.Menu [CanBeNull] private readonly Func createNextScreen; + [Resolved] + private RulesetStore rulesets { get; set; } + /// /// Whether the is provided by osu! resources, rather than a user beatmap. /// Only valid during or after . @@ -117,7 +121,11 @@ namespace osu.Game.Screens.Menu // we generally want a song to be playing on startup, so use the intro music even if a user has specified not to if no other track is available. if (initialBeatmap == null) { - if (!loadThemedIntro()) + // Intro beatmaps are generally made using the osu! ruleset. + // It might not be present in test projects for other rulesets. + bool osuRulesetPresent = rulesets.GetRuleset(0) != null; + + if (!loadThemedIntro() && osuRulesetPresent) { // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state. // this could happen if a user has nuked their files store. for now, reimport to repair this. From 2f6e65a9a268bf3c4b29a4005556cae95d524a31 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 16:35:39 +0900 Subject: [PATCH 0754/5199] Gracefully handle undefined `DateTimeOffset` values Only seems to happen in tests, but best to safeguard against this regardless. --- osu.Game/Extensions/TimeDisplayExtensions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Extensions/TimeDisplayExtensions.cs b/osu.Game/Extensions/TimeDisplayExtensions.cs index ee6f458398..54af6a5942 100644 --- a/osu.Game/Extensions/TimeDisplayExtensions.cs +++ b/osu.Game/Extensions/TimeDisplayExtensions.cs @@ -58,6 +58,9 @@ namespace osu.Game.Extensions /// A short relative string representing the input time. public static string ToShortRelativeTime(this DateTimeOffset time, TimeSpan lowerCutoff) { + if (time == default) + return "-"; + var now = DateTime.Now; var difference = now - time; From c466d6df94e741a5def1a4395a5d6d03b29240c9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 21 Feb 2022 17:19:35 +0900 Subject: [PATCH 0755/5199] Ensure to not multiply by 0 --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 42ab30d4d0..d5a5aa4592 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -239,6 +239,10 @@ namespace osu.Game.Rulesets.Scoring case ScoringMode.Classic: int totalHitObjects = statistics.Where(k => k.Key >= HitResult.Miss && k.Key <= HitResult.Perfect).Sum(k => k.Value); + // If there are no hitobjects then the beatmap can be composed of only ticks or spinners, so ensure we don't multiply by 0 at all times. + if (totalHitObjects == 0) + totalHitObjects = 1; + // This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring. // The invariant is important to ensure that scores don't get re-ordered on leaderboards between the two scoring modes. double scaledStandardised = GetScore(ScoringMode.Standardised, accuracyRatio, comboRatio, statistics) / max_score; From 1737128334db2da3095f52e5fbcbb1c3fd169b8a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 17:45:57 +0900 Subject: [PATCH 0756/5199] Allow room category to be copied even if `Spotlight` I remember that this conditional copy was added to support making copies of spotlight rooms without carrying across the `Spotlight` type, but in testing this is already handled web side to the point that it's not required. The rationale for allowing the copy is that this method is used for tests, where it was not being copied correctly from the input as expected (used at https://github.com/ppy/osu/blob/bdc3b76df0b58406796e2b08db13be7f2140fa7e/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs#L38). --- osu.Game/Online/Rooms/Room.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index bbe854f2dd..a328f8e8c0 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -168,8 +168,7 @@ namespace osu.Game.Online.Rooms RoomID.Value = other.RoomID.Value; Name.Value = other.Name.Value; - if (other.Category.Value != RoomCategory.Spotlight) - Category.Value = other.Category.Value; + Category.Value = other.Category.Value; if (other.Host.Value != null && Host.Value?.Id != other.Host.Value.Id) Host.Value = other.Host.Value; From ab8b502709f5da93709bcfc0f26a6e0053a9ccdc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 17:59:51 +0900 Subject: [PATCH 0757/5199] Add test coverage of spotlights being at the top of the listing --- .../Multiplayer/TestSceneLoungeRoomsContainer.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index c3d5f7ec23..9a66a2b05c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -44,15 +44,20 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestBasicListChanges() { - AddStep("add rooms", () => RoomManager.AddRooms(3)); + AddStep("add rooms", () => RoomManager.AddRooms(5)); - AddAssert("has 3 rooms", () => container.Rooms.Count == 3); - AddStep("remove first room", () => RoomManager.RemoveRoom(RoomManager.Rooms.FirstOrDefault())); - AddAssert("has 2 rooms", () => container.Rooms.Count == 2); + AddAssert("has 5 rooms", () => container.Rooms.Count == 5); + + AddAssert("all spotlights at top", () => container.Rooms + .SkipWhile(r => r.Room.Category.Value == RoomCategory.Spotlight) + .All(r => r.Room.Category.Value == RoomCategory.Normal)); + + AddStep("remove first room", () => RoomManager.RemoveRoom(RoomManager.Rooms.FirstOrDefault(r => r.RoomID.Value == 0))); + AddAssert("has 4 rooms", () => container.Rooms.Count == 4); AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0)); AddStep("select first room", () => container.Rooms.First().TriggerClick()); - AddAssert("first room selected", () => checkRoomSelected(RoomManager.Rooms.First())); + AddAssert("first spotlight selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category.Value == RoomCategory.Spotlight))); } [Test] From 02a85005004ae66dc4477bfd38932badc16ec894 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 17:59:09 +0900 Subject: [PATCH 0758/5199] Ensure spotlights always show at the top of the lounge listing As proposed at https://github.com/ppy/osu/discussions/16936. Spotlights are intended to have focus, so let's make sure they are the first thing the user sees for the time being. --- .../Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 9f917c978c..3260427192 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -124,7 +124,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void updateSorting() { foreach (var room in roomFlow) - roomFlow.SetLayoutPosition(room, -(room.Room.RoomID.Value ?? 0)); + { + roomFlow.SetLayoutPosition(room, room.Room.Category.Value == RoomCategory.Spotlight + // Always show spotlight playlists at the top of the listing. + ? float.MinValue + : -(room.Room.RoomID.Value ?? 0)); + } } protected override bool OnClick(ClickEvent e) From 46b408be75b21c3fe872227c298d696851150d3e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 21 Feb 2022 17:59:50 +0900 Subject: [PATCH 0759/5199] Update tests to match new behaviour --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index f0d9ece06f..c6e7988543 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -36,9 +36,9 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.Meh, 750_000)] [TestCase(ScoringMode.Standardised, HitResult.Ok, 800_000)] [TestCase(ScoringMode.Standardised, HitResult.Great, 1_000_000)] - [TestCase(ScoringMode.Classic, HitResult.Meh, 41)] - [TestCase(ScoringMode.Classic, HitResult.Ok, 46)] - [TestCase(ScoringMode.Classic, HitResult.Great, 72)] + [TestCase(ScoringMode.Classic, HitResult.Meh, 20)] + [TestCase(ScoringMode.Classic, HitResult.Ok, 23)] + [TestCase(ScoringMode.Classic, HitResult.Great, 36)] public void TestSingleOsuHit(ScoringMode scoringMode, HitResult hitResult, int expectedScore) { scoreProcessor.Mode.Value = scoringMode; @@ -86,17 +86,17 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 1_000_030)] // 1 * 300_000 + 700_000 (max combo 0) + 3 * 10 (bonus points) [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 1_000_150)] // 1 * 300_000 + 700_000 (max combo 0) + 3 * 50 (bonus points) [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] - [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 68)] - [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 81)] - [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 109)] - [TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 149)] - [TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 149)] - [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 9)] - [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 15)] + [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 86)] + [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 104)] + [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 140)] + [TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 190)] + [TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 190)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 18)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 31)] [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] - [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 149)] - [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 18)] - [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 18)] + [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 12)] + [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 36)] + [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 36)] public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) { var minResult = new TestJudgement(hitResult).MinResult; @@ -128,8 +128,8 @@ namespace osu.Game.Tests.Rulesets.Scoring /// [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, 978_571)] // (3 * 10 + 100) / (4 * 10 + 100) * 300_000 + (1 / 1) * 700_000 [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, 914_286)] // (3 * 0 + 100) / (4 * 10 + 100) * 300_000 + (1 / 1) * 700_000 - [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, 69)] // (((3 * 10 + 100) / (4 * 10 + 100)) * 1 * 300) * (1 + 0 / 25) - [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, 60)] // (((3 * 0 + 100) / (4 * 10 + 100)) * 1 * 300) * (1 + 0 / 25) + [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, 34)] + [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, 30)] public void TestSmallTicksAccuracy(ScoringMode scoringMode, HitResult hitResult, int expectedScore) { IEnumerable hitObjects = Enumerable From 7f4cc221d25c9d9733c5ec6189db61a3a0f0e2f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Feb 2022 18:33:27 +0900 Subject: [PATCH 0760/5199] Add API versioning --- osu.Game/Online/API/APIAccess.cs | 2 ++ osu.Game/Online/API/APIRequest.cs | 4 ++++ osu.Game/Online/API/DummyAPIAccess.cs | 2 ++ osu.Game/Online/API/IAPIProvider.cs | 5 +++++ 4 files changed, 13 insertions(+) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index c5302a393c..8c9741b98b 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -36,6 +36,8 @@ namespace osu.Game.Online.API public string WebsiteRootUrl { get; } + public int APIVersion => 20220217; // We may want to pull this from the game version eventually. + public Exception LastLoginError { get; private set; } public string ProvidedUsername { get; private set; } diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 91148c177f..776ff5fd8f 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Globalization; using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.IO.Network; @@ -112,6 +113,9 @@ namespace osu.Game.Online.API WebRequest = CreateWebRequest(); WebRequest.Failed += Fail; WebRequest.AllowRetryOnTimeout = false; + + WebRequest.AddHeader("x-api-version", API.APIVersion.ToString(CultureInfo.InvariantCulture)); + if (!string.IsNullOrEmpty(API.AccessToken)) WebRequest.AddHeader("Authorization", $"Bearer {API.AccessToken}"); diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 7131c3a7d4..f292e95bd1 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -33,6 +33,8 @@ namespace osu.Game.Online.API public string WebsiteRootUrl => "http://localhost"; + public int APIVersion => int.Parse(DateTime.Now.ToString("yyyyMMdd")); + public Exception LastLoginError { get; private set; } /// diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index a97eae77e3..470d46cd7f 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -57,6 +57,11 @@ namespace osu.Game.Online.API /// string WebsiteRootUrl { get; } + /// + /// The version of the API. + /// + int APIVersion { get; } + /// /// The last login error that occurred, if any. /// From 39d64e779cd576f5e0a7e62705c7063ceb49930a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Feb 2022 19:15:09 +0900 Subject: [PATCH 0761/5199] Handle API returned difficulty range for rooms --- osu.Game/Online/Rooms/Room.cs | 13 ++++++++++++ .../Components/StarRatingRangeDisplay.cs | 21 ++++++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index bbe854f2dd..6722dac51d 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -37,6 +37,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("channel_id")] public readonly Bindable ChannelId = new Bindable(); + [JsonProperty("difficulty_range")] + public readonly Bindable DifficultyRange = new Bindable(); + [Cached] [JsonIgnore] public readonly Bindable Category = new Bindable(); @@ -228,5 +231,15 @@ namespace osu.Game.Online.Rooms public bool ShouldSerializeEndDate() => false; #endregion + + [JsonObject(MemberSerialization.OptIn)] + public class RoomDifficultyRange + { + [JsonProperty("min")] + public double Min; + + [JsonProperty("max")] + public double Max; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs index 95ecadd21a..f27f5d6741 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs @@ -12,6 +12,7 @@ using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; +using osu.Game.Online.Rooms; using osuTK; namespace osu.Game.Screens.OnlinePlay.Components @@ -71,6 +72,9 @@ namespace osu.Game.Screens.OnlinePlay.Components }; } + [Resolved] + private Room room { get; set; } + protected override void LoadComplete() { base.LoadComplete(); @@ -80,10 +84,21 @@ namespace osu.Game.Screens.OnlinePlay.Components private void updateRange(object sender, NotifyCollectionChangedEventArgs e) { - var orderedDifficulties = Playlist.Select(p => p.Beatmap).OrderBy(b => b.StarRating).ToArray(); + StarDifficulty minDifficulty; + StarDifficulty maxDifficulty; - StarDifficulty minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarRating : 0, 0); - StarDifficulty maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarRating : 0, 0); + if (room.DifficultyRange != null) + { + minDifficulty = new StarDifficulty(room.DifficultyRange.Value.Min, 0); + maxDifficulty = new StarDifficulty(room.DifficultyRange.Value.Max, 0); + } + else + { + var orderedDifficulties = Playlist.Select(p => p.Beatmap).OrderBy(b => b.StarRating).ToArray(); + + minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarRating : 0, 0); + maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarRating : 0, 0); + } minDisplay.Current.Value = minDifficulty; maxDisplay.Current.Value = maxDifficulty; From b43008b9f6ffc796e5927bf218230d9a4e806dc6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 19:02:21 +0900 Subject: [PATCH 0762/5199] Add cover and count handling from newer response version --- osu.Game/Online/Rooms/Room.cs | 21 +++++++++++++++++++ .../Components/OnlinePlayBackgroundSprite.cs | 3 ++- .../Lounge/Components/PlaylistCountPill.cs | 13 +++++++----- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 6 ++++++ 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 6722dac51d..db24eeea41 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -37,6 +37,14 @@ namespace osu.Game.Online.Rooms [JsonProperty("channel_id")] public readonly Bindable ChannelId = new Bindable(); + [JsonProperty("current_playlist_item")] + [Cached] + public readonly Bindable CurrentPlaylistItem = new Bindable(); + + [JsonProperty("playlist_item_stats")] + [Cached] + public readonly Bindable PlaylistItemStats = new Bindable(); + [JsonProperty("difficulty_range")] public readonly Bindable DifficultyRange = new Bindable(); @@ -232,6 +240,19 @@ namespace osu.Game.Online.Rooms #endregion + [JsonObject(MemberSerialization.OptIn)] + public class RoomPlaylistItemStats + { + [JsonProperty("count_active")] + public int CountActive; + + [JsonProperty("count_total")] + public int CountTotal; + + [JsonProperty("ruleset_ids")] + public int[] RulesetIDs; + } + [JsonObject(MemberSerialization.OptIn)] public class RoomDifficultyRange { diff --git a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs index d46ff12279..2faa46e622 100644 --- a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs +++ b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs @@ -23,6 +23,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { InternalChild = sprite = CreateBackgroundSprite(); + CurrentPlaylistItem.BindValueChanged(_ => updateBeatmap()); Playlist.CollectionChanged += (_, __) => updateBeatmap(); updateBeatmap(); @@ -30,7 +31,7 @@ namespace osu.Game.Screens.OnlinePlay.Components private void updateBeatmap() { - sprite.Beatmap.Value = Playlist.GetCurrentItem()?.Beatmap; + sprite.Beatmap.Value = CurrentPlaylistItem.Value?.Beatmap ?? Playlist.GetCurrentItem()?.Beatmap; } protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(BeatmapSetCoverType) { RelativeSizeAxes = Axes.Both }; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs index ef2c2df4a6..f387adfeb0 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs @@ -1,13 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Specialized; using Humanizer; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { @@ -41,15 +42,17 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { base.LoadComplete(); - Playlist.BindCollectionChanged(updateCount, true); + PlaylistItemStats.BindValueChanged(updateCount, true); } - private void updateCount(object sender, NotifyCollectionChangedEventArgs e) + private void updateCount(ValueChangedEvent valueChangedEvent) { + int activeItems = valueChangedEvent.NewValue.CountActive; + count.Clear(); - count.AddText(Playlist.Count.ToLocalisableString(), s => s.Font = s.Font.With(weight: FontWeight.Bold)); + count.AddText(activeItems.ToLocalisableString(), s => s.Font = s.Font.With(weight: FontWeight.Bold)); count.AddText(" "); - count.AddText("Beatmap".ToQuantity(Playlist.Count, ShowQuantityAs.None)); + count.AddText("Beatmap".ToQuantity(activeItems, ShowQuantityAs.None)); } } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index c833621fbc..3148b74c9c 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -32,6 +32,12 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable Type { get; private set; } + [Resolved(typeof(Room))] + protected Bindable CurrentPlaylistItem { get; private set; } + + [Resolved(typeof(Room))] + protected Bindable PlaylistItemStats { get; private set; } + [Resolved(typeof(Room))] protected BindableList Playlist { get; private set; } From b5348e04077910c65f497a88b5287d402dcf6427 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 19:05:32 +0900 Subject: [PATCH 0763/5199] Update ruleset filtering to use newly provided array return --- osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 9f917c978c..a521306d7e 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { bool matchingFilter = true; - matchingFilter &= r.Room.Playlist.Count == 0 || criteria.Ruleset == null || r.Room.Playlist.Any(i => i.RulesetID == criteria.Ruleset.OnlineID); + matchingFilter &= criteria.Ruleset == null || r.Room.PlaylistItemStats.Value.RulesetIDs.Any(id => id == criteria.Ruleset.OnlineID); if (!string.IsNullOrEmpty(criteria.SearchString)) matchingFilter &= r.FilterTerms.Any(term => term.Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); From ffa5291b74b76c3083a4694a0a5961b631fba3d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 19:37:36 +0900 Subject: [PATCH 0764/5199] Add fallback handling for item count to support different request types --- .../OnlinePlay/Lounge/Components/PlaylistCountPill.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs index f387adfeb0..1aef41efbd 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs @@ -3,12 +3,10 @@ using Humanizer; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { @@ -42,12 +40,13 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { base.LoadComplete(); - PlaylistItemStats.BindValueChanged(updateCount, true); + PlaylistItemStats.BindValueChanged(_ => updateCount(), true); + Playlist.BindCollectionChanged((_, __) => updateCount(), true); } - private void updateCount(ValueChangedEvent valueChangedEvent) + private void updateCount() { - int activeItems = valueChangedEvent.NewValue.CountActive; + int activeItems = PlaylistItemStats.Value?.CountActive ?? Playlist.Count; count.Clear(); count.AddText(activeItems.ToLocalisableString(), s => s.Font = s.Font.With(weight: FontWeight.Bold)); From c7e9cf904bbd777bdc878f893fd37624a321e39f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 19:38:26 +0900 Subject: [PATCH 0765/5199] Fix incorrect null check on now-bindable `DifficultyRange` --- .../Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs index f27f5d6741..de56e6ff86 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs @@ -87,7 +87,7 @@ namespace osu.Game.Screens.OnlinePlay.Components StarDifficulty minDifficulty; StarDifficulty maxDifficulty; - if (room.DifficultyRange != null) + if (room.DifficultyRange.Value != null) { minDifficulty = new StarDifficulty(room.DifficultyRange.Value.Min, 0); maxDifficulty = new StarDifficulty(room.DifficultyRange.Value.Max, 0); From 655b23f408c483afb04916e16bfd4b15ebfbaf3a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 19:46:31 +0900 Subject: [PATCH 0766/5199] Update playlist room display to a three column layout Similar to the changes made to multiplayer. --- .../Playlists/PlaylistsRoomSubScreen.cs | 227 ++++++++++-------- 1 file changed, 125 insertions(+), 102 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 542851cb0f..338a9c856f 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -69,132 +69,155 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Room.MaxAttempts.BindValueChanged(attempts => progressSection.Alpha = Room.MaxAttempts.Value != null ? 1 : 0, true); } - protected override Drawable CreateMainContent() => new GridContainer + protected override Drawable CreateMainContent() => new Container { RelativeSizeAxes = Axes.Both, - Content = new[] + Padding = new MarginPadding { Horizontal = 5, Vertical = 10 }, + Child = new GridContainer { - new Drawable[] + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] { - new Container + new Dimension(), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(), + }, + Content = new[] + { + new Drawable[] { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = 5 }, - Child = new GridContainer + // Playlist items column + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = 5 }, + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { new OverlinedPlaylistHeader(), }, + new Drawable[] + { + new DrawableRoomPlaylist + { + RelativeSizeAxes = Axes.Both, + Items = { BindTarget = Room.Playlist }, + SelectedItem = { BindTarget = SelectedItem }, + AllowSelection = true, + AllowShowingResults = true, + RequestResults = item => + { + Debug.Assert(RoomId.Value != null); + ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false)); + } + } + }, + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + } + } + }, + // Spacer + null, + // Middle column (mods and leaderboard) + new GridContainer { RelativeSizeAxes = Axes.Both, Content = new[] { - new Drawable[] { new OverlinedPlaylistHeader(), }, + new[] + { + UserModsSection = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Alpha = 0, + Margin = new MarginPadding { Bottom = 10 }, + Children = new Drawable[] + { + new OverlinedHeader("Extra mods"), + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new UserModSelectButton + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = 90, + Text = "Select", + Action = ShowUserModSelect, + }, + new ModDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Current = UserMods, + Scale = new Vector2(0.8f), + }, + } + } + } + }, + }, new Drawable[] { - new DrawableRoomPlaylist + progressSection = new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - Items = { BindTarget = Room.Playlist }, - SelectedItem = { BindTarget = SelectedItem }, - AllowSelection = true, - AllowShowingResults = true, - RequestResults = item => + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Alpha = 0, + Margin = new MarginPadding { Bottom = 10 }, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - Debug.Assert(RoomId.Value != null); - ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false)); + new OverlinedHeader("Progress"), + new RoomLocalUserInfo(), } - } + }, }, + new Drawable[] + { + new OverlinedHeader("Leaderboard") + }, + new Drawable[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, }, + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + } + }, + // Spacer + null, + // Main right column + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { new OverlinedHeader("Chat") }, + new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } } }, RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), new Dimension(), } - } - }, - null, - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new[] - { - UserModsSection = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Alpha = 0, - Margin = new MarginPadding { Bottom = 10 }, - Children = new Drawable[] - { - new OverlinedHeader("Extra mods"), - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - Children = new Drawable[] - { - new UserModSelectButton - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Width = 90, - Text = "Select", - Action = ShowUserModSelect, - }, - new ModDisplay - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Current = UserMods, - Scale = new Vector2(0.8f), - }, - } - } - } - }, - }, - new Drawable[] - { - progressSection = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Alpha = 0, - Margin = new MarginPadding { Bottom = 10 }, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new OverlinedHeader("Progress"), - new RoomLocalUserInfo(), - } - }, - }, - new Drawable[] - { - new OverlinedHeader("Leaderboard") - }, - new Drawable[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, }, - new Drawable[] { new OverlinedHeader("Chat"), }, - new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } } }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Relative, size: 0.4f, minSize: 120), - } }, }, - }, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 400), - new Dimension(), - new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 600), } }; From 98c008b95f49ac3c1676619aa9195cce4ad46c54 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 19:48:39 +0900 Subject: [PATCH 0767/5199] Fix test failures due to order change --- .../Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs | 2 +- osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 9a66a2b05c..93cd281bc5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestBasicListChanges() { - AddStep("add rooms", () => RoomManager.AddRooms(5)); + AddStep("add rooms", () => RoomManager.AddRooms(5, withSpotlightRooms: true)); AddAssert("has 5 rooms", () => container.Rooms.Count == 5); diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index 8dfd969c51..6abcb2924c 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay base.JoinRoom(room, password, onSuccess, onError); } - public void AddRooms(int count, RulesetInfo ruleset = null, bool withPassword = false) + public void AddRooms(int count, RulesetInfo ruleset = null, bool withPassword = false, bool withSpotlightRooms = false) { for (int i = 0; i < count; i++) { @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay Name = { Value = $@"Room {currentRoomId}" }, Host = { Value = new APIUser { Username = @"Host" } }, EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, - Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal }, + Category = { Value = withSpotlightRooms && i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal }, }; if (withPassword) From 2aa0364092819f821cd1be69c4d56e2150a1218d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 00:14:33 +0900 Subject: [PATCH 0768/5199] Fix null reference in tests during attempted ruleset filtering --- osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index a521306d7e..f57bff13ca 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { bool matchingFilter = true; - matchingFilter &= criteria.Ruleset == null || r.Room.PlaylistItemStats.Value.RulesetIDs.Any(id => id == criteria.Ruleset.OnlineID); + matchingFilter &= criteria.Ruleset == null || r.Room.PlaylistItemStats.Value?.RulesetIDs.Any(id => id == criteria.Ruleset.OnlineID) != false; if (!string.IsNullOrEmpty(criteria.SearchString)) matchingFilter &= r.FilterTerms.Any(term => term.Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); From 113153e6a34becbae3934939172511b2f8ee591a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 00:25:00 +0900 Subject: [PATCH 0769/5199] Fix remaining filter tests --- osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index 8dfd969c51..7ade173c9c 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -43,6 +43,11 @@ namespace osu.Game.Tests.Visual.OnlinePlay if (ruleset != null) { + room.PlaylistItemStats.Value = new Room.RoomPlaylistItemStats + { + RulesetIDs = new[] { ruleset.OnlineID }, + }; + room.Playlist.Add(new PlaylistItem(new BeatmapInfo { Metadata = new BeatmapMetadata() }) { RulesetID = ruleset.OnlineID, From 8d70b85e41c8bba1aa2c056554c66a59d0002f56 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Mon, 21 Feb 2022 20:20:24 +0100 Subject: [PATCH 0770/5199] Revert changes --- osu.Android/OsuGameAndroid.cs | 35 ------------------------------- osu.Game/Screens/Menu/MainMenu.cs | 12 ----------- 2 files changed, 47 deletions(-) diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index 914a6f7502..050bf2b787 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -5,11 +5,7 @@ using System; using Android.App; using Android.OS; using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Platform; -using osu.Framework.Screens; using osu.Game; -using osu.Game.Screens.Menu; using osu.Game.Updater; using osu.Game.Utils; using Xamarin.Essentials; @@ -21,8 +17,6 @@ namespace osu.Android [Cached] private readonly OsuGameActivity gameActivity; - private readonly BindableBool allowExiting = new BindableBool(); - public OsuGameAndroid(OsuGameActivity activity) : base(null) { @@ -73,45 +67,16 @@ namespace osu.Android } } - public override void SetHost(GameHost host) - { - base.SetHost(host); - host.AllowExitingAndroid.AddSource(allowExiting); - } - protected override void LoadComplete() { base.LoadComplete(); LoadComponentAsync(new GameplayScreenRotationLocker(), Add); } - protected override void ScreenChanged(IScreen current, IScreen newScreen) - { - base.ScreenChanged(current, newScreen); - - switch (newScreen) - { - case MainMenu _: - // allow the MainMenu to (dis)allow exiting based on its ButtonSystemState. - allowExiting.Value = true; - break; - - default: - allowExiting.Value = false; - break; - } - } - protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager(); protected override BatteryInfo CreateBatteryInfo() => new AndroidBatteryInfo(); - protected override void Dispose(bool isDisposing) - { - Host.AllowExitingAndroid.RemoveSource(allowExiting); - base.Dispose(isDisposing); - } - private class AndroidBatteryInfo : BatteryInfo { public override double ChargeLevel => Battery.ChargeLevel; diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 2391903861..8b1bab52b3 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -70,16 +70,12 @@ namespace osu.Game.Screens.Menu private ParallaxContainer buttonsContainer; private SongTicker songTicker; - private readonly BindableBool allowExitingAndroid = new BindableBool(true); - [BackgroundDependencyLoader(true)] private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics) { holdDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); loginDisplayed = statics.GetBindable(Static.LoginOverlayDisplayed); - host.AllowExitingAndroid.AddSource(allowExitingAndroid); - if (host.CanExit) { AddInternal(exitConfirmOverlay = new ExitConfirmOverlay @@ -138,8 +134,6 @@ namespace osu.Game.Screens.Menu ApplyToBackground(b => b.FadeColour(OsuColour.Gray(0.8f), 500, Easing.OutSine)); break; } - - allowExitingAndroid.Value = state == ButtonSystemState.Initial; }; buttons.OnSettings = () => settings?.ToggleVisibility(); @@ -303,11 +297,5 @@ namespace osu.Game.Screens.Menu Schedule(loadSoloSongSelect); } - - protected override void Dispose(bool isDisposing) - { - host.AllowExitingAndroid.RemoveSource(allowExitingAndroid); - base.Dispose(isDisposing); - } } } From 3eee505aa26569059ca0fddf35c202518a484453 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Mon, 21 Feb 2022 20:24:17 +0100 Subject: [PATCH 0771/5199] Update "exit" flow when pressing back on Android --- osu.Game/Screens/Menu/ExitConfirmOverlay.cs | 23 ++++++++++++++++++ osu.Game/Screens/Menu/MainMenu.cs | 27 +++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs index a90b83c5fe..253ec17c28 100644 --- a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs +++ b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs @@ -3,6 +3,7 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Overlays; @@ -42,4 +43,26 @@ namespace osu.Game.Screens.Menu } } } + + /// + /// An that behaves as if the is always 0. + /// + /// This is useful for mobile devices using gesture navigation, where holding to confirm is not possible. + public class NoHoldExitConfirmOverlay : ExitConfirmOverlay, IKeyBindingHandler + { + public new bool OnPressed(KeyBindingPressEvent e) + { + if (e.Repeat) + return false; + + if (e.Action == GlobalAction.Back) + { + Progress.Value = 1; + Confirm(); + return true; + } + + return false; + } + } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 8b1bab52b3..7bc0cb48bf 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Platform; @@ -89,6 +90,14 @@ namespace osu.Game.Screens.Menu } }); } + else if (host.CanSuspendToBackground) + { + AddInternal(exitConfirmOverlay = new NoHoldExitConfirmOverlay + { + // treat as if the UIHoldActivationDelay is always 0. see NoHoldExitConfirmOverlay xmldoc for more info. + Action = this.Exit + }); + } AddRangeInternal(new[] { @@ -148,6 +157,24 @@ namespace osu.Game.Screens.Menu private void confirmAndExit() { + if (host.CanSuspendToBackground) + { + // cancel the overlay as we're not actually exiting. + // this is the same action as 'onCancel' in `ConfirmExitDialog`. + exitConfirmOverlay.Abort(); + + // fade the track so the Bass.Pause() on suspend isn't as jarring. + const double fade_time = 500; + musicController.CurrentTrack + .VolumeTo(0, fade_time, Easing.Out).Then() + .VolumeTo(1, fade_time, Easing.In); + + host.SuspendToBackground(); + + // on hosts that can only suspend, we don't ever want to exit the game. + return; + } + if (exitConfirmed) return; exitConfirmed = true; From cd3641137bc920ef042396125c0c323fd7875fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Feb 2022 00:02:54 +0100 Subject: [PATCH 0772/5199] Add `OsuColour` method mapping colours from basic theme to mod types --- osu.Game/Graphics/OsuColour.cs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 886ba7ef92..afedf36cad 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Beatmaps; using osu.Game.Overlays; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Utils; @@ -157,6 +158,36 @@ namespace osu.Game.Graphics } } + /// + /// Retrieves the main accent colour for a . + /// + public Color4 ForModType(ModType modType) + { + switch (modType) + { + case ModType.Automation: + return Blue1; + + case ModType.DifficultyIncrease: + return Red1; + + case ModType.DifficultyReduction: + return Lime1; + + case ModType.Conversion: + return Purple1; + + case ModType.Fun: + return Pink1; + + case ModType.System: + return Gray7; + + default: + throw new ArgumentOutOfRangeException(nameof(modType), modType, "Unknown mod type"); + } + } + /// /// Returns a foreground text colour that is supposed to contrast well with /// the supplied . From 5186693dad42b370b115da9a304b3fc7873470a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Feb 2022 00:03:07 +0100 Subject: [PATCH 0773/5199] Implement tiny mod switch --- .../UserInterface/TestSceneModSwitchTiny.cs | 85 +++++++++++++++++ osu.Game/Rulesets/UI/ModSwitchTiny.cs | 91 +++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModSwitchTiny.cs create mode 100644 osu.Game/Rulesets/UI/ModSwitchTiny.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSwitchTiny.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSwitchTiny.cs new file mode 100644 index 0000000000..dbde7ce425 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSwitchTiny.cs @@ -0,0 +1,85 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Overlays; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Taiko; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestSceneModSwitchTiny : OsuTestScene + { + [Test] + public void TestOsu() => createSwitchTestFor(new OsuRuleset()); + + [Test] + public void TestTaiko() => createSwitchTestFor(new TaikoRuleset()); + + [Test] + public void TestCatch() => createSwitchTestFor(new CatchRuleset()); + + [Test] + public void TestMania() => createSwitchTestFor(new ManiaRuleset()); + + private void createSwitchTestFor(Ruleset ruleset) + { + AddStep("no colour scheme", () => Child = createContent(ruleset, null)); + + foreach (var scheme in Enum.GetValues(typeof(OverlayColourScheme)).Cast()) + { + AddStep($"{scheme} colour scheme", () => Child = createContent(ruleset, scheme)); + } + + AddToggleStep("toggle active", active => this.ChildrenOfType().ForEach(s => s.Active.Value = active)); + } + + private static Drawable createContent(Ruleset ruleset, OverlayColourScheme? colourScheme) + { + var switchFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + Padding = new MarginPadding(20), + ChildrenEnumerable = ruleset.CreateAllMods() + .GroupBy(mod => mod.Type) + .Select(group => new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + Spacing = new Vector2(5), + ChildrenEnumerable = group.Select(mod => new ModSwitchTiny(mod)) + }) + }; + + if (colourScheme != null) + { + return new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] + { + (typeof(OverlayColourProvider), new OverlayColourProvider(colourScheme.Value)) + }, + Child = switchFlow + }; + } + + return switchFlow; + } + } +} diff --git a/osu.Game/Rulesets/UI/ModSwitchTiny.cs b/osu.Game/Rulesets/UI/ModSwitchTiny.cs new file mode 100644 index 0000000000..093c2271b6 --- /dev/null +++ b/osu.Game/Rulesets/UI/ModSwitchTiny.cs @@ -0,0 +1,91 @@ +// 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.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using osu.Game.Rulesets.Mods; +using osuTK; +using osuTK.Graphics; + +#nullable enable + +namespace osu.Game.Rulesets.UI +{ + public class ModSwitchTiny : CompositeDrawable + { + public BindableBool Active { get; } = new BindableBool(); + + private readonly IMod mod; + + private readonly Box background; + private readonly OsuSpriteText acronymText; + + private Color4 activeForegroundColour; + private Color4 inactiveForegroundColour; + + private Color4 activeBackgroundColour; + private Color4 inactiveBackgroundColour; + + public ModSwitchTiny(IMod mod) + { + this.mod = mod; + Size = new Vector2(73, 30); + + InternalChild = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + acronymText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Shadow = false, + Font = OsuFont.Numeric.With(size: 24, weight: FontWeight.Black), + Text = mod.Acronym, + Margin = new MarginPadding + { + Top = 4 + } + } + } + }; + } + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, OverlayColourProvider? colourProvider) + { + inactiveBackgroundColour = colourProvider?.Background5 ?? colours.Gray3; + activeBackgroundColour = colours.ForModType(mod.Type); + + inactiveForegroundColour = colourProvider?.Background2 ?? colours.Gray5; + activeForegroundColour = Interpolation.ValueAt(0.1f, Colour4.Black, activeForegroundColour, 0, 1); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Active.BindValueChanged(_ => updateState(), true); + FinishTransforms(true); + } + + private void updateState() + { + acronymText.FadeColour(Active.Value ? activeForegroundColour : inactiveForegroundColour, 200, Easing.OutQuint); + background.FadeColour(Active.Value ? activeBackgroundColour : inactiveBackgroundColour, 200, Easing.OutQuint); + } + } +} From cfc41a0a366cc0c311cbee0cf1aa9ea37d87784a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Feb 2022 00:03:12 +0100 Subject: [PATCH 0774/5199] Implement small mod switch --- .../UserInterface/TestSceneModSwitchSmall.cs | 85 ++++++++++++++ osu.Game/Rulesets/UI/ModSwitchSmall.cs | 109 ++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModSwitchSmall.cs create mode 100644 osu.Game/Rulesets/UI/ModSwitchSmall.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSwitchSmall.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSwitchSmall.cs new file mode 100644 index 0000000000..447352b7a6 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSwitchSmall.cs @@ -0,0 +1,85 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Overlays; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Taiko; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestSceneModSwitchSmall : OsuTestScene + { + [Test] + public void TestOsu() => createSwitchTestFor(new OsuRuleset()); + + [Test] + public void TestTaiko() => createSwitchTestFor(new TaikoRuleset()); + + [Test] + public void TestCatch() => createSwitchTestFor(new CatchRuleset()); + + [Test] + public void TestMania() => createSwitchTestFor(new ManiaRuleset()); + + private void createSwitchTestFor(Ruleset ruleset) + { + AddStep("no colour scheme", () => Child = createContent(ruleset, null)); + + foreach (var scheme in Enum.GetValues(typeof(OverlayColourScheme)).Cast()) + { + AddStep($"{scheme} colour scheme", () => Child = createContent(ruleset, scheme)); + } + + AddToggleStep("toggle active", active => this.ChildrenOfType().ForEach(s => s.Active.Value = active)); + } + + private static Drawable createContent(Ruleset ruleset, OverlayColourScheme? colourScheme) + { + var switchFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + Padding = new MarginPadding(20), + ChildrenEnumerable = ruleset.CreateAllMods() + .GroupBy(mod => mod.Type) + .Select(group => new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + Spacing = new Vector2(5), + ChildrenEnumerable = group.Select(mod => new ModSwitchSmall(mod)) + }) + }; + + if (colourScheme != null) + { + return new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] + { + (typeof(OverlayColourProvider), new OverlayColourProvider(colourScheme.Value)) + }, + Child = switchFlow + }; + } + + return switchFlow; + } + } +} diff --git a/osu.Game/Rulesets/UI/ModSwitchSmall.cs b/osu.Game/Rulesets/UI/ModSwitchSmall.cs new file mode 100644 index 0000000000..5d9d9075bc --- /dev/null +++ b/osu.Game/Rulesets/UI/ModSwitchSmall.cs @@ -0,0 +1,109 @@ +// 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.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Utils; +using osu.Game.Graphics; +using osu.Game.Overlays; +using osu.Game.Rulesets.Mods; +using osuTK; +using osuTK.Graphics; + +#nullable enable + +namespace osu.Game.Rulesets.UI +{ + public class ModSwitchSmall : CompositeDrawable + { + public BindableBool Active { get; } = new BindableBool(); + + private readonly IMod mod; + + private const float size = 60; + + private readonly SpriteIcon background; + private readonly SpriteIcon? modIcon; + + private Color4 activeForegroundColour; + private Color4 inactiveForegroundColour; + + private Color4 activeBackgroundColour; + private Color4 inactiveBackgroundColour; + + public ModSwitchSmall(IMod mod) + { + this.mod = mod; + + AutoSizeAxes = Axes.Both; + + FillFlowContainer contentFlow; + ModSwitchTiny tinySwitch; + + InternalChildren = new Drawable[] + { + background = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(size), + Icon = OsuIcon.ModBg + }, + contentFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(0, 4), + Direction = FillDirection.Vertical, + Child = tinySwitch = new ModSwitchTiny(mod) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Scale = new Vector2(0.6f), + Active = { BindTarget = Active } + } + } + }; + + if (mod.Icon != null) + { + contentFlow.Insert(-1, modIcon = new SpriteIcon + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Size = new Vector2(21), + Icon = mod.Icon.Value + }); + tinySwitch.Scale = new Vector2(0.3f); + } + } + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, OverlayColourProvider? colourProvider) + { + inactiveForegroundColour = colourProvider?.Background5 ?? colours.Gray3; + activeForegroundColour = colours.ForModType(mod.Type); + + inactiveBackgroundColour = colourProvider?.Background2 ?? colours.Gray5; + activeBackgroundColour = Interpolation.ValueAt(0.1f, Colour4.Black, activeForegroundColour, 0, 1); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Active.BindValueChanged(_ => updateState(), true); + FinishTransforms(true); + } + + private void updateState() + { + modIcon?.FadeColour(Active.Value ? activeForegroundColour : inactiveForegroundColour, 200, Easing.OutQuint); + background.FadeColour(Active.Value ? activeBackgroundColour : inactiveBackgroundColour, 200, Easing.OutQuint); + } + } +} From 0d56693b7aee6221d16ac2341d86d70b5a805a55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 14:11:22 +0900 Subject: [PATCH 0775/5199] Fix test not always checking the final bonus value Due to the previous logic not waiting until the spinner had completed, there could be false negatives as the check runs too early, with a potential additional bonus spin occurring afterwards. --- .../Mods/TestSceneOsuModSpunOut.cs | 52 +++++++++++++------ 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs index e71377a505..cb8eceb213 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -8,13 +8,15 @@ using NUnit.Framework; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Skinning.Default; -using osu.Game.Screens.Play; +using osu.Game.Rulesets.Scoring; using osuTK; namespace osu.Game.Rulesets.Osu.Tests.Mods @@ -66,25 +68,45 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods } [Test] - public void TestSpinnerOnlyComplete() => CreateModTest(new ModTestData + public void TestSpinnerGetsNoBonusScore() { - Mod = new OsuModSpunOut(), - Autoplay = false, - Beatmap = singleSpinnerBeatmap, - PassCondition = () => + DrawableSpinner spinner = null; + List results = new List(); + + CreateModTest(new ModTestData { - var spinner = Player.ChildrenOfType().SingleOrDefault(); - var gameplayClockContainer = Player.ChildrenOfType().SingleOrDefault(); + Mod = new OsuModSpunOut(), + Autoplay = false, + Beatmap = singleSpinnerBeatmap, + PassCondition = () => + { + // Bind to the first spinner's results for further tracking. + if (spinner == null) + { + // We only care about the first spinner we encounter for this test. + var nextSpinner = Player.ChildrenOfType().SingleOrDefault(); - if (spinner == null || gameplayClockContainer == null) - return false; + if (nextSpinner == null) + return false; - if (!Precision.AlmostEquals(gameplayClockContainer.CurrentTime, spinner.HitObject.StartTime + spinner.HitObject.Duration, 200.0f)) - return false; + spinner = nextSpinner; + spinner.OnNewResult += (o, result) => results.Add(result); - return Precision.AlmostEquals(spinner.Progress, 1.0f, 0.05f) && Precision.AlmostEquals(spinner.GainedBonus.Value, 0, 1); - } - }); + results.Clear(); + } + + // we should only be checking the bonus/progress after the spinner has fully completed. + if (!results.OfType().Any(r => r.TimeCompleted != null)) + return false; + + return + results.Any(r => r.Type == HitResult.SmallTickHit) + && !results.Any(r => r.Type == HitResult.LargeTickHit) + && Precision.AlmostEquals(spinner.Progress, 1.0f, 0.05f) + && Precision.AlmostEquals(spinner.GainedBonus.Value, 0, 1); + } + }); + } private Beatmap singleSpinnerBeatmap => new Beatmap { From 91acc9eec6a8859f665f64338420efc564faf33b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 14:36:08 +0900 Subject: [PATCH 0776/5199] Remove checks which are still going to occasionally fail due to pooling --- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs index cb8eceb213..93c4bd96de 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -101,9 +101,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods return results.Any(r => r.Type == HitResult.SmallTickHit) - && !results.Any(r => r.Type == HitResult.LargeTickHit) - && Precision.AlmostEquals(spinner.Progress, 1.0f, 0.05f) - && Precision.AlmostEquals(spinner.GainedBonus.Value, 0, 1); + && !results.Any(r => r.Type == HitResult.LargeTickHit); } }); } From 9e279c3ebc9cdc23ba5d4a1d192642b314019de8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 14:37:52 +0900 Subject: [PATCH 0777/5199] Fix completely incorrect judgement specification --- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs index 93c4bd96de..e61720d8b9 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -100,8 +100,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods return false; return - results.Any(r => r.Type == HitResult.SmallTickHit) - && !results.Any(r => r.Type == HitResult.LargeTickHit); + results.Any(r => r.Type == HitResult.SmallBonus) + && !results.Any(r => r.Type == HitResult.LargeBonus); } }); } From d6032a2335e0de00dae43ce7f6fcef95ffbbe50e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 14:55:52 +0900 Subject: [PATCH 0778/5199] Fix beatmap overlay not re-fetching results after login --- .../BeatmapListing/BeatmapListingFilterControl.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 157753c09f..0f87f04270 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -67,6 +67,8 @@ namespace osu.Game.Overlays.BeatmapListing [Resolved] private IAPIProvider api { get; set; } + private IBindable apiUser; + public BeatmapListingFilterControl() { RelativeSizeAxes = Axes.X; @@ -127,7 +129,7 @@ namespace osu.Game.Overlays.BeatmapListing } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load(OverlayColourProvider colourProvider, IAPIProvider api) { sortControlBackground.Colour = colourProvider.Background4; } @@ -161,6 +163,9 @@ namespace osu.Game.Overlays.BeatmapListing sortCriteria.BindValueChanged(_ => queueUpdateSearch()); sortDirection.BindValueChanged(_ => queueUpdateSearch()); + + apiUser = api.LocalUser.GetBoundCopy(); + apiUser.BindValueChanged(_ => queueUpdateSearch()); } public void TakeFocus() => searchControl.TakeFocus(); @@ -190,6 +195,9 @@ namespace osu.Game.Overlays.BeatmapListing resetSearch(); + if (!api.IsLoggedIn) + return; + queryChangedDebounce = Scheduler.AddDelayed(() => { resetSearch(); From e744840d07aec8f0f6a75abb81ba1bc9dd9dbba8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 14:56:04 +0900 Subject: [PATCH 0779/5199] Ensure old results are cleared from beatmap overlay on logout --- osu.Game/Overlays/BeatmapListingOverlay.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index fbed234cc7..3476968ded 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Localisation; using osu.Framework.Graphics; @@ -19,6 +20,7 @@ using osu.Game.Beatmaps.Drawables.Cards; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Containers; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapListing; using osu.Game.Resources.Localisation.Web; @@ -32,6 +34,11 @@ namespace osu.Game.Overlays [Resolved] private PreviewTrackManager previewTrackManager { get; set; } + [Resolved] + private IAPIProvider api { get; set; } + + private IBindable apiUser; + private Drawable currentContent; private Container panelTarget; private FillFlowContainer foundContent; @@ -93,6 +100,13 @@ namespace osu.Game.Overlays { base.LoadComplete(); filterControl.CardSize.BindValueChanged(_ => onCardSizeChanged()); + + apiUser = api.LocalUser.GetBoundCopy(); + apiUser.BindValueChanged(_ => + { + if (api.IsLoggedIn) + addContentToResultsArea(Drawable.Empty()); + }); } public void ShowWithSearch(string query) From cde3d9c08ba8e3b9af8e8e6192c9febdfa306baa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 15:15:57 +0900 Subject: [PATCH 0780/5199] Change precedence order to favour playlist as a source for beatmap count --- .../OnlinePlay/Lounge/Components/PlaylistCountPill.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs index 1aef41efbd..e21439e931 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.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 Humanizer; using osu.Framework.Allocation; using osu.Framework.Extensions.LocalisationExtensions; @@ -46,7 +47,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void updateCount() { - int activeItems = PlaylistItemStats.Value?.CountActive ?? Playlist.Count; + int activeItems = Playlist.Count > 0 || PlaylistItemStats.Value == null + // For now, use the playlist as the source of truth if it has any items. + // This allows the count to display correctly on the room screen (after joining a room). + ? Playlist.Count(i => !i.Expired) + : PlaylistItemStats.Value.CountActive; count.Clear(); count.AddText(activeItems.ToLocalisableString(), s => s.Font = s.Font.With(weight: FontWeight.Bold)); From f12044b03e97da4bbb883dd026c267f796345fe6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 15:31:08 +0900 Subject: [PATCH 0781/5199] Add mention of `PlaylistItem.Beatmap` being a placeholder in many cases --- osu.Game/Online/Rooms/PlaylistItem.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs index 33718f050b..f696362cbb 100644 --- a/osu.Game/Online/Rooms/PlaylistItem.cs +++ b/osu.Game/Online/Rooms/PlaylistItem.cs @@ -62,6 +62,10 @@ namespace osu.Game.Online.Rooms [JsonProperty("beatmap_id")] private int onlineBeatmapId => Beatmap.OnlineID; + /// + /// A beatmap representing this playlist item. + /// In many cases, this will *not* contain any usable information apart from OnlineID. + /// [JsonIgnore] public IBeatmapInfo Beatmap { get; set; } = null!; From 057fd6c352c354367ba5056dc7ed8c92976391de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 15:37:42 +0900 Subject: [PATCH 0782/5199] Add mention of `StarRatingRangeDisplay` fallback scenario being wrong for multiplayer --- .../Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs index de56e6ff86..0b673006ef 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs @@ -94,6 +94,8 @@ namespace osu.Game.Screens.OnlinePlay.Components } else { + // In multiplayer rooms, the beatmaps of playlist items will not be populated to a point this can be correct. + // Either populating them via BeatmapLookupCache or polling the API for the room's DifficultyRange will be required. var orderedDifficulties = Playlist.Select(p => p.Beatmap).OrderBy(b => b.StarRating).ToArray(); minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarRating : 0, 0); From 347a2346b9d73352a482b882d81ad49cf8ed4176 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 15:40:30 +0900 Subject: [PATCH 0783/5199] Fix `TestSceneEditorSaving` not waiting for timeline load As seen at https://github.com/ppy/osu/runs/5276431764?check_suite_focus=true. --- osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index adaa24d542..d1c1558003 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -49,6 +49,8 @@ namespace osu.Game.Tests.Visual.Editing double originalTimelineZoom = 0; double changedTimelineZoom = 0; + AddUntilStep("wait for timeline load", () => Editor.ChildrenOfType().SingleOrDefault()?.IsLoaded == true); + AddStep("Set beat divisor", () => Editor.Dependencies.Get().Value = 16); AddStep("Set timeline zoom", () => { From 61b3280de157b86d6b6ad61558902c491e617fc5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 15:47:00 +0900 Subject: [PATCH 0784/5199] Add missing property copies in `Room.CopyFrom` implementation --- osu.Game/Online/Rooms/Room.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index d24cb7e74b..8ed37ad6b5 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -194,6 +194,8 @@ namespace osu.Game.Online.Rooms EndDate.Value = other.EndDate.Value; UserScore.Value = other.UserScore.Value; QueueMode.Value = other.QueueMode.Value; + DifficultyRange.Value = other.DifficultyRange.Value; + PlaylistItemStats.Value = other.PlaylistItemStats.Value; if (EndDate.Value != null && DateTimeOffset.Now >= EndDate.Value) Status.Value = new RoomStatusEnded(); From ca0a04115374368184ca8363ac7026384b9c9dcf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 16:45:04 +0900 Subject: [PATCH 0785/5199] Fix missing escaping causing test failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Online/Chat/MessageFormatter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 4c477d58b6..39a51876c2 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -269,7 +269,7 @@ namespace osu.Game.Online.Chat handleAdvanced(advanced_link_regex, result, startIndex); // handle editor times - handleMatches(time_regex, "{0}", $"{OsuGameBase.OSU_PROTOCOL}edit/{0}", result, startIndex, LinkAction.OpenEditorTimestamp); + handleMatches(time_regex, "{0}", $@"{OsuGameBase.OSU_PROTOCOL}edit/{{0}}", result, startIndex, LinkAction.OpenEditorTimestamp); // handle channels handleMatches(channel_regex, "{0}", $"{OsuGameBase.OSU_PROTOCOL}chan/{0}", result, startIndex, LinkAction.OpenChannel); From ed008267d7a932c64dac5a3566af1bcdd145e415 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 16:45:18 +0900 Subject: [PATCH 0786/5199] Fix one more case of escaping not being present MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Online/Chat/MessageFormatter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 39a51876c2..333c554d44 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -272,7 +272,7 @@ namespace osu.Game.Online.Chat handleMatches(time_regex, "{0}", $@"{OsuGameBase.OSU_PROTOCOL}edit/{{0}}", result, startIndex, LinkAction.OpenEditorTimestamp); // handle channels - handleMatches(channel_regex, "{0}", $"{OsuGameBase.OSU_PROTOCOL}chan/{0}", result, startIndex, LinkAction.OpenChannel); + handleMatches(channel_regex, "{0}", $@"{OsuGameBase.OSU_PROTOCOL}chan/{{0}}", result, startIndex, LinkAction.OpenChannel); string empty = ""; while (space-- > 0) From 5efffa208acc118d8acf16cae9a609318e85b7b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 17:08:09 +0900 Subject: [PATCH 0787/5199] Add test coverage of beatmap set overlay actually showing requested beatmap --- .../TestSceneStartupBeatmapDisplay.cs | 34 ++++++++++++++++++- .../TestSceneStartupBeatmapSetDisplay.cs | 32 ++++++++++++++++- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapDisplay.cs b/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapDisplay.cs index 1efa24435e..961b7dedc3 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapDisplay.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapDisplay.cs @@ -5,18 +5,50 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; +using osu.Game.Overlays.BeatmapSet; namespace osu.Game.Tests.Visual.Navigation { public class TestSceneStartupBeatmapDisplay : OsuGameTestScene { - protected override TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API, new[] { "osu://b/75" }); + private const int requested_beatmap_id = 75; + private const int requested_beatmap_set_id = 1; + + protected override TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API, new[] { $"osu://b/{requested_beatmap_id}" }); + + [SetUp] + public void Setup() => Schedule(() => + { + ((DummyAPIAccess)API).HandleRequest = request => + { + switch (request) + { + case GetBeatmapSetRequest gbr: + var apiBeatmapSet = CreateAPIBeatmapSet(); + apiBeatmapSet.OnlineID = requested_beatmap_set_id; + apiBeatmapSet.Beatmaps = apiBeatmapSet.Beatmaps.Append(new APIBeatmap + { + DifficultyName = "Target difficulty", + OnlineID = requested_beatmap_id, + }).ToArray(); + + gbr.TriggerSuccess(apiBeatmapSet); + return true; + } + + return false; + }; + }); [Test] public void TestBeatmapLink() { AddUntilStep("Beatmap overlay displayed", () => Game.ChildrenOfType().FirstOrDefault()?.State.Value == Visibility.Visible); + AddUntilStep("Beatmap overlay showing content", () => Game.ChildrenOfType().FirstOrDefault()?.Beatmap.Value.OnlineID == requested_beatmap_id); } } } diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapSetDisplay.cs b/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapSetDisplay.cs index 1339c514e4..1aa56896d3 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapSetDisplay.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneStartupBeatmapSetDisplay.cs @@ -5,18 +5,48 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; namespace osu.Game.Tests.Visual.Navigation { public class TestSceneStartupBeatmapSetDisplay : OsuGameTestScene { - protected override TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API, new[] { "osu://s/1" }); + private const int requested_beatmap_set_id = 1; + + protected override TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API, new[] { $"osu://s/{requested_beatmap_set_id}" }); + + [SetUp] + public void Setup() => Schedule(() => + { + ((DummyAPIAccess)API).HandleRequest = request => + { + switch (request) + { + case GetBeatmapSetRequest gbr: + + var apiBeatmapSet = CreateAPIBeatmapSet(); + apiBeatmapSet.OnlineID = requested_beatmap_set_id; + apiBeatmapSet.Beatmaps = apiBeatmapSet.Beatmaps.Append(new APIBeatmap + { + DifficultyName = "Target difficulty", + OnlineID = 75, + }).ToArray(); + gbr.TriggerSuccess(apiBeatmapSet); + return true; + } + + return false; + }; + }); [Test] public void TestBeatmapSetLink() { AddUntilStep("Beatmap overlay displayed", () => Game.ChildrenOfType().FirstOrDefault()?.State.Value == Visibility.Visible); + AddUntilStep("Beatmap overlay showing content", () => Game.ChildrenOfType().FirstOrDefault()?.Header.BeatmapSet.Value.OnlineID == requested_beatmap_set_id); } } } From 6de4e05e492aa7ff03e068000ffdf83cb2dd65d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 17:14:00 +0900 Subject: [PATCH 0788/5199] Fix current selection not being correctly maintained when `BeatmapPicker` updates its display --- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 59e8e8db3c..031442814d 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -183,7 +183,14 @@ namespace osu.Game.Overlays.BeatmapSet } starRatingContainer.FadeOut(100); - Beatmap.Value = Difficulties.FirstOrDefault()?.Beatmap; + + // If a selection is already made, try and maintain it. + if (Beatmap.Value != null) + Beatmap.Value = Difficulties.FirstOrDefault(b => b.Beatmap.OnlineID == Beatmap.Value.OnlineID)?.Beatmap; + + // Else just choose the first available difficulty for now. + Beatmap.Value ??= Difficulties.FirstOrDefault()?.Beatmap; + plays.Value = BeatmapSet?.PlayCount ?? 0; favourites.Value = BeatmapSet?.FavouriteCount ?? 0; From bedd07d2e4dca93e160949614f9f1a75f03a2fe2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 22 Feb 2022 18:12:55 +0900 Subject: [PATCH 0789/5199] Add remark about usage of CalculateAll() --- osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 7d6c235fc1..0935f26de6 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -120,6 +120,9 @@ namespace osu.Game.Rulesets.Difficulty /// /// Calculates the difficulty of the beatmap using all mod combinations applicable to the beatmap. /// + /// + /// This should only be used to compute difficulties for legacy mod combinations. + /// /// A collection of structures describing the difficulty of the beatmap for each mod combination. public IEnumerable CalculateAll(CancellationToken cancellationToken = default) { From d1d6847d3205d5ea1226dc063d7315b95cd47491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Feb 2022 22:24:37 +0100 Subject: [PATCH 0790/5199] Add comment about split usage in osu:// protocol link handling --- osu.Game/OsuGame.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 390e96d768..77eec004c0 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -921,6 +921,9 @@ namespace osu.Game if (!url.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal)) throw new ArgumentException("Invalid osu URL provided.", nameof(url)); + // note that `StringSplitOptions.RemoveEmptyEntries` is not explicitly specified here + // in order to ensure that the protocol URL is valid (i.e. it has two slashes in the `osu://` part, + // causing the link content to be stored to the 2nd index rather than the 1st). string[] pieces = url.Split('/'); switch (pieces[2]) From d8fa443ea0eb21f607253aa9d8de679d94a51800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Feb 2022 23:22:00 +0100 Subject: [PATCH 0791/5199] Extract default mod switch measurements to constants For use later when specific sizes/scales of the mod switches are desired. --- osu.Game/Rulesets/UI/ModSwitchSmall.cs | 6 +++--- osu.Game/Rulesets/UI/ModSwitchTiny.cs | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/UI/ModSwitchSmall.cs b/osu.Game/Rulesets/UI/ModSwitchSmall.cs index 5d9d9075bc..676bbac95c 100644 --- a/osu.Game/Rulesets/UI/ModSwitchSmall.cs +++ b/osu.Game/Rulesets/UI/ModSwitchSmall.cs @@ -21,9 +21,9 @@ namespace osu.Game.Rulesets.UI { public BindableBool Active { get; } = new BindableBool(); - private readonly IMod mod; + public const float DEFAULT_SIZE = 60; - private const float size = 60; + private readonly IMod mod; private readonly SpriteIcon background; private readonly SpriteIcon? modIcon; @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.UI { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(size), + Size = new Vector2(DEFAULT_SIZE), Icon = OsuIcon.ModBg }, contentFlow = new FillFlowContainer diff --git a/osu.Game/Rulesets/UI/ModSwitchTiny.cs b/osu.Game/Rulesets/UI/ModSwitchTiny.cs index 093c2271b6..b1d453f588 100644 --- a/osu.Game/Rulesets/UI/ModSwitchTiny.cs +++ b/osu.Game/Rulesets/UI/ModSwitchTiny.cs @@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.UI { public BindableBool Active { get; } = new BindableBool(); + public const float DEFAULT_HEIGHT = 30; + private readonly IMod mod; private readonly Box background; @@ -36,7 +38,7 @@ namespace osu.Game.Rulesets.UI public ModSwitchTiny(IMod mod) { this.mod = mod; - Size = new Vector2(73, 30); + Size = new Vector2(73, DEFAULT_HEIGHT); InternalChild = new CircularContainer { From 7bdcb5952ed67bc895da12833767bc3746326d85 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Wed, 23 Feb 2022 00:36:56 +0100 Subject: [PATCH 0792/5199] Fix handling badly-formatted osu:// urls --- osu.Game/Online/Chat/MessageFormatter.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index d7974004b1..3517777325 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -236,8 +236,7 @@ namespace osu.Game.Online.Chat break; default: - linkType = LinkAction.External; - break; + return new LinkDetails(LinkAction.External, url); } return new LinkDetails(linkType, args[2]); From 2bea485af8da9bddd5fe404dd71e967a3c9b631b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 23 Feb 2022 13:37:45 +0900 Subject: [PATCH 0793/5199] Fix currently playing text not showing in lounge --- osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 3148b74c9c..7d1feb0316 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -91,9 +91,15 @@ namespace osu.Game.Screens.OnlinePlay Playlist.BindCollectionChanged((_, __) => UpdateSelectedItem(), true); } - protected virtual void UpdateSelectedItem() - => SelectedItem.Value = RoomID.Value == null || subScreenSelectedItem == null - ? Playlist.GetCurrentItem() - : subScreenSelectedItem.Value; + protected void UpdateSelectedItem() + { + if (RoomID.Value == null || subScreenSelectedItem == null) + { + SelectedItem.Value = CurrentPlaylistItem.Value ?? Playlist.GetCurrentItem(); + return; + } + + SelectedItem.Value = subScreenSelectedItem.Value; + } } } From 71a012bea6021ad559da8ccce2f92413bff80c21 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 23 Feb 2022 13:42:47 +0900 Subject: [PATCH 0794/5199] Don't update count twice immediately MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs index e21439e931..a6bbcd548d 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { base.LoadComplete(); - PlaylistItemStats.BindValueChanged(_ => updateCount(), true); + PlaylistItemStats.BindValueChanged(_ => updateCount()); Playlist.BindCollectionChanged((_, __) => updateCount(), true); } From 87da650dfb5651c608562865a37db35a0e04c32e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Feb 2022 13:51:25 +0900 Subject: [PATCH 0795/5199] Update framework --- osu.Android.props | 2 +- osu.Game/Tests/Visual/SkinnableTestScene.cs | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 24a0d20874..526ce959a6 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index cd675e467b..1107089a46 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load() { - var dllStore = new DllResourceStore(DynamicCompilationOriginal.GetType().Assembly); + var dllStore = new DllResourceStore(GetType().Assembly); metricsSkin = new TestLegacySkin(new SkinInfo { Name = "metrics-skin" }, new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), this, true); defaultSkin = new DefaultLegacySkin(this); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4c6f81defa..7dfd099df1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 99b9de3fe2..9d0e1790f0 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - + From a6b6644c2e441513bfba493fbe56e307996bd4ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Feb 2022 16:22:13 +0900 Subject: [PATCH 0796/5199] Replace LINQ queries with recommendations --- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs index e61720d8b9..70b7d1f740 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -96,12 +96,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods } // we should only be checking the bonus/progress after the spinner has fully completed. - if (!results.OfType().Any(r => r.TimeCompleted != null)) + if (results.OfType().All(r => r.TimeCompleted == null)) return false; return results.Any(r => r.Type == HitResult.SmallBonus) - && !results.Any(r => r.Type == HitResult.LargeBonus); + && results.All(r => r.Type != HitResult.LargeBonus); } }); } From 054ed546e3800e6aa0425b362b68dacf9e19fb75 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Feb 2022 16:56:50 +0900 Subject: [PATCH 0797/5199] Fix intermittent failures in remaining test method --- .../Mods/TestSceneOsuModSpunOut.cs | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs index 70b7d1f740..a8953c1a6f 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -26,13 +26,37 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods protected override bool AllowFail => true; [Test] - public void TestSpinnerAutoCompleted() => CreateModTest(new ModTestData + public void TestSpinnerAutoCompleted() { - Mod = new OsuModSpunOut(), - Autoplay = false, - Beatmap = singleSpinnerBeatmap, - PassCondition = () => Player.ChildrenOfType().SingleOrDefault()?.Progress >= 1 - }); + DrawableSpinner spinner = null; + JudgementResult lastResult = null; + + CreateModTest(new ModTestData + { + Mod = new OsuModSpunOut(), + Autoplay = false, + Beatmap = singleSpinnerBeatmap, + PassCondition = () => + { + // Bind to the first spinner's results for further tracking. + if (spinner == null) + { + // We only care about the first spinner we encounter for this test. + var nextSpinner = Player.ChildrenOfType().SingleOrDefault(); + + if (nextSpinner == null) + return false; + + lastResult = null; + + spinner = nextSpinner; + spinner.OnNewResult += (o, result) => lastResult = result; + } + + return lastResult?.Type == HitResult.Great; + } + }); + } [TestCase(null)] [TestCase(typeof(OsuModDoubleTime))] From 5d73691de4e13ed827d0a731ea909e7b33d115d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Feb 2022 17:02:39 +0900 Subject: [PATCH 0798/5199] Use existing `HandleLink` flow rather than reimplmenting --- osu.Game/OsuGame.cs | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 77eec004c0..fa5a336b7c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -906,7 +906,7 @@ namespace osu.Game if (firstPath.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal)) { - handleOsuProtocolUrl(firstPath); + HandleLink(firstPath); } else { @@ -916,30 +916,6 @@ namespace osu.Game } } - private void handleOsuProtocolUrl(string url) - { - if (!url.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal)) - throw new ArgumentException("Invalid osu URL provided.", nameof(url)); - - // note that `StringSplitOptions.RemoveEmptyEntries` is not explicitly specified here - // in order to ensure that the protocol URL is valid (i.e. it has two slashes in the `osu://` part, - // causing the link content to be stored to the 2nd index rather than the 1st). - string[] pieces = url.Split('/'); - - switch (pieces[2]) - { - case "s": - if (int.TryParse(pieces[3], out int beatmapSetId)) - ShowBeatmapSet(beatmapSetId); - break; - - case "b": - if (int.TryParse(pieces[3], out int beatmapId)) - ShowBeatmap(beatmapId); - break; - } - } - private void showOverlayAboveOthers(OverlayContainer overlay, OverlayContainer[] otherOverlays) { otherOverlays.Where(o => o != overlay).ForEach(o => o.Hide()); From 28c9c5ab6a0ac889223a7f46ba6b16504cc9a3ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Feb 2022 17:05:46 +0900 Subject: [PATCH 0799/5199] Remove unnecessary `ShouldSerialize` rules in `Room` --- osu.Game/Online/Rooms/Room.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 8ed37ad6b5..7512d4fcf9 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -230,15 +230,9 @@ namespace osu.Game.Online.Rooms // They rely on being named exactly the same as the corresponding fields (casing included) and as such should NOT be renamed // unless the fields are also renamed. - [UsedImplicitly] - public bool ShouldSerializeRoomID() => false; - [UsedImplicitly] public bool ShouldSerializeHost() => false; - [UsedImplicitly] - public bool ShouldSerializeEndDate() => false; - #endregion [JsonObject(MemberSerialization.OptIn)] From f14a9af801c53552356f798a74e68fb6a18b4a3d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Feb 2022 17:06:40 +0900 Subject: [PATCH 0800/5199] Make `Room` opt-in rather than opt-out for json serialization --- osu.Game/Online/Rooms/Room.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 7512d4fcf9..5deedaaa6b 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -15,6 +15,7 @@ using osu.Game.Utils; namespace osu.Game.Online.Rooms { + [JsonObject(MemberSerialization.OptIn)] public class Room : IDeepCloneable { [Cached] @@ -49,7 +50,6 @@ namespace osu.Game.Online.Rooms public readonly Bindable DifficultyRange = new Bindable(); [Cached] - [JsonIgnore] public readonly Bindable Category = new Bindable(); // Todo: osu-framework bug (https://github.com/ppy/osu-framework/issues/4106) @@ -62,19 +62,15 @@ namespace osu.Game.Online.Rooms } [Cached] - [JsonIgnore] public readonly Bindable MaxAttempts = new Bindable(); [Cached] - [JsonIgnore] public readonly Bindable Status = new Bindable(new RoomStatusOpen()); [Cached] - [JsonIgnore] public readonly Bindable Availability = new Bindable(); [Cached] - [JsonIgnore] public readonly Bindable Type = new Bindable(); // Todo: osu-framework bug (https://github.com/ppy/osu-framework/issues/4106) @@ -87,7 +83,6 @@ namespace osu.Game.Online.Rooms } [Cached] - [JsonIgnore] public readonly Bindable QueueMode = new Bindable(); [JsonConverter(typeof(SnakeCaseStringEnumConverter))] @@ -99,7 +94,6 @@ namespace osu.Game.Online.Rooms } [Cached] - [JsonIgnore] public readonly Bindable MaxParticipants = new Bindable(); [Cached] @@ -124,7 +118,6 @@ namespace osu.Game.Online.Rooms public readonly Bindable Password = new Bindable(); [Cached] - [JsonIgnore] public readonly Bindable Duration = new Bindable(); [JsonProperty("duration")] From 43c83d2de1cd2cb861ba57f80dc903c837cb8181 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Feb 2022 17:07:29 +0900 Subject: [PATCH 0801/5199] Add note about why `RoomID` is nulled in `DeepClone` --- osu.Game/Online/Rooms/Room.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 5deedaaa6b..b301c81921 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -162,6 +162,8 @@ namespace osu.Game.Online.Rooms var copy = new Room(); copy.CopyFrom(this); + + // ID must be unset as we use this as a marker for whether this is a client-side (not-yet-created) room or not. copy.RoomID.Value = null; return copy; From 53bbd0067545fbfc99e131de0a1bb6918980ed4e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Feb 2022 17:12:38 +0900 Subject: [PATCH 0802/5199] Also make `APIUser` opt-in and remove the remaining serialization exclusion rule --- osu.Game/Online/API/Requests/Responses/APIUser.cs | 1 + osu.Game/Online/Rooms/Room.cs | 12 ------------ 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 2b64e5de06..a53ac1cd9b 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -12,6 +12,7 @@ using osu.Game.Users; namespace osu.Game.Online.API.Requests.Responses { + [JsonObject(MemberSerialization.OptIn)] public class APIUser : IEquatable, IUser { [JsonProperty(@"id")] diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index b301c81921..c7f34905e2 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -3,7 +3,6 @@ using System; using System.Linq; -using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -219,17 +218,6 @@ namespace osu.Game.Online.Rooms Playlist.RemoveAll(i => i.Expired); } - #region Newtonsoft.Json implicit ShouldSerialize() methods - - // The properties in this region are used implicitly by Newtonsoft.Json to not serialise certain fields in some cases. - // They rely on being named exactly the same as the corresponding fields (casing included) and as such should NOT be renamed - // unless the fields are also renamed. - - [UsedImplicitly] - public bool ShouldSerializeHost() => false; - - #endregion - [JsonObject(MemberSerialization.OptIn)] public class RoomPlaylistItemStats { From 5dd0d48df968594b86f4463fc1634dc9c93abbe0 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Wed, 23 Feb 2022 14:06:22 +0100 Subject: [PATCH 0803/5199] Move the key handling logic to MainMenu and simplify it Also makes use of the host.SuspendToBackground() return value. --- osu.Game/Screens/Menu/ExitConfirmOverlay.cs | 23 -------- osu.Game/Screens/Menu/MainMenu.cs | 59 +++++++++++---------- 2 files changed, 32 insertions(+), 50 deletions(-) diff --git a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs index 253ec17c28..a90b83c5fe 100644 --- a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs +++ b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs @@ -3,7 +3,6 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Overlays; @@ -43,26 +42,4 @@ namespace osu.Game.Screens.Menu } } } - - /// - /// An that behaves as if the is always 0. - /// - /// This is useful for mobile devices using gesture navigation, where holding to confirm is not possible. - public class NoHoldExitConfirmOverlay : ExitConfirmOverlay, IKeyBindingHandler - { - public new bool OnPressed(KeyBindingPressEvent e) - { - if (e.Repeat) - return false; - - if (e.Action == GlobalAction.Back) - { - Progress.Value = 1; - Confirm(); - return true; - } - - return false; - } - } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 7bc0cb48bf..a2cb448d40 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -6,12 +6,15 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Input.Bindings; using osu.Game.IO; using osu.Game.Online.API; using osu.Game.Overlays; @@ -26,7 +29,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.Menu { - public class MainMenu : OsuScreen, IHandlePresentBeatmap + public class MainMenu : OsuScreen, IHandlePresentBeatmap, IKeyBindingHandler { public const float FADE_IN_DURATION = 300; @@ -90,14 +93,6 @@ namespace osu.Game.Screens.Menu } }); } - else if (host.CanSuspendToBackground) - { - AddInternal(exitConfirmOverlay = new NoHoldExitConfirmOverlay - { - // treat as if the UIHoldActivationDelay is always 0. see NoHoldExitConfirmOverlay xmldoc for more info. - Action = this.Exit - }); - } AddRangeInternal(new[] { @@ -157,24 +152,6 @@ namespace osu.Game.Screens.Menu private void confirmAndExit() { - if (host.CanSuspendToBackground) - { - // cancel the overlay as we're not actually exiting. - // this is the same action as 'onCancel' in `ConfirmExitDialog`. - exitConfirmOverlay.Abort(); - - // fade the track so the Bass.Pause() on suspend isn't as jarring. - const double fade_time = 500; - musicController.CurrentTrack - .VolumeTo(0, fade_time, Easing.Out).Then() - .VolumeTo(1, fade_time, Easing.In); - - host.SuspendToBackground(); - - // on hosts that can only suspend, we don't ever want to exit the game. - return; - } - if (exitConfirmed) return; exitConfirmed = true; @@ -324,5 +301,33 @@ namespace osu.Game.Screens.Menu Schedule(loadSoloSongSelect); } + + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Repeat) + return false; + + if (e.Action == GlobalAction.Back && host.CanSuspendToBackground) + { + bool didSuspend = host.SuspendToBackground(); + + if (didSuspend) + { + // fade the track so the Bass.Pause() on suspend isn't as jarring. + const double fade_time = 500; + musicController.CurrentTrack + .VolumeTo(0, fade_time, Easing.Out).Then() + .VolumeTo(1, fade_time, Easing.In); + + return true; + } + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } } } From 5ff4d3f8e5fca93f662bf4d0d0d171b712a7c43f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Feb 2022 19:05:46 +0900 Subject: [PATCH 0804/5199] Add support to `SpectatorClient` to resend failed frame bundles --- osu.Game/Online/Spectator/SpectatorClient.cs | 48 ++++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index a54ea0d9ee..ba0a9a77a0 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -168,8 +168,6 @@ namespace osu.Game.Online.Spectator }); } - public void SendFrames(FrameDataBundle data) => lastSend = SendFramesInternal(data); - public void EndPlaying(GameplayState state) { // This method is most commonly called via Dispose(), which is can be asynchronous (via the AsyncDisposalQueue). @@ -180,7 +178,7 @@ namespace osu.Game.Online.Spectator return; if (pendingFrames.Count > 0) - purgePendingFrames(true); + purgePendingFrames(); IsPlaying = false; currentBeatmap = null; @@ -230,9 +228,11 @@ namespace osu.Game.Online.Spectator protected abstract Task StopWatchingUserInternal(int userId); + private readonly Queue pendingFrameBundles = new Queue(); + private readonly Queue pendingFrames = new Queue(); - private double lastSendTime; + private double lastPurgeTime; private Task? lastSend; @@ -242,7 +242,7 @@ namespace osu.Game.Online.Spectator { base.Update(); - if (pendingFrames.Count > 0 && Time.Current - lastSendTime > TIME_BETWEEN_SENDS) + if (pendingFrames.Count > 0 && Time.Current - lastPurgeTime > TIME_BETWEEN_SENDS) purgePendingFrames(); } @@ -260,23 +260,41 @@ namespace osu.Game.Online.Spectator purgePendingFrames(); } - private void purgePendingFrames(bool force = false) + private void purgePendingFrames() { - if (lastSend?.IsCompleted == false && !force) - return; - if (pendingFrames.Count == 0) return; - var frames = pendingFrames.ToArray(); - - pendingFrames.Clear(); - Debug.Assert(currentScore != null); - SendFrames(new FrameDataBundle(currentScore.ScoreInfo, frames)); + var frames = pendingFrames.ToArray(); + var bundle = new FrameDataBundle(currentScore.ScoreInfo, frames); - lastSendTime = Time.Current; + pendingFrames.Clear(); + lastPurgeTime = Time.Current; + + pendingFrameBundles.Enqueue(bundle); + + sendNextBundleIfRequired(); + } + + private void sendNextBundleIfRequired() + { + if (lastSend?.IsCompleted == false) + return; + + if (!pendingFrameBundles.TryPeek(out var bundle)) + return; + + lastSend = SendFramesInternal(bundle); + lastSend.ContinueWith(t => Schedule(() => + { + // If the last bundle send wasn't successful, try again without dequeuing. + if (t.IsCompletedSuccessfully) + pendingFrameBundles.Dequeue(); + + sendNextBundleIfRequired(); + })); } } } From cff6f854724c8ce4f8ba8935734664a5e5f7c048 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Feb 2022 19:08:32 +0900 Subject: [PATCH 0805/5199] Add note about reconnection being insufficient currently --- osu.Game/Online/Spectator/SpectatorClient.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index ba0a9a77a0..7afceb41a4 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -94,6 +94,7 @@ namespace osu.Game.Online.Spectator // re-send state in case it wasn't received if (IsPlaying) + // TODO: this is likely sent out of order after a reconnect scenario. needs further consideration. BeginPlayingInternal(currentState); } else From 14c8ce50a0849fb97882db3f9dd2a9553ac118bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Feb 2022 17:56:07 +0900 Subject: [PATCH 0806/5199] Prefix all test send methods in `TestSpectatorClient` with `Send` --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 10 +++++----- .../TestSceneMultiSpectatorLeaderboard.cs | 2 +- .../Multiplayer/TestSceneMultiSpectatorScreen.cs | 12 ++++++------ .../TestSceneMultiplayerGameplayLeaderboard.cs | 2 +- .../TestSceneMultiplayerGameplayLeaderboardTeams.cs | 2 +- .../Online/TestSceneCurrentlyPlayingDisplay.cs | 4 ++-- .../Tests/Visual/Spectator/TestSpectatorClient.cs | 7 ++----- 7 files changed, 18 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index d47ebf9f0d..d6f8c7addf 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -257,7 +257,7 @@ namespace osu.Game.Tests.Visual.Gameplay sendFrames(); waitForPlayer(); - AddStep("send passed", () => spectatorClient.EndPlay(streamingUser.Id, SpectatedUserState.Passed)); + AddStep("send passed", () => spectatorClient.SendEndPlay(streamingUser.Id, SpectatedUserState.Passed)); AddUntilStep("state is passed", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Passed); start(); @@ -275,7 +275,7 @@ namespace osu.Game.Tests.Visual.Gameplay sendFrames(); waitForPlayer(); - AddStep("send quit", () => spectatorClient.EndPlay(streamingUser.Id)); + AddStep("send quit", () => spectatorClient.SendEndPlay(streamingUser.Id)); AddUntilStep("state is quit", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Quit); start(); @@ -293,7 +293,7 @@ namespace osu.Game.Tests.Visual.Gameplay sendFrames(); waitForPlayer(); - AddStep("send failed", () => spectatorClient.EndPlay(streamingUser.Id, SpectatedUserState.Failed)); + AddStep("send failed", () => spectatorClient.SendEndPlay(streamingUser.Id, SpectatedUserState.Failed)); AddUntilStep("state is failed", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Failed); start(); @@ -312,9 +312,9 @@ namespace osu.Game.Tests.Visual.Gameplay private void waitForPlayer() => AddUntilStep("wait for player", () => (Stack.CurrentScreen as Player)?.IsLoaded == true); - private void start(int? beatmapId = null) => AddStep("start play", () => spectatorClient.StartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId)); + private void start(int? beatmapId = null) => AddStep("start play", () => spectatorClient.SendStartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId)); - private void finish(SpectatedUserState state = SpectatedUserState.Quit) => AddStep("end play", () => spectatorClient.EndPlay(streamingUser.Id, state)); + private void finish(SpectatedUserState state = SpectatedUserState.Quit) => AddStep("end play", () => spectatorClient.SendEndPlay(streamingUser.Id, state)); private void checkPaused(bool state) => AddUntilStep($"game is {(state ? "paused" : "playing")}", () => player.ChildrenOfType().First().IsPaused.Value == state); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 6b3573b3cb..488ecdb8af 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach ((int userId, var _) in clocks) { - SpectatorClient.StartPlay(userId, 0); + SpectatorClient.SendStartPlay(userId, 0); OnlinePlayDependencies.MultiplayerClient.AddUser(new APIUser { Id = userId }); } }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 7ce0c6a94d..a785301f62 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -70,11 +70,11 @@ namespace osu.Game.Tests.Visual.Multiplayer loadSpectateScreen(false); AddWaitStep("wait a bit", 10); - AddStep("load player first_player_id", () => SpectatorClient.StartPlay(PLAYER_1_ID, importedBeatmapId)); + AddStep("load player first_player_id", () => SpectatorClient.SendStartPlay(PLAYER_1_ID, importedBeatmapId)); AddUntilStep("one player added", () => spectatorScreen.ChildrenOfType().Count() == 1); AddWaitStep("wait a bit", 10); - AddStep("load player second_player_id", () => SpectatorClient.StartPlay(PLAYER_2_ID, importedBeatmapId)); + AddStep("load player second_player_id", () => SpectatorClient.SendStartPlay(PLAYER_2_ID, importedBeatmapId)); AddUntilStep("two players added", () => spectatorScreen.ChildrenOfType().Count() == 2); } @@ -133,8 +133,8 @@ namespace osu.Game.Tests.Visual.Multiplayer TeamID = 1, }; - SpectatorClient.StartPlay(player1.UserID, importedBeatmapId); - SpectatorClient.StartPlay(player2.UserID, importedBeatmapId); + SpectatorClient.SendStartPlay(player1.UserID, importedBeatmapId); + SpectatorClient.SendStartPlay(player2.UserID, importedBeatmapId); playingUsers.Add(player1); playingUsers.Add(player2); @@ -397,7 +397,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }; OnlinePlayDependencies.MultiplayerClient.AddUser(user.User, true); - SpectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId); + SpectatorClient.SendStartPlay(id, beatmapId ?? importedBeatmapId); playingUsers.Add(user); } @@ -411,7 +411,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var user = playingUsers.Single(u => u.UserID == userId); OnlinePlayDependencies.MultiplayerClient.RemoveUser(user.User.AsNonNull()); - SpectatorClient.EndPlay(userId); + SpectatorClient.SendEndPlay(userId); playingUsers.Remove(user); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 6605f82d5c..cfac5da4ff 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach (int user in users) { - SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineID); + SpectatorClient.SendStartPlay(user, Beatmap.Value.BeatmapInfo.OnlineID); multiplayerUsers.Add(OnlinePlayDependencies.MultiplayerClient.AddUser(new APIUser { Id = user }, true)); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs index dabc1c1e5a..f751b162d1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach (int user in users) { - SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineID); + SpectatorClient.SendStartPlay(user, Beatmap.Value.BeatmapInfo.OnlineID); var roomUser = OnlinePlayDependencies.MultiplayerClient.AddUser(new APIUser { Id = user }, true); roomUser.MatchState = new TeamVersusUserState diff --git a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs index b5a03b558d..35a4f8cf2d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs @@ -56,9 +56,9 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestBasicDisplay() { - AddStep("Add playing user", () => spectatorClient.StartPlay(streamingUser.Id, 0)); + AddStep("Add playing user", () => spectatorClient.SendStartPlay(streamingUser.Id, 0)); AddUntilStep("Panel loaded", () => currentlyPlaying.ChildrenOfType()?.FirstOrDefault()?.User.Id == 2); - AddStep("Remove playing user", () => spectatorClient.EndPlay(streamingUser.Id)); + AddStep("Remove playing user", () => spectatorClient.SendEndPlay(streamingUser.Id)); AddUntilStep("Panel no longer present", () => !currentlyPlaying.ChildrenOfType().Any()); } diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index 1322a99ea7..741b489d95 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -3,7 +3,6 @@ #nullable enable -using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading.Tasks; @@ -47,7 +46,7 @@ namespace osu.Game.Tests.Visual.Spectator /// /// The user to start play for. /// The playing beatmap id. - public void StartPlay(int userId, int beatmapId) + public void SendStartPlay(int userId, int beatmapId) { userBeatmapDictionary[userId] = beatmapId; userNextFrameDictionary[userId] = 0; @@ -59,7 +58,7 @@ namespace osu.Game.Tests.Visual.Spectator /// /// The user to end play for. /// The spectator state to end play with. - public void EndPlay(int userId, SpectatedUserState state = SpectatedUserState.Quit) + public void SendEndPlay(int userId, SpectatedUserState state = SpectatedUserState.Quit) { if (!userBeatmapDictionary.ContainsKey(userId)) return; @@ -74,8 +73,6 @@ namespace osu.Game.Tests.Visual.Spectator userBeatmapDictionary.Remove(userId); } - public new void Schedule(Action action) => base.Schedule(action); - /// /// Sends frames for an arbitrary user, in bundles containing 10 frames each. /// From c94e7e2abe98799d9372b2d0ba4f5fc91fc668a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Feb 2022 02:18:35 +0900 Subject: [PATCH 0807/5199] Add ability to simulate network failures to `TestSpectatorClient` --- .../Visual/Gameplay/TestSceneSpectator.cs | 2 +- .../TestSceneMultiSpectatorLeaderboard.cs | 4 ++-- .../TestSceneMultiSpectatorScreen.cs | 4 ++-- .../Visual/Spectator/TestSpectatorClient.cs | 20 ++++++++++++++++--- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index d6f8c7addf..d614815316 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -321,7 +321,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void sendFrames(int count = 10) { - AddStep("send frames", () => spectatorClient.SendFrames(streamingUser.Id, count)); + AddStep("send frames", () => spectatorClient.SendFramesFromUser(streamingUser.Id, count)); } private void loadSpectatingScreen() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 488ecdb8af..42bb99de24 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -68,10 +68,10 @@ namespace osu.Game.Tests.Visual.Multiplayer // For player 2, send frames in sets of 10. for (int i = 0; i < 100; i++) { - SpectatorClient.SendFrames(PLAYER_1_ID, 1); + SpectatorClient.SendFramesFromUser(PLAYER_1_ID, 1); if (i % 10 == 0) - SpectatorClient.SendFrames(PLAYER_2_ID, 10); + SpectatorClient.SendFramesFromUser(PLAYER_2_ID, 10); } }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index a785301f62..4b89efe858 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -360,7 +360,7 @@ namespace osu.Game.Tests.Visual.Multiplayer // to ensure negative gameplay start time does not affect spectator, send frames exactly after StartGameplay(). // (similar to real spectating sessions in which the first frames get sent between StartGameplay() and player load complete) - AddStep("send frames at gameplay start", () => getInstance(PLAYER_1_ID).OnGameplayStarted += () => SpectatorClient.SendFrames(PLAYER_1_ID, 100)); + AddStep("send frames at gameplay start", () => getInstance(PLAYER_1_ID).OnGameplayStarted += () => SpectatorClient.SendFramesFromUser(PLAYER_1_ID, 100)); AddUntilStep("wait for player load", () => spectatorScreen.AllPlayersLoaded); @@ -424,7 +424,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("send frames", () => { foreach (int id in userIds) - SpectatorClient.SendFrames(id, count); + SpectatorClient.SendFramesFromUser(id, count); }); } diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index 741b489d95..ae5d20f4f4 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -3,6 +3,7 @@ #nullable enable +using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading.Tasks; @@ -20,10 +21,15 @@ namespace osu.Game.Tests.Visual.Spectator public class TestSpectatorClient : SpectatorClient { /// - /// Maximum number of frames sent per bundle via . + /// Maximum number of frames sent per bundle via . /// public const int FRAME_BUNDLE_SIZE = 10; + /// + /// Whether to force send operations to fail (simulating a network issue). + /// + public bool ShouldFailSendingFrames { get; set; } + public override IBindable IsConnected { get; } = new Bindable(true); public IReadOnlyDictionary LastReceivedUserFrames => lastReceivedUserFrames; @@ -75,10 +81,12 @@ namespace osu.Game.Tests.Visual.Spectator /// /// Sends frames for an arbitrary user, in bundles containing 10 frames each. + /// This bypasses the standard queueing mechanism completely and should only be used to test cases where multiple users need to be sending data. + /// Importantly, will have no effect. /// /// The user to send frames for. /// The total number of frames to send. - public void SendFrames(int userId, int count) + public void SendFramesFromUser(int userId, int count) { var frames = new List(); @@ -120,7 +128,13 @@ namespace osu.Game.Tests.Visual.Spectator return ((ISpectatorClient)this).UserBeganPlaying(api.LocalUser.Value.Id, state); } - protected override Task SendFramesInternal(FrameDataBundle data) => ((ISpectatorClient)this).UserSentFrames(api.LocalUser.Value.Id, data); + protected override Task SendFramesInternal(FrameDataBundle bundle) + { + if (ShouldFailSendingFrames) + return Task.FromException(new InvalidOperationException()); + + return ((ISpectatorClient)this).UserSentFrames(api.LocalUser.Value.Id, bundle); + } protected override Task EndPlayingInternal(SpectatorState state) => ((ISpectatorClient)this).UserFinishedPlaying(api.LocalUser.Value.Id, state); From 47b84295a6c32c4b9ac4ae4a8cd6d3d26d5206be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Feb 2022 02:19:12 +0900 Subject: [PATCH 0808/5199] Fix bundle send logic not correctly waiting for task completion (due to nested schedule) --- osu.Game/Online/Spectator/SpectatorClient.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 7afceb41a4..428a4cdbdb 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -221,8 +221,7 @@ namespace osu.Game.Online.Spectator protected abstract Task BeginPlayingInternal(SpectatorState state); - protected abstract Task SendFramesInternal(FrameDataBundle data); - + protected abstract Task SendFramesInternal(FrameDataBundle bundle); protected abstract Task EndPlayingInternal(SpectatorState state); protected abstract Task WatchUserInternal(int userId); @@ -281,19 +280,27 @@ namespace osu.Game.Online.Spectator private void sendNextBundleIfRequired() { + Debug.Assert(ThreadSafety.IsUpdateThread); + if (lastSend?.IsCompleted == false) return; if (!pendingFrameBundles.TryPeek(out var bundle)) return; - lastSend = SendFramesInternal(bundle); - lastSend.ContinueWith(t => Schedule(() => + TaskCompletionSource tcs = new TaskCompletionSource(); + + lastSend = tcs.Task; + + SendFramesInternal(bundle).ContinueWith(t => Schedule(() => { + bool wasSuccessful = t.Exception == null; + // If the last bundle send wasn't successful, try again without dequeuing. - if (t.IsCompletedSuccessfully) + if (wasSuccessful) pendingFrameBundles.Dequeue(); + tcs.SetResult(wasSuccessful); sendNextBundleIfRequired(); })); } From 260cf793fefe2e3f862373e2f35c7c1bf18de8ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Feb 2022 02:18:42 +0900 Subject: [PATCH 0809/5199] Add test coverage of more advanced frame delivery scenarios to `TestSceneSpectatorPlayback` --- .../Gameplay/TestSceneSpectatorPlayback.cs | 57 +++++++++++++++---- .../Visual/Spectator/TestSpectatorClient.cs | 4 ++ 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index a4d8460846..4ec46036f6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -41,8 +41,12 @@ namespace osu.Game.Tests.Visual.Gameplay private Replay replay; + private TestSpectatorClient spectatorClient; + private ManualClock manualClock; + private TestReplayRecorder recorder; + private OsuSpriteText latencyDisplay; private TestFramedReplayInputHandler replayHandler; @@ -54,7 +58,6 @@ namespace osu.Game.Tests.Visual.Gameplay { replay = new Replay(); manualClock = new ManualClock(); - SpectatorClient spectatorClient; Child = new DependencyProvidingContainer { @@ -76,7 +79,7 @@ namespace osu.Game.Tests.Visual.Gameplay { recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { - Recorder = new TestReplayRecorder + Recorder = recorder = new TestReplayRecorder { ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), }, @@ -143,22 +146,52 @@ namespace osu.Game.Tests.Visual.Gameplay }); } + [Test] + public void TestBasic() + { + AddUntilStep("received frames", () => replay.Frames.Count > 50); + AddStep("stop sending frames", () => recorder.Expire()); + AddUntilStep("wait for all frames received", () => replay.Frames.Count == recorder.SentFrames.Count); + } + + [Test] + public void TestWithSendFailure() + { + AddUntilStep("received frames", () => replay.Frames.Count > 50); + + int framesReceivedSoFar = 0; + int frameSendAttemptsSoFar = 0; + + AddStep("start failing sends", () => + { + spectatorClient.ShouldFailSendingFrames = true; + framesReceivedSoFar = replay.Frames.Count; + frameSendAttemptsSoFar = spectatorClient.FrameSendAttempts; + }); + + AddUntilStep("wait for send attempts", () => spectatorClient.FrameSendAttempts > frameSendAttemptsSoFar + 5); + AddAssert("frames did not increase", () => framesReceivedSoFar == replay.Frames.Count); + + AddStep("stop failing sends", () => spectatorClient.ShouldFailSendingFrames = false); + + AddUntilStep("wait for next frames", () => framesReceivedSoFar < replay.Frames.Count); + + AddStep("stop sending frames", () => recorder.Expire()); + + AddUntilStep("wait for all frames received", () => replay.Frames.Count == recorder.SentFrames.Count); + AddAssert("ensure frames were received in the correct sequence", () => replay.Frames.Select(f => f.Time).SequenceEqual(recorder.SentFrames.Select(f => f.Time))); + } + private void onNewFrames(int userId, FrameDataBundle frames) { - Logger.Log($"Received {frames.Frames.Count} new frames ({string.Join(',', frames.Frames.Select(f => ((int)f.Time).ToString()))})"); - foreach (var legacyFrame in frames.Frames) { var frame = new TestReplayFrame(); frame.FromLegacy(legacyFrame, null); replay.Frames.Add(frame); } - } - [Test] - public void TestBasic() - { - AddStep("Wait for user input", () => { }); + Logger.Log($"Received {frames.Frames.Count} new frames (total {replay.Frames.Count} of {recorder.SentFrames.Count})"); } private double latency = SpectatorClient.TIME_BETWEEN_SENDS; @@ -318,6 +351,8 @@ namespace osu.Game.Tests.Visual.Gameplay internal class TestReplayRecorder : ReplayRecorder { + public List SentFrames = new List(); + public TestReplayRecorder() : base(new Score { @@ -332,7 +367,9 @@ namespace osu.Game.Tests.Visual.Gameplay protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame) { - return new TestReplayFrame(Time.Current, mousePosition, actions.ToArray()); + var testReplayFrame = new TestReplayFrame(Time.Current, mousePosition, actions.ToArray()); + SentFrames.Add(testReplayFrame); + return testReplayFrame; } } } diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index ae5d20f4f4..f5da95bd7b 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -30,6 +30,8 @@ namespace osu.Game.Tests.Visual.Spectator /// public bool ShouldFailSendingFrames { get; set; } + public int FrameSendAttempts { get; private set; } + public override IBindable IsConnected { get; } = new Bindable(true); public IReadOnlyDictionary LastReceivedUserFrames => lastReceivedUserFrames; @@ -130,6 +132,8 @@ namespace osu.Game.Tests.Visual.Spectator protected override Task SendFramesInternal(FrameDataBundle bundle) { + FrameSendAttempts++; + if (ShouldFailSendingFrames) return Task.FromException(new InvalidOperationException()); From 694c6ad872c87aeabdc1d430fbadf082f24463bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Feb 2022 00:59:25 +0900 Subject: [PATCH 0810/5199] Fix frames potentially getting lost due to non-matching `Schedule` usage --- osu.Game/Online/Spectator/SpectatorClient.cs | 38 ++++++++++++-------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 428a4cdbdb..0620643bc3 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -12,6 +12,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Development; using osu.Framework.Graphics; +using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Replays.Legacy; @@ -156,6 +157,8 @@ namespace osu.Game.Online.Spectator IsPlaying = true; + totalBundledFrames = 0; + // transfer state at point of beginning play currentState.BeatmapID = score.ScoreInfo.BeatmapInfo.OnlineID; currentState.RulesetID = score.ScoreInfo.RulesetID; @@ -169,6 +172,21 @@ namespace osu.Game.Online.Spectator }); } + public void HandleFrame(ReplayFrame frame) => Schedule(() => + { + if (!IsPlaying) + { + Logger.Log($"Frames arrived at {nameof(SpectatorClient)} outside of gameplay scope and will be ignored."); + return; + } + + if (frame is IConvertibleReplayFrame convertible) + pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap)); + + if (pendingFrames.Count > max_pending_frames) + purgePendingFrames(); + }); + public void EndPlaying(GameplayState state) { // This method is most commonly called via Dispose(), which is can be asynchronous (via the AsyncDisposalQueue). @@ -236,6 +254,8 @@ namespace osu.Game.Online.Spectator private Task? lastSend; + private int totalBundledFrames; + private const int max_pending_frames = 30; protected override void Update() @@ -246,20 +266,6 @@ namespace osu.Game.Online.Spectator purgePendingFrames(); } - public void HandleFrame(ReplayFrame frame) - { - Debug.Assert(ThreadSafety.IsUpdateThread); - - if (!IsPlaying) - return; - - if (frame is IConvertibleReplayFrame convertible) - pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap)); - - if (pendingFrames.Count > max_pending_frames) - purgePendingFrames(); - } - private void purgePendingFrames() { if (pendingFrames.Count == 0) @@ -270,6 +276,10 @@ namespace osu.Game.Online.Spectator var frames = pendingFrames.ToArray(); var bundle = new FrameDataBundle(currentScore.ScoreInfo, frames); + totalBundledFrames += frames.Length; + + Console.WriteLine($"Purging {pendingFrames.Count} frames (total {totalBundledFrames})"); + pendingFrames.Clear(); lastPurgeTime = Time.Current; From 5ffdd57895bd61652855683e2064713006ae7fda Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Feb 2022 02:19:37 +0900 Subject: [PATCH 0811/5199] Rename weirdly named parameter --- osu.Game/Online/Spectator/OnlineSpectatorClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs index ddde69c627..4d6ca0b311 100644 --- a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs +++ b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs @@ -57,14 +57,14 @@ namespace osu.Game.Online.Spectator return connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), state); } - protected override Task SendFramesInternal(FrameDataBundle data) + protected override Task SendFramesInternal(FrameDataBundle bundle) { if (!IsConnected.Value) return Task.CompletedTask; Debug.Assert(connection != null); - return connection.SendAsync(nameof(ISpectatorServer.SendFrameData), data); + return connection.SendAsync(nameof(ISpectatorServer.SendFrameData), bundle); } protected override Task EndPlayingInternal(SpectatorState state) From 3f2ef030e4a1d82978643b8e31a080629c8654a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Feb 2022 02:29:49 +0900 Subject: [PATCH 0812/5199] Group `SpectatorClient` private fields together --- osu.Game/Online/Spectator/SpectatorClient.cs | 49 ++++++++++---------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 0620643bc3..d0b89e981d 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -46,18 +46,6 @@ namespace osu.Game.Online.Spectator /// public IBindableList PlayingUsers => playingUsers; - /// - /// All users currently being watched. - /// - private readonly List watchedUsers = new List(); - - private readonly BindableDictionary watchedUserStates = new BindableDictionary(); - private readonly BindableList playingUsers = new BindableList(); - private readonly SpectatorState currentState = new SpectatorState(); - - private IBeatmap? currentBeatmap; - private Score? currentScore; - /// /// Whether the local user is playing. /// @@ -78,6 +66,30 @@ namespace osu.Game.Online.Spectator /// public event Action? OnUserFinishedPlaying; + /// + /// All users currently being watched. + /// + private readonly List watchedUsers = new List(); + + private readonly BindableDictionary watchedUserStates = new BindableDictionary(); + private readonly BindableList playingUsers = new BindableList(); + private readonly SpectatorState currentState = new SpectatorState(); + + private IBeatmap? currentBeatmap; + private Score? currentScore; + + private readonly Queue pendingFrameBundles = new Queue(); + + private readonly Queue pendingFrames = new Queue(); + + private double lastPurgeTime; + + private Task? lastSend; + + private int totalBundledFrames; + + private const int max_pending_frames = 30; + [BackgroundDependencyLoader] private void load() { @@ -240,24 +252,13 @@ namespace osu.Game.Online.Spectator protected abstract Task BeginPlayingInternal(SpectatorState state); protected abstract Task SendFramesInternal(FrameDataBundle bundle); + protected abstract Task EndPlayingInternal(SpectatorState state); protected abstract Task WatchUserInternal(int userId); protected abstract Task StopWatchingUserInternal(int userId); - private readonly Queue pendingFrameBundles = new Queue(); - - private readonly Queue pendingFrames = new Queue(); - - private double lastPurgeTime; - - private Task? lastSend; - - private int totalBundledFrames; - - private const int max_pending_frames = 30; - protected override void Update() { base.Update(); From 8a0aba6c596bcadf1c0e15ef661e8846503841d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Feb 2022 23:11:12 +0100 Subject: [PATCH 0813/5199] Implement mod panel for new mod select screen --- .../Visual/UserInterface/TestSceneModPanel.cs | 42 +++ osu.Game/Overlays/Mods/ModPanel.cs | 272 ++++++++++++++++++ 2 files changed, 314 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModPanel.cs create mode 100644 osu.Game/Overlays/Mods/ModPanel.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModPanel.cs new file mode 100644 index 0000000000..bdf3f98863 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModPanel.cs @@ -0,0 +1,42 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; +using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestSceneModPanel : OsuManualInputManagerTestScene + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + + [Test] + public void TestVariousPanels() + { + AddStep("create content", () => Child = new FillFlowContainer + { + Width = 300, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(0, 5), + Children = new[] + { + new ModPanel(new OsuModHalfTime()), + new ModPanel(new OsuModFlashlight()), + new ModPanel(new OsuModAutoplay()), + new ModPanel(new OsuModAlternate()), + new ModPanel(new OsuModApproachDifferent()) + } + }); + } + } +} diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs new file mode 100644 index 0000000000..4d08b22017 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -0,0 +1,272 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Framework.Utils; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; +using osuTK; +using osuTK.Input; + +#nullable enable + +namespace osu.Game.Overlays.Mods +{ + public class ModPanel : OsuClickableContainer + { + public Mod Mod { get; } + public BindableBool Active { get; } = new BindableBool(); + + protected readonly Box Background; + protected readonly Container SwitchContainer; + protected readonly Container MainContentContainer; + protected readonly Box TextBackground; + protected readonly FillFlowContainer TextFlow; + + [Resolved] + protected OverlayColourProvider ColourProvider { get; private set; } = null!; + + protected const double TRANSITION_DURATION = 150; + protected const float SHEAR_X = 0.2f; + + protected const float HEIGHT = 42; + protected const float CORNER_RADIUS = 7; + protected const float IDLE_SWITCH_WIDTH = 54; + protected const float EXPANDED_SWITCH_WIDTH = 70; + + private Colour4 activeColour; + private Colour4 activeHoverColour; + + private Sample? sampleOff; + private Sample? sampleOn; + + public ModPanel(Mod mod) + { + Mod = mod; + + RelativeSizeAxes = Axes.X; + Height = 42; + + // all below properties are applied to `Content` rather than the `ModPanel` in its entirety + // to allow external components to set these properties on the panel without affecting + // its "internal" appearance. + Content.Masking = true; + Content.CornerRadius = CORNER_RADIUS; + Content.BorderThickness = 2; + Content.Shear = new Vector2(SHEAR_X, 0); + + Children = new Drawable[] + { + Background = new Box + { + RelativeSizeAxes = Axes.Both + }, + SwitchContainer = new Container + { + RelativeSizeAxes = Axes.Y, + Child = new ModSwitchSmall(mod) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Active = { BindTarget = Active }, + Shear = new Vector2(-SHEAR_X, 0), + Scale = new Vector2(HEIGHT / ModSwitchSmall.DEFAULT_SIZE) + } + }, + MainContentContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = CORNER_RADIUS, + Children = new Drawable[] + { + TextBackground = new Box + { + RelativeSizeAxes = Axes.Both + }, + TextFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Horizontal = 17.5f, + Vertical = 4 + }, + Direction = FillDirection.Vertical, + Children = new[] + { + new OsuSpriteText + { + Text = mod.Name, + Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold), + Shear = new Vector2(-SHEAR_X, 0), + Margin = new MarginPadding + { + Left = -18 * SHEAR_X + } + }, + new OsuSpriteText + { + Text = mod.Description, + Font = OsuFont.Default.With(size: 12), + RelativeSizeAxes = Axes.X, + Truncate = true, + Shear = new Vector2(-SHEAR_X, 0) + } + } + } + } + } + } + }; + + Action = Active.Toggle; + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio, OsuColour colours) + { + sampleOn = audio.Samples.Get(@"UI/check-on"); + sampleOff = audio.Samples.Get(@"UI/check-off"); + + activeColour = colours.ForModType(Mod.Type); + activeHoverColour = activeColour.Lighten(0.3f); + } + + protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet); + + protected override void LoadComplete() + { + base.LoadComplete(); + Active.BindValueChanged(_ => + { + playStateChangeSamples(); + UpdateState(); + }); + + UpdateState(); + FinishTransforms(true); + } + + private void playStateChangeSamples() + { + if (Active.Value) + sampleOn?.Play(); + else + sampleOff?.Play(); + } + + protected override bool OnHover(HoverEvent e) + { + UpdateState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + UpdateState(); + base.OnHoverLost(e); + } + + private double? mouseDownTime; + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (e.Button == MouseButton.Left) + mouseDownTime = Time.Current; + return true; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + mouseDownTime = null; + base.OnMouseUp(e); + } + + protected override bool OnDragStart(DragStartEvent e) + { + mouseDownTime = null; + return true; + } + + protected override void Update() + { + base.Update(); + + if (mouseDownTime != null) + { + double startTime = mouseDownTime.Value; + double endTime = startTime + 600; + + float startValue = Active.Value ? EXPANDED_SWITCH_WIDTH : IDLE_SWITCH_WIDTH; + float endValue = IDLE_SWITCH_WIDTH + (EXPANDED_SWITCH_WIDTH - IDLE_SWITCH_WIDTH) * (Active.Value ? 0.2f : 0.8f); + + float targetWidth = Interpolation.ValueAt(Math.Clamp(Time.Current, startTime, endTime), startValue, endValue, startTime, endTime, Easing.OutQuint); + + SwitchContainer.Width = targetWidth; + MainContentContainer.Padding = new MarginPadding + { + Left = targetWidth, + Right = CORNER_RADIUS + }; + } + } + + protected virtual void UpdateState() + { + if (Active.Value) + { + Colour4 backgroundTextColour = IsHovered ? activeHoverColour : activeColour; + Colour4 backgroundColour = Interpolation.ValueAt(0.7f, Colour4.Black, backgroundTextColour, 0, 1); + + Content.TransformTo(nameof(BorderColour), (ColourInfo)backgroundColour, TRANSITION_DURATION, Easing.OutQuint); + Background.FadeColour(backgroundColour, TRANSITION_DURATION, Easing.OutQuint); + SwitchContainer.ResizeWidthTo(EXPANDED_SWITCH_WIDTH, TRANSITION_DURATION, Easing.OutQuint); + MainContentContainer.TransformTo(nameof(Padding), new MarginPadding + { + Left = EXPANDED_SWITCH_WIDTH, + Right = CORNER_RADIUS + }, TRANSITION_DURATION, Easing.OutQuint); + TextBackground.FadeColour(backgroundTextColour, TRANSITION_DURATION, Easing.OutQuint); + TextFlow.FadeColour(ColourProvider.Background6, TRANSITION_DURATION, Easing.OutQuint); + } + else + { + Colour4 backgroundColour = ColourProvider.Background3; + if (IsHovered) + backgroundColour = Interpolation.ValueAt(0.25f, backgroundColour, activeColour, 0, 1); + + Colour4 textBackgroundColour = ColourProvider.Background2; + if (IsHovered) + textBackgroundColour = Interpolation.ValueAt(0.25f, textBackgroundColour, activeColour, 0, 1); + + Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(backgroundColour, textBackgroundColour), TRANSITION_DURATION, Easing.OutQuint); + Background.FadeColour(backgroundColour, TRANSITION_DURATION, Easing.OutQuint); + SwitchContainer.ResizeWidthTo(IDLE_SWITCH_WIDTH, TRANSITION_DURATION, Easing.OutQuint); + MainContentContainer.TransformTo(nameof(Padding), new MarginPadding + { + Left = IDLE_SWITCH_WIDTH, + Right = CORNER_RADIUS + }, TRANSITION_DURATION, Easing.OutQuint); + TextBackground.FadeColour(textBackgroundColour, TRANSITION_DURATION, Easing.OutQuint); + TextFlow.FadeColour(Colour4.White, TRANSITION_DURATION, Easing.OutQuint); + } + } + } +} From bbe2dfa458c5c0a0dfa56d68717c8c625c4fd01a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Feb 2022 23:11:26 +0100 Subject: [PATCH 0814/5199] Move out incompatibility displaying tooltip to own class --- .../IncompatibilityDisplayingModButton.cs | 51 --------------- .../Mods/IncompatibilityDisplayingTooltip.cs | 64 +++++++++++++++++++ 2 files changed, 64 insertions(+), 51 deletions(-) create mode 100644 osu.Game/Overlays/Mods/IncompatibilityDisplayingTooltip.cs diff --git a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs index 0f51439252..6e2cb40596 100644 --- a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs +++ b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs @@ -8,11 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Play.HUD; using osu.Game.Utils; using osuTK; @@ -66,52 +62,5 @@ namespace osu.Game.Overlays.Mods } public override ITooltip GetCustomTooltip() => new IncompatibilityDisplayingTooltip(); - - private class IncompatibilityDisplayingTooltip : ModButtonTooltip - { - private readonly OsuSpriteText incompatibleText; - - private readonly Bindable> incompatibleMods = new Bindable>(); - - [Resolved] - private Bindable ruleset { get; set; } - - public IncompatibilityDisplayingTooltip() - { - AddRange(new Drawable[] - { - incompatibleText = new OsuSpriteText - { - Margin = new MarginPadding { Top = 5 }, - Font = OsuFont.GetFont(weight: FontWeight.Regular), - Text = "Incompatible with:" - }, - new ModDisplay - { - Current = incompatibleMods, - ExpansionMode = ExpansionMode.AlwaysExpanded, - Scale = new Vector2(0.7f) - } - }); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - incompatibleText.Colour = colours.BlueLight; - } - - protected override void UpdateDisplay(Mod mod) - { - base.UpdateDisplay(mod); - - var incompatibleTypes = mod.IncompatibleMods; - - var allMods = ruleset.Value.CreateInstance().AllMods; - - incompatibleMods.Value = allMods.Where(m => m.GetType() != mod.GetType() && incompatibleTypes.Any(t => t.IsInstanceOfType(m))).Select(m => m.CreateInstance()).ToList(); - incompatibleText.Text = incompatibleMods.Value.Any() ? "Incompatible with:" : "Compatible with all mods"; - } - } } } diff --git a/osu.Game/Overlays/Mods/IncompatibilityDisplayingTooltip.cs b/osu.Game/Overlays/Mods/IncompatibilityDisplayingTooltip.cs new file mode 100644 index 0000000000..d8117c8f00 --- /dev/null +++ b/osu.Game/Overlays/Mods/IncompatibilityDisplayingTooltip.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play.HUD; +using osuTK; + +namespace osu.Game.Overlays.Mods +{ + internal class IncompatibilityDisplayingTooltip : ModButtonTooltip + { + private readonly OsuSpriteText incompatibleText; + + private readonly Bindable> incompatibleMods = new Bindable>(); + + [Resolved] + private Bindable ruleset { get; set; } + + public IncompatibilityDisplayingTooltip() + { + AddRange(new Drawable[] + { + incompatibleText = new OsuSpriteText + { + Margin = new MarginPadding { Top = 5 }, + Font = OsuFont.GetFont(weight: FontWeight.Regular), + Text = "Incompatible with:" + }, + new ModDisplay + { + Current = incompatibleMods, + ExpansionMode = ExpansionMode.AlwaysExpanded, + Scale = new Vector2(0.7f) + } + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + incompatibleText.Colour = colours.BlueLight; + } + + protected override void UpdateDisplay(Mod mod) + { + base.UpdateDisplay(mod); + + var incompatibleTypes = mod.IncompatibleMods; + + var allMods = ruleset.Value.CreateInstance().AllMods; + + incompatibleMods.Value = allMods.Where(m => m.GetType() != mod.GetType() && incompatibleTypes.Any(t => t.IsInstanceOfType(m))).Select(m => m.CreateInstance()).ToList(); + incompatibleText.Text = incompatibleMods.Value.Any() ? "Incompatible with:" : "Compatible with all mods"; + } + } +} From 713f89a59c7d0c964d5c4f1c0760dd45fe596bdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Feb 2022 23:11:38 +0100 Subject: [PATCH 0815/5199] Implement incompatibility-displaying variant of mod panel --- .../Visual/UserInterface/TestSceneModPanel.cs | 39 ++++++++ .../Mods/IncompatibilityDisplayingModPanel.cs | 88 +++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModPanel.cs index bdf3f98863..95323e5dfa 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModPanel.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModPanel.cs @@ -1,14 +1,17 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Overlays; using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osuTK; +using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface { @@ -38,5 +41,41 @@ namespace osu.Game.Tests.Visual.UserInterface } }); } + + [Test] + public void TestIncompatibilityDisplay() + { + IncompatibilityDisplayingModPanel panel = null; + + AddStep("create panel with DT", () => Child = panel = new IncompatibilityDisplayingModPanel(new OsuModDoubleTime()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.None, + Width = 300 + }); + + clickPanel(); + AddAssert("panel active", () => panel.Active.Value); + + clickPanel(); + AddAssert("panel not active", () => !panel.Active.Value); + + AddStep("set incompatible mod", () => SelectedMods.Value = new[] { new OsuModHalfTime() }); + + clickPanel(); + AddAssert("panel not active", () => !panel.Active.Value); + + AddStep("reset mods", () => SelectedMods.Value = Array.Empty()); + + clickPanel(); + AddAssert("panel active", () => panel.Active.Value); + + void clickPanel() => AddStep("click panel", () => + { + InputManager.MoveMouseTo(panel); + InputManager.Click(MouseButton.Left); + }); + } } } diff --git a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs new file mode 100644 index 0000000000..4b6759c209 --- /dev/null +++ b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs @@ -0,0 +1,88 @@ +// 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.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Mods; +using osu.Game.Utils; + +namespace osu.Game.Overlays.Mods +{ + public class IncompatibilityDisplayingModPanel : ModPanel, IHasCustomTooltip + { + private readonly BindableBool incompatible = new BindableBool(); + + [Resolved] + private Bindable> selectedMods { get; set; } + + public IncompatibilityDisplayingModPanel(Mod mod) + : base(mod) + { + } + + protected override void LoadComplete() + { + selectedMods.BindValueChanged(_ => updateIncompatibility(), true); + incompatible.BindValueChanged(_ => Scheduler.AddOnce(UpdateState)); + // base call will run `UpdateState()` first time and finish transforms. + base.LoadComplete(); + } + + private void updateIncompatibility() + { + incompatible.Value = selectedMods.Value.Count > 0 && !selectedMods.Value.Contains(Mod) && !ModUtils.CheckCompatibleSet(selectedMods.Value.Append(Mod)); + } + + protected override void UpdateState() + { + Action = incompatible.Value ? () => { } : (Action)Active.Toggle; + + if (incompatible.Value) + { + Colour4 backgroundColour = ColourProvider.Background5; + Colour4 textBackgroundColour = ColourProvider.Background4; + + Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(backgroundColour, textBackgroundColour), TRANSITION_DURATION, Easing.OutQuint); + Background.FadeColour(backgroundColour, TRANSITION_DURATION, Easing.OutQuint); + + SwitchContainer.ResizeWidthTo(IDLE_SWITCH_WIDTH, TRANSITION_DURATION, Easing.OutQuint); + SwitchContainer.FadeColour(Colour4.Gray, TRANSITION_DURATION, Easing.OutQuint); + MainContentContainer.TransformTo(nameof(Padding), new MarginPadding + { + Left = IDLE_SWITCH_WIDTH, + Right = CORNER_RADIUS + }, TRANSITION_DURATION, Easing.OutQuint); + + TextBackground.FadeColour(textBackgroundColour, TRANSITION_DURATION, Easing.OutQuint); + TextFlow.FadeColour(Colour4.White.Opacity(0.5f), TRANSITION_DURATION, Easing.OutQuint); + return; + } + + SwitchContainer.FadeColour(Colour4.White, TRANSITION_DURATION, Easing.OutQuint); + base.UpdateState(); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (incompatible.Value) + return true; // bypasses base call purposely in order to not play out the intermediate state animation. + + return base.OnMouseDown(e); + } + + #region IHasCustomTooltip + + public ITooltip GetCustomTooltip() => new IncompatibilityDisplayingTooltip(); + + public Mod TooltipContent => Mod; + + #endregion + } +} From 435bdd0b4a68d7abf66b2f7eb0c5d70f16246580 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Feb 2022 14:55:35 +0900 Subject: [PATCH 0816/5199] Combine and simplify state management logic This makes a few changes to bring things into a better shape during mouse interactions with the mod panels: - Dragging away from the panel now works in line with other buttons (ie. `OsuButton`) - Hovering now uses a lightened version of the current state, rather than always using the active colour. I think this feels better. - Mouse down now uses a transform point of 0.5. This is to give the button a feeling of one of those latching light switches which resists until reaching a point of overcoming the spring and switching state. I think 0.4 (non-active) and 0.6 (from active) may work better, but left at 0.5 for simplicity of implementation and I think it's good enough? - Border always uses the gradiented version. I did this for simplicity of implementation, but also think it looks better. - Adjusted transform durations to feel better to me. --- osu.Game/Overlays/Mods/ModPanel.cs | 102 ++++++++++------------------- 1 file changed, 35 insertions(+), 67 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index 4d08b22017..13e2b5bb0b 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -40,6 +40,7 @@ namespace osu.Game.Overlays.Mods protected OverlayColourProvider ColourProvider { get; private set; } = null!; protected const double TRANSITION_DURATION = 150; + protected const float SHEAR_X = 0.2f; protected const float HEIGHT = 42; @@ -48,7 +49,6 @@ namespace osu.Game.Overlays.Mods protected const float EXPANDED_SWITCH_WIDTH = 70; private Colour4 activeColour; - private Colour4 activeHoverColour; private Sample? sampleOff; private Sample? sampleOn; @@ -146,7 +146,6 @@ namespace osu.Game.Overlays.Mods sampleOff = audio.Samples.Get(@"UI/check-off"); activeColour = colours.ForModType(Mod.Type); - activeHoverColour = activeColour.Lighten(0.3f); } protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet); @@ -184,89 +183,58 @@ namespace osu.Game.Overlays.Mods base.OnHoverLost(e); } - private double? mouseDownTime; + private bool mouseDown; protected override bool OnMouseDown(MouseDownEvent e) { if (e.Button == MouseButton.Left) - mouseDownTime = Time.Current; + mouseDown = true; + + UpdateState(); return true; } protected override void OnMouseUp(MouseUpEvent e) { - mouseDownTime = null; + mouseDown = false; + + UpdateState(); base.OnMouseUp(e); } - protected override bool OnDragStart(DragStartEvent e) - { - mouseDownTime = null; - return true; - } - - protected override void Update() - { - base.Update(); - - if (mouseDownTime != null) - { - double startTime = mouseDownTime.Value; - double endTime = startTime + 600; - - float startValue = Active.Value ? EXPANDED_SWITCH_WIDTH : IDLE_SWITCH_WIDTH; - float endValue = IDLE_SWITCH_WIDTH + (EXPANDED_SWITCH_WIDTH - IDLE_SWITCH_WIDTH) * (Active.Value ? 0.2f : 0.8f); - - float targetWidth = Interpolation.ValueAt(Math.Clamp(Time.Current, startTime, endTime), startValue, endValue, startTime, endTime, Easing.OutQuint); - - SwitchContainer.Width = targetWidth; - MainContentContainer.Padding = new MarginPadding - { - Left = targetWidth, - Right = CORNER_RADIUS - }; - } - } - protected virtual void UpdateState() { - if (Active.Value) + float targetWidth = Active.Value ? EXPANDED_SWITCH_WIDTH : IDLE_SWITCH_WIDTH; + double transitionDuration = TRANSITION_DURATION; + + Colour4 textBackgroundColour = Active.Value ? activeColour : (Colour4)ColourProvider.Background2; + Colour4 mainBackgroundColour = Active.Value ? activeColour.Darken(0.3f) : (Colour4)ColourProvider.Background3; + Colour4 textColour = Active.Value ? (Colour4)ColourProvider.Background6 : Colour4.White; + + // Hover affects colour of button background + if (IsHovered) { - Colour4 backgroundTextColour = IsHovered ? activeHoverColour : activeColour; - Colour4 backgroundColour = Interpolation.ValueAt(0.7f, Colour4.Black, backgroundTextColour, 0, 1); - - Content.TransformTo(nameof(BorderColour), (ColourInfo)backgroundColour, TRANSITION_DURATION, Easing.OutQuint); - Background.FadeColour(backgroundColour, TRANSITION_DURATION, Easing.OutQuint); - SwitchContainer.ResizeWidthTo(EXPANDED_SWITCH_WIDTH, TRANSITION_DURATION, Easing.OutQuint); - MainContentContainer.TransformTo(nameof(Padding), new MarginPadding - { - Left = EXPANDED_SWITCH_WIDTH, - Right = CORNER_RADIUS - }, TRANSITION_DURATION, Easing.OutQuint); - TextBackground.FadeColour(backgroundTextColour, TRANSITION_DURATION, Easing.OutQuint); - TextFlow.FadeColour(ColourProvider.Background6, TRANSITION_DURATION, Easing.OutQuint); + textBackgroundColour = textBackgroundColour.Lighten(0.1f); + mainBackgroundColour = mainBackgroundColour.Lighten(0.1f); } - else + + // Mouse down adds a halfway tween of the movement + if (mouseDown) { - Colour4 backgroundColour = ColourProvider.Background3; - if (IsHovered) - backgroundColour = Interpolation.ValueAt(0.25f, backgroundColour, activeColour, 0, 1); - - Colour4 textBackgroundColour = ColourProvider.Background2; - if (IsHovered) - textBackgroundColour = Interpolation.ValueAt(0.25f, textBackgroundColour, activeColour, 0, 1); - - Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(backgroundColour, textBackgroundColour), TRANSITION_DURATION, Easing.OutQuint); - Background.FadeColour(backgroundColour, TRANSITION_DURATION, Easing.OutQuint); - SwitchContainer.ResizeWidthTo(IDLE_SWITCH_WIDTH, TRANSITION_DURATION, Easing.OutQuint); - MainContentContainer.TransformTo(nameof(Padding), new MarginPadding - { - Left = IDLE_SWITCH_WIDTH, - Right = CORNER_RADIUS - }, TRANSITION_DURATION, Easing.OutQuint); - TextBackground.FadeColour(textBackgroundColour, TRANSITION_DURATION, Easing.OutQuint); - TextFlow.FadeColour(Colour4.White, TRANSITION_DURATION, Easing.OutQuint); + targetWidth = (float)Interpolation.Lerp(IDLE_SWITCH_WIDTH, EXPANDED_SWITCH_WIDTH, 0.5f); + transitionDuration *= 4; } + + Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(mainBackgroundColour, textBackgroundColour), transitionDuration, Easing.OutQuint); + Background.FadeColour(mainBackgroundColour, transitionDuration, Easing.OutQuint); + SwitchContainer.ResizeWidthTo(targetWidth, transitionDuration, Easing.OutQuint); + MainContentContainer.TransformTo(nameof(Padding), new MarginPadding + { + Left = targetWidth, + Right = CORNER_RADIUS + }, transitionDuration, Easing.OutQuint); + TextBackground.FadeColour(textBackgroundColour, transitionDuration, Easing.OutQuint); + TextFlow.FadeColour(textColour, transitionDuration, Easing.OutQuint); } } } From 3f6bdc5585de6653642d8c6ec119e6917dcb67d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Feb 2022 15:40:06 +0900 Subject: [PATCH 0817/5199] Don't expose "mark as read" errors to the user via notifications This can happen if the user leaves the channel before the request is fired. You can't mark a channel as read when you're not in the channel. Addresses https://github.com/ppy/osu/discussions/16973. --- osu.Game/Online/Chat/ChannelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 77b52c34d9..38e655db71 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -618,7 +618,7 @@ namespace osu.Game.Online.Chat var req = new MarkChannelAsReadRequest(channel, message); req.Success += () => channel.LastReadId = message.Id; - req.Failure += e => Logger.Error(e, $"Failed to mark channel {channel} up to '{message}' as read"); + req.Failure += e => Logger.Log($"Failed to mark channel {channel} up to '{message}' as read ({e.Message})", LoggingTarget.Network, LogLevel.Verbose); api.Queue(req); } From 401cf2a955eb82d45afa03105090df7ecf6931eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Feb 2022 15:54:45 +0900 Subject: [PATCH 0818/5199] Allow pausing game-wide audio via hotkey as long as `LocalUserPlaying` is not set `Player` seems to handle this correctly locally already, which is to say if the user attempts to toggle the pause state incorrectly, it will still recover. The logic stoppic this operation was only in the key binding handler, which meant it was already possible from the now playing overlay this whole time, so I *think* this should be quite safe. --- osu.Game/Overlays/Music/MusicKeyBindingHandler.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs index baee17fb00..6b33c9200e 100644 --- a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs +++ b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs @@ -30,17 +30,20 @@ namespace osu.Game.Overlays.Music [Resolved(canBeNull: true)] private OnScreenDisplay onScreenDisplay { get; set; } + [Resolved] + private OsuGame game { get; set; } + public bool OnPressed(KeyBindingPressEvent e) { if (e.Repeat) return false; - if (beatmap.Disabled) - return false; - switch (e.Action) { case GlobalAction.MusicPlay: + if (game.LocalUserPlaying.Value) + return false; + // 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; @@ -49,11 +52,17 @@ namespace osu.Game.Overlays.Music return true; case GlobalAction.MusicNext: + if (beatmap.Disabled) + return false; + musicController.NextTrack(() => onScreenDisplay?.Display(new MusicActionToast(GlobalActionKeyBindingStrings.MusicNext, e.Action))); return true; case GlobalAction.MusicPrev: + if (beatmap.Disabled) + return false; + musicController.PreviousTrack(res => { switch (res) From b4a54b38e71a960bcb6382aadc32f1cbe2ade358 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Feb 2022 16:02:16 +0900 Subject: [PATCH 0819/5199] Remove redundant parameter specification --- osu.Game/Online/Chat/ChannelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 38e655db71..47e45e67d1 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -618,7 +618,7 @@ namespace osu.Game.Online.Chat var req = new MarkChannelAsReadRequest(channel, message); req.Success += () => channel.LastReadId = message.Id; - req.Failure += e => Logger.Log($"Failed to mark channel {channel} up to '{message}' as read ({e.Message})", LoggingTarget.Network, LogLevel.Verbose); + req.Failure += e => Logger.Log($"Failed to mark channel {channel} up to '{message}' as read ({e.Message})", LoggingTarget.Network); api.Queue(req); } From c6d78b93257b172d044cc8932bd857555a82571d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Feb 2022 16:12:15 +0900 Subject: [PATCH 0820/5199] Fix several oversights in data linking causing drawable rooms not updating as expected --- osu.Game/Online/Rooms/Room.cs | 2 ++ .../Components/StarRatingRangeDisplay.cs | 16 ++++++---------- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 +++ 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index c7f34905e2..a33150fe08 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -46,6 +46,7 @@ namespace osu.Game.Online.Rooms public readonly Bindable PlaylistItemStats = new Bindable(); [JsonProperty("difficulty_range")] + [Cached] public readonly Bindable DifficultyRange = new Bindable(); [Cached] @@ -190,6 +191,7 @@ namespace osu.Game.Online.Rooms QueueMode.Value = other.QueueMode.Value; DifficultyRange.Value = other.DifficultyRange.Value; PlaylistItemStats.Value = other.PlaylistItemStats.Value; + CurrentPlaylistItem.Value = other.CurrentPlaylistItem.Value; if (EndDate.Value != null && DateTimeOffset.Now >= EndDate.Value) Status.Value = new RoomStatusEnded(); diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs index 0b673006ef..7425e46bd3 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -12,7 +11,6 @@ using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; -using osu.Game.Online.Rooms; using osuTK; namespace osu.Game.Screens.OnlinePlay.Components @@ -72,25 +70,23 @@ namespace osu.Game.Screens.OnlinePlay.Components }; } - [Resolved] - private Room room { get; set; } - protected override void LoadComplete() { base.LoadComplete(); - Playlist.BindCollectionChanged(updateRange, true); + DifficultyRange.BindValueChanged(_ => updateRange()); + Playlist.BindCollectionChanged((_, __) => updateRange(), true); } - private void updateRange(object sender, NotifyCollectionChangedEventArgs e) + private void updateRange() { StarDifficulty minDifficulty; StarDifficulty maxDifficulty; - if (room.DifficultyRange.Value != null) + if (DifficultyRange.Value != null) { - minDifficulty = new StarDifficulty(room.DifficultyRange.Value.Min, 0); - maxDifficulty = new StarDifficulty(room.DifficultyRange.Value.Max, 0); + minDifficulty = new StarDifficulty(DifficultyRange.Value.Min, 0); + maxDifficulty = new StarDifficulty(DifficultyRange.Value.Max, 0); } else { diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 7d1feb0316..984880dc3c 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -41,6 +41,9 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected BindableList Playlist { get; private set; } + [Resolved(typeof(Room))] + protected Bindable DifficultyRange { get; private set; } + [Resolved(typeof(Room))] protected Bindable Category { get; private set; } From bb1aa032bdd70c62abb6d6fe3833dacde8b32a71 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Feb 2022 16:20:40 +0900 Subject: [PATCH 0821/5199] Combine `SelectedItem` and `CurrentPlaylistItem` into same storage --- .../Lounge/Components/DrawableRoom.cs | 2 +- .../Match/MultiplayerMatchSettingsOverlay.cs | 4 ++-- .../Match/MultiplayerReadyButton.cs | 4 ++-- .../Match/Playlist/MultiplayerPlaylist.cs | 4 ++-- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 22 ++++++++----------- 5 files changed, 16 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index a1a82c907a..5adce862a0 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -388,7 +388,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components protected override void LoadComplete() { base.LoadComplete(); - SelectedItem.BindValueChanged(onSelectedItemChanged, true); + CurrentPlaylistItem.BindValueChanged(onSelectedItemChanged, true); } private CancellationTokenSource beatmapLookupCancellation; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 7f1db733b3..be98a9d4e9 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -343,7 +343,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match base.LoadComplete(); drawablePlaylist.Items.BindTo(Playlist); - drawablePlaylist.SelectedItem.BindTo(SelectedItem); + drawablePlaylist.SelectedItem.BindTo(CurrentPlaylistItem); } protected override void Update() @@ -419,7 +419,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match if (text.StartsWith(not_found_prefix, StringComparison.Ordinal)) { ErrorText.Text = "The selected beatmap is not available online."; - SelectedItem.Value.MarkInvalid(); + CurrentPlaylistItem.Value.MarkInvalid(); } else { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index 06959d942f..023af85f3b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { base.LoadComplete(); - SelectedItem.BindValueChanged(_ => updateState()); + CurrentPlaylistItem.BindValueChanged(_ => updateState()); } protected override void OnRoomUpdated() @@ -111,7 +111,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match bool enableButton = Room?.State == MultiplayerRoomState.Open - && SelectedItem.Value?.ID == Room.Settings.PlaylistItemId + && CurrentPlaylistItem.Value?.ID == Room.Settings.PlaylistItemId && !Room.Playlist.Single(i => i.ID == Room.Settings.PlaylistItemId).Expired && !operationInProgress.Value; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs index 7b90532cce..eeafebfec0 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs @@ -52,14 +52,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist queueList = new MultiplayerQueueList { RelativeSizeAxes = Axes.Both, - SelectedItem = { BindTarget = SelectedItem }, + SelectedItem = { BindTarget = CurrentPlaylistItem }, RequestEdit = item => RequestEdit?.Invoke(item) }, historyList = new MultiplayerHistoryList { RelativeSizeAxes = Axes.Both, Alpha = 0, - SelectedItem = { BindTarget = SelectedItem } + SelectedItem = { BindTarget = CurrentPlaylistItem } } } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 984880dc3c..95d9b2af15 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -32,6 +32,10 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable Type { get; private set; } + /// + /// The currently selected item in the , or the current item from + /// if this is not within a . + /// [Resolved(typeof(Room))] protected Bindable CurrentPlaylistItem { get; private set; } @@ -80,12 +84,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(CanBeNull = true)] private IBindable subScreenSelectedItem { get; set; } - /// - /// The currently selected item in the , or the current item from - /// if this is not within a . - /// - protected readonly Bindable SelectedItem = new Bindable(); - protected override void LoadComplete() { base.LoadComplete(); @@ -96,13 +94,11 @@ namespace osu.Game.Screens.OnlinePlay protected void UpdateSelectedItem() { - if (RoomID.Value == null || subScreenSelectedItem == null) - { - SelectedItem.Value = CurrentPlaylistItem.Value ?? Playlist.GetCurrentItem(); - return; - } - - SelectedItem.Value = subScreenSelectedItem.Value; + // null room ID means this is a room in the process of being created. + if (RoomID.Value == null) + CurrentPlaylistItem.Value = Playlist.GetCurrentItem(); + else if (subScreenSelectedItem != null) + CurrentPlaylistItem.Value = subScreenSelectedItem.Value; } } } From 328166f0d56043199ce8e1de9e6af080dc22de35 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 24 Feb 2022 17:01:04 +0900 Subject: [PATCH 0822/5199] Add failing test --- osu.Game.Tests/Online/TestAPIModJsonSerialization.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs index 1b7a7656b5..d5ea3e492c 100644 --- a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs @@ -121,6 +121,17 @@ namespace osu.Game.Tests.Online Assert.That((deserialised?.Mods[0])?.Settings["speed_change"], Is.EqualTo(2)); } + [Test] + public void TestAPIModDetachedFromSource() + { + var mod = new OsuModDoubleTime { SpeedChange = { Value = 1.01 } }; + var apiMod = new APIMod(mod); + + mod.SpeedChange.Value = 1.5; + + Assert.That(apiMod.Settings["speed_change"], Is.EqualTo(1.01d)); + } + private class TestRuleset : Ruleset { public override IEnumerable GetModsFor(ModType type) => new Mod[] From 2acaffd5e72b80bbb90ae0a0b2e250527db7365c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 24 Feb 2022 17:01:11 +0900 Subject: [PATCH 0823/5199] Fix APIMod storing bindables instead of value --- osu.Game/Online/API/APIMod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index 62f9976c0f..67041cae07 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -42,7 +42,7 @@ namespace osu.Game.Online.API var bindable = (IBindable)property.GetValue(mod); if (!bindable.IsDefault) - Settings.Add(property.Name.Underscore(), bindable); + Settings.Add(property.Name.Underscore(), ModUtils.GetSettingUnderlyingValue(bindable)); } } From 7193bc8554a0fb3db8cc2fd08fb31f3082fc91fa Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 24 Feb 2022 17:01:43 +0900 Subject: [PATCH 0824/5199] Fix playlists comparing mod equality via APIMod --- osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 8403e1e0fe..7efeae8129 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -9,7 +9,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Screens; using osu.Game.Extensions; -using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Scoring; @@ -40,8 +39,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists if (ruleset.Value.OnlineID != PlaylistItem.RulesetID) throw new InvalidOperationException("Current Ruleset does not match PlaylistItem's Ruleset"); - var localMods = Mods.Value.Select(m => new APIMod(m)).ToArray(); - if (!PlaylistItem.RequiredMods.All(m => localMods.Any(m.Equals))) + var requiredLocalMods = PlaylistItem.RequiredMods.Select(m => m.ToMod(GameplayState.Ruleset)); + if (!requiredLocalMods.All(m => Mods.Value.Any(m.Equals))) throw new InvalidOperationException("Current Mods do not match PlaylistItem's RequiredMods"); } From 255b3b067b3774b39a4f0cbf6f07555b5d3fbd2b Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Thu, 24 Feb 2022 10:13:27 +0100 Subject: [PATCH 0825/5199] Remove track fade --- osu.Game/Screens/Menu/MainMenu.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index a2cb448d40..81e03b94ae 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -309,18 +309,8 @@ namespace osu.Game.Screens.Menu if (e.Action == GlobalAction.Back && host.CanSuspendToBackground) { - bool didSuspend = host.SuspendToBackground(); - - if (didSuspend) - { - // fade the track so the Bass.Pause() on suspend isn't as jarring. - const double fade_time = 500; - musicController.CurrentTrack - .VolumeTo(0, fade_time, Easing.Out).Then() - .VolumeTo(1, fade_time, Easing.In); - + if (host.SuspendToBackground()) return true; - } } return false; From 6f29cbccd140cd222d3c72e8c2b17c94080ae43c Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Thu, 24 Feb 2022 10:36:10 +0100 Subject: [PATCH 0826/5199] Remove unused using --- osu.Game/Screens/Menu/MainMenu.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 81e03b94ae..0357332cf6 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -3,7 +3,6 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; From c5b1e5cbf84d9c7f2ea52caa8b5e83b67c54b7d7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 24 Feb 2022 20:11:58 +0900 Subject: [PATCH 0827/5199] Fix union resolver failing on multiple derived types --- osu.Game/Online/SignalRUnionWorkaroundResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/SignalRUnionWorkaroundResolver.cs b/osu.Game/Online/SignalRUnionWorkaroundResolver.cs index e64f9ed91c..c96f93df78 100644 --- a/osu.Game/Online/SignalRUnionWorkaroundResolver.cs +++ b/osu.Game/Online/SignalRUnionWorkaroundResolver.cs @@ -27,7 +27,7 @@ namespace osu.Game.Online // This should not be required. The fallback should work. But something is weird with the way caching is done. // For future adventurers, I would not advise looking into this further. It's likely not worth the effort. - baseMap = baseMap.Concat(baseMap.Select(t => (t.baseType, t.baseType))); + baseMap = baseMap.Concat(baseMap.Select(t => (t.baseType, t.baseType)).Distinct()); return new Dictionary(baseMap.Select(t => { From cd8190b2e744f635e86fe197cd6b360c9460963d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 24 Feb 2022 20:45:15 +0900 Subject: [PATCH 0828/5199] Remove Microsoft.CodeAnalysis.NetAnalyzers package --- Directory.Build.props | 1 - 1 file changed, 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index c1682638c2..5bdf12218c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -18,7 +18,6 @@ - $(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset From c189cc5d002c0a14ab15b83cdc6c462c2d03f78f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 24 Feb 2022 21:01:37 +0100 Subject: [PATCH 0829/5199] Remove unused using directive --- osu.Game/Overlays/Mods/ModPanel.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index 13e2b5bb0b..af8ad3eb18 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.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 osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; From 33a87976a8ac17f8ac0d559b254c6c4a0862c35e Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Thu, 24 Feb 2022 21:11:49 +0100 Subject: [PATCH 0830/5199] Rewrite to read better Co-authored-by: Dean Herbert --- osu.Game/Screens/Menu/MainMenu.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 0357332cf6..b0208a0ae8 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -306,10 +307,13 @@ namespace osu.Game.Screens.Menu if (e.Repeat) return false; - if (e.Action == GlobalAction.Back && host.CanSuspendToBackground) + switch (e.Action) { - if (host.SuspendToBackground()) - return true; + case GlobalAction.Back: + // In the case of a host being able to exit, the back action is handled by ExitConfirmOverlay. + Debug.Assert(!host.CanExit); + + return host.SuspendToBackground(); } return false; From f9d9ad388b565a517bc2ad918368265a6a05fe56 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 25 Feb 2022 16:03:28 +0900 Subject: [PATCH 0831/5199] Add chat display to multiplayer spectator screen --- .../TestSceneMultiSpectatorScreen.cs | 7 ++--- .../Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- .../Spectate/MultiSpectatorScreen.cs | 27 +++++++++++++------ 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 7ce0c6a94d..77a06e5746 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -15,6 +15,7 @@ using osu.Game.Configuration; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; +using osu.Game.Online.Rooms; using osu.Game.Rulesets.UI; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play; @@ -377,7 +378,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Beatmap.Value = beatmapManager.GetWorkingBeatmap(importedBeatmap); Ruleset.Value = importedBeatmap.Ruleset; - LoadScreen(spectatorScreen = new TestMultiSpectatorScreen(playingUsers.ToArray(), gameplayStartTime)); + LoadScreen(spectatorScreen = new TestMultiSpectatorScreen(SelectedRoom.Value, playingUsers.ToArray(), gameplayStartTime)); }); AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded)); @@ -465,8 +466,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { private readonly double? gameplayStartTime; - public TestMultiSpectatorScreen(MultiplayerRoomUser[] users, double? gameplayStartTime = null) - : base(users) + public TestMultiSpectatorScreen(Room room, MultiplayerRoomUser[] users, double? gameplayStartTime = null) + : base(room, users) { this.gameplayStartTime = gameplayStartTime; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 429b0ad89b..c78dcb7cb6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -449,7 +449,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer switch (client.LocalUser.State) { case MultiplayerUserState.Spectating: - return new MultiSpectatorScreen(users.Take(PlayerGrid.MAX_PLAYERS).ToArray()); + return new MultiSpectatorScreen(Room, users.Take(PlayerGrid.MAX_PLAYERS).ToArray()); default: return new MultiplayerPlayerLoader(() => new MultiplayerPlayer(Room, SelectedItem.Value, users)); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index e5eeeb3448..1cc4497675 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -11,10 +11,12 @@ using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; using osu.Game.Online.Spectator; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Spectate; +using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { @@ -48,15 +50,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private PlayerArea currentAudioSource; private bool canStartMasterClock; + private readonly Room room; private readonly MultiplayerRoomUser[] users; /// /// Creates a new . /// + /// The room. /// The players to spectate. - public MultiSpectatorScreen(MultiplayerRoomUser[] users) + public MultiSpectatorScreen(Room room, MultiplayerRoomUser[] users) : base(users.Select(u => u.UserID).ToArray()) { + this.room = room; this.users = users; instances = new PlayerArea[Users.Count]; @@ -65,7 +70,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate [BackgroundDependencyLoader] private void load() { - Container leaderboardContainer; + FillFlowContainer leaderboardFlow; Container scoreDisplayContainer; masterClockContainer = CreateMasterGameplayClockContainer(Beatmap.Value); @@ -97,10 +102,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { new Drawable[] { - leaderboardContainer = new Container + leaderboardFlow = new FillFlowContainer { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(5) }, grid = new PlayerGrid { RelativeSizeAxes = Axes.Both } } @@ -125,14 +133,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, users) { Expanded = { Value = true }, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, }, l => { foreach (var instance in instances) leaderboard.AddClock(instance.UserId, instance.GameplayClock); - leaderboardContainer.Add(leaderboard); + leaderboardFlow.Insert(0, leaderboard); if (leaderboard.TeamScores.Count == 2) { @@ -143,6 +149,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate }, scoreDisplayContainer.Add); } }); + + LoadComponentAsync(new GameplayChatDisplay(room) + { + Expanded = { Value = true }, + }, chat => leaderboardFlow.Insert(1, chat)); } protected override void LoadComplete() From 48ed9c61442ca05bb7fda4eaf8d7c64f8a37cdad Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 25 Feb 2022 16:03:46 +0900 Subject: [PATCH 0832/5199] Enable high chat polling rate --- osu.Game/OsuGame.cs | 3 ++- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 3 +++ osu.Game/Users/UserActivity.cs | 10 ++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index fa5a336b7c..fb81e4fd14 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -838,7 +838,8 @@ namespace osu.Game channelManager.HighPollRate.Value = chatOverlay.State.Value == Visibility.Visible || API.Activity.Value is UserActivity.InLobby - || API.Activity.Value is UserActivity.InMultiplayerGame; + || API.Activity.Value is UserActivity.InMultiplayerGame + || API.Activity.Value is UserActivity.SpectatingMultiplayerGame; } Add(difficultyRecommender); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 1cc4497675..3bb76c4a76 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -16,6 +16,7 @@ using osu.Game.Online.Spectator; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Spectate; +using osu.Game.Users; using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate @@ -36,6 +37,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public bool AllPlayersLoaded => instances.All(p => p?.PlayerLoaded == true); + protected override UserActivity InitialActivity => new UserActivity.SpectatingMultiplayerGame(Beatmap.Value.BeatmapInfo, Ruleset.Value); + [Resolved] private OsuColour colours { get; set; } diff --git a/osu.Game/Users/UserActivity.cs b/osu.Game/Users/UserActivity.cs index 516aa80652..2f945d6e1c 100644 --- a/osu.Game/Users/UserActivity.cs +++ b/osu.Game/Users/UserActivity.cs @@ -50,6 +50,16 @@ namespace osu.Game.Users public override string Status => $@"{base.Status} with others"; } + public class SpectatingMultiplayerGame : InGame + { + public SpectatingMultiplayerGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset) + : base(beatmapInfo, ruleset) + { + } + + public override string Status => $"Watching others {base.Status.ToLowerInvariant()}"; + } + public class InPlaylistGame : InGame { public InPlaylistGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset) From 387ae59bc4108372afaa75f3050b10eb04ee26dc Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 25 Feb 2022 16:12:25 +0900 Subject: [PATCH 0833/5199] Fix nullref in tests --- .../OnlinePlay/Multiplayer/GameplayChatDisplay.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs index 53fd111c27..d08a63e21f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -15,10 +16,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { public class GameplayChatDisplay : MatchChatDisplay, IKeyBindingHandler { - [Resolved] + [Resolved(CanBeNull = true)] + [CanBeNull] private ILocalUserPlayInfo localUserInfo { get; set; } - private IBindable localUserPlaying = new Bindable(); + private readonly IBindable localUserPlaying = new Bindable(); public override bool PropagatePositionalInputSubTree => !localUserPlaying.Value; @@ -46,7 +48,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { base.LoadComplete(); - localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy(); + if (localUserInfo != null) + localUserPlaying.BindTo(localUserInfo.IsPlaying); + localUserPlaying.BindValueChanged(playing => { // for now let's never hold focus. this avoid misdirected gameplay keys entering chat. From 8eef1774d5b2b6a58f67a17d1ca3804ce2dbbf76 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 25 Feb 2022 20:18:22 +0900 Subject: [PATCH 0834/5199] Use Logger.Log instead of console --- osu.Game/Online/Spectator/SpectatorClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index d0b89e981d..70baf69037 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -279,7 +279,7 @@ namespace osu.Game.Online.Spectator totalBundledFrames += frames.Length; - Console.WriteLine($"Purging {pendingFrames.Count} frames (total {totalBundledFrames})"); + Logger.Log($"Purging {pendingFrames.Count} frames (total {totalBundledFrames})"); pendingFrames.Clear(); lastPurgeTime = Time.Current; From 67082b8c5de9d97d353ea973f34bc6773af4029f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Feb 2022 22:25:36 +0900 Subject: [PATCH 0835/5199] Remove verbose logging from `SpectatorClient` for now --- osu.Game/Online/Spectator/SpectatorClient.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 70baf69037..8f22078010 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -86,8 +86,6 @@ namespace osu.Game.Online.Spectator private Task? lastSend; - private int totalBundledFrames; - private const int max_pending_frames = 30; [BackgroundDependencyLoader] @@ -169,8 +167,6 @@ namespace osu.Game.Online.Spectator IsPlaying = true; - totalBundledFrames = 0; - // transfer state at point of beginning play currentState.BeatmapID = score.ScoreInfo.BeatmapInfo.OnlineID; currentState.RulesetID = score.ScoreInfo.RulesetID; @@ -277,10 +273,6 @@ namespace osu.Game.Online.Spectator var frames = pendingFrames.ToArray(); var bundle = new FrameDataBundle(currentScore.ScoreInfo, frames); - totalBundledFrames += frames.Length; - - Logger.Log($"Purging {pendingFrames.Count} frames (total {totalBundledFrames})"); - pendingFrames.Clear(); lastPurgeTime = Time.Current; From 926108af7da97c9f0114702ba94adc0f3643c4c7 Mon Sep 17 00:00:00 2001 From: Shivam Date: Fri, 25 Feb 2022 14:15:19 +0100 Subject: [PATCH 0836/5199] Change launch args from dotnet 5 to dotnet 6 --- .vscode/launch.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 1b590008cd..d93fddf42d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Desktop/bin/Debug/net5.0/osu!.dll" + "${workspaceRoot}/osu.Desktop/bin/Debug/net6.0/osu!.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build osu! (Debug)", @@ -19,7 +19,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Desktop/bin/Release/net5.0/osu!.dll" + "${workspaceRoot}/osu.Desktop/bin/Release/net6.0/osu!.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build osu! (Release)", @@ -31,7 +31,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Game.Tests/bin/Debug/net5.0/osu.Game.Tests.dll" + "${workspaceRoot}/osu.Game.Tests/bin/Debug/net6.0/osu.Game.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build tests (Debug)", @@ -43,7 +43,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Game.Tests/bin/Release/net5.0/osu.Game.Tests.dll" + "${workspaceRoot}/osu.Game.Tests/bin/Release/net6.0/osu.Game.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build tests (Release)", @@ -55,7 +55,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Desktop/bin/Debug/net5.0/osu!.dll", + "${workspaceRoot}/osu.Desktop/bin/Debug/net6.0/osu!.dll", "--tournament" ], "cwd": "${workspaceRoot}", @@ -68,7 +68,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Desktop/bin/Release/net5.0/osu!.dll", + "${workspaceRoot}/osu.Desktop/bin/Release/net6.0/osu!.dll", "--tournament" ], "cwd": "${workspaceRoot}", @@ -81,7 +81,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net5.0/osu.Game.Tournament.Tests.dll", + "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net6.0/osu.Game.Tournament.Tests.dll", "--tournament" ], "cwd": "${workspaceRoot}", @@ -94,7 +94,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net5.0/osu.Game.Tournament.Tests.dll", + "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net6.0/osu.Game.Tournament.Tests.dll", "--tournament" ], "cwd": "${workspaceRoot}", @@ -105,7 +105,7 @@ "name": "Benchmark", "type": "coreclr", "request": "launch", - "program": "${workspaceRoot}/osu.Game.Benchmarks/bin/Release/net5.0/osu.Game.Benchmarks.dll", + "program": "${workspaceRoot}/osu.Game.Benchmarks/bin/Release/net6.0/osu.Game.Benchmarks.dll", "args": [ "--filter", "*" From 1a7a160f0a5a10345fd1537bf106022589038b18 Mon Sep 17 00:00:00 2001 From: Shivam Date: Fri, 25 Feb 2022 14:26:44 +0100 Subject: [PATCH 0837/5199] Update vscode launch.json files for all other projects --- .../osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/launch.json | 4 ++-- .../osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json | 4 ++-- .../.vscode/launch.json | 4 ++-- .../osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json | 4 ++-- osu.Game.Rulesets.Catch.Tests/.vscode/launch.json | 4 ++-- osu.Game.Rulesets.Mania.Tests/.vscode/launch.json | 4 ++-- osu.Game.Rulesets.Osu.Tests/.vscode/launch.json | 4 ++-- osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json | 4 ++-- osu.Game.Tournament.Tests/.vscode/launch.json | 4 ++-- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/launch.json b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/launch.json index fd03878699..b433819346 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/launch.json +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net5.0/osu.Game.Rulesets.EmptyFreeformRuleset.Tests.dll" + "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.EmptyFreeformRuleset.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.EmptyFreeformRuleset.Tests.dll" + "${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.EmptyFreeformRuleset.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json index bd9db14259..d60bc2571d 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net5.0/osu.Game.Rulesets.Pippidon.Tests.dll" + "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.Pippidon.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.Pippidon.Tests.dll" + "${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Pippidon.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/.vscode/launch.json b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/.vscode/launch.json index 24e4873ed6..f1f37f6363 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/.vscode/launch.json +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net5.0/osu.Game.Rulesets.EmptyScrolling.Tests.dll" + "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.EmptyScrolling.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.EmptyScrolling.Tests.dll" + "${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.EmptyScrolling.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json index bd9db14259..d60bc2571d 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net5.0/osu.Game.Rulesets.Pippidon.Tests.dll" + "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.Pippidon.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.Pippidon.Tests.dll" + "${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Pippidon.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json b/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json index 9aaaf418c2..201343a036 100644 --- a/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net5.0/osu.Game.Rulesets.Catch.Tests.dll" + "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.Catch.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.Catch.Tests.dll" + "${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Catch.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json b/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json index e3d7956e85..f6a067a831 100644 --- a/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net5.0/osu.Game.Rulesets.Mania.Tests.dll" + "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.Mania.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.Mania.Tests.dll" + "${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Mania.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json b/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json index 01a5985464..61be25b845 100644 --- a/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net5.0/osu.Game.Rulesets.Osu.Tests.dll" + "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.Osu.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.Osu.Tests.dll" + "${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Osu.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json b/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json index 779ebba9ae..56ec7d8d9c 100644 --- a/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net5.0/osu.Game.Rulesets.Taiko.Tests.dll" + "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.Taiko.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.Taiko.Tests.dll" + "${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Taiko.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/osu.Game.Tournament.Tests/.vscode/launch.json b/osu.Game.Tournament.Tests/.vscode/launch.json index 28532d3ed3..51aa541811 100644 --- a/osu.Game.Tournament.Tests/.vscode/launch.json +++ b/osu.Game.Tournament.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/net5.0/osu.Game.Tournament.Tests.dll" + "${workspaceRoot}/bin/Debug/net6.0/osu.Game.Tournament.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/net5.0/osu.Game.Tournament.Tests.dll" + "${workspaceRoot}/bin/Release/net6.0/osu.Game.Tournament.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", From c7f2759eb92385acb9e80f1cfd5addc79ad28ab7 Mon Sep 17 00:00:00 2001 From: Shivam Date: Fri, 25 Feb 2022 14:27:54 +0100 Subject: [PATCH 0838/5199] Update build configurations for Rider to net6.0 paths --- .../.idea/runConfigurations/Benchmarks.xml | 6 +++--- .../.idea/runConfigurations/CatchRuleset__Tests_.xml | 6 +++--- .../.idea/runConfigurations/ManiaRuleset__Tests_.xml | 6 +++--- .../.idea/runConfigurations/OsuRuleset__Tests_.xml | 6 +++--- .../.idea/runConfigurations/TaikoRuleset__Tests_.xml | 6 +++--- .../.idea/runConfigurations/Tournament.xml | 6 +++--- .../.idea/runConfigurations/Tournament__Tests_.xml | 6 +++--- .idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml | 6 +++--- .../.idea/runConfigurations/osu___Tests_.xml | 6 +++--- 9 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml index 498a710df9..d500c595c0 100644 --- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml +++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml @@ -1,8 +1,8 @@ - Custom, - - First = Common, - Last = Custom } } From 7de5dad4f0ae616c7b3803f3a4b1d2d260b33bf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 27 Feb 2022 19:23:02 +0100 Subject: [PATCH 0847/5199] Add test coverage for divisor behaviour --- .../Editing/TestSceneBeatDivisorControl.cs | 115 ++++++++++++++++++ .../Compose/Components/BeatDivisorControl.cs | 6 +- 2 files changed, 118 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs index e37019e5d3..4503b1a5f6 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; @@ -81,5 +82,119 @@ namespace osu.Game.Tests.Visual.Editing sliderDrawQuad.Centre.Y ); } + + [Test] + public void TestBeatChevronNavigation() + { + switchBeatSnap(1); + assertBeatSnap(1); + + switchBeatSnap(3); + assertBeatSnap(8); + + switchBeatSnap(-1); + assertBeatSnap(4); + + switchBeatSnap(-3); + assertBeatSnap(16); + } + + [Test] + public void TestBeatPresetNavigation() + { + assertPreset(BeatDivisorType.Common); + + switchPresets(1); + assertPreset(BeatDivisorType.Triplets); + + switchPresets(1); + assertPreset(BeatDivisorType.Common); + + switchPresets(-1); + assertPreset(BeatDivisorType.Triplets); + + switchPresets(-1); + assertPreset(BeatDivisorType.Common); + + setDivisorViaInput(3); + assertPreset(BeatDivisorType.Triplets); + + setDivisorViaInput(8); + assertPreset(BeatDivisorType.Common); + + setDivisorViaInput(15); + assertPreset(BeatDivisorType.Custom, 15); + + switchBeatSnap(-1); + assertBeatSnap(5); + + switchBeatSnap(-1); + assertBeatSnap(3); + + setDivisorViaInput(5); + assertPreset(BeatDivisorType.Custom, 15); + + switchPresets(1); + assertPreset(BeatDivisorType.Common); + + switchPresets(-1); + assertPreset(BeatDivisorType.Triplets); + } + + private void switchBeatSnap(int direction) => AddRepeatStep($"move snap {(direction > 0 ? "forward" : "backward")}", () => + { + int chevronIndex = direction > 0 ? 1 : 0; + var chevronButton = beatDivisorControl.ChildrenOfType().ElementAt(chevronIndex); + InputManager.MoveMouseTo(chevronButton); + InputManager.Click(MouseButton.Left); + }, Math.Abs(direction)); + + private void assertBeatSnap(int expected) => AddAssert($"beat snap is {expected}", + () => bindableBeatDivisor.Value == expected); + + private void switchPresets(int direction) => AddRepeatStep($"move presets {(direction > 0 ? "forward" : "backward")}", () => + { + int chevronIndex = direction > 0 ? 3 : 2; + var chevronButton = beatDivisorControl.ChildrenOfType().ElementAt(chevronIndex); + InputManager.MoveMouseTo(chevronButton); + InputManager.Click(MouseButton.Left); + }, Math.Abs(direction)); + + private void assertPreset(BeatDivisorType type, int? maxDivisor = null) + { + AddAssert($"preset is {type}", () => bindableBeatDivisor.ValidDivisors.Value.Type == type); + + if (type == BeatDivisorType.Custom) + { + Debug.Assert(maxDivisor != null); + AddAssert($"max divisor is {maxDivisor}", () => bindableBeatDivisor.ValidDivisors.Value.Presets.Max() == maxDivisor.Value); + } + } + + private void setDivisorViaInput(int divisor) + { + AddStep("open divisor input popover", () => + { + var button = beatDivisorControl.ChildrenOfType().Single(); + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + + BeatDivisorControl.CustomDivisorPopover popover = null; + AddUntilStep("wait for popover", () => (popover = this.ChildrenOfType().SingleOrDefault()) != null && popover.IsLoaded); + AddStep($"set divisor to {divisor}", () => + { + var textBox = popover.ChildrenOfType().Single(); + InputManager.MoveMouseTo(textBox); + InputManager.Click(MouseButton.Left); + textBox.Text = divisor.ToString(); + InputManager.Key(Key.Enter); + }); + AddStep("dismiss popover", () => + { + InputManager.Key(Key.Escape); + }); + AddUntilStep("wait for dismiss", () => !this.ChildrenOfType().Any()); + } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 04e9ac842c..d5b0ab19aa 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -214,7 +214,7 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - private class DivisorDisplay : OsuAnimatedButton, IHasPopover + internal class DivisorDisplay : OsuAnimatedButton, IHasPopover { public BindableBeatDivisor BeatDivisor { get; } = new BindableBeatDivisor(); @@ -264,7 +264,7 @@ namespace osu.Game.Screens.Edit.Compose.Components }; } - private class CustomDivisorPopover : OsuPopover + internal class CustomDivisorPopover : OsuPopover { public BindableBeatDivisor BeatDivisor { get; } = new BindableBeatDivisor(); @@ -347,7 +347,7 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - private class ChevronButton : IconButton + internal class ChevronButton : IconButton { public ChevronButton() { From a5600516f0200a8e596fa2011ba488106259bc1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 27 Feb 2022 20:13:44 +0100 Subject: [PATCH 0848/5199] Fix test failures --- .../Visual/Editing/TestSceneBeatDivisorControl.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs index 4503b1a5f6..94c0822235 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs @@ -43,10 +43,10 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestBindableBeatDivisor() { - AddRepeatStep("move previous", () => bindableBeatDivisor.Previous(), 4); + AddRepeatStep("move previous", () => bindableBeatDivisor.Previous(), 2); AddAssert("divisor is 4", () => bindableBeatDivisor.Value == 4); - AddRepeatStep("move next", () => bindableBeatDivisor.Next(), 3); - AddAssert("divisor is 12", () => bindableBeatDivisor.Value == 12); + AddRepeatStep("move next", () => bindableBeatDivisor.Next(), 1); + AddAssert("divisor is 12", () => bindableBeatDivisor.Value == 8); } [Test] From 2e04a83554b982d3468e3692dbd70bef36e6c46a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 19 Feb 2022 17:52:16 +0100 Subject: [PATCH 0849/5199] Implement column display for new mod design --- .../UserInterface/TestSceneModColumn.cs | 48 +++++ osu.Game/Overlays/Mods/ModColumn.cs | 196 ++++++++++++++++++ osu.Game/Overlays/Mods/ModPanel.cs | 4 +- 3 files changed, 246 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs create mode 100644 osu.Game/Overlays/Mods/ModColumn.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs new file mode 100644 index 0000000000..59641ae000 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; +using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Taiko; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestSceneModColumn : OsuTestScene + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + + [TestCase(ModType.DifficultyReduction)] + [TestCase(ModType.DifficultyIncrease)] + [TestCase(ModType.Conversion)] + [TestCase(ModType.Automation)] + [TestCase(ModType.Fun)] + public void TestBasic(ModType modType) + { + AddStep("create content", () => Child = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(30), + Child = new ModColumn(modType) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + } + }); + + AddStep("change ruleset to osu!", () => Ruleset.Value = new OsuRuleset().RulesetInfo); + AddStep("change ruleset to taiko", () => Ruleset.Value = new TaikoRuleset().RulesetInfo); + AddStep("change ruleset to catch", () => Ruleset.Value = new CatchRuleset().RulesetInfo); + AddStep("change ruleset to mania", () => Ruleset.Value = new ManiaRuleset().RulesetInfo); + } + } +} diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs new file mode 100644 index 0000000000..03ef3b889a --- /dev/null +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -0,0 +1,196 @@ +// 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 System.Threading; +using Humanizer; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Rulesets.Mods; +using osu.Game.Utils; +using osuTK; + +#nullable enable + +namespace osu.Game.Overlays.Mods +{ + public class ModColumn : CompositeDrawable + { + private readonly ModType modType; + + private readonly Bindable>> availableMods = new Bindable>>(); + + private readonly TextFlowContainer headerText; + private readonly Box headerBackground; + private readonly Container contentContainer; + private readonly Box contentBackground; + private readonly FillFlowContainer panelFlow; + + private Colour4 accentColour; + + private const float header_height = 60; + + public ModColumn(ModType modType) + { + this.modType = modType; + + Width = 450; + RelativeSizeAxes = Axes.Y; + Shear = new Vector2(ModPanel.SHEAR_X, 0); + CornerRadius = ModPanel.CORNER_RADIUS; + Masking = true; + + InternalChildren = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + Height = header_height + ModPanel.CORNER_RADIUS, + Children = new Drawable[] + { + headerBackground = new Box + { + RelativeSizeAxes = Axes.X, + Height = header_height + ModPanel.CORNER_RADIUS + }, + headerText = new OsuTextFlowContainer(t => + { + t.Font = OsuFont.TorusAlternate.With(size: 24); + t.Shadow = false; + t.Colour = Colour4.Black; + }) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Shear = new Vector2(-ModPanel.SHEAR_X, 0), + Padding = new MarginPadding + { + Horizontal = 15, + Bottom = ModPanel.CORNER_RADIUS + } + } + } + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = header_height }, + Child = contentContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = ModPanel.CORNER_RADIUS, + BorderThickness = 4, + Children = new Drawable[] + { + contentBackground = new Box + { + RelativeSizeAxes = Axes.Both + }, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 50), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 10) + }, + Content = new[] + { + new[] + { + Empty() + }, + new Drawable[] + { + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + ScrollbarOverlapsContent = false, + Child = panelFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0, 10), + Padding = new MarginPadding + { + Horizontal = 10 + }, + } + } + }, + new[] { Empty() } + } + } + } + } + } + }; + + createHeaderText(); + } + + private void createHeaderText() + { + IEnumerable headerTextWords = modType.Humanize(LetterCasing.Title).Split(' '); + + if (headerTextWords.Count() > 1) + { + headerText.AddText($"{headerTextWords.First()} ", t => t.Font = t.Font.With(weight: FontWeight.SemiBold)); + headerTextWords = headerTextWords.Skip(1); + } + + headerText.AddText(string.Join(' ', headerTextWords)); + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase game, OverlayColourProvider colourProvider, OsuColour colours) + { + availableMods.BindTo(game.AvailableMods); + + headerBackground.Colour = accentColour = colours.ForModType(modType); + + contentContainer.BorderColour = ColourInfo.GradientVertical(colourProvider.Background4, colourProvider.Background3); + contentBackground.Colour = colourProvider.Background4; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + availableMods.BindValueChanged(_ => Scheduler.AddOnce(updateMods), true); + } + + private CancellationTokenSource? cancellationTokenSource; + + private void updateMods() + { + var newMods = ModUtils.FlattenMods(availableMods.Value.GetValueOrDefault(modType) ?? Array.Empty()).ToList(); + + if (newMods.SequenceEqual(panelFlow.Children.Select(p => p.Mod))) + return; + + cancellationTokenSource?.Cancel(); + + var panels = newMods.Select(mod => new ModPanel(mod) + { + Shear = new Vector2(-ModPanel.SHEAR_X, 0) + }); + + LoadComponentsAsync(panels, loaded => + { + panelFlow.ChildrenEnumerable = loaded; + }, (cancellationTokenSource = new CancellationTokenSource()).Token); + } + } +} diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index af8ad3eb18..446ebe12c5 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -40,10 +40,10 @@ namespace osu.Game.Overlays.Mods protected const double TRANSITION_DURATION = 150; - protected const float SHEAR_X = 0.2f; + public const float SHEAR_X = 0.2f; + public const float CORNER_RADIUS = 7; protected const float HEIGHT = 42; - protected const float CORNER_RADIUS = 7; protected const float IDLE_SWITCH_WIDTH = 54; protected const float EXPANDED_SWITCH_WIDTH = 70; From f40bd394871ddff25ef2a50827500663c747df8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 19 Feb 2022 18:33:13 +0100 Subject: [PATCH 0850/5199] Add toggle all checkbox to column display --- osu.Game/Overlays/Mods/ModColumn.cs | 85 ++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 03ef3b889a..a42b2bcb96 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -8,15 +8,19 @@ using System.Threading; using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Mods; using osu.Game.Utils; using osuTK; +using osuTK.Graphics; #nullable enable @@ -33,12 +37,13 @@ namespace osu.Game.Overlays.Mods private readonly Container contentContainer; private readonly Box contentBackground; private readonly FillFlowContainer panelFlow; + private readonly ToggleAllCheckbox? toggleAllCheckbox; private Colour4 accentColour; private const float header_height = 60; - public ModColumn(ModType modType) + public ModColumn(ModType modType, bool allowBulkSelection) { this.modType = modType; @@ -48,6 +53,7 @@ namespace osu.Game.Overlays.Mods CornerRadius = ModPanel.CORNER_RADIUS; Masking = true; + Container controlContainer; InternalChildren = new Drawable[] { new Container @@ -108,9 +114,13 @@ namespace osu.Game.Overlays.Mods }, Content = new[] { - new[] + new Drawable[] { - Empty() + controlContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = 20 } + } }, new Drawable[] { @@ -139,6 +149,18 @@ namespace osu.Game.Overlays.Mods }; createHeaderText(); + + if (allowBulkSelection) + { + controlContainer.Add(toggleAllCheckbox = new ToggleAllCheckbox + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + LabelText = "Enable All", + Shear = new Vector2(-ModPanel.SHEAR_X, 0) + }); + } } private void createHeaderText() @@ -161,6 +183,12 @@ namespace osu.Game.Overlays.Mods headerBackground.Colour = accentColour = colours.ForModType(modType); + if (toggleAllCheckbox != null) + { + toggleAllCheckbox.AccentColour = accentColour; + toggleAllCheckbox.AccentHoverColour = accentColour.Lighten(0.3f); + } + contentContainer.BorderColour = ColourInfo.GradientVertical(colourProvider.Background4, colourProvider.Background3); contentBackground.Colour = colourProvider.Background4; } @@ -192,5 +220,56 @@ namespace osu.Game.Overlays.Mods panelFlow.ChildrenEnumerable = loaded; }, (cancellationTokenSource = new CancellationTokenSource()).Token); } + + private class ToggleAllCheckbox : OsuCheckbox + { + private Color4 accentColour; + + public Color4 AccentColour + { + get => accentColour; + set + { + accentColour = value; + updateState(); + } + } + + private Color4 accentHoverColour; + + public Color4 AccentHoverColour + { + get => accentHoverColour; + set + { + accentHoverColour = value; + updateState(); + } + } + + public ToggleAllCheckbox() + : base(false) + { + } + + protected override void ApplyLabelParameters(SpriteText text) + { + base.ApplyLabelParameters(text); + text.Font = text.Font.With(weight: FontWeight.SemiBold); + } + + [BackgroundDependencyLoader] + private void load() + { + updateState(); + } + + private void updateState() + { + Nub.AccentColour = AccentColour; + Nub.GlowingAccentColour = AccentHoverColour; + Nub.GlowColour = AccentHoverColour.Opacity(0.2f); + } + } } } From 53e8072632ae3302ec6ee56c0f45e2fddecf7946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 19 Feb 2022 18:45:04 +0100 Subject: [PATCH 0851/5199] Port multiselection from previous design --- .../UserInterface/TestSceneModColumn.cs | 17 +++- osu.Game/Overlays/Mods/ModColumn.cs | 81 ++++++++++++++++++- 2 files changed, 95 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index 59641ae000..e58649d989 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.UserInterface { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(30), - Child = new ModColumn(modType) + Child = new ModColumn(modType, false) { Anchor = Anchor.Centre, Origin = Anchor.Centre @@ -44,5 +44,20 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("change ruleset to catch", () => Ruleset.Value = new CatchRuleset().RulesetInfo); AddStep("change ruleset to mania", () => Ruleset.Value = new ManiaRuleset().RulesetInfo); } + + [Test] + public void TestMultiSelection() + { + AddStep("create content", () => Child = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(30), + Child = new ModColumn(ModType.DifficultyIncrease, true) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + } + }); + } } } diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index a42b2bcb96..8ca968368e 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -152,7 +152,8 @@ namespace osu.Game.Overlays.Mods if (allowBulkSelection) { - controlContainer.Add(toggleAllCheckbox = new ToggleAllCheckbox + controlContainer.Height = 50; + controlContainer.Add(toggleAllCheckbox = new ToggleAllCheckbox(this) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -218,9 +219,72 @@ namespace osu.Game.Overlays.Mods LoadComponentsAsync(panels, loaded => { panelFlow.ChildrenEnumerable = loaded; + foreach (var panel in panelFlow) + panel.Active.BindValueChanged(_ => updateToggleState()); + updateToggleState(); }, (cancellationTokenSource = new CancellationTokenSource()).Token); } + #region Bulk select / deselect + + private const double initial_multiple_selection_delay = 120; + + private double selectionDelay = initial_multiple_selection_delay; + private double lastSelection; + + private readonly Queue pendingSelectionOperations = new Queue(); + + protected override void Update() + { + base.Update(); + + if (selectionDelay == initial_multiple_selection_delay || Time.Current - lastSelection >= selectionDelay) + { + if (pendingSelectionOperations.TryDequeue(out var dequeuedAction)) + { + dequeuedAction(); + + // each time we play an animation, we decrease the time until the next animation (to ramp the visual and audible elements). + selectionDelay = Math.Max(30, selectionDelay * 0.8f); + lastSelection = Time.Current; + } + else + { + // reset the selection delay after all animations have been completed. + // this will cause the next action to be immediately performed. + selectionDelay = initial_multiple_selection_delay; + } + } + } + + private void updateToggleState() + { + if (toggleAllCheckbox != null && pendingSelectionOperations.Count == 0) + toggleAllCheckbox.Current.Value = panelFlow.All(panel => panel.Active.Value); + } + + /// + /// Selects all mods. + /// + public void SelectAll() + { + pendingSelectionOperations.Clear(); + + foreach (var button in panelFlow.Where(b => !b.Active.Value)) + pendingSelectionOperations.Enqueue(() => button.Active.Value = true); + } + + /// + /// Deselects all mods. + /// + public void DeselectAll() + { + pendingSelectionOperations.Clear(); + + foreach (var button in panelFlow.Where(b => b.Active.Value)) + pendingSelectionOperations.Enqueue(() => button.Active.Value = false); + } + private class ToggleAllCheckbox : OsuCheckbox { private Color4 accentColour; @@ -247,9 +311,12 @@ namespace osu.Game.Overlays.Mods } } - public ToggleAllCheckbox() + private readonly ModColumn column; + + public ToggleAllCheckbox(ModColumn column) : base(false) { + this.column = column; } protected override void ApplyLabelParameters(SpriteText text) @@ -270,6 +337,16 @@ namespace osu.Game.Overlays.Mods Nub.GlowingAccentColour = AccentHoverColour; Nub.GlowColour = AccentHoverColour.Opacity(0.2f); } + + protected override void OnUserChange(bool value) + { + if (value) + column.SelectAll(); + else + column.DeselectAll(); + } } + + #endregion } } From a80b4334ff841c5e09662159589b9354c39f40ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Feb 2022 12:18:59 +0100 Subject: [PATCH 0852/5199] Tweak layout of column display for better spacing --- osu.Game/Overlays/Mods/ModColumn.cs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 8ca968368e..15b2874edb 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -108,9 +108,8 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.Both, RowDimensions = new[] { - new Dimension(GridSizeMode.Absolute, 50), - new Dimension(), - new Dimension(GridSizeMode.Absolute, 10) + new Dimension(GridSizeMode.AutoSize), + new Dimension() }, Content = new[] { @@ -118,7 +117,7 @@ namespace osu.Game.Overlays.Mods { controlContainer = new Container { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, Padding = new MarginPadding { Horizontal = 20 } } }, @@ -133,14 +132,10 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Spacing = new Vector2(0, 10), - Padding = new MarginPadding - { - Horizontal = 10 - }, + Padding = new MarginPadding(10) } } - }, - new[] { Empty() } + } } } } @@ -161,6 +156,12 @@ namespace osu.Game.Overlays.Mods LabelText = "Enable All", Shear = new Vector2(-ModPanel.SHEAR_X, 0) }); + panelFlow.Padding = new MarginPadding + { + Top = 0, + Bottom = 10, + Horizontal = 10 + }; } } From fe4e4bf9c5295d04bfcbac3e38e9127b3855b0b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Feb 2022 13:09:06 +0100 Subject: [PATCH 0853/5199] Add test coverage of multiselection behaviour --- .../UserInterface/TestSceneModColumn.cs | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index e58649d989..0401d516ed 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -1,10 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Catch; @@ -12,11 +16,12 @@ using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Taiko; +using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestSceneModColumn : OsuTestScene + public class TestSceneModColumn : OsuManualInputManagerTestScene { [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); @@ -58,6 +63,25 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.Centre } }); + + clickToggle(); + AddUntilStep("all panels selected", () => this.ChildrenOfType().All(panel => panel.Active.Value)); + + clickToggle(); + AddUntilStep("all panels deselected", () => this.ChildrenOfType().All(panel => !panel.Active.Value)); + + AddStep("manually activate all panels", () => this.ChildrenOfType().ForEach(panel => panel.Active.Value = true)); + AddUntilStep("checkbox selected", () => this.ChildrenOfType().Single().Current.Value); + + AddStep("deselect first panel", () => this.ChildrenOfType().First().Active.Value = false); + AddUntilStep("checkbox selected", () => !this.ChildrenOfType().Single().Current.Value); + + void clickToggle() => AddStep("click toggle", () => + { + var checkbox = this.ChildrenOfType().Single(); + InputManager.MoveMouseTo(checkbox); + InputManager.Click(MouseButton.Left); + }); } } } From a83f96b026a9d5657d60eda44b594ef090014d6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Feb 2022 13:40:52 +0100 Subject: [PATCH 0854/5199] Add filtering support to mod column --- .../UserInterface/TestSceneModColumn.cs | 50 +++++++++++++++++++ osu.Game/Overlays/Mods/ModColumn.cs | 37 ++++++++++++-- osu.Game/Overlays/Mods/ModPanel.cs | 17 +++++++ 3 files changed, 100 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index 0401d516ed..71a974da7d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.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.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -83,5 +84,54 @@ namespace osu.Game.Tests.Visual.UserInterface InputManager.Click(MouseButton.Left); }); } + + [Test] + public void TestFiltering() + { + TestModColumn column = null; + + AddStep("create content", () => Child = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(30), + Child = column = new TestModColumn(ModType.Fun, true) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + } + }); + + AddStep("set filter", () => column.Filter = mod => mod.Name.Contains("Wind", StringComparison.CurrentCultureIgnoreCase)); + AddUntilStep("two panels visible", () => column.ChildrenOfType().Count(panel => !panel.Filtered.Value) == 2); + + clickToggle(); + AddUntilStep("wait for animation", () => !column.SelectionAnimationRunning); + AddAssert("only visible items selected", () => column.ChildrenOfType().Where(panel => panel.Active.Value).All(panel => !panel.Filtered.Value)); + + AddStep("unset filter", () => column.Filter = null); + AddUntilStep("all panels visible", () => column.ChildrenOfType().All(panel => !panel.Filtered.Value)); + AddAssert("checkbox not selected", () => !column.ChildrenOfType().Single().Current.Value); + + AddStep("set filter", () => column.Filter = mod => mod.Name.Contains("Wind", StringComparison.CurrentCultureIgnoreCase)); + AddUntilStep("two panels visible", () => column.ChildrenOfType().Count(panel => !panel.Filtered.Value) == 2); + AddAssert("checkbox selected", () => column.ChildrenOfType().Single().Current.Value); + + void clickToggle() => AddStep("click toggle", () => + { + var checkbox = this.ChildrenOfType().Single(); + InputManager.MoveMouseTo(checkbox); + InputManager.Click(MouseButton.Left); + }); + } + + private class TestModColumn : ModColumn + { + public new bool SelectionAnimationRunning => base.SelectionAnimationRunning; + + public TestModColumn(ModType modType, bool allowBulkSelection) + : base(modType, allowBulkSelection) + { + } + } } } diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 15b2874edb..eae57f3574 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -28,6 +28,18 @@ namespace osu.Game.Overlays.Mods { public class ModColumn : CompositeDrawable { + private Func? filter; + + public Func? Filter + { + get => filter; + set + { + filter = value; + updateFilter(); + } + } + private readonly ModType modType; private readonly Bindable>> availableMods = new Bindable>>(); @@ -220,9 +232,12 @@ namespace osu.Game.Overlays.Mods LoadComponentsAsync(panels, loaded => { panelFlow.ChildrenEnumerable = loaded; + foreach (var panel in panelFlow) panel.Active.BindValueChanged(_ => updateToggleState()); updateToggleState(); + + updateFilter(); }, (cancellationTokenSource = new CancellationTokenSource()).Token); } @@ -235,6 +250,8 @@ namespace osu.Game.Overlays.Mods private readonly Queue pendingSelectionOperations = new Queue(); + protected bool SelectionAnimationRunning => pendingSelectionOperations.Count > 0; + protected override void Update() { base.Update(); @@ -260,8 +277,8 @@ namespace osu.Game.Overlays.Mods private void updateToggleState() { - if (toggleAllCheckbox != null && pendingSelectionOperations.Count == 0) - toggleAllCheckbox.Current.Value = panelFlow.All(panel => panel.Active.Value); + if (toggleAllCheckbox != null && !SelectionAnimationRunning) + toggleAllCheckbox.Current.Value = panelFlow.Where(panel => !panel.Filtered.Value).All(panel => panel.Active.Value); } /// @@ -271,7 +288,7 @@ namespace osu.Game.Overlays.Mods { pendingSelectionOperations.Clear(); - foreach (var button in panelFlow.Where(b => !b.Active.Value)) + foreach (var button in panelFlow.Where(b => !b.Active.Value && !b.Filtered.Value)) pendingSelectionOperations.Enqueue(() => button.Active.Value = true); } @@ -282,7 +299,7 @@ namespace osu.Game.Overlays.Mods { pendingSelectionOperations.Clear(); - foreach (var button in panelFlow.Where(b => b.Active.Value)) + foreach (var button in panelFlow.Where(b => b.Active.Value && !b.Filtered.Value)) pendingSelectionOperations.Enqueue(() => button.Active.Value = false); } @@ -349,5 +366,17 @@ namespace osu.Game.Overlays.Mods } #endregion + + #region Filtering support + + private void updateFilter() + { + foreach (var modPanel in panelFlow) + modPanel.ApplyFilter(Filter); + + updateToggleState(); + } + + #endregion } } diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index 446ebe12c5..7e4d19850d 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.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 osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -28,6 +29,7 @@ namespace osu.Game.Overlays.Mods { public Mod Mod { get; } public BindableBool Active { get; } = new BindableBool(); + public BindableBool Filtered { get; } = new BindableBool(); protected readonly Box Background; protected readonly Container SwitchContainer; @@ -157,6 +159,7 @@ namespace osu.Game.Overlays.Mods playStateChangeSamples(); UpdateState(); }); + Filtered.BindValueChanged(_ => updateFilterState()); UpdateState(); FinishTransforms(true); @@ -235,5 +238,19 @@ namespace osu.Game.Overlays.Mods TextBackground.FadeColour(textBackgroundColour, transitionDuration, Easing.OutQuint); TextFlow.FadeColour(textColour, transitionDuration, Easing.OutQuint); } + + #region Filtering support + + public void ApplyFilter(Func? filter) + { + Filtered.Value = filter != null && !filter.Invoke(Mod); + } + + private void updateFilterState() + { + this.FadeTo(Filtered.Value ? 0 : 1); + } + + #endregion } } From b690df05de195561d8d691d476982323087aee93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Feb 2022 14:14:19 +0100 Subject: [PATCH 0855/5199] Hide multiselection checkbox if everything is filtered --- osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs | 8 ++++++++ osu.Game/Overlays/Mods/ModColumn.cs | 3 +++ 2 files changed, 11 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index 71a974da7d..534e9e0144 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -116,6 +116,14 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("two panels visible", () => column.ChildrenOfType().Count(panel => !panel.Filtered.Value) == 2); AddAssert("checkbox selected", () => column.ChildrenOfType().Single().Current.Value); + AddStep("filter out everything", () => column.Filter = _ => false); + AddUntilStep("no panels visible", () => column.ChildrenOfType().All(panel => panel.Filtered.Value)); + AddUntilStep("checkbox hidden", () => !column.ChildrenOfType().Single().IsPresent); + + AddStep("inset filter", () => column.Filter = null); + AddUntilStep("no panels visible", () => column.ChildrenOfType().All(panel => !panel.Filtered.Value)); + AddUntilStep("checkbox hidden", () => column.ChildrenOfType().Single().IsPresent); + void clickToggle() => AddStep("click toggle", () => { var checkbox = this.ChildrenOfType().Single(); diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index eae57f3574..649f54a96a 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -278,7 +278,10 @@ namespace osu.Game.Overlays.Mods private void updateToggleState() { if (toggleAllCheckbox != null && !SelectionAnimationRunning) + { + toggleAllCheckbox.Alpha = panelFlow.Any(panel => !panel.Filtered.Value) ? 1 : 0; toggleAllCheckbox.Current.Value = panelFlow.Where(panel => !panel.Filtered.Value).All(panel => panel.Active.Value); + } } /// From 16c6b9b3b315096d0a8be6638aaac445a475f124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Feb 2022 14:10:35 +0100 Subject: [PATCH 0856/5199] Add keyboard selection support to mod column --- .../UserInterface/TestSceneModColumn.cs | 37 +++++++++++++++++++ osu.Game/Overlays/Mods/ModColumn.cs | 25 ++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index 534e9e0144..01ef4b4b60 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -132,6 +132,43 @@ namespace osu.Game.Tests.Visual.UserInterface }); } + [Test] + public void TestKeyboardSelection() + { + ModColumn column = null; + AddStep("create content", () => Child = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(30), + Child = column = new ModColumn(ModType.DifficultyReduction, true, new Key[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + } + }); + + AddStep("press W", () => InputManager.Key(Key.W)); + AddAssert("NF panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NF").Active.Value); + + AddStep("press W again", () => InputManager.Key(Key.W)); + AddAssert("NF panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NF").Active.Value); + + AddStep("set filter to NF", () => column.Filter = mod => mod.Acronym == "NF"); + + AddStep("press W", () => InputManager.Key(Key.W)); + AddAssert("NF panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NF").Active.Value); + + AddStep("press W again", () => InputManager.Key(Key.W)); + AddAssert("NF panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NF").Active.Value); + + AddStep("filter out everything", () => column.Filter = _ => false); + + AddStep("press W", () => InputManager.Key(Key.W)); + AddAssert("NF panel not selected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NF").Active.Value); + + AddStep("clear filter", () => column.Filter = null); + } + private class TestModColumn : ModColumn { public new bool SelectionAnimationRunning => base.SelectionAnimationRunning; diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 649f54a96a..dd3b7274fa 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -21,6 +22,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Utils; using osuTK; using osuTK.Graphics; +using osuTK.Input; #nullable enable @@ -41,6 +43,7 @@ namespace osu.Game.Overlays.Mods } private readonly ModType modType; + private readonly Key[]? toggleKeys; private readonly Bindable>> availableMods = new Bindable>>(); @@ -55,9 +58,10 @@ namespace osu.Game.Overlays.Mods private const float header_height = 60; - public ModColumn(ModType modType, bool allowBulkSelection) + public ModColumn(ModType modType, bool allowBulkSelection, Key[]? toggleKeys = null) { this.modType = modType; + this.toggleKeys = toggleKeys; Width = 450; RelativeSizeAxes = Axes.Y; @@ -381,5 +385,24 @@ namespace osu.Game.Overlays.Mods } #endregion + + #region Keyboard selection support + + protected override bool OnKeyDown(KeyDownEvent e) + { + if (e.ControlPressed || e.AltPressed) return false; + if (toggleKeys == null) return false; + + int index = Array.IndexOf(toggleKeys, e.Key); + if (index < 0) return false; + + var panel = panelFlow.ElementAtOrDefault(index); + if (panel == null || panel.Filtered.Value) return false; + + panel.Active.Toggle(); + return true; + } + + #endregion } } From 774952addaf2c2f65a59b470a8c3475cd0024f22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 27 Feb 2022 23:08:31 +0100 Subject: [PATCH 0857/5199] Rescale components from figma to real dimensions --- osu.Game/Overlays/Mods/ModColumn.cs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index dd3b7274fa..7f3cc8249f 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -56,14 +56,14 @@ namespace osu.Game.Overlays.Mods private Colour4 accentColour; - private const float header_height = 60; + private const float header_height = 42; public ModColumn(ModType modType, bool allowBulkSelection, Key[]? toggleKeys = null) { this.modType = modType; this.toggleKeys = toggleKeys; - Width = 450; + Width = 320; RelativeSizeAxes = Axes.Y; Shear = new Vector2(ModPanel.SHEAR_X, 0); CornerRadius = ModPanel.CORNER_RADIUS; @@ -85,7 +85,7 @@ namespace osu.Game.Overlays.Mods }, headerText = new OsuTextFlowContainer(t => { - t.Font = OsuFont.TorusAlternate.With(size: 24); + t.Font = OsuFont.TorusAlternate.With(size: 17); t.Shadow = false; t.Colour = Colour4.Black; }) @@ -97,7 +97,7 @@ namespace osu.Game.Overlays.Mods Shear = new Vector2(-ModPanel.SHEAR_X, 0), Padding = new MarginPadding { - Horizontal = 15, + Horizontal = 17, Bottom = ModPanel.CORNER_RADIUS } } @@ -112,7 +112,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.Both, Masking = true, CornerRadius = ModPanel.CORNER_RADIUS, - BorderThickness = 4, + BorderThickness = 3, Children = new Drawable[] { contentBackground = new Box @@ -134,7 +134,7 @@ namespace osu.Game.Overlays.Mods controlContainer = new Container { RelativeSizeAxes = Axes.X, - Padding = new MarginPadding { Horizontal = 20 } + Padding = new MarginPadding { Horizontal = 14 } } }, new Drawable[] @@ -147,8 +147,8 @@ namespace osu.Game.Overlays.Mods { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0, 10), - Padding = new MarginPadding(10) + Spacing = new Vector2(0, 7), + Padding = new MarginPadding(7) } } } @@ -163,11 +163,12 @@ namespace osu.Game.Overlays.Mods if (allowBulkSelection) { - controlContainer.Height = 50; + controlContainer.Height = 35; controlContainer.Add(toggleAllCheckbox = new ToggleAllCheckbox(this) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, + Scale = new Vector2(0.8f), RelativeSizeAxes = Axes.X, LabelText = "Enable All", Shear = new Vector2(-ModPanel.SHEAR_X, 0) @@ -175,8 +176,8 @@ namespace osu.Game.Overlays.Mods panelFlow.Padding = new MarginPadding { Top = 0, - Bottom = 10, - Horizontal = 10 + Bottom = 7, + Horizontal = 7 }; } } From a1786f62d71999fc9b8991d3c43161932a0eeeb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 27 Feb 2022 23:10:22 +0100 Subject: [PATCH 0858/5199] Fix test failure due to attempting to set non-present divisor With the latest changes permitting fully custom beat snapping, the 1/3 snap divisor isn't immediately available in editor, requiring a switch to "triplets" mode first. --- .../Editor/TestSceneSliderStreamConversion.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs index 559d612037..70a9c03e65 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs @@ -7,6 +7,7 @@ using osu.Framework.Utils; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose.Components; using osuTK; using osuTK.Input; @@ -72,7 +73,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor EditorClock.Seek(slider.StartTime); EditorBeatmap.SelectedHitObjects.Add(slider); }); - AddStep("change beat divisor", () => beatDivisor.Value = 3); + AddStep("change beat divisor", () => + { + beatDivisor.ValidDivisors.Value = BeatDivisorPresetCollection.TRIPLETS; + beatDivisor.Value = 3; + }); convertToStream(); From fe63a09a0f2bb4a640ad92fad039c1f6a1fc323e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 28 Feb 2022 11:59:10 +0900 Subject: [PATCH 0859/5199] Fix missing dependency in test --- osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs b/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs index 868946d80f..b6004c651b 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs @@ -2,16 +2,21 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps.Legacy; using osu.Game.Tests.Visual; using osu.Game.Tournament.Components; +using osu.Game.Tournament.Models; namespace osu.Game.Tournament.Tests.Components { [TestFixture] public class TestSceneSongBar : OsuTestScene { + [Cached] + private readonly LadderInfo ladder = new LadderInfo(); + [Test] public void TestSongBar() { From 4a555d067da4804d307b74c7bfd90fb7fbb6635c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Feb 2022 14:32:50 +0900 Subject: [PATCH 0860/5199] Change `ModPanel` to not handle `OnMouseDown` to allow drag scrolling in `ModColumn` --- osu.Game/Overlays/Mods/ModPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index 7e4d19850d..312171cf74 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -193,7 +193,7 @@ namespace osu.Game.Overlays.Mods mouseDown = true; UpdateState(); - return true; + return false; } protected override void OnMouseUp(MouseUpEvent e) From 3634e12e66ff499831a441d5ff27f7ceab1e8627 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Feb 2022 15:21:01 +0900 Subject: [PATCH 0861/5199] Automatically focus divisor textbox and hide popover after successful change --- osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs | 4 ---- .../Screens/Edit/Compose/Components/BeatDivisorControl.cs | 5 +++++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs index 94c0822235..6a0950c6dd 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs @@ -190,10 +190,6 @@ namespace osu.Game.Tests.Visual.Editing textBox.Text = divisor.ToString(); InputManager.Key(Key.Enter); }); - AddStep("dismiss popover", () => - { - InputManager.Key(Key.Escape); - }); AddUntilStep("wait for dismiss", () => !this.ChildrenOfType().Any()); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index d5b0ab19aa..c65423ef9f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -16,6 +16,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -299,6 +300,8 @@ namespace osu.Game.Screens.Edit.Compose.Components base.LoadComplete(); BeatDivisor.BindValueChanged(_ => updateState(), true); divisorTextBox.OnCommit += (_, __) => setPresets(); + + Schedule(() => GetContainingInputManager().ChangeFocus(divisorTextBox)); } private void setPresets() @@ -320,6 +323,8 @@ namespace osu.Game.Screens.Edit.Compose.Components } BeatDivisor.Value = divisor; + + this.HidePopover(); } private void updateState() From 368eadd8d1f02ac7263b093835cb4d98d69ec27b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Feb 2022 15:24:02 +0900 Subject: [PATCH 0862/5199] Remove unused using statement --- osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index c65423ef9f..bea72b7447 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -16,7 +16,6 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; From 2be40f36f77c81501d5530ff136908111ce3ddb8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Feb 2022 15:26:09 +0900 Subject: [PATCH 0863/5199] Reword popup text to read better (or more vaguely) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed some words but also don't mention "smaller" because it's... musically incorrect and also functionally incorrect – entering 1/[8] will result in 1/16 also being populated for instance. --- osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index bea72b7447..370c9016c7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -288,7 +288,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Text = "All other applicable smaller divisors will be automatically added to the list of presets." + Text = "Related divisors will be added to the list of presets." } } }; From 2e96f74c94a008e62a523e9ca15cf0b21ba05a71 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Feb 2022 16:37:14 +0900 Subject: [PATCH 0864/5199] Allow `LegacyScoreEncoder` to be used without a beatmap if frames are already legacy frames --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 9460ec680c..14a6495282 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -5,9 +5,11 @@ using System; using System.IO; using System.Linq; using System.Text; +using JetBrains.Annotations; using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.IO.Legacy; +using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Replays.Types; using SharpCompress.Compressors.LZMA; @@ -29,12 +31,21 @@ namespace osu.Game.Scoring.Legacy private readonly Score score; private readonly IBeatmap beatmap; - public LegacyScoreEncoder(Score score, IBeatmap beatmap) + /// + /// Create a new score encoder for a specific score. + /// + /// The score to be encoded. + /// The beatmap used to convert frames for the score. May be null if the frames are already s. + /// + public LegacyScoreEncoder(Score score, [CanBeNull] IBeatmap beatmap) { this.score = score; this.beatmap = beatmap; - if (score.ScoreInfo.BeatmapInfo.Ruleset.OnlineID < 0 || score.ScoreInfo.BeatmapInfo.Ruleset.OnlineID > 3) + if (beatmap == null && !score.Replay.Frames.All(f => f is LegacyReplayFrame)) + throw new ArgumentException("Beatmap must be provided if frames are not already legacy frames.", nameof(beatmap)); + + if (score.ScoreInfo.Ruleset.OnlineID < 0 || score.ScoreInfo.Ruleset.OnlineID > 3) throw new ArgumentException("Only scores in the osu, taiko, catch, or mania rulesets can be encoded to the legacy score format.", nameof(score)); } From 723e96309ae8b75b3a89a940ddebeef8e0ea45cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Feb 2022 16:40:00 +0900 Subject: [PATCH 0865/5199] Only convert non-legacy frames (and throw on conversion failure) --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 14a6495282..3355efc830 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -10,6 +10,7 @@ using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.IO.Legacy; using osu.Game.Replays.Legacy; +using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; using SharpCompress.Compressors.LZMA; @@ -112,11 +113,16 @@ namespace osu.Game.Scoring.Legacy { int lastTime = 0; - foreach (var f in score.Replay.Frames.OfType().Select(f => f.ToLegacy(beatmap))) + foreach (var f in score.Replay.Frames) { + var legacyFrame = getLegacyFrame(f); + + if (legacyFrame == null) + throw new ArgumentException("One or more frames could not be converted to legacy frames"); + // Rounding because stable could only parse integral values - int time = (int)Math.Round(f.Time); - replayData.Append(FormattableString.Invariant($"{time - lastTime}|{f.MouseX ?? 0}|{f.MouseY ?? 0}|{(int)f.ButtonState},")); + int time = (int)Math.Round(legacyFrame.Time); + replayData.Append(FormattableString.Invariant($"{time - lastTime}|{legacyFrame.MouseX ?? 0}|{legacyFrame.MouseY ?? 0}|{(int)legacyFrame.ButtonState},")); lastTime = time; } } @@ -128,6 +134,14 @@ namespace osu.Game.Scoring.Legacy } } + private LegacyReplayFrame getLegacyFrame(ReplayFrame replayFrame) + { + if (replayFrame is LegacyReplayFrame legacyFrame) + return legacyFrame; + + return (replayFrame as IConvertibleReplayFrame)?.ToLegacy(beatmap); + } + private string getHpGraphFormatted() { // todo: implement, maybe? From 52e50db6b94ca11aa8df4ca2b14dbbb39c68ac47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Feb 2022 16:41:39 +0900 Subject: [PATCH 0866/5199] Enable `nullable` for `LegacyScoreEncoder` --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 3355efc830..1e39b66ead 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -5,7 +5,6 @@ using System; using System.IO; using System.Linq; using System.Text; -using JetBrains.Annotations; using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.IO.Legacy; @@ -14,6 +13,8 @@ using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; using SharpCompress.Compressors.LZMA; +#nullable enable + namespace osu.Game.Scoring.Legacy { public class LegacyScoreEncoder @@ -30,7 +31,7 @@ namespace osu.Game.Scoring.Legacy public const int FIRST_LAZER_VERSION = 30000000; private readonly Score score; - private readonly IBeatmap beatmap; + private readonly IBeatmap? beatmap; /// /// Create a new score encoder for a specific score. @@ -38,16 +39,16 @@ namespace osu.Game.Scoring.Legacy /// The score to be encoded. /// The beatmap used to convert frames for the score. May be null if the frames are already s. /// - public LegacyScoreEncoder(Score score, [CanBeNull] IBeatmap beatmap) + public LegacyScoreEncoder(Score score, IBeatmap? beatmap) { this.score = score; this.beatmap = beatmap; if (beatmap == null && !score.Replay.Frames.All(f => f is LegacyReplayFrame)) - throw new ArgumentException("Beatmap must be provided if frames are not already legacy frames.", nameof(beatmap)); + throw new ArgumentException(@"Beatmap must be provided if frames are not already legacy frames.", nameof(beatmap)); if (score.ScoreInfo.Ruleset.OnlineID < 0 || score.ScoreInfo.Ruleset.OnlineID > 3) - throw new ArgumentException("Only scores in the osu, taiko, catch, or mania rulesets can be encoded to the legacy score format.", nameof(score)); + throw new ArgumentException(@"Only scores in the osu, taiko, catch, or mania rulesets can be encoded to the legacy score format.", nameof(score)); } public void Encode(Stream stream) @@ -117,9 +118,6 @@ namespace osu.Game.Scoring.Legacy { var legacyFrame = getLegacyFrame(f); - if (legacyFrame == null) - throw new ArgumentException("One or more frames could not be converted to legacy frames"); - // Rounding because stable could only parse integral values int time = (int)Math.Round(legacyFrame.Time); replayData.Append(FormattableString.Invariant($"{time - lastTime}|{legacyFrame.MouseX ?? 0}|{legacyFrame.MouseY ?? 0}|{(int)legacyFrame.ButtonState},")); @@ -136,10 +134,17 @@ namespace osu.Game.Scoring.Legacy private LegacyReplayFrame getLegacyFrame(ReplayFrame replayFrame) { - if (replayFrame is LegacyReplayFrame legacyFrame) - return legacyFrame; + switch (replayFrame) + { + case LegacyReplayFrame legacyFrame: + return legacyFrame; - return (replayFrame as IConvertibleReplayFrame)?.ToLegacy(beatmap); + case IConvertibleReplayFrame convertibleFrame: + return convertibleFrame.ToLegacy(beatmap); + + default: + throw new ArgumentException(@"Frame could not be converted to legacy frames", nameof(replayFrame)); + } } private string getHpGraphFormatted() From a41e1c80f18b2b37f46963096bbc9c3082336bd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Feb 2022 19:11:06 +0900 Subject: [PATCH 0867/5199] Show hit error on results screen Leading up to implementation of "local offset", this feels like a good thing to have visible first and foremost. --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 1 + osu.Game.Rulesets.Osu/OsuRuleset.cs | 1 + osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 1 + .../Rulesets/Scoring/HitEventExtensions.cs | 3 +++ .../Ranking/Statistics/AverageHitError.cs | 27 +++++++++++++++++++ 5 files changed, 33 insertions(+) create mode 100644 osu.Game/Screens/Ranking/Statistics/AverageHitError.cs diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 180b9ef71b..859b6cfe76 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -394,6 +394,7 @@ namespace osu.Game.Rulesets.Mania { new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[] { + new AverageHitError(score.HitEvents), new UnstableRate(score.HitEvents) }), true) } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index ad00a025a1..5ade164566 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -314,6 +314,7 @@ namespace osu.Game.Rulesets.Osu { new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[] { + new AverageHitError(timedHitEvents), new UnstableRate(timedHitEvents) }), true) } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index e56aabaf9d..de0ef8d95b 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -237,6 +237,7 @@ namespace osu.Game.Rulesets.Taiko { new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[] { + new AverageHitError(timedHitEvents), new UnstableRate(timedHitEvents) }), true) } diff --git a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs index f645b12483..5c0b4ecac3 100644 --- a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs +++ b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs @@ -22,6 +22,9 @@ namespace osu.Game.Rulesets.Scoring return 10 * standardDeviation(timeOffsets); } + public static double? CalculateAverageHitError(this IEnumerable hitEvents) => + hitEvents.Where(affectsUnstableRate).Select(ev => ev.TimeOffset).Average(); + private static bool affectsUnstableRate(HitEvent e) => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit(); private static double? standardDeviation(double[] timeOffsets) diff --git a/osu.Game/Screens/Ranking/Statistics/AverageHitError.cs b/osu.Game/Screens/Ranking/Statistics/AverageHitError.cs new file mode 100644 index 0000000000..d0e70251e7 --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/AverageHitError.cs @@ -0,0 +1,27 @@ +// 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.Game.Rulesets.Scoring; + +namespace osu.Game.Screens.Ranking.Statistics +{ + /// + /// Displays the unstable rate statistic for a given play. + /// + public class AverageHitError : SimpleStatisticItem + { + /// + /// Creates and computes an statistic. + /// + /// Sequence of s to calculate the unstable rate based on. + public AverageHitError(IEnumerable hitEvents) + : base("Average Hit Error") + { + Value = hitEvents.CalculateAverageHitError(); + } + + protected override string DisplayValue(double? value) => value == null ? "(not available)" : $"{Math.Abs(value.Value):N2} ms {(value.Value < 0 ? "early" : "late")}"; + } +} From 159db38f8a5ff2ffac1b75c9aa67e6c782356d0b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Feb 2022 19:14:43 +0900 Subject: [PATCH 0868/5199] Add missing xmldoc --- osu.Game/Rulesets/Scoring/HitEventExtensions.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs index 5c0b4ecac3..637d0a872a 100644 --- a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs +++ b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs @@ -22,6 +22,13 @@ namespace osu.Game.Rulesets.Scoring return 10 * standardDeviation(timeOffsets); } + /// + /// Calculates the average hit offset/error for a sequence of s, where negative numbers mean the user hit too early on average. + /// + /// + /// A non-null value if unstable rate could be calculated, + /// and if unstable rate cannot be calculated due to being empty. + /// public static double? CalculateAverageHitError(this IEnumerable hitEvents) => hitEvents.Where(affectsUnstableRate).Select(ev => ev.TimeOffset).Average(); From f95b7b992b618d621157b92b6a31e59b798a1cba Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Feb 2022 17:16:02 +0300 Subject: [PATCH 0869/5199] Disable running `mono-cil-strip` during iOS builds --- osu.iOS.props | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.iOS.props b/osu.iOS.props index 9d0e1790f0..80600655aa 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -15,7 +15,8 @@ --nosymbolstrip=BASS_FX_BPM_BeatCallbackReset --nosymbolstrip=BASS_FX_BPM_BeatCallbackSet --nosymbolstrip=BASS_FX_BPM_BeatDecodeGet --nosymbolstrip=BASS_FX_BPM_BeatFree --nosymbolstrip=BASS_FX_BPM_BeatGetParameters --nosymbolstrip=BASS_FX_BPM_BeatSetParameters --nosymbolstrip=BASS_FX_BPM_CallbackReset --nosymbolstrip=BASS_FX_BPM_CallbackSet --nosymbolstrip=BASS_FX_BPM_DecodeGet --nosymbolstrip=BASS_FX_BPM_Free --nosymbolstrip=BASS_FX_BPM_Translate --nosymbolstrip=BASS_FX_GetVersion --nosymbolstrip=BASS_FX_ReverseCreate --nosymbolstrip=BASS_FX_ReverseGetSource --nosymbolstrip=BASS_FX_TempoCreate --nosymbolstrip=BASS_FX_TempoGetRateRatio --nosymbolstrip=BASS_FX_TempoGetSource --nosymbolstrip=BASS_Mixer_ChannelFlags --nosymbolstrip=BASS_Mixer_ChannelGetData --nosymbolstrip=BASS_Mixer_ChannelGetEnvelopePos --nosymbolstrip=BASS_Mixer_ChannelGetLevel --nosymbolstrip=BASS_Mixer_ChannelGetLevelEx --nosymbolstrip=BASS_Mixer_ChannelGetMatrix --nosymbolstrip=BASS_Mixer_ChannelGetMixer --nosymbolstrip=BASS_Mixer_ChannelGetPosition --nosymbolstrip=BASS_Mixer_ChannelGetPositionEx --nosymbolstrip=BASS_Mixer_ChannelIsActive --nosymbolstrip=BASS_Mixer_ChannelRemove --nosymbolstrip=BASS_Mixer_ChannelRemoveSync --nosymbolstrip=BASS_Mixer_ChannelSetEnvelope --nosymbolstrip=BASS_Mixer_ChannelSetEnvelopePos --nosymbolstrip=BASS_Mixer_ChannelSetMatrix --nosymbolstrip=BASS_Mixer_ChannelSetMatrixEx --nosymbolstrip=BASS_Mixer_ChannelSetPosition --nosymbolstrip=BASS_Mixer_ChannelSetSync --nosymbolstrip=BASS_Mixer_GetVersion --nosymbolstrip=BASS_Mixer_StreamAddChannel --nosymbolstrip=BASS_Mixer_StreamAddChannelEx --nosymbolstrip=BASS_Mixer_StreamCreate --nosymbolstrip=BASS_Mixer_StreamGetChannels --nosymbolstrip=BASS_Split_StreamCreate --nosymbolstrip=BASS_Split_StreamGetAvailable --nosymbolstrip=BASS_Split_StreamGetSource --nosymbolstrip=BASS_Split_StreamGetSplits --nosymbolstrip=BASS_Split_StreamReset --nosymbolstrip=BASS_Split_StreamResetEx - --nolinkaway $(GeneratedMtouchSymbolStripFlags) + + --nolinkaway --nostrip $(GeneratedMtouchSymbolStripFlags) true From 42b27e305081884b3442b01e1b6de3e7ecbaee74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Feb 2022 20:44:13 +0100 Subject: [PATCH 0870/5199] Clean up test step names --- osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index 01ef4b4b60..01afde8f7e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("checkbox selected", () => this.ChildrenOfType().Single().Current.Value); AddStep("deselect first panel", () => this.ChildrenOfType().First().Active.Value = false); - AddUntilStep("checkbox selected", () => !this.ChildrenOfType().Single().Current.Value); + AddUntilStep("checkbox not selected", () => !this.ChildrenOfType().Single().Current.Value); void clickToggle() => AddStep("click toggle", () => { @@ -121,8 +121,8 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("checkbox hidden", () => !column.ChildrenOfType().Single().IsPresent); AddStep("inset filter", () => column.Filter = null); - AddUntilStep("no panels visible", () => column.ChildrenOfType().All(panel => !panel.Filtered.Value)); - AddUntilStep("checkbox hidden", () => column.ChildrenOfType().Single().IsPresent); + AddUntilStep("all panels visible", () => column.ChildrenOfType().All(panel => !panel.Filtered.Value)); + AddUntilStep("checkbox visible", () => column.ChildrenOfType().Single().IsPresent); void clickToggle() => AddStep("click toggle", () => { From 6cc972aa6afc457ca0815ed11194d9d5ce07a79c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Feb 2022 21:36:13 +0100 Subject: [PATCH 0871/5199] Fix test failures by waiting for panel load --- .../Visual/UserInterface/TestSceneModColumn.cs | 9 +++++++-- osu.Game/Overlays/Mods/ModColumn.cs | 13 ++++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index 01afde8f7e..e47ae860c6 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -54,17 +54,20 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestMultiSelection() { + ModColumn column = null; AddStep("create content", () => Child = new Container { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(30), - Child = new ModColumn(ModType.DifficultyIncrease, true) + Child = column = new ModColumn(ModType.DifficultyIncrease, true) { Anchor = Anchor.Centre, Origin = Anchor.Centre } }); + AddUntilStep("wait for panel load", () => column.IsLoaded && column.ItemsLoaded); + clickToggle(); AddUntilStep("all panels selected", () => this.ChildrenOfType().All(panel => panel.Active.Value)); @@ -140,13 +143,15 @@ namespace osu.Game.Tests.Visual.UserInterface { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(30), - Child = column = new ModColumn(ModType.DifficultyReduction, true, new Key[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }) + Child = column = new ModColumn(ModType.DifficultyReduction, true, new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }) { Anchor = Anchor.Centre, Origin = Anchor.Centre } }); + AddUntilStep("wait for panel load", () => column.IsLoaded && column.ItemsLoaded); + AddStep("press W", () => InputManager.Key(Key.W)); AddAssert("NF panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NF").Active.Value); diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 7f3cc8249f..b615b7bd32 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -56,6 +57,9 @@ namespace osu.Game.Overlays.Mods private Colour4 accentColour; + private Task? latestLoadTask; + internal bool ItemsLoaded => latestLoadTask == null; + private const float header_height = 42; public ModColumn(ModType modType, bool allowBulkSelection, Key[]? toggleKeys = null) @@ -234,7 +238,9 @@ namespace osu.Game.Overlays.Mods Shear = new Vector2(-ModPanel.SHEAR_X, 0) }); - LoadComponentsAsync(panels, loaded => + Task? loadTask; + + latestLoadTask = loadTask = LoadComponentsAsync(panels, loaded => { panelFlow.ChildrenEnumerable = loaded; @@ -244,6 +250,11 @@ namespace osu.Game.Overlays.Mods updateFilter(); }, (cancellationTokenSource = new CancellationTokenSource()).Token); + loadTask.ContinueWith(_ => + { + if (loadTask == latestLoadTask) + latestLoadTask = null; + }); } #region Bulk select / deselect From e8701f46f115348b502686916cc9f27fbbd5df01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Feb 2022 21:39:21 +0100 Subject: [PATCH 0872/5199] Add xmldoc to `Filter` to explain usage --- osu.Game/Overlays/Mods/ModColumn.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index b615b7bd32..3654f5246d 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -33,6 +33,11 @@ namespace osu.Game.Overlays.Mods { private Func? filter; + /// + /// Function determining whether each mod in the column should be displayed. + /// A return value of means that the mod is not filtered and therefore its corresponding panel should be displayed. + /// A return value of means that the mod is filtered out and therefore its corresponding panel should be hidden. + /// public Func? Filter { get => filter; From 899b95e61bfb7d0a21a58cd2a9ede511fe554408 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Feb 2022 21:46:58 +0100 Subject: [PATCH 0873/5199] Do not delay inital mod update by a frame --- osu.Game/Overlays/Mods/ModColumn.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 3654f5246d..736a0205e2 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -224,7 +224,8 @@ namespace osu.Game.Overlays.Mods protected override void LoadComplete() { base.LoadComplete(); - availableMods.BindValueChanged(_ => Scheduler.AddOnce(updateMods), true); + availableMods.BindValueChanged(_ => Scheduler.AddOnce(updateMods)); + updateMods(); } private CancellationTokenSource? cancellationTokenSource; From eb75a29b2024b1001001fe4ef7b523624e33ef3d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Mar 2022 12:07:03 +0900 Subject: [PATCH 0874/5199] Use constant for maximum legacy ruleset id --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 1e39b66ead..1326395695 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -9,6 +9,7 @@ using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.IO.Legacy; using osu.Game.Replays.Legacy; +using osu.Game.Rulesets; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; using SharpCompress.Compressors.LZMA; @@ -47,7 +48,7 @@ namespace osu.Game.Scoring.Legacy if (beatmap == null && !score.Replay.Frames.All(f => f is LegacyReplayFrame)) throw new ArgumentException(@"Beatmap must be provided if frames are not already legacy frames.", nameof(beatmap)); - if (score.ScoreInfo.Ruleset.OnlineID < 0 || score.ScoreInfo.Ruleset.OnlineID > 3) + if (score.ScoreInfo.Ruleset.OnlineID < 0 || score.ScoreInfo.Ruleset.OnlineID > ILegacyRuleset.MAX_LEGACY_RULESET_ID) throw new ArgumentException(@"Only scores in the osu, taiko, catch, or mania rulesets can be encoded to the legacy score format.", nameof(score)); } From 7fa58427832e5dfaa6f9743650be7df15645a414 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Mar 2022 18:30:55 +0900 Subject: [PATCH 0875/5199] Add global statistics output for all realm reads/writes --- osu.Game/Database/RealmAccess.cs | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 9bdbebfe89..f63e858b6f 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -14,14 +14,14 @@ using osu.Framework.Input.Bindings; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Statistics; -using osu.Game.Configuration; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Models; -using osu.Game.Skinning; -using osu.Game.Stores; using osu.Game.Rulesets; using osu.Game.Scoring; +using osu.Game.Skinning; +using osu.Game.Stores; using Realms; using Realms.Exceptions; @@ -85,6 +85,14 @@ namespace osu.Game.Database private static readonly GlobalStatistic total_subscriptions = GlobalStatistics.Get(@"Realm", @"Subscriptions"); + private static readonly GlobalStatistic total_reads_update = GlobalStatistics.Get(@"Realm", @"Reads (Update)"); + + private static readonly GlobalStatistic total_reads_async = GlobalStatistics.Get(@"Realm", @"Reads (Async)"); + + private static readonly GlobalStatistic total_writes_update = GlobalStatistics.Get(@"Realm", @"Writes (Update)"); + + private static readonly GlobalStatistic total_writes_async = GlobalStatistics.Get(@"Realm", @"Writes (Async)"); + private readonly object realmLock = new object(); private Realm? updateRealm; @@ -213,8 +221,12 @@ namespace osu.Game.Database public T Run(Func action) { if (ThreadSafety.IsUpdateThread) + { + total_reads_update.Value++; return action(Realm); + } + total_reads_async.Value++; using (var realm = getRealmInstance()) return action(realm); } @@ -226,9 +238,13 @@ namespace osu.Game.Database public void Run(Action action) { if (ThreadSafety.IsUpdateThread) + { + total_reads_update.Value++; action(Realm); + } else { + total_reads_async.Value++; using (var realm = getRealmInstance()) action(realm); } @@ -241,9 +257,14 @@ namespace osu.Game.Database public void Write(Action action) { if (ThreadSafety.IsUpdateThread) + { + total_writes_update.Value++; Realm.Write(action); + } else { + total_writes_async.Value++; + using (var realm = getRealmInstance()) realm.Write(action); } From 9a117467b5ea36389da063102c051ce7eef86ed2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Mar 2022 18:31:33 +0900 Subject: [PATCH 0876/5199] Add `RealmAccess.WriteAsync` method --- .../RealmSubscriptionRegistrationTests.cs | 29 +++++++++++++++++++ osu.Game/Database/RealmAccess.cs | 12 ++++++++ 2 files changed, 41 insertions(+) diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs index d62ce3b585..02d617d0e0 100644 --- a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs +++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs @@ -4,8 +4,10 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Tests.Resources; @@ -18,6 +20,33 @@ namespace osu.Game.Tests.Database [TestFixture] public class RealmSubscriptionRegistrationTests : RealmTest { + [Test] + public void TestSubscriptionWithAsyncWrite() + { + ChangeSet? lastChanges = null; + + RunTestWithRealm((realm, _) => + { + var registration = realm.RegisterForNotifications(r => r.All(), onChanged); + + realm.Run(r => r.Refresh()); + + // Without forcing the write onto its own thread, realm will internally run the operation synchronously, which can cause a deadlock with `WaitSafely`. + Task.Run(async () => + { + await realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); + }).WaitSafely(); + + realm.Run(r => r.Refresh()); + + Assert.That(lastChanges?.InsertedIndices, Has.One.Items); + + registration.Dispose(); + }); + + void onChanged(IRealmCollection sender, ChangeSet? changes, Exception error) => lastChanges = changes; + } + [Test] public void TestSubscriptionWithContextLoss() { diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index f63e858b6f..bf2b48ea52 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using System.Reflection; using System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Development; using osu.Framework.Input.Bindings; @@ -270,6 +271,17 @@ namespace osu.Game.Database } } + /// + /// Write changes to realm asynchronously, guaranteeing order of execution. + /// + /// The work to run. + public async Task WriteAsync(Action action) + { + total_writes_async.Value++; + using (var realm = getRealmInstance()) + await realm.WriteAsync(action); + } + /// /// Subscribe to a realm collection and begin watching for asynchronous changes. /// From 4117a6adf757afa89277246396d2c5e6f59b7cfa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Mar 2022 14:13:27 +0900 Subject: [PATCH 0877/5199] Move player loader audio settings to new group --- osu.Game/Screens/Play/PlayerLoader.cs | 1 + .../Play/PlayerSettings/AudioSettings.cs | 29 +++++++++++++++++++ .../Play/PlayerSettings/VisualSettings.cs | 3 -- 3 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Screens/Play/PlayerSettings/AudioSettings.cs diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 6009c85583..20c41958c9 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -167,6 +167,7 @@ namespace osu.Game.Screens.Play Children = new PlayerSettingsGroup[] { VisualSettings = new VisualSettings(), + new AudioSettings(), new InputSettings() } }, diff --git a/osu.Game/Screens/Play/PlayerSettings/AudioSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AudioSettings.cs new file mode 100644 index 0000000000..93457980f3 --- /dev/null +++ b/osu.Game/Screens/Play/PlayerSettings/AudioSettings.cs @@ -0,0 +1,29 @@ +// 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.Game.Configuration; + +namespace osu.Game.Screens.Play.PlayerSettings +{ + public class AudioSettings : PlayerSettingsGroup + { + private readonly PlayerCheckbox beatmapHitsoundsToggle; + + public AudioSettings() + : base("Audio Settings") + { + Children = new Drawable[] + { + beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hitsounds" } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + beatmapHitsoundsToggle.Current = config.GetBindable(OsuSetting.BeatmapHitsounds); + } + } +} diff --git a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs index a97078c461..81950efa9e 100644 --- a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs @@ -15,7 +15,6 @@ namespace osu.Game.Screens.Play.PlayerSettings private readonly PlayerCheckbox showStoryboardToggle; private readonly PlayerCheckbox beatmapSkinsToggle; private readonly PlayerCheckbox beatmapColorsToggle; - private readonly PlayerCheckbox beatmapHitsoundsToggle; public VisualSettings() : base("Visual Settings") @@ -45,7 +44,6 @@ namespace osu.Game.Screens.Play.PlayerSettings showStoryboardToggle = new PlayerCheckbox { LabelText = "Storyboard / Video" }, beatmapSkinsToggle = new PlayerCheckbox { LabelText = "Beatmap skins" }, beatmapColorsToggle = new PlayerCheckbox { LabelText = "Beatmap colours" }, - beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hitsounds" } }; } @@ -57,7 +55,6 @@ namespace osu.Game.Screens.Play.PlayerSettings showStoryboardToggle.Current = config.GetBindable(OsuSetting.ShowStoryboard); beatmapSkinsToggle.Current = config.GetBindable(OsuSetting.BeatmapSkins); beatmapColorsToggle.Current = config.GetBindable(OsuSetting.BeatmapColours); - beatmapHitsoundsToggle.Current = config.GetBindable(OsuSetting.BeatmapHitsounds); } } } From 5e47e35f0d2b9d85b3f01eeaa54e40db1b3fa998 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Mar 2022 15:40:37 +0900 Subject: [PATCH 0878/5199] Add ability to change distribution of test `HitEvent`s --- .../Ranking/TestSceneHitEventTimingDistributionGraph.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index 4bc843096f..221001e40b 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -71,16 +71,16 @@ namespace osu.Game.Tests.Visual.Ranking }; }); - public static List CreateDistributedHitEvents() + public static List CreateDistributedHitEvents(double centre = 0, double range = 25) { var hitEvents = new List(); - for (int i = 0; i < 50; i++) + for (int i = 0; i < range * 2; i++) { - int count = (int)(Math.Pow(25 - Math.Abs(i - 25), 2)); + int count = (int)(Math.Pow(range - Math.Abs(i - range), 2)); for (int j = 0; j < count; j++) - hitEvents.Add(new HitEvent(i - 25, HitResult.Perfect, new HitCircle(), new HitCircle(), null)); + hitEvents.Add(new HitEvent(centre + i - range, HitResult.Perfect, new HitCircle(), new HitCircle(), null)); } return hitEvents; From 1847f69bf95a95fe7dce07ebb2d4119e946aa470 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Mar 2022 15:40:19 +0900 Subject: [PATCH 0879/5199] Add basic beatmap offset adjustment control --- .../Gameplay/TestSceneBeatmapOffsetControl.cs | 27 ++++++ .../PlayerSettings/BeatmapOffsetControl.cs | 88 +++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs create mode 100644 osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs new file mode 100644 index 0000000000..bd87039797 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Screens.Play.PlayerSettings; +using osu.Game.Tests.Visual.Ranking; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneBeatmapOffsetControl : OsuTestScene + { + [Test] + public void TestDisplay() + { + Child = new PlayerSettingsGroup("Some settings") + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new BeatmapOffsetControl(TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(-4.5)) + } + }; + } + } +} diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs new file mode 100644 index 0000000000..c05c5beb31 --- /dev/null +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -0,0 +1,88 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Ranking.Statistics; +using osuTK; + +namespace osu.Game.Screens.Play.PlayerSettings +{ + public class BeatmapOffsetControl : CompositeDrawable + { + private readonly SettingsButton useAverageButton; + + private readonly double lastPlayAverage; + + public Bindable Current { get; } = new BindableDouble + { + Default = 0, + Value = 0, + MinValue = -50, + MaxValue = 50, + Precision = 0.1, + }; + + public BeatmapOffsetControl(IReadOnlyList hitEvents) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + FillFlowContainer flow; + + InternalChild = flow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + Children = new Drawable[] + { + new PlayerSliderBar + { + KeyboardStep = 5, + LabelText = "Beatmap offset", + Current = Current, + }, + } + }; + + if (hitEvents.CalculateAverageHitError() is double average) + { + lastPlayAverage = average; + + flow.AddRange(new Drawable[] + { + new OsuSpriteText + { + Text = "Last play:" + }, + new HitEventTimingDistributionGraph(hitEvents) + { + RelativeSizeAxes = Axes.X, + Height = 50, + }, + new AverageHitError(hitEvents), + useAverageButton = new SettingsButton + { + Text = "Calibrate using last play", + Action = () => Current.Value = lastPlayAverage + }, + }); + } + + Current.BindValueChanged(offset => + { + if (useAverageButton != null) + { + useAverageButton.Enabled.Value = offset.NewValue != lastPlayAverage; + } + }, true); + } + } +} From 350b0b488c048d114cb25adc4353517d75ac2081 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Mar 2022 14:47:06 +0900 Subject: [PATCH 0880/5199] TODO: Get score from previous play session for further analysis --- osu.Game/Screens/Play/Player.cs | 6 +++++- osu.Game/Screens/Play/PlayerLoader.cs | 9 +++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d4b02622d3..86ea412488 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -136,7 +136,11 @@ namespace osu.Game.Screens.Play public readonly PlayerConfiguration Configuration; - protected Score Score { get; private set; } + /// + /// The score for the current play session. + /// Available only after the player is loaded. + /// + public Score Score { get; private set; } /// /// Create a new player instance. diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 20c41958c9..863246cd05 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -23,6 +23,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Input; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Menu; using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Users; @@ -226,6 +227,14 @@ namespace osu.Game.Screens.Play { base.OnResuming(last); + var lastScore = player.Score; + + if (lastScore != null) + { + // TODO: use this + double? lastPlayHitError = lastScore.ScoreInfo.HitEvents.CalculateAverageHitError(); + } + // prepare for a retry. player = null; playerConsumed = false; From 7215f3f66b6c7891f7d9b8de14ff6ba7fe155ed8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Mar 2022 16:02:48 +0900 Subject: [PATCH 0881/5199] Fix `CalculateAverageHitError` throwing if there are zero `HitEvent`s --- osu.Game/Rulesets/Scoring/HitEventExtensions.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs index 637d0a872a..fea13cf4b6 100644 --- a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs +++ b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs @@ -29,8 +29,15 @@ namespace osu.Game.Rulesets.Scoring /// A non-null value if unstable rate could be calculated, /// and if unstable rate cannot be calculated due to being empty. /// - public static double? CalculateAverageHitError(this IEnumerable hitEvents) => - hitEvents.Where(affectsUnstableRate).Select(ev => ev.TimeOffset).Average(); + public static double? CalculateAverageHitError(this IEnumerable hitEvents) + { + double[] timeOffsets = hitEvents.Where(affectsUnstableRate).Select(ev => ev.TimeOffset).ToArray(); + + if (timeOffsets.Length == 0) + return null; + + return timeOffsets.Average(); + } private static bool affectsUnstableRate(HitEvent e) => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit(); From 2901d2a6505c6a1a44ee86101635cd35837cc231 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Mar 2022 16:14:57 +0900 Subject: [PATCH 0882/5199] Hook offset adjustment control up to last play via `PlayerLoader` --- osu.Game/Screens/Play/PlayerLoader.cs | 11 +-- .../Play/PlayerSettings/AudioSettings.cs | 10 ++- .../PlayerSettings/BeatmapOffsetControl.cs | 79 +++++++++++-------- 3 files changed, 61 insertions(+), 39 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 863246cd05..f6d63a8ec5 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -23,7 +23,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Input; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; -using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Menu; using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Users; @@ -62,6 +61,8 @@ namespace osu.Game.Screens.Play protected VisualSettings VisualSettings { get; private set; } + protected AudioSettings AudioSettings { get; private set; } + protected Task LoadTask { get; private set; } protected Task DisposalTask { get; private set; } @@ -168,7 +169,7 @@ namespace osu.Game.Screens.Play Children = new PlayerSettingsGroup[] { VisualSettings = new VisualSettings(), - new AudioSettings(), + AudioSettings = new AudioSettings(), new InputSettings() } }, @@ -229,11 +230,7 @@ namespace osu.Game.Screens.Play var lastScore = player.Score; - if (lastScore != null) - { - // TODO: use this - double? lastPlayHitError = lastScore.ScoreInfo.HitEvents.CalculateAverageHitError(); - } + AudioSettings.ReferenceScore.Value = lastScore?.ScoreInfo; // prepare for a retry. player = null; diff --git a/osu.Game/Screens/Play/PlayerSettings/AudioSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AudioSettings.cs index 93457980f3..32de5333e1 100644 --- a/osu.Game/Screens/Play/PlayerSettings/AudioSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/AudioSettings.cs @@ -2,13 +2,17 @@ // 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.Configuration; +using osu.Game.Scoring; namespace osu.Game.Screens.Play.PlayerSettings { public class AudioSettings : PlayerSettingsGroup { + public Bindable ReferenceScore { get; } = new Bindable(); + private readonly PlayerCheckbox beatmapHitsoundsToggle; public AudioSettings() @@ -16,7 +20,11 @@ namespace osu.Game.Screens.Play.PlayerSettings { Children = new Drawable[] { - beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hitsounds" } + beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hitsounds" }, + new BeatmapOffsetControl + { + ReferenceScore = { BindTarget = ReferenceScore }, + }, }; } diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index c05c5beb31..5d287a3730 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -1,13 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; using osuTK; @@ -15,9 +15,7 @@ namespace osu.Game.Screens.Play.PlayerSettings { public class BeatmapOffsetControl : CompositeDrawable { - private readonly SettingsButton useAverageButton; - - private readonly double lastPlayAverage; + public Bindable ReferenceScore { get; } = new Bindable(); public Bindable Current { get; } = new BindableDouble { @@ -28,14 +26,18 @@ namespace osu.Game.Screens.Play.PlayerSettings Precision = 0.1, }; - public BeatmapOffsetControl(IReadOnlyList hitEvents) + private SettingsButton useAverageButton; + + private double lastPlayAverage; + + private readonly FillFlowContainer referenceScoreContainer; + + public BeatmapOffsetControl() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - FillFlowContainer flow; - - InternalChild = flow = new FillFlowContainer + InternalChild = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -49,32 +51,17 @@ namespace osu.Game.Screens.Play.PlayerSettings LabelText = "Beatmap offset", Current = Current, }, + referenceScoreContainer = new FillFlowContainer + { + Spacing = new Vector2(10), + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, } }; - if (hitEvents.CalculateAverageHitError() is double average) - { - lastPlayAverage = average; - - flow.AddRange(new Drawable[] - { - new OsuSpriteText - { - Text = "Last play:" - }, - new HitEventTimingDistributionGraph(hitEvents) - { - RelativeSizeAxes = Axes.X, - Height = 50, - }, - new AverageHitError(hitEvents), - useAverageButton = new SettingsButton - { - Text = "Calibrate using last play", - Action = () => Current.Value = lastPlayAverage - }, - }); - } + ReferenceScore.BindValueChanged(scoreChanged, true); Current.BindValueChanged(offset => { @@ -84,5 +71,35 @@ namespace osu.Game.Screens.Play.PlayerSettings } }, true); } + + private void scoreChanged(ValueChangedEvent score) + { + if (!(score.NewValue?.HitEvents.CalculateAverageHitError() is double average)) + { + referenceScoreContainer.Clear(); + return; + } + + lastPlayAverage = average; + + referenceScoreContainer.Children = new Drawable[] + { + new OsuSpriteText + { + Text = "Last play:" + }, + new HitEventTimingDistributionGraph(score.NewValue.HitEvents) + { + RelativeSizeAxes = Axes.X, + Height = 50, + }, + new AverageHitError(score.NewValue.HitEvents), + useAverageButton = new SettingsButton + { + Text = "Calibrate using last play", + Action = () => Current.Value = lastPlayAverage + }, + }; + } } } From fab09575ec30d2cec7aef5487f59b6b8fa10732a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Mar 2022 16:15:13 +0900 Subject: [PATCH 0883/5199] Add full testing flow for `BeatmapOffsetControl` --- .../Gameplay/TestSceneBeatmapOffsetControl.cs | 46 ++++++++++++++++--- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs index bd87039797..3fb10b2d5c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs @@ -1,8 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Overlays.Settings; +using osu.Game.Scoring; using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Tests.Visual.Ranking; @@ -10,18 +14,46 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneBeatmapOffsetControl : OsuTestScene { + private BeatmapOffsetControl offsetControl; + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Create control", () => + { + Child = new PlayerSettingsGroup("Some settings") + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + offsetControl = new BeatmapOffsetControl() + } + }; + }); + } + [Test] public void TestDisplay() { - Child = new PlayerSettingsGroup("Some settings") + const double average_error = -4.5; + + AddAssert("Offset is neutral", () => offsetControl.Current.Value == 0); + AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); + AddStep("Set reference score", () => { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new Drawable[] + offsetControl.ReferenceScore.Value = new ScoreInfo { - new BeatmapOffsetControl(TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(-4.5)) - } - }; + HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error) + }; + }); + + AddAssert("Has calibration button", () => offsetControl.ChildrenOfType().Any()); + AddStep("Press button", () => offsetControl.ChildrenOfType().Single().TriggerClick()); + AddAssert("Offset is adjusted", () => offsetControl.Current.Value == average_error); + + AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null); + AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); } } } From acf8db13acad60857ed919f00399f0f50c80f869 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Mar 2022 16:22:51 +0900 Subject: [PATCH 0884/5199] Store user settings to realm --- osu.Game/Beatmaps/BeatmapInfo.cs | 3 +++ osu.Game/Beatmaps/BeatmapUserSettings.cs | 19 +++++++++++++++++++ osu.Game/Database/RealmAccess.cs | 3 ++- 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Beatmaps/BeatmapUserSettings.cs diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 305b3979a0..c6f69286cd 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -40,6 +40,8 @@ namespace osu.Game.Beatmaps [Backlink(nameof(ScoreInfo.BeatmapInfo))] public IQueryable Scores { get; } = null!; + public BeatmapUserSettings UserSettings { get; set; } = null!; + public BeatmapInfo(RulesetInfo? ruleset = null, BeatmapDifficulty? difficulty = null, BeatmapMetadata? metadata = null) { ID = Guid.NewGuid(); @@ -51,6 +53,7 @@ namespace osu.Game.Beatmaps }; Difficulty = difficulty ?? new BeatmapDifficulty(); Metadata = metadata ?? new BeatmapMetadata(); + UserSettings = new BeatmapUserSettings(); } [UsedImplicitly] diff --git a/osu.Game/Beatmaps/BeatmapUserSettings.cs b/osu.Game/Beatmaps/BeatmapUserSettings.cs new file mode 100644 index 0000000000..5c71bf34b1 --- /dev/null +++ b/osu.Game/Beatmaps/BeatmapUserSettings.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable +using Realms; + +namespace osu.Game.Beatmaps +{ + /// + /// User settings overrides that are attached to a beatmap. + /// + public class BeatmapUserSettings : EmbeddedObject + { + /// + /// An audio offset that can be used for timing adjustments. + /// + public double Offset { get; set; } + } +} diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index bf2b48ea52..fc2f519ead 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -54,8 +54,9 @@ namespace osu.Game.Database /// 11 2021-11-22 Use ShortName instead of RulesetID for ruleset key bindings. /// 12 2021-11-24 Add Status to RealmBeatmapSet. /// 13 2022-01-13 Final migration of beatmaps and scores to realm (multiple new storage fields). + /// 14 2022-03-01 Added BeatmapUserSettings to BeatmapInfo. /// - private const int schema_version = 13; + private const int schema_version = 14; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. From 047e801da9f9fe31b9a13d6c8c59b6240626fcc2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Mar 2022 16:59:33 +0900 Subject: [PATCH 0885/5199] Store and retrieve offset from realm --- osu.Game/Database/RealmAccess.cs | 5 +++ .../Play/MasterGameplayClockContainer.cs | 17 +++++---- .../PlayerSettings/BeatmapOffsetControl.cs | 36 +++++++++++++++---- 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index fc2f519ead..fb3052d850 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -565,6 +565,11 @@ namespace osu.Game.Database } break; + + case 14: + foreach (var beatmap in migration.NewRealm.All()) + beatmap.UserSettings = new BeatmapUserSettings(); + break; } } diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 200921680e..d27a989067 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Database; namespace osu.Game.Screens.Play { @@ -43,7 +44,7 @@ namespace osu.Game.Screens.Play Precision = 0.1, }; - private double totalAppliedOffset => userOffsetClock.RateAdjustedOffset + platformOffsetClock.RateAdjustedOffset; + private double totalAppliedOffset => userBeatmapOffsetClock.RateAdjustedOffset + userGlobalOffsetClock.RateAdjustedOffset + platformOffsetClock.RateAdjustedOffset; private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1); @@ -52,7 +53,8 @@ namespace osu.Game.Screens.Play private readonly bool startAtGameplayStart; private readonly double firstHitObjectTime; - private HardwareCorrectionOffsetClock userOffsetClock; + private HardwareCorrectionOffsetClock userGlobalOffsetClock; + private HardwareCorrectionOffsetClock userBeatmapOffsetClock; private HardwareCorrectionOffsetClock platformOffsetClock; private MasterGameplayClock masterGameplayClock; private Bindable userAudioOffset; @@ -69,10 +71,12 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, RealmAccess realm) { userAudioOffset = config.GetBindable(OsuSetting.AudioOffset); - userAudioOffset.BindValueChanged(offset => userOffsetClock.Offset = offset.NewValue, true); + userAudioOffset.BindValueChanged(offset => userGlobalOffsetClock.Offset = offset.NewValue, true); + + userBeatmapOffsetClock.Offset = realm.Run(r => r.Find(beatmap.BeatmapInfo.ID).UserSettings?.Offset) ?? 0; // sane default provided by ruleset. startOffset = gameplayStartTime; @@ -161,9 +165,10 @@ namespace osu.Game.Screens.Play platformOffsetClock = new HardwareCorrectionOffsetClock(source, pauseFreqAdjust) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 }; // the final usable gameplay clock with user-set offsets applied. - userOffsetClock = new HardwareCorrectionOffsetClock(platformOffsetClock, pauseFreqAdjust); + userGlobalOffsetClock = new HardwareCorrectionOffsetClock(platformOffsetClock, pauseFreqAdjust); + userBeatmapOffsetClock = new HardwareCorrectionOffsetClock(userGlobalOffsetClock, pauseFreqAdjust); - return masterGameplayClock = new MasterGameplayClock(userOffsetClock); + return masterGameplayClock = new MasterGameplayClock(userBeatmapOffsetClock); } /// diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 5d287a3730..75f8c89d34 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -1,9 +1,12 @@ // 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.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics.Sprites; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Scoring; @@ -60,16 +63,37 @@ namespace osu.Game.Screens.Play.PlayerSettings }, } }; + } + + [Resolved] + private RealmAccess realm { get; set; } + + [Resolved] + private IBindable beatmap { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); ReferenceScore.BindValueChanged(scoreChanged, true); - Current.BindValueChanged(offset => + Current.BindValueChanged(currentChanged); + Current.Value = realm.Run(r => r.Find(beatmap.Value.BeatmapInfo.ID).UserSettings?.Offset) ?? 0; + } + + private void currentChanged(ValueChangedEvent offset) + { + if (useAverageButton != null) { - if (useAverageButton != null) - { - useAverageButton.Enabled.Value = offset.NewValue != lastPlayAverage; - } - }, true); + useAverageButton.Enabled.Value = offset.NewValue != lastPlayAverage; + } + + realm.Write(r => + { + var settings = r.Find(beatmap.Value.BeatmapInfo.ID).UserSettings; + + settings.Offset = offset.NewValue; + }); } private void scoreChanged(ValueChangedEvent score) From 071ba5c1dfbf785d45a12713bf3a2d3d3121a710 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Mar 2022 18:28:53 +0900 Subject: [PATCH 0886/5199] Make writes asynchronously to avoid synchronous overhead --- .../PlayerSettings/BeatmapOffsetControl.cs | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 75f8c89d34..2f2d1b81e5 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -1,8 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; @@ -78,9 +80,11 @@ namespace osu.Game.Screens.Play.PlayerSettings ReferenceScore.BindValueChanged(scoreChanged, true); Current.BindValueChanged(currentChanged); - Current.Value = realm.Run(r => r.Find(beatmap.Value.BeatmapInfo.ID).UserSettings?.Offset) ?? 0; + Current.Value = realm.Run(r => r.Find(beatmap.Value.BeatmapInfo.ID).UserSettings.Offset); } + private Task realmWrite; + private void currentChanged(ValueChangedEvent offset) { if (useAverageButton != null) @@ -88,12 +92,25 @@ namespace osu.Game.Screens.Play.PlayerSettings useAverageButton.Enabled.Value = offset.NewValue != lastPlayAverage; } - realm.Write(r => - { - var settings = r.Find(beatmap.Value.BeatmapInfo.ID).UserSettings; + Scheduler.AddOnce(updateOffset); - settings.Offset = offset.NewValue; - }); + void updateOffset() + { + // ensure the previous write has completed. + if (realmWrite?.IsCompleted == false) + { + Scheduler.AddOnce(updateOffset); + return; + } + + realmWrite?.WaitSafely(); + realmWrite = realm.WriteAsync(r => + { + var settings = r.Find(beatmap.Value.BeatmapInfo.ID).UserSettings; + + settings.Offset = offset.NewValue; + }); + } } private void scoreChanged(ValueChangedEvent score) From bb8caabb8be3e0391fee7adede8f0bed09948737 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Mar 2022 18:29:04 +0900 Subject: [PATCH 0887/5199] Subscribe to changes in offset --- .../Play/MasterGameplayClockContainer.cs | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index d27a989067..6a90e7adea 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using osu.Framework; using osu.Framework.Allocation; @@ -70,13 +71,38 @@ namespace osu.Game.Screens.Play firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime; } - [BackgroundDependencyLoader] - private void load(OsuConfigManager config, RealmAccess realm) + [Resolved] + private RealmAccess realm { get; set; } + + [Resolved] + private OsuConfigManager config { get; set; } + + private IDisposable beatmapOffsetSubscription; + + protected override void LoadComplete() { + base.LoadComplete(); + userAudioOffset = config.GetBindable(OsuSetting.AudioOffset); userAudioOffset.BindValueChanged(offset => userGlobalOffsetClock.Offset = offset.NewValue, true); - userBeatmapOffsetClock.Offset = realm.Run(r => r.Find(beatmap.BeatmapInfo.ID).UserSettings?.Offset) ?? 0; + beatmapOffsetSubscription = realm.RegisterCustomSubscription(r => + { + var userSettings = r.Find(beatmap.BeatmapInfo.ID).UserSettings; + + void onUserSettingsOnPropertyChanged(object sender, PropertyChangedEventArgs args) + { + if (args.PropertyName == nameof(BeatmapUserSettings.Offset)) + updateOffset(); + } + + updateOffset(); + userSettings.PropertyChanged += onUserSettingsOnPropertyChanged; + + return new InvokeOnDisposal(() => userSettings.PropertyChanged -= onUserSettingsOnPropertyChanged); + + void updateOffset() => userBeatmapOffsetClock.Offset = userSettings.Offset; + }); // sane default provided by ruleset. startOffset = gameplayStartTime; @@ -214,6 +240,7 @@ namespace osu.Game.Screens.Play protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); + beatmapOffsetSubscription?.Dispose(); removeSourceClockAdjustments(); } From 99c1ba19aa22f5f88f21f136a27cb3b2c63a392f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Mar 2022 19:13:40 +0900 Subject: [PATCH 0888/5199] Allow `BeatmapOffsetControl` to react to external changes to offset --- .../PlayerSettings/BeatmapOffsetControl.cs | 47 ++++++++++++++----- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 2f2d1b81e5..487d044f5b 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -1,12 +1,14 @@ // 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.ComponentModel; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics.Sprites; @@ -22,7 +24,7 @@ namespace osu.Game.Screens.Play.PlayerSettings { public Bindable ReferenceScore { get; } = new Bindable(); - public Bindable Current { get; } = new BindableDouble + public BindableDouble Current { get; } = new BindableDouble { Default = 0, Value = 0, @@ -73,42 +75,59 @@ namespace osu.Game.Screens.Play.PlayerSettings [Resolved] private IBindable beatmap { get; set; } + private IDisposable beatmapOffsetSubscription; + protected override void LoadComplete() { base.LoadComplete(); ReferenceScore.BindValueChanged(scoreChanged, true); + beatmapOffsetSubscription = realm.RegisterCustomSubscription(r => + { + var userSettings = r.Find(beatmap.Value.BeatmapInfo.ID).UserSettings; + + Current.Value = userSettings.Offset; + userSettings.PropertyChanged += onUserSettingsOnPropertyChanged; + + return new InvokeOnDisposal(() => userSettings.PropertyChanged -= onUserSettingsOnPropertyChanged); + + void onUserSettingsOnPropertyChanged(object sender, PropertyChangedEventArgs args) + { + if (args.PropertyName == nameof(BeatmapUserSettings.Offset)) + Current.Value = userSettings.Offset; + } + }); + Current.BindValueChanged(currentChanged); - Current.Value = realm.Run(r => r.Find(beatmap.Value.BeatmapInfo.ID).UserSettings.Offset); } private Task realmWrite; private void currentChanged(ValueChangedEvent offset) { - if (useAverageButton != null) - { - useAverageButton.Enabled.Value = offset.NewValue != lastPlayAverage; - } - Scheduler.AddOnce(updateOffset); void updateOffset() { - // ensure the previous write has completed. + // ensure the previous write has completed. ignoring performance concerns, if we don't do this, the async writes could be out of sequence. if (realmWrite?.IsCompleted == false) { Scheduler.AddOnce(updateOffset); return; } - realmWrite?.WaitSafely(); + if (useAverageButton != null) + useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, Current.Value, Current.Precision); + realmWrite = realm.WriteAsync(r => { var settings = r.Find(beatmap.Value.BeatmapInfo.ID).UserSettings; - settings.Offset = offset.NewValue; + if (Precision.AlmostEquals(settings.Offset, Current.Value)) + return; + + settings.Offset = Current.Value; }); } } @@ -142,5 +161,11 @@ namespace osu.Game.Screens.Play.PlayerSettings }, }; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + beatmapOffsetSubscription?.Dispose(); + } } } From bc2a15db96f945cb0660617027901a4bb784da37 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Mar 2022 19:20:18 +0900 Subject: [PATCH 0889/5199] Handle cases of beatmaps not existing in realm for tests --- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 5 ++++- .../Play/PlayerSettings/BeatmapOffsetControl.cs | 10 ++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 6a90e7adea..c7c967abfd 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -88,7 +88,10 @@ namespace osu.Game.Screens.Play beatmapOffsetSubscription = realm.RegisterCustomSubscription(r => { - var userSettings = r.Find(beatmap.BeatmapInfo.ID).UserSettings; + var userSettings = r.Find(beatmap.BeatmapInfo.ID)?.UserSettings; + + if (userSettings == null) // only the case for tests. + return null; void onUserSettingsOnPropertyChanged(object sender, PropertyChangedEventArgs args) { diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 487d044f5b..7fbaaaeffc 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -85,7 +85,10 @@ namespace osu.Game.Screens.Play.PlayerSettings beatmapOffsetSubscription = realm.RegisterCustomSubscription(r => { - var userSettings = r.Find(beatmap.Value.BeatmapInfo.ID).UserSettings; + var userSettings = r.Find(beatmap.Value.BeatmapInfo.ID)?.UserSettings; + + if (userSettings == null) // only the case for tests. + return null; Current.Value = userSettings.Offset; userSettings.PropertyChanged += onUserSettingsOnPropertyChanged; @@ -122,7 +125,10 @@ namespace osu.Game.Screens.Play.PlayerSettings realmWrite = realm.WriteAsync(r => { - var settings = r.Find(beatmap.Value.BeatmapInfo.ID).UserSettings; + var settings = r.Find(beatmap.Value.BeatmapInfo.ID)?.UserSettings; + + if (settings == null) // only the case for tests. + return; if (Precision.AlmostEquals(settings.Offset, Current.Value)) return; From 4d9efe771bd5aab0db19e057410a2d678b882ca5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Mar 2022 19:31:11 +0900 Subject: [PATCH 0890/5199] Don't display calibration options when the previous play was too short to be useful --- .../Gameplay/TestSceneBeatmapOffsetControl.cs | 14 ++++++ .../PlayerSettings/BeatmapOffsetControl.cs | 46 +++++++++++++++---- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs index 3fb10b2d5c..7704233adf 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs @@ -33,6 +33,20 @@ namespace osu.Game.Tests.Visual.Gameplay }); } + [Test] + public void TestTooShortToDisplay() + { + AddStep("Set short reference score", () => + { + offsetControl.ReferenceScore.Value = new ScoreInfo + { + HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(0, 2) + }; + }); + + AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); + } + [Test] public void TestDisplay() { diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 7fbaaaeffc..a8ed3f562b 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -11,6 +11,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Scoring; @@ -75,6 +77,9 @@ namespace osu.Game.Screens.Play.PlayerSettings [Resolved] private IBindable beatmap { get; set; } + [Resolved] + private OsuColour colours { get; set; } + private IDisposable beatmapOffsetSubscription; protected override void LoadComplete() @@ -140,32 +145,53 @@ namespace osu.Game.Screens.Play.PlayerSettings private void scoreChanged(ValueChangedEvent score) { - if (!(score.NewValue?.HitEvents.CalculateAverageHitError() is double average)) - { - referenceScoreContainer.Clear(); - return; - } + var hitEvents = score.NewValue?.HitEvents; - lastPlayAverage = average; + referenceScoreContainer.Clear(); + + if (!(hitEvents?.CalculateAverageHitError() is double average)) + return; referenceScoreContainer.Children = new Drawable[] { new OsuSpriteText { - Text = "Last play:" + Text = "Previous play:" }, - new HitEventTimingDistributionGraph(score.NewValue.HitEvents) + }; + + if (hitEvents.Count < 10) + { + referenceScoreContainer.AddRange(new Drawable[] + { + new OsuTextFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Colour = colours.Red1, + Text = "Previous play too short to use for calibration" + }, + }); + + return; + } + + lastPlayAverage = average; + + referenceScoreContainer.AddRange(new Drawable[] + { + new HitEventTimingDistributionGraph(hitEvents) { RelativeSizeAxes = Axes.X, Height = 50, }, - new AverageHitError(score.NewValue.HitEvents), + new AverageHitError(hitEvents), useAverageButton = new SettingsButton { Text = "Calibrate using last play", Action = () => Current.Value = lastPlayAverage }, - }; + }); } protected override void Dispose(bool isDisposing) From 4aee57c9c1f4700116fc740ef6db0c1dddd40860 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Mar 2022 19:33:30 +0900 Subject: [PATCH 0891/5199] Add localisation of all beatmap offset strings --- .../BeatmapOffsetControlStrings.cs | 34 +++++++++++++++++++ .../PlayerSettings/BeatmapOffsetControl.cs | 9 ++--- 2 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Localisation/BeatmapOffsetControlStrings.cs diff --git a/osu.Game/Localisation/BeatmapOffsetControlStrings.cs b/osu.Game/Localisation/BeatmapOffsetControlStrings.cs new file mode 100644 index 0000000000..7b2a9e50b2 --- /dev/null +++ b/osu.Game/Localisation/BeatmapOffsetControlStrings.cs @@ -0,0 +1,34 @@ +// 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 BeatmapOffsetControlStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.BeatmapOffsetControl"; + + /// + /// "Beatmap offset" + /// + public static LocalisableString BeatmapOffset => new TranslatableString(getKey(@"beatmap_offset"), @"Beatmap offset"); + + /// + /// "Previous play:" + /// + public static LocalisableString PreviousPlay => new TranslatableString(getKey(@"previous_play"), @"Previous play:"); + + /// + /// "Previous play too short to use for calibration" + /// + public static LocalisableString PreviousPlayTooShortToUseForCalibration => new TranslatableString(getKey(@"previous_play_too_short_to_use_for_calibration"), @"Previous play too short to use for calibration"); + + /// + /// "Calibrate using last play" + /// + public static LocalisableString CalibrateUsingLastPlay => new TranslatableString(getKey(@"calibrate_using_last_play"), @"Calibrate using last play"); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} \ No newline at end of file diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index a8ed3f562b..864a46fc8f 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -19,6 +19,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; using osuTK; +using osu.Game.Localisation; namespace osu.Game.Screens.Play.PlayerSettings { @@ -57,7 +58,7 @@ namespace osu.Game.Screens.Play.PlayerSettings new PlayerSliderBar { KeyboardStep = 5, - LabelText = "Beatmap offset", + LabelText = BeatmapOffsetControlStrings.BeatmapOffset, Current = Current, }, referenceScoreContainer = new FillFlowContainer @@ -156,7 +157,7 @@ namespace osu.Game.Screens.Play.PlayerSettings { new OsuSpriteText { - Text = "Previous play:" + Text = BeatmapOffsetControlStrings.PreviousPlay }, }; @@ -169,7 +170,7 @@ namespace osu.Game.Screens.Play.PlayerSettings RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Colour = colours.Red1, - Text = "Previous play too short to use for calibration" + Text = BeatmapOffsetControlStrings.PreviousPlayTooShortToUseForCalibration }, }); @@ -188,7 +189,7 @@ namespace osu.Game.Screens.Play.PlayerSettings new AverageHitError(hitEvents), useAverageButton = new SettingsButton { - Text = "Calibrate using last play", + Text = BeatmapOffsetControlStrings.CalibrateUsingLastPlay, Action = () => Current.Value = lastPlayAverage }, }); From 9792f0653ad46fe0b324fa16d341ede9f5f0d0d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Mar 2022 19:41:51 +0900 Subject: [PATCH 0892/5199] Don't show calibration controls for autoplay --- .../Play/PlayerSettings/BeatmapOffsetControl.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 864a46fc8f..1cd89f691e 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -3,6 +3,7 @@ using System; using System.ComponentModel; +using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -20,6 +21,7 @@ using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; using osuTK; using osu.Game.Localisation; +using osu.Game.Rulesets.Mods; namespace osu.Game.Screens.Play.PlayerSettings { @@ -146,11 +148,17 @@ namespace osu.Game.Screens.Play.PlayerSettings private void scoreChanged(ValueChangedEvent score) { - var hitEvents = score.NewValue?.HitEvents; - referenceScoreContainer.Clear(); - if (!(hitEvents?.CalculateAverageHitError() is double average)) + if (score.NewValue == null) + return; + + if (score.NewValue.Mods.Any(m => m is ModAutoplay)) + return; + + var hitEvents = score.NewValue.HitEvents; + + if (!(hitEvents.CalculateAverageHitError() is double average)) return; referenceScoreContainer.Children = new Drawable[] From 7d11cfb301249b20f49ca9f150eaec936f5389fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Mar 2022 19:51:15 +0900 Subject: [PATCH 0893/5199] Add detach mapping for `BeatmapUserSettings` --- osu.Game/Database/RealmObjectExtensions.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index f89bbbe19d..6dc18df9e0 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -38,6 +38,7 @@ namespace osu.Game.Database c.CreateMap() .ForMember(s => s.Ruleset, cc => cc.Ignore()) .ForMember(s => s.Metadata, cc => cc.Ignore()) + .ForMember(s => s.UserSettings, cc => cc.Ignore()) .ForMember(s => s.Difficulty, cc => cc.Ignore()) .ForMember(s => s.BeatmapSet, cc => cc.Ignore()) .AfterMap((s, d) => @@ -154,6 +155,7 @@ namespace osu.Game.Database c.CreateMap(); c.CreateMap(); + c.CreateMap(); c.CreateMap(); c.CreateMap(); c.CreateMap(); From 6c09237956c6535c54c37ef4ee2877af3cd0f10a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Mar 2022 19:57:09 +0900 Subject: [PATCH 0894/5199] Reorder fields in `BeatmapOffsetControl` and `MasterGameplayClockContainer` --- .../Play/MasterGameplayClockContainer.cs | 16 +++++++------- .../PlayerSettings/BeatmapOffsetControl.cs | 22 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index c7c967abfd..2b6db5f59e 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -61,6 +61,14 @@ namespace osu.Game.Screens.Play private Bindable userAudioOffset; private double startOffset; + private IDisposable beatmapOffsetSubscription; + + [Resolved] + private RealmAccess realm { get; set; } + + [Resolved] + private OsuConfigManager config { get; set; } + public MasterGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime, bool startAtGameplayStart = false) : base(beatmap.Track) { @@ -71,14 +79,6 @@ namespace osu.Game.Screens.Play firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime; } - [Resolved] - private RealmAccess realm { get; set; } - - [Resolved] - private OsuConfigManager config { get; set; } - - private IDisposable beatmapOffsetSubscription; - protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 1cd89f691e..02359afd16 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -44,6 +44,17 @@ namespace osu.Game.Screens.Play.PlayerSettings private readonly FillFlowContainer referenceScoreContainer; + private IDisposable beatmapOffsetSubscription; + + [Resolved] + private RealmAccess realm { get; set; } + + [Resolved] + private IBindable beatmap { get; set; } + + [Resolved] + private OsuColour colours { get; set; } + public BeatmapOffsetControl() { RelativeSizeAxes = Axes.X; @@ -74,17 +85,6 @@ namespace osu.Game.Screens.Play.PlayerSettings }; } - [Resolved] - private RealmAccess realm { get; set; } - - [Resolved] - private IBindable beatmap { get; set; } - - [Resolved] - private OsuColour colours { get; set; } - - private IDisposable beatmapOffsetSubscription; - protected override void LoadComplete() { base.LoadComplete(); From 222f50d2119b9041c430ba7f4a54ab4c87d86dfe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Mar 2022 20:41:54 +0900 Subject: [PATCH 0895/5199] Fix calibration being back-to-front --- osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs | 2 +- osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs index 7704233adf..42b579bc89 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("Has calibration button", () => offsetControl.ChildrenOfType().Any()); AddStep("Press button", () => offsetControl.ChildrenOfType().Single().TriggerClick()); - AddAssert("Offset is adjusted", () => offsetControl.Current.Value == average_error); + AddAssert("Offset is adjusted", () => offsetControl.Current.Value == -average_error); AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null); AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 02359afd16..e6bc510564 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -198,7 +198,7 @@ namespace osu.Game.Screens.Play.PlayerSettings useAverageButton = new SettingsButton { Text = BeatmapOffsetControlStrings.CalibrateUsingLastPlay, - Action = () => Current.Value = lastPlayAverage + Action = () => Current.Value = -lastPlayAverage }, }); } From 2767dda9d63bf11a9eff75737cd47b53bb705649 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 1 Mar 2022 20:21:32 +0300 Subject: [PATCH 0896/5199] Add failing test case --- .../Visual/Ranking/TestSceneResultsScreen.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 167acc94c4..cc380df183 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -17,10 +17,13 @@ using osu.Game.Database; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; +using osu.Game.Screens.Ranking.Expanded.Statistics; using osu.Game.Screens.Ranking.Statistics; using osu.Game.Tests.Resources; using osuTK; @@ -256,6 +259,23 @@ namespace osu.Game.Tests.Visual.Ranking AddAssert("download button is enabled", () => screen.ChildrenOfType().Last().Enabled.Value); } + [Test] + public void TestRulesetWithNoPerformanceCalculator() + { + var ruleset = new RulesetWithNoPerformanceCalculator(); + var score = TestResources.CreateTestScoreInfo(ruleset.RulesetInfo); + + AddStep("load results", () => Child = new TestResultsContainer(createResultsScreen(score))); + AddUntilStep("wait for load", () => this.ChildrenOfType().Single().AllPanelsVisible); + + AddAssert("PP displayed as 0", () => + { + var performance = this.ChildrenOfType().Single(); + var counter = performance.ChildrenOfType().Single(); + return counter.Current.Value == 0; + }); + } + private TestResultsScreen createResultsScreen(ScoreInfo score = null) => new TestResultsScreen(score ?? TestResources.CreateTestScoreInfo()); private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(TestResources.CreateTestScoreInfo()); @@ -367,5 +387,10 @@ namespace osu.Game.Tests.Visual.Ranking RetryOverlay = InternalChildren.OfType().SingleOrDefault(); } } + + private class RulesetWithNoPerformanceCalculator : OsuRuleset + { + public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => null; + } } } From 97c54de3bff4787aa8161b4a33dd53500dd3bfdc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 1 Mar 2022 20:43:20 +0300 Subject: [PATCH 0897/5199] Fix performance statistic not handling rulesets with unimplemented calculator --- .../Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index 859b42d66d..95f017d625 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics else { performanceCache.CalculatePerformanceAsync(score, cancellationTokenSource.Token) - .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely().Total)), cancellationTokenSource.Token); + .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely()?.Total)), cancellationTokenSource.Token); } } From c9b205afeb64f721e228ee51adb8eabc0cb93327 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 1 Mar 2022 21:12:06 +0800 Subject: [PATCH 0898/5199] Add adaptive speed mod --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 1 + osu.Game.Rulesets.Osu/OsuRuleset.cs | 1 + osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 1 + osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 122 +++++++++++++++++++++ osu.Game/Rulesets/Mods/ModRateAdjust.cs | 2 +- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 +- 6 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 859b6cfe76..f139a88f50 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -258,6 +258,7 @@ namespace osu.Game.Rulesets.Mania { new MultiMod(new ModWindUp(), new ModWindDown()), new ManiaModMuted(), + new ModAdaptiveSpeed() }; default: diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 5ade164566..5b936b1bf1 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -195,6 +195,7 @@ namespace osu.Game.Rulesets.Osu new OsuModMuted(), new OsuModNoScope(), new OsuModAimAssist(), + new ModAdaptiveSpeed() }; case ModType.System: diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index de0ef8d95b..dc90845d92 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -151,6 +151,7 @@ namespace osu.Game.Rulesets.Taiko { new MultiMod(new ModWindUp(), new ModWindDown()), new TaikoModMuted(), + new ModAdaptiveSpeed() }; default: diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs new file mode 100644 index 0000000000..1c6fbe88cc --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -0,0 +1,122 @@ +// 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.Audio; +using osu.Framework.Audio.Track; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Audio; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Mods +{ + public class ModAdaptiveSpeed : Mod, IApplicableToRate, IApplicableToDrawableHitObject, IApplicableToBeatmap + { + private const double fastest_rate = 2f; + + private const double slowest_rate = 0.5f; + + /// + /// Adjust track rate using the average speed of the last x hits + /// + private const int average_count = 10; + + public override string Name => "Adaptive Speed"; + + public override string Acronym => "AS"; + + public override string Description => "Let track speed adapt to you."; + + public override ModType Type => ModType.Fun; + + public override double ScoreMultiplier => 1; + + public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModTimeRamp) }; + + [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] + public BindableBool AdjustPitch { get; } = new BindableBool + { + Default = true, + Value = true + }; + + public BindableNumber SpeedChange { get; } = new BindableDouble + { + Default = 1, + Value = 1, + Precision = 0.01, + }; + + private ITrack track; + + private readonly List recentRates = Enumerable.Range(0, average_count).Select(x => 1d).ToList(); + + // rates are calculated using the end time of the previous hit object + // caching them here for easy access + private readonly Dictionary previousEndTimes = new Dictionary(); + + public ModAdaptiveSpeed() + { + AdjustPitch.BindValueChanged(applyPitchAdjustment); + } + + public void ApplyToTrack(ITrack track) + { + this.track = track; + + AdjustPitch.TriggerChange(); + } + + public void ApplyToSample(DrawableSample sample) + { + sample.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); + } + + public double ApplyToRate(double time, double rate = 1) => rate; + + private void applyPitchAdjustment(ValueChangedEvent adjustPitchSetting) + { + // remove existing old adjustment + track?.RemoveAdjustment(adjustmentForPitchSetting(adjustPitchSetting.OldValue), SpeedChange); + + track?.AddAdjustment(adjustmentForPitchSetting(adjustPitchSetting.NewValue), SpeedChange); + } + + private AdjustableProperty adjustmentForPitchSetting(bool adjustPitchSettingValue) + => adjustPitchSettingValue ? AdjustableProperty.Frequency : AdjustableProperty.Tempo; + + public void ApplyToDrawableHitObject(DrawableHitObject drawable) + { + drawable.OnNewResult += (o, result) => + { + if (!result.IsHit) return; + if (!previousEndTimes.ContainsKey(result.HitObject)) return; + + double prevEndTime = previousEndTimes[result.HitObject]; + + recentRates.Add(Math.Clamp((result.HitObject.GetEndTime() - prevEndTime) / (result.TimeAbsolute - prevEndTime) * SpeedChange.Value, slowest_rate, fastest_rate)); + if (recentRates.Count > average_count) + recentRates.RemoveAt(0); + + SpeedChange.Value = recentRates.Average(); + }; + } + + public void ApplyToBeatmap(IBeatmap beatmap) + { + for (int i = 1; i < beatmap.HitObjects.Count; i++) + { + var hitObject = beatmap.HitObjects[i]; + var previousObject = beatmap.HitObjects.Take(i).LastOrDefault(o => !Precision.AlmostBigger(o.GetEndTime(), hitObject.GetEndTime())); + + if (previousObject != null) + previousEndTimes.Add(hitObject, previousObject.GetEndTime()); + } + } + } +} diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index e66650f7b4..ebe18f2188 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Mods public double ApplyToRate(double time, double rate) => rate * SpeedChange.Value; - public override Type[] IncompatibleMods => new[] { typeof(ModTimeRamp) }; + public override Type[] IncompatibleMods => new[] { typeof(ModTimeRamp), typeof(ModAdaptiveSpeed) }; public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:N2}x"; } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index b5cd64dafa..b6b2decede 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] public abstract BindableBool AdjustPitch { get; } - public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust) }; + public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModAdaptiveSpeed) }; public override string SettingDescription => $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x"; From 783f43ccfbc53199f7f88b37c4bea8cbd486345f Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 1 Mar 2022 21:50:17 +0800 Subject: [PATCH 0899/5199] Add initial rate setting --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 1c6fbe88cc..8847420995 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -38,6 +38,16 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModTimeRamp) }; + [SettingSource("Initial rate", "The starting speed of the track")] + public BindableNumber InitialRate { get; } = new BindableDouble + { + MinValue = 0.5, + MaxValue = 2, + Default = 1, + Value = 1, + Precision = 0.01, + }; + [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] public BindableBool AdjustPitch { get; } = new BindableBool { @@ -54,7 +64,7 @@ namespace osu.Game.Rulesets.Mods private ITrack track; - private readonly List recentRates = Enumerable.Range(0, average_count).Select(x => 1d).ToList(); + private readonly List recentRates = Enumerable.Range(0, average_count).Select(_ => 1d).ToList(); // rates are calculated using the end time of the previous hit object // caching them here for easy access @@ -62,6 +72,7 @@ namespace osu.Game.Rulesets.Mods public ModAdaptiveSpeed() { + InitialRate.BindValueChanged(val => SpeedChange.Value = val.NewValue); AdjustPitch.BindValueChanged(applyPitchAdjustment); } @@ -69,7 +80,10 @@ namespace osu.Game.Rulesets.Mods { this.track = track; + InitialRate.TriggerChange(); AdjustPitch.TriggerChange(); + recentRates.Clear(); + recentRates.AddRange(Enumerable.Range(0, average_count).Select(_ => InitialRate.Value)); } public void ApplyToSample(DrawableSample sample) From c6934b4bce3d6a68ff82da111dacb38731bde479 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Mar 2022 09:53:28 +0800 Subject: [PATCH 0900/5199] Improve adaptive speed algorithm and add rewind support --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 63 ++++++++++++++++++---- 1 file changed, 53 insertions(+), 10 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 8847420995..9a6705ea09 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -1,5 +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 System.Linq; @@ -12,14 +13,15 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mods { public class ModAdaptiveSpeed : Mod, IApplicableToRate, IApplicableToDrawableHitObject, IApplicableToBeatmap { - private const double fastest_rate = 2f; - - private const double slowest_rate = 0.5f; + // use a wider range so there's still room for adjustment when the initial rate is extreme + private const double fastest_rate = 2.5f; + private const double slowest_rate = 0.4f; /// /// Adjust track rate using the average speed of the last x hits @@ -45,7 +47,7 @@ namespace osu.Game.Rulesets.Mods MaxValue = 2, Default = 1, Value = 1, - Precision = 0.01, + Precision = 0.01 }; [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] @@ -59,17 +61,21 @@ namespace osu.Game.Rulesets.Mods { Default = 1, Value = 1, - Precision = 0.01, + Precision = 0.01 }; private ITrack track; private readonly List recentRates = Enumerable.Range(0, average_count).Select(_ => 1d).ToList(); - // rates are calculated using the end time of the previous hit object + // rate for a hit is calculated using the end time of another hit object earlier in time // caching them here for easy access private readonly Dictionary previousEndTimes = new Dictionary(); + // record the value removed from recentRates when an object is hit + // for rewind support + private readonly Dictionary dequeuedRates = new Dictionary(); + public ModAdaptiveSpeed() { InitialRate.BindValueChanged(val => SpeedChange.Value = val.NewValue); @@ -91,7 +97,7 @@ namespace osu.Game.Rulesets.Mods sample.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); } - public double ApplyToRate(double time, double rate = 1) => rate; + public double ApplyToRate(double time, double rate = 1) => rate * InitialRate.Value; private void applyPitchAdjustment(ValueChangedEvent adjustPitchSetting) { @@ -114,8 +120,31 @@ namespace osu.Game.Rulesets.Mods double prevEndTime = previousEndTimes[result.HitObject]; recentRates.Add(Math.Clamp((result.HitObject.GetEndTime() - prevEndTime) / (result.TimeAbsolute - prevEndTime) * SpeedChange.Value, slowest_rate, fastest_rate)); + if (recentRates.Count > average_count) + { + dequeuedRates.Add(result.HitObject, recentRates[0]); recentRates.RemoveAt(0); + } + + SpeedChange.Value = recentRates.Average(); + }; + drawable.OnRevertResult += (o, result) => + { + if (!result.IsHit) return; + if (!previousEndTimes.ContainsKey(result.HitObject)) return; + + if (dequeuedRates.ContainsKey(result.HitObject)) + { + recentRates.Insert(0, dequeuedRates[result.HitObject]); + recentRates.RemoveAt(recentRates.Count - 1); + dequeuedRates.Remove(result.HitObject); + } + else + { + recentRates.Insert(0, InitialRate.Value); + recentRates.RemoveAt(recentRates.Count - 1); + } SpeedChange.Value = recentRates.Average(); }; @@ -123,13 +152,27 @@ namespace osu.Game.Rulesets.Mods public void ApplyToBeatmap(IBeatmap beatmap) { + var endTimes = getEndTimes(beatmap.HitObjects).OrderBy(x => x).ToList(); + for (int i = 1; i < beatmap.HitObjects.Count; i++) { var hitObject = beatmap.HitObjects[i]; - var previousObject = beatmap.HitObjects.Take(i).LastOrDefault(o => !Precision.AlmostBigger(o.GetEndTime(), hitObject.GetEndTime())); + double prevEndTime = endTimes.LastOrDefault(ht => !Precision.AlmostBigger(ht, hitObject.GetEndTime())); - if (previousObject != null) - previousEndTimes.Add(hitObject, previousObject.GetEndTime()); + if (prevEndTime != default) + previousEndTimes.Add(hitObject, prevEndTime); + } + } + + private IEnumerable getEndTimes(IEnumerable hitObjects) + { + foreach (var hitObject in hitObjects) + { + if (!(hitObject.HitWindows is HitWindows.EmptyHitWindows)) + yield return hitObject.GetEndTime(); + + foreach (double hitTime in getEndTimes(hitObject.NestedHitObjects)) + yield return hitTime; } } } From c342030b2c084790724021aca3b0b3557c54fb76 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 14:10:59 +0900 Subject: [PATCH 0901/5199] Add specific placeholder message for custom rulesets rather than showing network error --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 3 ++- osu.Game/Online/Leaderboards/Leaderboard.cs | 5 ++++- osu.Game/Online/Leaderboards/LeaderboardState.cs | 3 ++- .../Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 11 +++++++++-- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 31bd3a203c..1ed6648131 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -119,7 +119,8 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep(@"Network failure", () => leaderboard.SetErrorState(LeaderboardState.NetworkFailure)); AddStep(@"No supporter", () => leaderboard.SetErrorState(LeaderboardState.NotSupporter)); AddStep(@"Not logged in", () => leaderboard.SetErrorState(LeaderboardState.NotLoggedIn)); - AddStep(@"Unavailable", () => leaderboard.SetErrorState(LeaderboardState.Unavailable)); + AddStep(@"Ruleset unavailable", () => leaderboard.SetErrorState(LeaderboardState.RulesetUnavailable)); + AddStep(@"Beatmap unavailable", () => leaderboard.SetErrorState(LeaderboardState.BeatmapUnavailable)); AddStep(@"None selected", () => leaderboard.SetErrorState(LeaderboardState.NoneSelected)); } diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 5dd3e46b4a..dde53c39e4 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -319,7 +319,10 @@ namespace osu.Game.Online.Leaderboards case LeaderboardState.NoneSelected: return new MessagePlaceholder(@"Please select a beatmap!"); - case LeaderboardState.Unavailable: + case LeaderboardState.RulesetUnavailable: + return new MessagePlaceholder(@"Leaderboards are not available for this ruleset!"); + + case LeaderboardState.BeatmapUnavailable: return new MessagePlaceholder(@"Leaderboards are not available for this beatmap!"); case LeaderboardState.NoScores: diff --git a/osu.Game/Online/Leaderboards/LeaderboardState.cs b/osu.Game/Online/Leaderboards/LeaderboardState.cs index 75e2c6e6db..6b07500a98 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardState.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardState.cs @@ -8,7 +8,8 @@ namespace osu.Game.Online.Leaderboards Success, Retrieving, NetworkFailure, - Unavailable, + BeatmapUnavailable, + RulesetUnavailable, NoneSelected, NoScores, NotLoggedIn, diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 907a2c9bda..6daaae9d04 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -98,6 +98,7 @@ namespace osu.Game.Screens.Select.Leaderboards protected override APIRequest FetchScores(CancellationToken cancellationToken) { var fetchBeatmapInfo = BeatmapInfo; + var fetchRuleset = ruleset.Value ?? fetchBeatmapInfo.Ruleset; if (fetchBeatmapInfo == null) { @@ -117,9 +118,15 @@ namespace osu.Game.Screens.Select.Leaderboards return null; } + if (fetchRuleset.OnlineID <= 0 || fetchRuleset.OnlineID > ILegacyRuleset.MAX_LEGACY_RULESET_ID) + { + SetErrorState(LeaderboardState.RulesetUnavailable); + return null; + } + if (fetchBeatmapInfo.OnlineID <= 0 || fetchBeatmapInfo.Status <= BeatmapOnlineStatus.Pending) { - SetErrorState(LeaderboardState.Unavailable); + SetErrorState(LeaderboardState.BeatmapUnavailable); return null; } @@ -137,7 +144,7 @@ namespace osu.Game.Screens.Select.Leaderboards else if (filterMods) requestMods = mods.Value; - var req = new GetScoresRequest(fetchBeatmapInfo, ruleset.Value ?? fetchBeatmapInfo.Ruleset, Scope, requestMods); + var req = new GetScoresRequest(fetchBeatmapInfo, fetchRuleset, Scope, requestMods); req.Success += r => { From d4a2645510d2a7e133d60a664d7950244e9dedaa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 14:14:44 +0900 Subject: [PATCH 0902/5199] Add localisation support for leaderboard error text --- osu.Game/Localisation/LeaderboardStrings.cs | 49 +++++++++++++++++++ osu.Game/Online/Leaderboards/Leaderboard.cs | 15 +++--- .../Online/Placeholders/LoginPlaceholder.cs | 3 +- 3 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Localisation/LeaderboardStrings.cs diff --git a/osu.Game/Localisation/LeaderboardStrings.cs b/osu.Game/Localisation/LeaderboardStrings.cs new file mode 100644 index 0000000000..8e53f8e88c --- /dev/null +++ b/osu.Game/Localisation/LeaderboardStrings.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.Localisation; + +namespace osu.Game.Localisation +{ + public static class LeaderboardStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.Leaderboard"; + + /// + /// "Couldn't fetch scores!" + /// + public static LocalisableString CouldntFetchScores => new TranslatableString(getKey(@"couldnt_fetch_scores"), @"Couldn't fetch scores!"); + + /// + /// "Please select a beatmap!" + /// + public static LocalisableString PleaseSelectABeatmap => new TranslatableString(getKey(@"please_select_a_beatmap"), @"Please select a beatmap!"); + + /// + /// "Leaderboards are not available for this ruleset!" + /// + public static LocalisableString LeaderboardsAreNotAvailableForThisRuleset => new TranslatableString(getKey(@"leaderboards_are_not_available_for_this_ruleset"), @"Leaderboards are not available for this ruleset!"); + + /// + /// "Leaderboards are not available for this beatmap!" + /// + public static LocalisableString LeaderboardsAreNotAvailableForThisBeatmap => new TranslatableString(getKey(@"leaderboards_are_not_available_for_this_beatmap"), @"Leaderboards are not available for this beatmap!"); + + /// + /// "No records yet!" + /// + public static LocalisableString NoRecordsYet => new TranslatableString(getKey(@"no_records_yet"), @"No records yet!"); + + /// + /// "Please sign in to view online leaderboards!" + /// + public static LocalisableString PleaseSignInToViewOnlineLeaderboards => new TranslatableString(getKey(@"please_sign_in_to_view_online_leaderboards"), @"Please sign in to view online leaderboards!"); + + /// + /// "Please invest in an osu!supporter tag to view this leaderboard!" + /// + public static LocalisableString PleaseInvestInAnOsuSupporterTagToViewThisLeaderboard => new TranslatableString(getKey(@"please_invest_in_an_osu_supporter_tag_to_view_this_leaderboard"), @"Please invest in an osu!supporter tag to view this leaderboard!"); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index dde53c39e4..c94a6d3361 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -22,6 +22,7 @@ using osu.Game.Online.API; using osu.Game.Online.Placeholders; using osuTK; using osuTK.Graphics; +using osu.Game.Localisation; namespace osu.Game.Online.Leaderboards { @@ -311,28 +312,28 @@ namespace osu.Game.Online.Leaderboards switch (state) { case LeaderboardState.NetworkFailure: - return new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync) + return new ClickablePlaceholder(LeaderboardStrings.CouldntFetchScores, FontAwesome.Solid.Sync) { Action = RefetchScores }; case LeaderboardState.NoneSelected: - return new MessagePlaceholder(@"Please select a beatmap!"); + return new MessagePlaceholder(LeaderboardStrings.PleaseSelectABeatmap); case LeaderboardState.RulesetUnavailable: - return new MessagePlaceholder(@"Leaderboards are not available for this ruleset!"); + return new MessagePlaceholder(LeaderboardStrings.LeaderboardsAreNotAvailableForThisRuleset); case LeaderboardState.BeatmapUnavailable: - return new MessagePlaceholder(@"Leaderboards are not available for this beatmap!"); + return new MessagePlaceholder(LeaderboardStrings.LeaderboardsAreNotAvailableForThisBeatmap); case LeaderboardState.NoScores: - return new MessagePlaceholder(@"No records yet!"); + return new MessagePlaceholder(LeaderboardStrings.NoRecordsYet); case LeaderboardState.NotLoggedIn: - return new LoginPlaceholder(@"Please sign in to view online leaderboards!"); + return new LoginPlaceholder(LeaderboardStrings.PleaseSignInToViewOnlineLeaderboards); case LeaderboardState.NotSupporter: - return new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!"); + return new MessagePlaceholder(LeaderboardStrings.PleaseInvestInAnOsuSupporterTagToViewThisLeaderboard); case LeaderboardState.Retrieving: return null; diff --git a/osu.Game/Online/Placeholders/LoginPlaceholder.cs b/osu.Game/Online/Placeholders/LoginPlaceholder.cs index f8a326a52e..d03b3d8ffc 100644 --- a/osu.Game/Online/Placeholders/LoginPlaceholder.cs +++ b/osu.Game/Online/Placeholders/LoginPlaceholder.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Overlays; namespace osu.Game.Online.Placeholders @@ -12,7 +13,7 @@ namespace osu.Game.Online.Placeholders [Resolved(CanBeNull = true)] private LoginOverlay login { get; set; } - public LoginPlaceholder(string actionMessage) + public LoginPlaceholder(LocalisableString actionMessage) : base(actionMessage, FontAwesome.Solid.UserLock) { Action = () => login?.Show(); From c07f7545653edc5efd3f4f90b1faa292774cc9b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 14:34:24 +0900 Subject: [PATCH 0903/5199] Enable `nullable` on `BeatmapOffsetControl` --- .../PlayerSettings/BeatmapOffsetControl.cs | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index e6bc510564..98820cabf8 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -23,6 +23,8 @@ using osuTK; using osu.Game.Localisation; using osu.Game.Rulesets.Mods; +#nullable enable + namespace osu.Game.Screens.Play.PlayerSettings { public class BeatmapOffsetControl : CompositeDrawable @@ -38,22 +40,24 @@ namespace osu.Game.Screens.Play.PlayerSettings Precision = 0.1, }; - private SettingsButton useAverageButton; + private readonly FillFlowContainer referenceScoreContainer; + + [Resolved] + private RealmAccess realm { get; set; } = null!; + + [Resolved] + private IBindable beatmap { get; set; } = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; private double lastPlayAverage; - private readonly FillFlowContainer referenceScoreContainer; + private SettingsButton? useAverageButton; - private IDisposable beatmapOffsetSubscription; + private IDisposable? beatmapOffsetSubscription; - [Resolved] - private RealmAccess realm { get; set; } - - [Resolved] - private IBindable beatmap { get; set; } - - [Resolved] - private OsuColour colours { get; set; } + private Task? realmWriteTask; public BeatmapOffsetControl() { @@ -113,8 +117,6 @@ namespace osu.Game.Screens.Play.PlayerSettings Current.BindValueChanged(currentChanged); } - private Task realmWrite; - private void currentChanged(ValueChangedEvent offset) { Scheduler.AddOnce(updateOffset); @@ -122,7 +124,7 @@ namespace osu.Game.Screens.Play.PlayerSettings void updateOffset() { // ensure the previous write has completed. ignoring performance concerns, if we don't do this, the async writes could be out of sequence. - if (realmWrite?.IsCompleted == false) + if (realmWriteTask?.IsCompleted == false) { Scheduler.AddOnce(updateOffset); return; @@ -131,7 +133,7 @@ namespace osu.Game.Screens.Play.PlayerSettings if (useAverageButton != null) useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, Current.Value, Current.Precision); - realmWrite = realm.WriteAsync(r => + realmWriteTask = realm.WriteAsync(r => { var settings = r.Find(beatmap.Value.BeatmapInfo.ID)?.UserSettings; From 3cbcb702f6f4aa9d3d165f63735d6c57b2cf829c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 14:36:15 +0900 Subject: [PATCH 0904/5199] Fix calibration button disabled state not checking in corrrect direction --- osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs | 1 + osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs index 42b579bc89..67f5db548b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs @@ -66,6 +66,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Press button", () => offsetControl.ChildrenOfType().Single().TriggerClick()); AddAssert("Offset is adjusted", () => offsetControl.Current.Value == -average_error); + AddAssert("Button is disabled", () => !offsetControl.ChildrenOfType().Single().Enabled.Value); AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null); AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); } diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 98820cabf8..ca683cec66 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -131,7 +131,7 @@ namespace osu.Game.Screens.Play.PlayerSettings } if (useAverageButton != null) - useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, Current.Value, Current.Precision); + useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, -Current.Value, Current.Precision); realmWriteTask = realm.WriteAsync(r => { From 8bd66f1ed7302577801926dc55baeeac4d1720d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 14:36:49 +0900 Subject: [PATCH 0905/5199] Fix incorrect precision specification for button disable check --- osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index ca683cec66..63045013b5 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -131,7 +131,7 @@ namespace osu.Game.Screens.Play.PlayerSettings } if (useAverageButton != null) - useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, -Current.Value, Current.Precision); + useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, -Current.Value, Current.Precision / 2); realmWriteTask = realm.WriteAsync(r => { From e184b26cdd67101488bbbcdf8c03940997a13da3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 14:39:28 +0900 Subject: [PATCH 0906/5199] Remove `Precision` call for database write shortcutting Shouldn't be required. --- osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 63045013b5..f19a326cdf 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -140,7 +140,7 @@ namespace osu.Game.Screens.Play.PlayerSettings if (settings == null) // only the case for tests. return; - if (Precision.AlmostEquals(settings.Offset, Current.Value)) + if (settings.Offset == Current.Value) return; settings.Offset = Current.Value; From 763f881d4a0ade9930cfffafb974883c6f6e2385 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 14:42:08 +0900 Subject: [PATCH 0907/5199] Use more correct mod check to encompass more than just autoplay --- osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index f19a326cdf..dc3e80d695 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -15,13 +15,12 @@ using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Localisation; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; using osuTK; -using osu.Game.Localisation; -using osu.Game.Rulesets.Mods; #nullable enable @@ -155,7 +154,7 @@ namespace osu.Game.Screens.Play.PlayerSettings if (score.NewValue == null) return; - if (score.NewValue.Mods.Any(m => m is ModAutoplay)) + if (score.NewValue.Mods.Any(m => !m.UserPlayable)) return; var hitEvents = score.NewValue.HitEvents; From ed9ecd695114e1afabd51213767a206798791c92 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 14:45:39 +0900 Subject: [PATCH 0908/5199] Fix test scene failures by ensuring that first `GameplayClock` frame has processed first --- osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index e03c8d7561..b195d2aa74 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -131,9 +131,9 @@ namespace osu.Game.Tests.Visual.Gameplay public double GameplayClockTime => GameplayClockContainer.GameplayClock.CurrentTime; - protected override void Update() + protected override void UpdateAfterChildren() { - base.Update(); + base.UpdateAfterChildren(); if (!FirstFrameClockTime.HasValue) { From 411252e004f15f51488b67cab5909372f99490d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 17:01:27 +0900 Subject: [PATCH 0909/5199] Replace squirrel fork with `Clowd.Squirrel` --- osu.Desktop/osu.Desktop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index b1117bf796..32ead231c7 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -24,10 +24,10 @@ + - all From 6a4d731eb3a94c991cdea9a4f461ead85f31d5d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 17:11:24 +0900 Subject: [PATCH 0910/5199] Update obsolete usages in line with `Clowd.Squirrel` changes --- osu.Desktop/OsuGameDesktop.cs | 3 +++ osu.Desktop/Updater/SquirrelUpdateManager.cs | 18 ++++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index cd3fb7eb61..be8159a7cc 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -96,6 +97,8 @@ namespace osu.Desktop switch (RuntimeInfo.OS) { case RuntimeInfo.Platform.Windows: + Debug.Assert(OperatingSystem.IsWindows()); + return new SquirrelUpdateManager(); default: diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 7b60bc03e4..b307146b10 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Runtime.Versioning; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -16,10 +17,11 @@ using osu.Game.Overlays.Notifications; using osuTK; using osuTK.Graphics; using Squirrel; -using LogLevel = Splat.LogLevel; +using Squirrel.SimpleSplat; namespace osu.Desktop.Updater { + [SupportedOSPlatform("windows")] public class SquirrelUpdateManager : osu.Game.Updater.UpdateManager { private UpdateManager updateManager; @@ -34,12 +36,14 @@ namespace osu.Desktop.Updater /// private bool updatePending; + private readonly SquirrelLogger squirrelLogger = new SquirrelLogger(); + [BackgroundDependencyLoader] private void load(NotificationOverlay notification) { notificationOverlay = notification; - Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger)); + SquirrelLocator.CurrentMutable.Register(() => squirrelLogger, typeof(ILogger)); } protected override async Task PerformUpdateCheck() => await checkForUpdateAsync().ConfigureAwait(false); @@ -49,9 +53,11 @@ namespace osu.Desktop.Updater // should we schedule a retry on completion of this check? bool scheduleRecheck = true; + const string github_token = null; // TODO: populate. + try { - updateManager ??= await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true).ConfigureAwait(false); + updateManager ??= new GithubUpdateManager(@"https://github.com/ppy/osu", false, github_token, @"osulazer"); var info = await updateManager.CheckForUpdate(!useDeltaPatching).ConfigureAwait(false); @@ -201,11 +207,11 @@ namespace osu.Desktop.Updater } } - private class SquirrelLogger : Splat.ILogger, IDisposable + private class SquirrelLogger : ILogger, IDisposable { - public LogLevel Level { get; set; } = LogLevel.Info; + public Squirrel.SimpleSplat.LogLevel Level { get; set; } = Squirrel.SimpleSplat.LogLevel.Info; - public void Write(string message, LogLevel logLevel) + public void Write(string message, Squirrel.SimpleSplat.LogLevel logLevel) { if (logLevel < Level) return; From 1c705f3b331e4fab9e33f4465ab3a4611c4f5916 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 17:31:45 +0900 Subject: [PATCH 0911/5199] Mark `osu.Desktop` as squirrel-aware --- osu.Desktop/app.manifest | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Desktop/app.manifest b/osu.Desktop/app.manifest index 2e9127bf44..a11cee132c 100644 --- a/osu.Desktop/app.manifest +++ b/osu.Desktop/app.manifest @@ -1,6 +1,7 @@ + 1 @@ -17,4 +18,4 @@ true - \ No newline at end of file + From 3aa2d4548ab40cca9b023a913c49719a43a57c6e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 17:54:33 +0900 Subject: [PATCH 0912/5199] Add startup squirrel icon/association handling --- osu.Desktop/Program.cs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index b944068e78..0e11e172e1 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; using osu.Desktop.LegacyIpc; @@ -12,6 +13,7 @@ using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.IPC; using osu.Game.Tournament; +using Squirrel; namespace osu.Desktop { @@ -24,6 +26,10 @@ namespace osu.Desktop [STAThread] public static void Main(string[] args) { + // run Squirrel first, as the app may exit after these run + if (OperatingSystem.IsWindows()) + setupSquirrel(); + // Back up the cwd before DesktopGameHost changes it string cwd = Environment.CurrentDirectory; @@ -104,6 +110,25 @@ namespace osu.Desktop } } + [SupportedOSPlatform("windows")] + private static void setupSquirrel() + { + SquirrelAwareApp.HandleEvents(onInitialInstall: (version, tools) => + { + tools.CreateShortcutForThisExe(); + }, onAppUninstall: (version, tools) => + { + tools.RemoveShortcutForThisExe(); + tools.RemoveUninstallerRegistryEntry(); + }, onEveryRun: (version, tools, firstRun) => + { + tools.SetProcessAppUserModelId(); + + if (firstRun) + tools.CreateUninstallerRegistryEntry(); + }); + } + private static int allowableExceptions = DebugUtils.IsDebugBuild ? 0 : 1; /// From c06703d662b1b887330e28bee4c439c48d913d8b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 18:29:07 +0900 Subject: [PATCH 0913/5199] Add ability to select which display the game runs on --- .../Localisation/GraphicsSettingsStrings.cs | 5 +++ .../Sections/Graphics/LayoutSettings.cs | 33 ++++++++++++++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/osu.Game/Localisation/GraphicsSettingsStrings.cs b/osu.Game/Localisation/GraphicsSettingsStrings.cs index 996a1350eb..1c9aa64df5 100644 --- a/osu.Game/Localisation/GraphicsSettingsStrings.cs +++ b/osu.Game/Localisation/GraphicsSettingsStrings.cs @@ -54,6 +54,11 @@ namespace osu.Game.Localisation /// public static LocalisableString Resolution => new TranslatableString(getKey(@"resolution"), @"Resolution"); + /// + /// "Display" + /// + public static LocalisableString Display => new TranslatableString(getKey(@"display"), @"Display"); + /// /// "UI scaling" /// diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index adf1453d1a..a1688b87cc 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private FillFlowContainer> scalingSettings; - private readonly IBindable currentDisplay = new Bindable(); + private readonly Bindable currentDisplay = new Bindable(); private readonly IBindableList windowModes = new BindableList(); private Bindable scalingMode; @@ -39,6 +39,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private OsuGameBase game { get; set; } private SettingsDropdown resolutionDropdown; + private SettingsDropdown displayDropdown; private SettingsDropdown windowModeDropdown; private Bindable scalingPositionX; @@ -72,6 +73,12 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics ItemSource = windowModes, Current = config.GetBindable(FrameworkSetting.WindowMode), }, + displayDropdown = new DisplaySettingsDropdown + { + LabelText = GraphicsSettingsStrings.Display, + Items = host.Window?.Displays, + Current = currentDisplay, + }, resolutionDropdown = new ResolutionSettingsDropdown { LabelText = GraphicsSettingsStrings.Resolution, @@ -142,7 +149,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics windowModeDropdown.Current.BindValueChanged(mode => { - updateResolutionDropdown(); + updateFullscreenDropdowns(); windowModeDropdown.WarningText = mode.NewValue != WindowMode.Fullscreen ? GraphicsSettingsStrings.NotFullscreenNote : default; }, true); @@ -168,7 +175,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics .Distinct()); } - updateResolutionDropdown(); + updateFullscreenDropdowns(); }), true); scalingMode.BindValueChanged(mode => @@ -183,12 +190,17 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics // initial update bypasses transforms updateScalingModeVisibility(); - void updateResolutionDropdown() + void updateFullscreenDropdowns() { if (resolutions.Count > 1 && windowModeDropdown.Current.Value == WindowMode.Fullscreen) resolutionDropdown.Show(); else resolutionDropdown.Hide(); + + if (displayDropdown.Items.Count() > 1) + displayDropdown.Show(); + else + displayDropdown.Hide(); } void updateScalingModeVisibility() @@ -243,6 +255,19 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics public override LocalisableString TooltipText => base.TooltipText + "x"; } + private class DisplaySettingsDropdown : SettingsDropdown + { + protected override OsuDropdown CreateDropdown() => new DisplaySettingsDropdownControl(); + + private class DisplaySettingsDropdownControl : DropdownControl + { + protected override LocalisableString GenerateItemText(Display item) + { + return $"{item.Index}: {item.Name} ({item.Bounds.Width}x{item.Bounds.Height})"; + } + } + } + private class ResolutionSettingsDropdown : SettingsDropdown { protected override OsuDropdown CreateDropdown() => new ResolutionDropdownControl(); From 1a474592625ccc97b47b11bdb953dcdbf3fdd8a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 18:38:17 +0900 Subject: [PATCH 0914/5199] Fix taiko difficulty adjust scroll speed being shown with too low precision --- osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs index 9540e35780..99a064d35f 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Mods { get { - string scrollSpeed = ScrollSpeed.IsDefault ? string.Empty : $"Scroll x{ScrollSpeed.Value:N1}"; + string scrollSpeed = ScrollSpeed.IsDefault ? string.Empty : $"Scroll x{ScrollSpeed.Value:N2}"; return string.Join(", ", new[] { From f15b8781bb92e9d74c89357a10b32e77a0de84b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 19:19:39 +0900 Subject: [PATCH 0915/5199] Move editor mode selector out of `EditorMenuBar` to allow for better reuse --- .../Editing/TestSceneEditorScreenModes.cs | 4 +- .../Edit/Components/Menus/EditorMenuBar.cs | 22 ----- osu.Game/Screens/Edit/Editor.cs | 82 +++++++++++-------- 3 files changed, 47 insertions(+), 61 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorScreenModes.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorScreenModes.cs index 98d8a41674..2efd125f81 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorScreenModes.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorScreenModes.cs @@ -4,11 +4,9 @@ using System; using System.Linq; using NUnit.Framework; -using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Components.Menus; namespace osu.Game.Tests.Visual.Editing { @@ -22,7 +20,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("switch between all screens at once", () => { foreach (var screen in Enum.GetValues(typeof(EditorScreenMode)).Cast()) - Editor.ChildrenOfType().Single().Mode.Value = screen; + Editor.Mode.Value = screen; }); } } diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs index c6787a1fb1..2a8435ff47 100644 --- a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -18,8 +17,6 @@ namespace osu.Game.Screens.Edit.Components.Menus { public class EditorMenuBar : OsuMenu { - public readonly Bindable Mode = new Bindable(); - public EditorMenuBar() : base(Direction.Horizontal, true) { @@ -28,25 +25,6 @@ namespace osu.Game.Screens.Edit.Components.Menus MaskingContainer.CornerRadius = 0; ItemsContainer.Padding = new MarginPadding { Left = 100 }; BackgroundColour = Color4Extensions.FromHex("111"); - - ScreenSelectionTabControl tabControl; - AddRangeInternal(new Drawable[] - { - tabControl = new ScreenSelectionTabControl - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - X = -15 - } - }); - - Mode.BindTo(tabControl.Current); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - Mode.TriggerChange(); } protected override Framework.Graphics.UserInterface.Menu CreateSubMenu() => new SubMenu(); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index c2775ae101..dcb7e3a282 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -89,6 +89,8 @@ namespace osu.Game.Screens.Edit [Resolved(canBeNull: true)] private NotificationOverlay notifications { get; set; } + public readonly Bindable Mode = new Bindable(); + public IBindable SamplePlaybackDisabled => samplePlaybackDisabled; private readonly Bindable samplePlaybackDisabled = new Bindable(); @@ -115,8 +117,6 @@ namespace osu.Game.Screens.Edit [CanBeNull] // Should be non-null once it can support custom rulesets. private EditorChangeHandler changeHandler; - private EditorMenuBar menuBar; - private DependencyContainer dependencies; private TestGameplayButton testGameplayButton; @@ -239,40 +239,49 @@ namespace osu.Game.Screens.Edit Name = "Top bar", RelativeSizeAxes = Axes.X, Height = 40, - Child = menuBar = new EditorMenuBar + Children = new Drawable[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - Mode = { Value = isNewBeatmap ? EditorScreenMode.SongSetup : EditorScreenMode.Compose }, - Items = new[] + new EditorMenuBar { - new MenuItem("File") + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Items = new[] { - Items = createFileMenuItems() - }, - new MenuItem("Edit") - { - Items = new[] + new MenuItem("File") { - undoMenuItem = new EditorMenuItem("Undo", MenuItemType.Standard, Undo), - redoMenuItem = new EditorMenuItem("Redo", MenuItemType.Standard, Redo), - new EditorMenuItemSpacer(), - cutMenuItem = new EditorMenuItem("Cut", MenuItemType.Standard, Cut), - copyMenuItem = new EditorMenuItem("Copy", MenuItemType.Standard, Copy), - pasteMenuItem = new EditorMenuItem("Paste", MenuItemType.Standard, Paste), - } - }, - new MenuItem("View") - { - Items = new MenuItem[] + Items = createFileMenuItems() + }, + new MenuItem("Edit") { - new WaveformOpacityMenuItem(config.GetBindable(OsuSetting.EditorWaveformOpacity)), - new HitAnimationsMenuItem(config.GetBindable(OsuSetting.EditorHitAnimations)) + Items = new[] + { + undoMenuItem = new EditorMenuItem("Undo", MenuItemType.Standard, Undo), + redoMenuItem = new EditorMenuItem("Redo", MenuItemType.Standard, Redo), + new EditorMenuItemSpacer(), + cutMenuItem = new EditorMenuItem("Cut", MenuItemType.Standard, Cut), + copyMenuItem = new EditorMenuItem("Copy", MenuItemType.Standard, Copy), + pasteMenuItem = new EditorMenuItem("Paste", MenuItemType.Standard, Paste), + } + }, + new MenuItem("View") + { + Items = new MenuItem[] + { + new WaveformOpacityMenuItem(config.GetBindable(OsuSetting.EditorWaveformOpacity)), + new HitAnimationsMenuItem(config.GetBindable(OsuSetting.EditorHitAnimations)) + } } } - } - } + }, + new ScreenSelectionTabControl + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + X = -15, + Current = Mode, + }, + }, }, new Container { @@ -340,14 +349,15 @@ namespace osu.Game.Screens.Edit changeHandler?.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true); changeHandler?.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true); - - menuBar.Mode.ValueChanged += onModeChanged; } protected override void LoadComplete() { base.LoadComplete(); setUpClipboardActionAvailability(); + + Mode.Value = isNewBeatmap ? EditorScreenMode.SongSetup : EditorScreenMode.Compose; + Mode.BindValueChanged(onModeChanged, true); } /// @@ -517,23 +527,23 @@ namespace osu.Game.Screens.Edit return true; case GlobalAction.EditorComposeMode: - menuBar.Mode.Value = EditorScreenMode.Compose; + Mode.Value = EditorScreenMode.Compose; return true; case GlobalAction.EditorDesignMode: - menuBar.Mode.Value = EditorScreenMode.Design; + Mode.Value = EditorScreenMode.Design; return true; case GlobalAction.EditorTimingMode: - menuBar.Mode.Value = EditorScreenMode.Timing; + Mode.Value = EditorScreenMode.Timing; return true; case GlobalAction.EditorSetupMode: - menuBar.Mode.Value = EditorScreenMode.SongSetup; + Mode.Value = EditorScreenMode.SongSetup; return true; case GlobalAction.EditorVerifyMode: - menuBar.Mode.Value = EditorScreenMode.Verify; + Mode.Value = EditorScreenMode.Verify; return true; case GlobalAction.EditorTestGameplay: From 1916011ebff0064370a5589a948caa33fcafde91 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 19:41:47 +0900 Subject: [PATCH 0916/5199] Tween corner radius when scaling container becomes non-fullscreen --- osu.Game/Graphics/Containers/ScalingContainer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 0d543bdbc8..781e85f82e 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -56,6 +56,8 @@ namespace osu.Game.Graphics.Containers } } + private const float corner_radius = 10; + /// /// Create a new instance. /// @@ -69,7 +71,7 @@ namespace osu.Game.Graphics.Containers { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, - CornerRadius = 10, + CornerRadius = corner_radius, Child = content = new ScalingDrawSizePreservingFillContainer(targetMode != ScalingMode.Gameplay) }; } @@ -176,6 +178,7 @@ namespace osu.Game.Graphics.Containers sizableContainer.MoveTo(targetPosition, 500, Easing.OutQuart); sizableContainer.ResizeTo(targetSize, 500, Easing.OutQuart).OnComplete(_ => { sizableContainer.Masking = requiresMasking; }); + sizableContainer.TransformTo(nameof(CornerRadius), requiresMasking ? corner_radius : 0, 500, requiresMasking ? Easing.OutQuart : Easing.None); } private class ScalingBackgroundScreen : BackgroundScreenDefault From ff7db4f4059f70a67d91f6c37fb683c6a7effa23 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 20:05:37 +0900 Subject: [PATCH 0917/5199] Replace jank buttons with menu in skin editor --- osu.Game/Skinning/Editor/SkinEditor.cs | 88 ++++++++++++-------------- 1 file changed, 39 insertions(+), 49 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 8052f82c93..ae5cbc95f0 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -8,14 +8,14 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Testing; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; -using osu.Game.Resources.Localisation.Web; -using osuTK; +using osu.Game.Screens.Edit.Components.Menus; namespace osu.Game.Skinning.Editor { @@ -57,13 +57,43 @@ namespace osu.Game.Skinning.Editor RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - headerText = new OsuTextFlowContainer + new Container { - TextAnchor = Anchor.TopCentre, - Padding = new MarginPadding(20), - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X + Name = "Top bar", + RelativeSizeAxes = Axes.X, + Depth = float.MinValue, + Height = 40, + Children = new Drawable[] + { + new EditorMenuBar + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Items = new[] + { + new MenuItem("File") + { + Items = new[] + { + new EditorMenuItem("Save", MenuItemType.Standard, Save), + new EditorMenuItem("Revert to default", MenuItemType.Destructive, revert), + new EditorMenuItemSpacer(), + new EditorMenuItem("Exit", MenuItemType.Standard, Hide), + }, + }, + } + }, + headerText = new OsuTextFlowContainer + { + TextAnchor = Anchor.TopRight, + Padding = new MarginPadding(5), + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + }, + }, }, new GridContainer { @@ -89,46 +119,6 @@ namespace osu.Game.Skinning.Editor Children = new Drawable[] { new SkinBlueprintContainer(targetScreen), - new TriangleButton - { - Margin = new MarginPadding(10), - Text = CommonStrings.ButtonsClose, - Width = 100, - Action = Hide, - }, - new FillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Spacing = new Vector2(5), - Padding = new MarginPadding - { - Top = 10, - Left = 10, - }, - Margin = new MarginPadding - { - Right = 10, - Bottom = 10, - }, - Children = new Drawable[] - { - new TriangleButton - { - Text = "Save Changes", - Width = 140, - Action = Save, - }, - new DangerousTriangleButton - { - Text = "Revert to default", - Width = 140, - Action = revert, - }, - } - }, } }, } @@ -161,7 +151,7 @@ namespace osu.Game.Skinning.Editor { headerText.Clear(); - headerText.AddParagraph("Skin editor", cp => cp.Font = OsuFont.Default.With(size: 24)); + headerText.AddParagraph("Skin editor", cp => cp.Font = OsuFont.Default.With(size: 16)); headerText.NewParagraph(); headerText.AddText("Currently editing ", cp => { From 29ed419d537f140943e77b23a4a965a757b7ca1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 20:04:53 +0900 Subject: [PATCH 0918/5199] Change how custom scales are applied to `ScalingContainer` to allow for better transitions --- .../Graphics/Containers/ScalingContainer.cs | 45 ++++++++++++------- .../Skinning/Editor/SkinComponentToolbox.cs | 4 +- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 21 ++------- 3 files changed, 34 insertions(+), 36 deletions(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 781e85f82e..5888be2ae7 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; using osu.Framework.Screens; using osu.Game.Configuration; using osu.Game.Screens; @@ -38,22 +39,18 @@ namespace osu.Game.Graphics.Containers private BackgroundScreenStack backgroundStack; - private bool allowScaling = true; + private RectangleF? customScale; + private bool customScaleIsRelativePosition; /// - /// Whether user scaling preferences should be applied. Enabled by default. + /// Set a custom position and scale which overrides any user specification. /// - public bool AllowScaling + public void SetCustomScale(RectangleF? scale, bool relativePosition = false) { - get => allowScaling; - set - { - if (value == allowScaling) - return; + customScale = scale; + customScaleIsRelativePosition = relativePosition; - allowScaling = value; - if (IsLoaded) Scheduler.AddOnce(updateSize); - } + if (IsLoaded) Scheduler.AddOnce(updateSize); } private const float corner_radius = 10; @@ -164,11 +161,25 @@ namespace osu.Game.Graphics.Containers backgroundStack?.FadeOut(fade_time); } - bool scaling = AllowScaling && (targetMode == null || scalingMode.Value == targetMode); + RectangleF targetSize = new RectangleF(Vector2.Zero, Vector2.One); - var targetSize = scaling ? new Vector2(sizeX.Value, sizeY.Value) : Vector2.One; - var targetPosition = scaling ? new Vector2(posX.Value, posY.Value) * (Vector2.One - targetSize) : Vector2.Zero; - bool requiresMasking = (scaling && targetSize != Vector2.One) + if (customScale != null) + { + sizableContainer.RelativePositionAxes = customScaleIsRelativePosition ? Axes.Both : Axes.None; + + targetSize = customScale.Value; + } + else if (targetMode == null || scalingMode.Value == targetMode) + { + sizableContainer.RelativePositionAxes = Axes.Both; + + Vector2 scale = new Vector2(sizeX.Value, sizeY.Value); + Vector2 pos = new Vector2(posX.Value, posY.Value) * (Vector2.One - scale); + + targetSize = new RectangleF(pos, scale); + } + + bool requiresMasking = targetSize.Size != Vector2.One // For the top level scaling container, for now we apply masking if safe areas are in use. // In the future this can likely be removed as more of the actual UI supports overflowing into the safe areas. || (targetMode == ScalingMode.Everything && safeAreaPadding.Value.Total != Vector2.Zero); @@ -176,8 +187,8 @@ namespace osu.Game.Graphics.Containers if (requiresMasking) sizableContainer.Masking = true; - sizableContainer.MoveTo(targetPosition, 500, Easing.OutQuart); - sizableContainer.ResizeTo(targetSize, 500, Easing.OutQuart).OnComplete(_ => { sizableContainer.Masking = requiresMasking; }); + sizableContainer.MoveTo(targetSize.Location, 500, Easing.OutQuart); + sizableContainer.ResizeTo(targetSize.Size, 500, Easing.OutQuart).OnComplete(_ => { sizableContainer.Masking = requiresMasking; }); sizableContainer.TransformTo(nameof(CornerRadius), requiresMasking ? corner_radius : 0, 500, requiresMasking ? Easing.OutQuart : Easing.None); } diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 935d2756fb..ce9afd650a 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -23,6 +23,8 @@ namespace osu.Game.Skinning.Editor { public class SkinComponentToolbox : ScrollingToolboxGroup { + public const float WIDTH = 200; + public Action RequestPlacement; private const float component_display_scale = 0.8f; @@ -41,7 +43,7 @@ namespace osu.Game.Skinning.Editor : base("Components", height) { RelativeSizeAxes = Axes.None; - Width = 200; + Width = WIDTH; } [BackgroundDependencyLoader] diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 86854ab6ff..dcfe28aaea 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -5,6 +5,7 @@ using JetBrains.Annotations; using osu.Framework.Bindables; 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.Graphics.Containers; @@ -100,30 +101,14 @@ namespace osu.Game.Skinning.Editor { if (visibility.NewValue == Visibility.Visible) { - updateMasking(); - target.AllowScaling = false; - target.RelativePositionAxes = Axes.Both; - - target.ScaleTo(VISIBLE_TARGET_SCALE, SkinEditor.TRANSITION_DURATION, Easing.OutQuint); - target.MoveToX(0.095f, SkinEditor.TRANSITION_DURATION, Easing.OutQuint); + target.SetCustomScale(new RectangleF(0.18f, 0.1f, VISIBLE_TARGET_SCALE, VISIBLE_TARGET_SCALE), true); } else { - target.AllowScaling = true; - - target.ScaleTo(1, SkinEditor.TRANSITION_DURATION, Easing.OutQuint).OnComplete(_ => updateMasking()); - target.MoveToX(0f, SkinEditor.TRANSITION_DURATION, Easing.OutQuint); + target.SetCustomScale(null); } } - private void updateMasking() - { - if (skinEditor == null) - return; - - target.Masking = skinEditor.State.Value == Visibility.Visible; - } - public void OnReleased(KeyBindingReleaseEvent e) { } From 8d7cdbd8833ba4db7990bd5d5fcbf808d5c65d21 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 20:25:34 +0900 Subject: [PATCH 0919/5199] Add note about nested masking case --- .../Graphics/Containers/ScalingContainer.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 5888be2ae7..df27c561d5 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -136,7 +136,7 @@ namespace osu.Game.Graphics.Containers private void updateSize() { - const float fade_time = 500; + const float duration = 500; if (targetMode == ScalingMode.Everything) { @@ -155,10 +155,10 @@ namespace osu.Game.Graphics.Containers backgroundStack.Push(new ScalingBackgroundScreen()); } - backgroundStack.FadeIn(fade_time); + backgroundStack.FadeIn(duration); } else - backgroundStack?.FadeOut(fade_time); + backgroundStack?.FadeOut(duration); } RectangleF targetSize = new RectangleF(Vector2.Zero, Vector2.One); @@ -187,9 +187,14 @@ namespace osu.Game.Graphics.Containers if (requiresMasking) sizableContainer.Masking = true; - sizableContainer.MoveTo(targetSize.Location, 500, Easing.OutQuart); - sizableContainer.ResizeTo(targetSize.Size, 500, Easing.OutQuart).OnComplete(_ => { sizableContainer.Masking = requiresMasking; }); - sizableContainer.TransformTo(nameof(CornerRadius), requiresMasking ? corner_radius : 0, 500, requiresMasking ? Easing.OutQuart : Easing.None); + sizableContainer.MoveTo(targetSize.Location, duration, Easing.OutQuart); + sizableContainer.ResizeTo(targetSize.Size, duration, Easing.OutQuart); + + // Of note, this will not working great in the case of nested ScalingContainers where multiple are applying corner radius. + // There should likely only be masking and corner radius applied at one point in the full game stack to fix this. + // An example of how this can occur is it the skin editor is visible and the game screen scaling is set to "Everything". + sizableContainer.TransformTo(nameof(CornerRadius), requiresMasking ? corner_radius : 0, duration, requiresMasking ? Easing.OutQuart : Easing.None) + .OnComplete(_ => { sizableContainer.Masking = requiresMasking; }); } private class ScalingBackgroundScreen : BackgroundScreenDefault From b5684aaa76d8b345397eb1fff8c60dd2c25fcd60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 20:33:28 +0900 Subject: [PATCH 0920/5199] Scale -> Rect to read better --- .../Graphics/Containers/ScalingContainer.cs | 26 +++++++++---------- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 4 +-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index df27c561d5..dd611b0904 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -39,16 +39,16 @@ namespace osu.Game.Graphics.Containers private BackgroundScreenStack backgroundStack; - private RectangleF? customScale; - private bool customScaleIsRelativePosition; + private RectangleF? customRect; + private bool customRectIsRelativePosition; /// /// Set a custom position and scale which overrides any user specification. /// - public void SetCustomScale(RectangleF? scale, bool relativePosition = false) + public void SetCustomRect(RectangleF? rect, bool relativePosition = false) { - customScale = scale; - customScaleIsRelativePosition = relativePosition; + customRect = rect; + customRectIsRelativePosition = relativePosition; if (IsLoaded) Scheduler.AddOnce(updateSize); } @@ -161,13 +161,13 @@ namespace osu.Game.Graphics.Containers backgroundStack?.FadeOut(duration); } - RectangleF targetSize = new RectangleF(Vector2.Zero, Vector2.One); + RectangleF targetRect = new RectangleF(Vector2.Zero, Vector2.One); - if (customScale != null) + if (customRect != null) { - sizableContainer.RelativePositionAxes = customScaleIsRelativePosition ? Axes.Both : Axes.None; + sizableContainer.RelativePositionAxes = customRectIsRelativePosition ? Axes.Both : Axes.None; - targetSize = customScale.Value; + targetRect = customRect.Value; } else if (targetMode == null || scalingMode.Value == targetMode) { @@ -176,10 +176,10 @@ namespace osu.Game.Graphics.Containers Vector2 scale = new Vector2(sizeX.Value, sizeY.Value); Vector2 pos = new Vector2(posX.Value, posY.Value) * (Vector2.One - scale); - targetSize = new RectangleF(pos, scale); + targetRect = new RectangleF(pos, scale); } - bool requiresMasking = targetSize.Size != Vector2.One + bool requiresMasking = targetRect.Size != Vector2.One // For the top level scaling container, for now we apply masking if safe areas are in use. // In the future this can likely be removed as more of the actual UI supports overflowing into the safe areas. || (targetMode == ScalingMode.Everything && safeAreaPadding.Value.Total != Vector2.Zero); @@ -187,8 +187,8 @@ namespace osu.Game.Graphics.Containers if (requiresMasking) sizableContainer.Masking = true; - sizableContainer.MoveTo(targetSize.Location, duration, Easing.OutQuart); - sizableContainer.ResizeTo(targetSize.Size, duration, Easing.OutQuart); + sizableContainer.MoveTo(targetRect.Location, duration, Easing.OutQuart); + sizableContainer.ResizeTo(targetRect.Size, duration, Easing.OutQuart); // Of note, this will not working great in the case of nested ScalingContainers where multiple are applying corner radius. // There should likely only be masking and corner radius applied at one point in the full game stack to fix this. diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index dcfe28aaea..61c363b019 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -101,11 +101,11 @@ namespace osu.Game.Skinning.Editor { if (visibility.NewValue == Visibility.Visible) { - target.SetCustomScale(new RectangleF(0.18f, 0.1f, VISIBLE_TARGET_SCALE, VISIBLE_TARGET_SCALE), true); + target.SetCustomRect(new RectangleF(0.18f, 0.1f, VISIBLE_TARGET_SCALE, VISIBLE_TARGET_SCALE), true); } else { - target.SetCustomScale(null); + target.SetCustomRect(null); } } From 6caecf79a012f988a9f08ab94fe6930631e6fd8f Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Mar 2022 20:06:28 +0800 Subject: [PATCH 0921/5199] Use smooth speed change --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 9a6705ea09..b6df56dd76 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -14,10 +15,11 @@ using osu.Game.Configuration; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Mods { - public class ModAdaptiveSpeed : Mod, IApplicableToRate, IApplicableToDrawableHitObject, IApplicableToBeatmap + public class ModAdaptiveSpeed : Mod, IApplicableToRate, IApplicableToDrawableHitObject, IApplicableToBeatmap, IApplicableToHUD { // use a wider range so there's still room for adjustment when the initial rate is extreme private const double fastest_rate = 2.5f; @@ -65,6 +67,7 @@ namespace osu.Game.Rulesets.Mods }; private ITrack track; + private HUDOverlay overlay; private readonly List recentRates = Enumerable.Range(0, average_count).Select(_ => 1d).ToList(); @@ -82,6 +85,12 @@ namespace osu.Game.Rulesets.Mods AdjustPitch.BindValueChanged(applyPitchAdjustment); } + public void ApplyToHUD(HUDOverlay overlay) + { + // this is only used to transform the SpeedChange bindable + this.overlay = overlay; + } + public void ApplyToTrack(ITrack track) { this.track = track; @@ -127,7 +136,7 @@ namespace osu.Game.Rulesets.Mods recentRates.RemoveAt(0); } - SpeedChange.Value = recentRates.Average(); + overlay.TransformBindableTo(SpeedChange, recentRates.Average(), 100); }; drawable.OnRevertResult += (o, result) => { @@ -146,7 +155,7 @@ namespace osu.Game.Rulesets.Mods recentRates.RemoveAt(recentRates.Count - 1); } - SpeedChange.Value = recentRates.Average(); + overlay.TransformBindableTo(SpeedChange, recentRates.Average(), 100); }; } From 17bc7142979d43fedcd23cfe1f67c7d87c6300d0 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Mar 2022 20:48:57 +0800 Subject: [PATCH 0922/5199] Allow the mod to properly react to nested hit objects --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 41 ++++++++++------------ 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index b6df56dd76..db631520ab 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -123,37 +123,32 @@ namespace osu.Game.Rulesets.Mods { drawable.OnNewResult += (o, result) => { + if (dequeuedRates.ContainsKey(result.HitObject)) return; + if (!result.IsHit) return; + if (!result.Type.AffectsAccuracy()) return; if (!previousEndTimes.ContainsKey(result.HitObject)) return; double prevEndTime = previousEndTimes[result.HitObject]; recentRates.Add(Math.Clamp((result.HitObject.GetEndTime() - prevEndTime) / (result.TimeAbsolute - prevEndTime) * SpeedChange.Value, slowest_rate, fastest_rate)); - if (recentRates.Count > average_count) - { - dequeuedRates.Add(result.HitObject, recentRates[0]); - recentRates.RemoveAt(0); - } + dequeuedRates.Add(result.HitObject, recentRates[0]); + recentRates.RemoveAt(0); overlay.TransformBindableTo(SpeedChange, recentRates.Average(), 100); }; drawable.OnRevertResult += (o, result) => { + if (!dequeuedRates.ContainsKey(result.HitObject)) return; + if (!result.IsHit) return; + if (!result.Type.AffectsAccuracy()) return; if (!previousEndTimes.ContainsKey(result.HitObject)) return; - if (dequeuedRates.ContainsKey(result.HitObject)) - { - recentRates.Insert(0, dequeuedRates[result.HitObject]); - recentRates.RemoveAt(recentRates.Count - 1); - dequeuedRates.Remove(result.HitObject); - } - else - { - recentRates.Insert(0, InitialRate.Value); - recentRates.RemoveAt(recentRates.Count - 1); - } + recentRates.Insert(0, dequeuedRates[result.HitObject]); + recentRates.RemoveAt(recentRates.Count - 1); + dequeuedRates.Remove(result.HitObject); overlay.TransformBindableTo(SpeedChange, recentRates.Average(), 100); }; @@ -161,11 +156,11 @@ namespace osu.Game.Rulesets.Mods public void ApplyToBeatmap(IBeatmap beatmap) { - var endTimes = getEndTimes(beatmap.HitObjects).OrderBy(x => x).ToList(); + var hitObjects = getAllApplicableHitObjects(beatmap.HitObjects).ToList(); + var endTimes = hitObjects.Select(x => x.GetEndTime()).OrderBy(x => x).ToList(); - for (int i = 1; i < beatmap.HitObjects.Count; i++) + foreach (HitObject hitObject in hitObjects) { - var hitObject = beatmap.HitObjects[i]; double prevEndTime = endTimes.LastOrDefault(ht => !Precision.AlmostBigger(ht, hitObject.GetEndTime())); if (prevEndTime != default) @@ -173,15 +168,15 @@ namespace osu.Game.Rulesets.Mods } } - private IEnumerable getEndTimes(IEnumerable hitObjects) + private IEnumerable getAllApplicableHitObjects(IEnumerable hitObjects) { foreach (var hitObject in hitObjects) { if (!(hitObject.HitWindows is HitWindows.EmptyHitWindows)) - yield return hitObject.GetEndTime(); + yield return hitObject; - foreach (double hitTime in getEndTimes(hitObject.NestedHitObjects)) - yield return hitTime; + foreach (HitObject nested in getAllApplicableHitObjects(hitObject.NestedHitObjects)) + yield return nested; } } } From d335a2229f448a7a9f30f6b144910aafe398be83 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 2 Mar 2022 21:07:57 +0800 Subject: [PATCH 0923/5199] Tweak `average_count` --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index db631520ab..7c92bd8141 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mods /// /// Adjust track rate using the average speed of the last x hits /// - private const int average_count = 10; + private const int average_count = 6; public override string Name => "Adaptive Speed"; From e14a35b469de3841c3d573758b9a5266d99abb93 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 2 Mar 2022 20:32:03 +0300 Subject: [PATCH 0924/5199] Add failing test case --- .../Gameplay/TestSceneStoryboardSamples.cs | 59 ------------- .../TestSceneStoryboardSamplePlayback.cs | 83 +++++++++++++++++-- osu.Game/Tests/Visual/TestPlayer.cs | 3 - 3 files changed, 78 insertions(+), 67 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 88862ea28b..6457a23a1b 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -1,29 +1,23 @@ // 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.IO; -using System.Linq; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Testing; -using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.IO; using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osu.Game.Skinning; @@ -118,59 +112,6 @@ namespace osu.Game.Tests.Gameplay AddUntilStep("sample has lifetime end", () => sample.LifetimeEnd < double.MaxValue); } - [TestCase(typeof(OsuModDoubleTime), 1.5)] - [TestCase(typeof(OsuModHalfTime), 0.75)] - [TestCase(typeof(ModWindUp), 1.5)] - [TestCase(typeof(ModWindDown), 0.75)] - [TestCase(typeof(OsuModDoubleTime), 2)] - [TestCase(typeof(OsuModHalfTime), 0.5)] - [TestCase(typeof(ModWindUp), 2)] - [TestCase(typeof(ModWindDown), 0.5)] - public void TestSamplePlaybackWithRateMods(Type expectedMod, double expectedRate) - { - GameplayClockContainer gameplayContainer = null; - StoryboardSampleInfo sampleInfo = null; - TestDrawableStoryboardSample sample = null; - - Mod testedMod = Activator.CreateInstance(expectedMod) as Mod; - - switch (testedMod) - { - case ModRateAdjust m: - m.SpeedChange.Value = expectedRate; - break; - - case ModTimeRamp m: - m.FinalRate.Value = m.InitialRate.Value = expectedRate; - break; - } - - AddStep("setup storyboard sample", () => - { - Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, this); - SelectedMods.Value = new[] { testedMod }; - - var beatmapSkinSourceContainer = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin); - - Add(gameplayContainer = new MasterGameplayClockContainer(Beatmap.Value, 0) - { - Child = beatmapSkinSourceContainer - }); - - beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(sampleInfo = new StoryboardSampleInfo("test-sample", 1, 1)) - { - Clock = gameplayContainer.GameplayClock - }); - }); - - AddStep("start", () => gameplayContainer.Start()); - - AddAssert("sample playback rate matches mod rates", () => - testedMod != null && Precision.AlmostEquals( - sample.ChildrenOfType().First().AggregateFrequency.Value, - ((IApplicableToRate)testedMod).ApplyToRate(sampleInfo.StartTime))); - } - [Test] public void TestSamplePlaybackWithBeatmapHitsoundsOff() { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs index 95603b5c04..7a74a00c68 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs @@ -1,17 +1,23 @@ // 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; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics.Audio; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Storyboards; using osu.Game.Storyboards.Drawables; +using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { @@ -19,6 +25,10 @@ namespace osu.Game.Tests.Visual.Gameplay { private Storyboard storyboard; + private IReadOnlyList storyboardMods; + + protected override bool HasCustomSteps => true; + [BackgroundDependencyLoader] private void load(OsuConfigManager config) { @@ -31,10 +41,13 @@ namespace osu.Game.Tests.Visual.Gameplay backgroundLayer.Add(new StoryboardSampleInfo("Intro/welcome.mp3", time: 0, volume: 20)); } + [SetUp] + public void SetUp() => Schedule(() => storyboardMods = Array.Empty()); + [Test] public void TestStoryboardSamplesStopDuringPause() { - checkForFirstSamplePlayback(); + createPlayerTest(); AddStep("player paused", () => Player.Pause()); AddAssert("player is currently paused", () => Player.GameplayClockContainer.IsPaused.Value); @@ -47,26 +60,86 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestStoryboardSamplesStopOnSkip() { - checkForFirstSamplePlayback(); + createPlayerTest(true); - AddStep("skip intro", () => InputManager.Key(osuTK.Input.Key.Space)); AddAssert("all storyboard samples stopped immediately", () => allStoryboardSamples.All(sound => !sound.IsPlaying)); AddUntilStep("any storyboard samples playing after skip", () => allStoryboardSamples.Any(sound => sound.IsPlaying)); } - private void checkForFirstSamplePlayback() + [TestCase(typeof(OsuModDoubleTime), 1.5)] + [TestCase(typeof(OsuModDoubleTime), 2)] + [TestCase(typeof(OsuModHalfTime), 0.75)] + [TestCase(typeof(OsuModHalfTime), 0.5)] + public void TestStoryboardSamplesPlaybackWithRateAdjustMods(Type expectedMod, double expectedRate) { + AddStep("setup mod", () => + { + ModRateAdjust testedMod = (ModRateAdjust)Activator.CreateInstance(expectedMod).AsNonNull(); + testedMod.SpeedChange.Value = expectedRate; + storyboardMods = new[] { testedMod }; + }); + + createPlayerTest(true); + + AddAssert("sample playback rate matches mod rates", () => allStoryboardSamples.All(sound => + { + return sound.ChildrenOfType().All(s => s.AggregateFrequency.Value == expectedRate); + })); + } + + [TestCase(typeof(ModWindUp), 0.5, 2)] + [TestCase(typeof(ModWindUp), 1.51, 2)] + [TestCase(typeof(ModWindDown), 2, 0.5)] + [TestCase(typeof(ModWindDown), 0.99, 0.5)] + public void TestStoryboardSamplesPlaybackWithTimeRampMods(Type expectedMod, double initialRate, double finalRate) + { + AddStep("setup mod", () => + { + ModTimeRamp testedMod = (ModTimeRamp)Activator.CreateInstance(expectedMod).AsNonNull(); + testedMod.InitialRate.Value = initialRate; + testedMod.FinalRate.Value = finalRate; + storyboardMods = new[] { testedMod }; + }); + + createPlayerTest(true); + + ModTimeRamp gameplayMod = null; + + AddUntilStep("mod speed change updated", () => + { + gameplayMod = Player.GameplayState.Mods.OfType().Single(); + return gameplayMod.SpeedChange.Value != initialRate; + }); + + AddAssert("sample playback rate matches mod rates", () => allStoryboardSamples.All(sound => + { + return sound.ChildrenOfType().All(s => s.AggregateFrequency.Value == gameplayMod.SpeedChange.Value); + })); + } + + private void createPlayerTest(bool skipIntro = false) + { + CreateTest(null); + AddAssert("storyboard loaded", () => Player.Beatmap.Value.Storyboard != null); AddUntilStep("any storyboard samples playing", () => allStoryboardSamples.Any(sound => sound.IsPlaying)); + + if (skipIntro) + AddStep("skip intro", () => InputManager.Key(Key.Space)); } private IEnumerable allStoryboardSamples => Player.ChildrenOfType(); protected override bool AllowFail => false; + protected override TestPlayer CreatePlayer(Ruleset ruleset) + { + SelectedMods.Value = SelectedMods.Value.Concat(storyboardMods).ToArray(); + return new TestPlayer(true, false); + } + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); - protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, false); protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new ClockBackedTestWorkingBeatmap(beatmap, storyboard ?? this.storyboard, Clock, Audio); diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index 368f792e28..d463905cf4 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -26,9 +26,6 @@ namespace osu.Game.Tests.Visual public new DrawableRuleset DrawableRuleset => base.DrawableRuleset; - /// - /// Mods from *player* (not OsuScreen). - /// public new Bindable> Mods => base.Mods; public new HUDOverlay HUDOverlay => base.HUDOverlay; From cbb8dc28914a4c7a8cb636ea5b58534869eea934 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 2 Mar 2022 20:33:46 +0300 Subject: [PATCH 0925/5199] Fix storyboard samples rate not adjusted from actual gameplay mods --- .../Visual/Gameplay/TestSceneStoryboard.cs | 4 ++-- .../Backgrounds/BeatmapBackgroundWithStoryboard.cs | 8 +++++++- osu.Game/Screens/Play/DimmableStoryboard.cs | 9 +++++++-- osu.Game/Screens/Play/Player.cs | 2 +- .../Storyboards/Drawables/DrawableStoryboard.cs | 9 ++++++++- .../Drawables/DrawableStoryboardSample.cs | 13 ++++++++----- osu.Game/Storyboards/Storyboard.cs | 5 +++-- 7 files changed, 36 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs index 3b6d02c67c..014ccb1652 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.Gameplay var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true }; storyboardContainer.Clock = decoupledClock; - storyboard = working.Storyboard.CreateDrawable(Beatmap.Value); + storyboard = working.Storyboard.CreateDrawable(SelectedMods.Value); storyboard.Passing = false; storyboardContainer.Add(storyboard); @@ -118,7 +118,7 @@ namespace osu.Game.Tests.Visual.Gameplay sb = decoder.Decode(bfr); } - storyboard = sb.CreateDrawable(Beatmap.Value); + storyboard = sb.CreateDrawable(SelectedMods.Value); storyboardContainer.Add(storyboard); decoupledClock.ChangeSource(Beatmap.Value.Track); diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index 56ef87c1f4..7aed442800 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -3,12 +3,15 @@ #nullable enable +using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Overlays; +using osu.Game.Rulesets.Mods; using osu.Game.Storyboards.Drawables; namespace osu.Game.Graphics.Backgrounds @@ -20,6 +23,9 @@ namespace osu.Game.Graphics.Backgrounds [Resolved(CanBeNull = true)] private MusicController? musicController { get; set; } + [Resolved] + private IBindable> mods { get; set; } = null!; + public BeatmapBackgroundWithStoryboard(WorkingBeatmap beatmap, string fallbackTextureName = "Backgrounds/bg1") : base(beatmap, fallbackTextureName) { @@ -39,7 +45,7 @@ namespace osu.Game.Graphics.Backgrounds { RelativeSizeAxes = Axes.Both, Volume = { Value = 0 }, - Child = new DrawableStoryboard(Beatmap.Storyboard) { Clock = storyboardClock } + Child = new DrawableStoryboard(Beatmap.Storyboard, mods.Value) { Clock = storyboardClock } }, AddInternal); } diff --git a/osu.Game/Screens/Play/DimmableStoryboard.cs b/osu.Game/Screens/Play/DimmableStoryboard.cs index f8cedddfbe..5a3ef1e9d3 100644 --- a/osu.Game/Screens/Play/DimmableStoryboard.cs +++ b/osu.Game/Screens/Play/DimmableStoryboard.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; +using osu.Game.Rulesets.Mods; using osu.Game.Storyboards; using osu.Game.Storyboards.Drawables; @@ -18,6 +20,8 @@ namespace osu.Game.Screens.Play public Container OverlayLayerContainer { get; private set; } private readonly Storyboard storyboard; + private readonly IReadOnlyList mods; + private DrawableStoryboard drawableStoryboard; /// @@ -28,9 +32,10 @@ namespace osu.Game.Screens.Play /// public IBindable HasStoryboardEnded = new BindableBool(true); - public DimmableStoryboard(Storyboard storyboard) + public DimmableStoryboard(Storyboard storyboard, IReadOnlyList mods) { this.storyboard = storyboard; + this.mods = mods; } [BackgroundDependencyLoader] @@ -57,7 +62,7 @@ namespace osu.Game.Screens.Play if (!ShowStoryboard.Value && !IgnoreUserSettings.Value) return; - drawableStoryboard = storyboard.CreateDrawable(); + drawableStoryboard = storyboard.CreateDrawable(mods); HasStoryboardEnded.BindTo(drawableStoryboard.HasStoryboardEnded); if (async) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d4b02622d3..8ce3f1587d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -355,7 +355,7 @@ namespace osu.Game.Screens.Play protected virtual GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart); private Drawable createUnderlayComponents() => - DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }; + DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard, GameplayState.Mods) { RelativeSizeAxes = Axes.Both }; private Drawable createGameplayComponents(IWorkingBeatmap working) => new ScalingContainer(ScalingMode.Gameplay) { diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index e6528a83bd..840500347f 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -1,6 +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 System; +using System.Collections.Generic; using System.Linq; using System.Threading; using osuTK; @@ -11,6 +13,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.Platform; using osu.Game.Database; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play; using osu.Game.Stores; @@ -50,14 +53,18 @@ namespace osu.Game.Storyboards.Drawables private double? lastEventEndTime; + [Cached(typeof(IReadOnlyList))] + public IReadOnlyList Mods { get; } + private DependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - public DrawableStoryboard(Storyboard storyboard) + public DrawableStoryboard(Storyboard storyboard, IReadOnlyList mods) { Storyboard = storyboard; + Mods = mods ?? Array.Empty(); Size = new Vector2(640, 480); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 672274a2ad..4e3f72512c 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -28,17 +28,20 @@ namespace osu.Game.Storyboards.Drawables LifetimeStart = sampleInfo.StartTime; } - [Resolved] - private IBindable> mods { get; set; } + [Resolved(CanBeNull = true)] + private IReadOnlyList mods { get; set; } protected override void SkinChanged(ISkinSource skin) { base.SkinChanged(skin); - foreach (var mod in mods.Value.OfType()) + if (mods != null) { - foreach (var sample in DrawableSamples) - mod.ApplyToSample(sample); + foreach (var mod in mods.OfType()) + { + foreach (var sample in DrawableSamples) + mod.ApplyToSample(sample); + } } } diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index c4864c0334..2faed98ae0 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; using osu.Game.Extensions; +using osu.Game.Rulesets.Mods; using osu.Game.Skinning; using osu.Game.Storyboards.Drawables; @@ -90,8 +91,8 @@ namespace osu.Game.Storyboards } } - public DrawableStoryboard CreateDrawable(IWorkingBeatmap working = null) => - new DrawableStoryboard(this); + public DrawableStoryboard CreateDrawable(IReadOnlyList mods = null) => + new DrawableStoryboard(this, mods); public Drawable CreateSpriteFromResourcePath(string path, TextureStore textureStore) { From b286122413b21be910bf29c8349f3030d3e4b2f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 03:54:39 +0900 Subject: [PATCH 0926/5199] Move uninstaller registry operation to `onInitialInstall` --- osu.Desktop/Program.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 0e11e172e1..e317a44bc3 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -116,6 +116,7 @@ namespace osu.Desktop SquirrelAwareApp.HandleEvents(onInitialInstall: (version, tools) => { tools.CreateShortcutForThisExe(); + tools.CreateUninstallerRegistryEntry(); }, onAppUninstall: (version, tools) => { tools.RemoveShortcutForThisExe(); @@ -123,9 +124,6 @@ namespace osu.Desktop }, onEveryRun: (version, tools, firstRun) => { tools.SetProcessAppUserModelId(); - - if (firstRun) - tools.CreateUninstallerRegistryEntry(); }); } From a812ed4462a5126ff9ba2eb2d858814e59a2ddee Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 2 Mar 2022 23:40:14 +0300 Subject: [PATCH 0927/5199] Ensure there is at least one sample during rate assertion --- .../Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs index 7a74a00c68..44529aa78c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs @@ -83,9 +83,7 @@ namespace osu.Game.Tests.Visual.Gameplay createPlayerTest(true); AddAssert("sample playback rate matches mod rates", () => allStoryboardSamples.All(sound => - { - return sound.ChildrenOfType().All(s => s.AggregateFrequency.Value == expectedRate); - })); + sound.ChildrenOfType().First().AggregateFrequency.Value == expectedRate)); } [TestCase(typeof(ModWindUp), 0.5, 2)] @@ -113,9 +111,7 @@ namespace osu.Game.Tests.Visual.Gameplay }); AddAssert("sample playback rate matches mod rates", () => allStoryboardSamples.All(sound => - { - return sound.ChildrenOfType().All(s => s.AggregateFrequency.Value == gameplayMod.SpeedChange.Value); - })); + sound.ChildrenOfType().First().AggregateFrequency.Value == gameplayMod.SpeedChange.Value)); } private void createPlayerTest(bool skipIntro = false) From 82bbc32d74147ce99680e4a5c99e4ffed1bb00b9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 2 Mar 2022 23:44:58 +0300 Subject: [PATCH 0928/5199] Remove unnecessary `Schedule` during setup --- .../Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs index 44529aa78c..1d50901fec 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Gameplay } [SetUp] - public void SetUp() => Schedule(() => storyboardMods = Array.Empty()); + public void SetUp() => storyboardMods = Array.Empty(); [Test] public void TestStoryboardSamplesStopDuringPause() From bb94d68139478a11054f76dc0258e590997e6b4c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 2 Mar 2022 23:55:42 +0300 Subject: [PATCH 0929/5199] Separate storyboard samples and skip intro steps to own methods --- .../TestSceneStoryboardSamplePlayback.cs | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs index 1d50901fec..6b74868944 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs @@ -51,20 +51,21 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("player paused", () => Player.Pause()); AddAssert("player is currently paused", () => Player.GameplayClockContainer.IsPaused.Value); - AddAssert("all storyboard samples stopped immediately", () => allStoryboardSamples.All(sound => !sound.IsPlaying)); + allStoryobardSamplesStopped(); AddStep("player resume", () => Player.Resume()); - AddUntilStep("any storyboard samples playing after resume", () => allStoryboardSamples.Any(sound => sound.IsPlaying)); + waitUntilStoryboardSamplesPlay(); } [Test] public void TestStoryboardSamplesStopOnSkip() { - createPlayerTest(true); + createPlayerTest(); - AddAssert("all storyboard samples stopped immediately", () => allStoryboardSamples.All(sound => !sound.IsPlaying)); + skipIntro(); + allStoryobardSamplesStopped(); - AddUntilStep("any storyboard samples playing after skip", () => allStoryboardSamples.Any(sound => sound.IsPlaying)); + waitUntilStoryboardSamplesPlay(); } [TestCase(typeof(OsuModDoubleTime), 1.5)] @@ -80,7 +81,8 @@ namespace osu.Game.Tests.Visual.Gameplay storyboardMods = new[] { testedMod }; }); - createPlayerTest(true); + createPlayerTest(); + skipIntro(); AddAssert("sample playback rate matches mod rates", () => allStoryboardSamples.All(sound => sound.ChildrenOfType().First().AggregateFrequency.Value == expectedRate)); @@ -100,7 +102,8 @@ namespace osu.Game.Tests.Visual.Gameplay storyboardMods = new[] { testedMod }; }); - createPlayerTest(true); + createPlayerTest(); + skipIntro(); ModTimeRamp gameplayMod = null; @@ -114,17 +117,20 @@ namespace osu.Game.Tests.Visual.Gameplay sound.ChildrenOfType().First().AggregateFrequency.Value == gameplayMod.SpeedChange.Value)); } - private void createPlayerTest(bool skipIntro = false) + private void createPlayerTest() { CreateTest(null); AddAssert("storyboard loaded", () => Player.Beatmap.Value.Storyboard != null); - AddUntilStep("any storyboard samples playing", () => allStoryboardSamples.Any(sound => sound.IsPlaying)); - - if (skipIntro) - AddStep("skip intro", () => InputManager.Key(Key.Space)); + waitUntilStoryboardSamplesPlay(); } + private void waitUntilStoryboardSamplesPlay() => AddUntilStep("any storyboard samples playing", () => allStoryboardSamples.Any(sound => sound.IsPlaying)); + + private void allStoryobardSamplesStopped() => AddAssert("all storyboard samples stopped immediately", () => allStoryboardSamples.All(sound => !sound.IsPlaying)); + + private void skipIntro() => AddStep("skip intro", () => InputManager.Key(Key.Space)); + private IEnumerable allStoryboardSamples => Player.ChildrenOfType(); protected override bool AllowFail => false; From 2ce4faa3564f13d334e5ed282560488257f19c22 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 3 Mar 2022 00:02:36 +0300 Subject: [PATCH 0930/5199] Fix typo in method name --- .../Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs index 6b74868944..909cab5e3d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("player paused", () => Player.Pause()); AddAssert("player is currently paused", () => Player.GameplayClockContainer.IsPaused.Value); - allStoryobardSamplesStopped(); + allStoryboardSamplesStopped(); AddStep("player resume", () => Player.Resume()); waitUntilStoryboardSamplesPlay(); @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Gameplay createPlayerTest(); skipIntro(); - allStoryobardSamplesStopped(); + allStoryboardSamplesStopped(); waitUntilStoryboardSamplesPlay(); } @@ -127,7 +127,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void waitUntilStoryboardSamplesPlay() => AddUntilStep("any storyboard samples playing", () => allStoryboardSamples.Any(sound => sound.IsPlaying)); - private void allStoryobardSamplesStopped() => AddAssert("all storyboard samples stopped immediately", () => allStoryboardSamples.All(sound => !sound.IsPlaying)); + private void allStoryboardSamplesStopped() => AddAssert("all storyboard samples stopped immediately", () => allStoryboardSamples.All(sound => !sound.IsPlaying)); private void skipIntro() => AddStep("skip intro", () => InputManager.Key(Key.Space)); From 3630ab2db2da2ee6b0291bf0e3cedfd47a8f020f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 3 Mar 2022 00:09:12 +0300 Subject: [PATCH 0931/5199] Remove unnecessary nullability of storyboard mods list --- osu.Game/Storyboards/Drawables/DrawableStoryboard.cs | 3 +-- osu.Game/Storyboards/Storyboard.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index 840500347f..01e4dfca02 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.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 System.Linq; using System.Threading; @@ -64,7 +63,7 @@ namespace osu.Game.Storyboards.Drawables public DrawableStoryboard(Storyboard storyboard, IReadOnlyList mods) { Storyboard = storyboard; - Mods = mods ?? Array.Empty(); + Mods = mods; Size = new Vector2(640, 480); diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 2faed98ae0..844950336d 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -91,7 +91,7 @@ namespace osu.Game.Storyboards } } - public DrawableStoryboard CreateDrawable(IReadOnlyList mods = null) => + public DrawableStoryboard CreateDrawable(IReadOnlyList mods) => new DrawableStoryboard(this, mods); public Drawable CreateSpriteFromResourcePath(string path, TextureStore textureStore) From 55737226a31ba2f190abeb88953a99e6d704be54 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 3 Mar 2022 10:18:36 +0800 Subject: [PATCH 0932/5199] Use `Enumerable.Repeat` --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 7c92bd8141..f155005f67 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Mods private ITrack track; private HUDOverlay overlay; - private readonly List recentRates = Enumerable.Range(0, average_count).Select(_ => 1d).ToList(); + private readonly List recentRates = Enumerable.Repeat(1d, average_count).ToList(); // rate for a hit is calculated using the end time of another hit object earlier in time // caching them here for easy access @@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Mods InitialRate.TriggerChange(); AdjustPitch.TriggerChange(); recentRates.Clear(); - recentRates.AddRange(Enumerable.Range(0, average_count).Select(_ => InitialRate.Value)); + recentRates.AddRange(Enumerable.Repeat(InitialRate.Value, average_count)); } public void ApplyToSample(DrawableSample sample) From ff7f65de2712200496499ac647fc7fa8a9193cda Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 3 Mar 2022 10:43:04 +0800 Subject: [PATCH 0933/5199] Extract duplicated conditionals --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index f155005f67..69d5a956d9 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Audio; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; @@ -124,10 +125,7 @@ namespace osu.Game.Rulesets.Mods drawable.OnNewResult += (o, result) => { if (dequeuedRates.ContainsKey(result.HitObject)) return; - - if (!result.IsHit) return; - if (!result.Type.AffectsAccuracy()) return; - if (!previousEndTimes.ContainsKey(result.HitObject)) return; + if (!shouldProcessResult(result)) return; double prevEndTime = previousEndTimes[result.HitObject]; @@ -141,10 +139,7 @@ namespace osu.Game.Rulesets.Mods drawable.OnRevertResult += (o, result) => { if (!dequeuedRates.ContainsKey(result.HitObject)) return; - - if (!result.IsHit) return; - if (!result.Type.AffectsAccuracy()) return; - if (!previousEndTimes.ContainsKey(result.HitObject)) return; + if (!shouldProcessResult(result)) return; recentRates.Insert(0, dequeuedRates[result.HitObject]); recentRates.RemoveAt(recentRates.Count - 1); @@ -179,5 +174,14 @@ namespace osu.Game.Rulesets.Mods yield return nested; } } + + private bool shouldProcessResult(JudgementResult result) + { + if (!result.IsHit) return false; + if (!result.Type.AffectsAccuracy()) return false; + if (!previousEndTimes.ContainsKey(result.HitObject)) return false; + + return true; + } } } From 95a40c5dc561fbc55d7d4fe60ce6a34c57326149 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 3 Mar 2022 10:43:30 +0800 Subject: [PATCH 0934/5199] Remove pointless comment --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 69d5a956d9..946d25172c 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -111,7 +111,6 @@ namespace osu.Game.Rulesets.Mods private void applyPitchAdjustment(ValueChangedEvent adjustPitchSetting) { - // remove existing old adjustment track?.RemoveAdjustment(adjustmentForPitchSetting(adjustPitchSetting.OldValue), SpeedChange); track?.AddAdjustment(adjustmentForPitchSetting(adjustPitchSetting.NewValue), SpeedChange); From 51258dbab4fca3c77cdeb767bf616fab633b16e2 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 3 Mar 2022 11:21:20 +0800 Subject: [PATCH 0935/5199] Use binary search in `ApplyToBeatmap` --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 946d25172c..276f3466f4 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -9,7 +9,6 @@ using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; -using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets.Judgements; @@ -151,14 +150,16 @@ namespace osu.Game.Rulesets.Mods public void ApplyToBeatmap(IBeatmap beatmap) { var hitObjects = getAllApplicableHitObjects(beatmap.HitObjects).ToList(); - var endTimes = hitObjects.Select(x => x.GetEndTime()).OrderBy(x => x).ToList(); + var endTimes = hitObjects.Select(x => x.GetEndTime()).OrderBy(x => x).Distinct().ToList(); foreach (HitObject hitObject in hitObjects) { - double prevEndTime = endTimes.LastOrDefault(ht => !Precision.AlmostBigger(ht, hitObject.GetEndTime())); + int index = endTimes.BinarySearch(hitObject.GetEndTime()); + if (index < 0) index = ~index; // BinarySearch returns the next larger element in bitwise complement if there's no exact match + index -= 1; - if (prevEndTime != default) - previousEndTimes.Add(hitObject, prevEndTime); + if (index >= 0) + previousEndTimes.Add(hitObject, endTimes[index]); } } From 09254407fe894b0da3dae1a4ac9cacfa0bac8be2 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 3 Mar 2022 12:02:39 +0800 Subject: [PATCH 0936/5199] Interpolate speed change using `IUpdatableByPlayfield` --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 33 +++++++++++----------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 276f3466f4..0ad31ebb9c 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -1,25 +1,24 @@ // 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.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.Play; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mods { - public class ModAdaptiveSpeed : Mod, IApplicableToRate, IApplicableToDrawableHitObject, IApplicableToBeatmap, IApplicableToHUD + public class ModAdaptiveSpeed : Mod, IApplicableToRate, IApplicableToDrawableHitObject, IApplicableToBeatmap, IUpdatableByPlayfield { // use a wider range so there's still room for adjustment when the initial rate is extreme private const double fastest_rate = 2.5f; @@ -62,12 +61,11 @@ namespace osu.Game.Rulesets.Mods public BindableNumber SpeedChange { get; } = new BindableDouble { Default = 1, - Value = 1, - Precision = 0.01 + Value = 1 }; private ITrack track; - private HUDOverlay overlay; + private double targetRate = 1d; private readonly List recentRates = Enumerable.Repeat(1d, average_count).ToList(); @@ -81,16 +79,14 @@ namespace osu.Game.Rulesets.Mods public ModAdaptiveSpeed() { - InitialRate.BindValueChanged(val => SpeedChange.Value = val.NewValue); + InitialRate.BindValueChanged(val => + { + SpeedChange.Value = val.NewValue; + targetRate = val.NewValue; + }); AdjustPitch.BindValueChanged(applyPitchAdjustment); } - public void ApplyToHUD(HUDOverlay overlay) - { - // this is only used to transform the SpeedChange bindable - this.overlay = overlay; - } - public void ApplyToTrack(ITrack track) { this.track = track; @@ -106,6 +102,11 @@ namespace osu.Game.Rulesets.Mods sample.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); } + public void Update(Playfield playfield) + { + SpeedChange.Value = Interpolation.DampContinuously(SpeedChange.Value, targetRate, 50, playfield.Clock.ElapsedFrameTime); + } + public double ApplyToRate(double time, double rate = 1) => rate * InitialRate.Value; private void applyPitchAdjustment(ValueChangedEvent adjustPitchSetting) @@ -132,7 +133,7 @@ namespace osu.Game.Rulesets.Mods dequeuedRates.Add(result.HitObject, recentRates[0]); recentRates.RemoveAt(0); - overlay.TransformBindableTo(SpeedChange, recentRates.Average(), 100); + targetRate = recentRates.Average(); }; drawable.OnRevertResult += (o, result) => { @@ -143,7 +144,7 @@ namespace osu.Game.Rulesets.Mods recentRates.RemoveAt(recentRates.Count - 1); dequeuedRates.Remove(result.HitObject); - overlay.TransformBindableTo(SpeedChange, recentRates.Average(), 100); + targetRate = recentRates.Average(); }; } From ae71dcceeb0d7478210eaf631f62732352e4debd Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 3 Mar 2022 13:03:53 +0800 Subject: [PATCH 0937/5199] Convert comments to xmldoc --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 0ad31ebb9c..5a2edfa17d 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -69,12 +69,15 @@ namespace osu.Game.Rulesets.Mods private readonly List recentRates = Enumerable.Repeat(1d, average_count).ToList(); - // rate for a hit is calculated using the end time of another hit object earlier in time - // caching them here for easy access + /// + /// Rate for a hit is calculated using the end time of another hit object earlier in time, + /// caching them here for easy access + /// private readonly Dictionary previousEndTimes = new Dictionary(); - // record the value removed from recentRates when an object is hit - // for rewind support + /// + /// Record the value removed from when an object is hit for rewind support + /// private readonly Dictionary dequeuedRates = new Dictionary(); public ModAdaptiveSpeed() From 9c2aa511943c3b89279beb00aa954e1204539d98 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 3 Mar 2022 13:07:30 +0800 Subject: [PATCH 0938/5199] Rename `applyPitchAdjustment` to `adjustPitchChanged` --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 5a2edfa17d..2e77b7c6fc 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Mods SpeedChange.Value = val.NewValue; targetRate = val.NewValue; }); - AdjustPitch.BindValueChanged(applyPitchAdjustment); + AdjustPitch.BindValueChanged(adjustPitchChanged); } public void ApplyToTrack(ITrack track) @@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Mods public double ApplyToRate(double time, double rate = 1) => rate * InitialRate.Value; - private void applyPitchAdjustment(ValueChangedEvent adjustPitchSetting) + private void adjustPitchChanged(ValueChangedEvent adjustPitchSetting) { track?.RemoveAdjustment(adjustmentForPitchSetting(adjustPitchSetting.OldValue), SpeedChange); From fab9323707655e3ce08145290bfeb84f886e0f8d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 14:08:48 +0900 Subject: [PATCH 0939/5199] Replace all legacy ruleset checks with a helper property call --- osu.Desktop/DiscordRichPresence.cs | 5 +---- osu.Game/Beatmaps/DifficultyRecommender.cs | 2 +- osu.Game/Rulesets/IRulesetInfo.cs | 5 +++++ osu.Game/Rulesets/RulesetInfo.cs | 5 +++++ osu.Game/Screens/Play/SoloPlayer.cs | 3 +-- osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- 6 files changed, 14 insertions(+), 8 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 3642f70a56..fe687e8dab 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -108,10 +108,7 @@ namespace osu.Desktop presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.GlobalRank > 0 ? $" (rank #{user.Value.Statistics.GlobalRank:N0})" : string.Empty); // update ruleset - int onlineID = ruleset.Value.OnlineID; - bool isLegacyRuleset = onlineID >= 0 && onlineID <= ILegacyRuleset.MAX_LEGACY_RULESET_ID; - - presence.Assets.SmallImageKey = isLegacyRuleset ? $"mode_{onlineID}" : "mode_custom"; + presence.Assets.SmallImageKey = ruleset.Value.IsLegacyRuleset ? $"mode_{ruleset.Value.OnlineID}" : "mode_custom"; presence.Assets.SmallImageText = ruleset.Value.Name; client.SetPresence(presence); diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs index 3949e84f4a..8c3e832293 100644 --- a/osu.Game/Beatmaps/DifficultyRecommender.cs +++ b/osu.Game/Beatmaps/DifficultyRecommender.cs @@ -83,7 +83,7 @@ namespace osu.Game.Beatmaps requestedUserId = api.LocalUser.Value.Id; // only query API for built-in rulesets - rulesets.AvailableRulesets.Where(ruleset => ruleset.OnlineID >= 0 && ruleset.OnlineID <= ILegacyRuleset.MAX_LEGACY_RULESET_ID).ForEach(rulesetInfo => + rulesets.AvailableRulesets.Where(ruleset => ruleset.IsLegacyRuleset).ForEach(rulesetInfo => { var req = new GetUserRequest(api.LocalUser.Value.Id, rulesetInfo); diff --git a/osu.Game/Rulesets/IRulesetInfo.cs b/osu.Game/Rulesets/IRulesetInfo.cs index 60a02212fc..a8ed1683c2 100644 --- a/osu.Game/Rulesets/IRulesetInfo.cs +++ b/osu.Game/Rulesets/IRulesetInfo.cs @@ -29,5 +29,10 @@ namespace osu.Game.Rulesets string InstantiationInfo { get; } Ruleset CreateInstance(); + + /// + /// Whether this ruleset's online ID is within the range that defines it as a legacy ruleset (ie. either osu!, osu!taiko, osu!catch or osu!mania). + /// + public bool IsLegacyRuleset => OnlineID >= 0 && OnlineID <= ILegacyRuleset.MAX_LEGACY_RULESET_ID; } } diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 88e3988431..cf7d84c2b4 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -91,6 +91,11 @@ namespace osu.Game.Rulesets Available = Available }; + /// + /// Whether this ruleset's online ID is within the range that defines it as a legacy ruleset (ie. either osu!, osu!taiko, osu!catch or osu!mania). + /// + public bool IsLegacyRuleset => ((IRulesetInfo)this).IsLegacyRuleset; + public Ruleset CreateInstance() { if (!Available) diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index 824c0072e3..b877ee1c11 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -7,7 +7,6 @@ using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Online.Solo; -using osu.Game.Rulesets; using osu.Game.Scoring; namespace osu.Game.Screens.Play @@ -32,7 +31,7 @@ namespace osu.Game.Screens.Play if (beatmapId <= 0) return null; - if (rulesetId < 0 || rulesetId > ILegacyRuleset.MAX_LEGACY_RULESET_ID) + if (!Ruleset.Value.IsLegacyRuleset) return null; return new CreateSoloScoreRequest(beatmapId, rulesetId, Game.VersionHash); diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 6daaae9d04..95910ed0aa 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -118,7 +118,7 @@ namespace osu.Game.Screens.Select.Leaderboards return null; } - if (fetchRuleset.OnlineID <= 0 || fetchRuleset.OnlineID > ILegacyRuleset.MAX_LEGACY_RULESET_ID) + if (!fetchRuleset.IsLegacyRuleset) { SetErrorState(LeaderboardState.RulesetUnavailable); return null; From 4ce2044e4c842f50f0beb7ea62a8b2b493439a37 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 3 Mar 2022 13:09:29 +0800 Subject: [PATCH 0940/5199] Reorder members --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 2e77b7c6fc..b3a318e307 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -1,5 +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 System.Linq; @@ -112,16 +113,6 @@ namespace osu.Game.Rulesets.Mods public double ApplyToRate(double time, double rate = 1) => rate * InitialRate.Value; - private void adjustPitchChanged(ValueChangedEvent adjustPitchSetting) - { - track?.RemoveAdjustment(adjustmentForPitchSetting(adjustPitchSetting.OldValue), SpeedChange); - - track?.AddAdjustment(adjustmentForPitchSetting(adjustPitchSetting.NewValue), SpeedChange); - } - - private AdjustableProperty adjustmentForPitchSetting(bool adjustPitchSettingValue) - => adjustPitchSettingValue ? AdjustableProperty.Frequency : AdjustableProperty.Tempo; - public void ApplyToDrawableHitObject(DrawableHitObject drawable) { drawable.OnNewResult += (o, result) => @@ -167,6 +158,16 @@ namespace osu.Game.Rulesets.Mods } } + private void adjustPitchChanged(ValueChangedEvent adjustPitchSetting) + { + track?.RemoveAdjustment(adjustmentForPitchSetting(adjustPitchSetting.OldValue), SpeedChange); + + track?.AddAdjustment(adjustmentForPitchSetting(adjustPitchSetting.NewValue), SpeedChange); + } + + private AdjustableProperty adjustmentForPitchSetting(bool adjustPitchSettingValue) + => adjustPitchSettingValue ? AdjustableProperty.Frequency : AdjustableProperty.Tempo; + private IEnumerable getAllApplicableHitObjects(IEnumerable hitObjects) { foreach (var hitObject in hitObjects) From 42e07b7308c05bd0792ceee0ae691a54f74f8e0d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 14:15:25 +0900 Subject: [PATCH 0941/5199] Convert to extension method to avoid recursive calls --- osu.Desktop/DiscordRichPresence.cs | 3 ++- osu.Game/Beatmaps/DifficultyRecommender.cs | 3 ++- osu.Game/Extensions/ModelExtensions.cs | 5 +++++ osu.Game/Rulesets/IRulesetInfo.cs | 5 ----- osu.Game/Rulesets/RulesetInfo.cs | 5 ----- osu.Game/Screens/Play/SoloPlayer.cs | 3 ++- osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 3 ++- 7 files changed, 13 insertions(+), 14 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index fe687e8dab..d87b25a4c7 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Configuration; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; @@ -108,7 +109,7 @@ namespace osu.Desktop presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.GlobalRank > 0 ? $" (rank #{user.Value.Statistics.GlobalRank:N0})" : string.Empty); // update ruleset - presence.Assets.SmallImageKey = ruleset.Value.IsLegacyRuleset ? $"mode_{ruleset.Value.OnlineID}" : "mode_custom"; + presence.Assets.SmallImageKey = ruleset.Value.IsLegacyRuleset() ? $"mode_{ruleset.Value.OnlineID}" : "mode_custom"; presence.Assets.SmallImageText = ruleset.Value.Name; client.SetPresence(presence); diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs index 8c3e832293..93c2fccbc7 100644 --- a/osu.Game/Beatmaps/DifficultyRecommender.cs +++ b/osu.Game/Beatmaps/DifficultyRecommender.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; @@ -83,7 +84,7 @@ namespace osu.Game.Beatmaps requestedUserId = api.LocalUser.Value.Id; // only query API for built-in rulesets - rulesets.AvailableRulesets.Where(ruleset => ruleset.IsLegacyRuleset).ForEach(rulesetInfo => + rulesets.AvailableRulesets.Where(ruleset => ruleset.IsLegacyRuleset()).ForEach(rulesetInfo => { var req = new GetUserRequest(api.LocalUser.Value.Id, rulesetInfo); diff --git a/osu.Game/Extensions/ModelExtensions.cs b/osu.Game/Extensions/ModelExtensions.cs index f178a5c97b..13c25e45c8 100644 --- a/osu.Game/Extensions/ModelExtensions.cs +++ b/osu.Game/Extensions/ModelExtensions.cs @@ -72,6 +72,11 @@ namespace osu.Game.Extensions return result; } + /// + /// Check whether this 's online ID is within the range that defines it as a legacy ruleset (ie. either osu!, osu!taiko, osu!catch or osu!mania). + /// + public static bool IsLegacyRuleset(this IRulesetInfo ruleset) => ruleset.OnlineID >= 0 && ruleset.OnlineID <= ILegacyRuleset.MAX_LEGACY_RULESET_ID; + /// /// Check whether the online ID of two s match. /// diff --git a/osu.Game/Rulesets/IRulesetInfo.cs b/osu.Game/Rulesets/IRulesetInfo.cs index a8ed1683c2..60a02212fc 100644 --- a/osu.Game/Rulesets/IRulesetInfo.cs +++ b/osu.Game/Rulesets/IRulesetInfo.cs @@ -29,10 +29,5 @@ namespace osu.Game.Rulesets string InstantiationInfo { get; } Ruleset CreateInstance(); - - /// - /// Whether this ruleset's online ID is within the range that defines it as a legacy ruleset (ie. either osu!, osu!taiko, osu!catch or osu!mania). - /// - public bool IsLegacyRuleset => OnlineID >= 0 && OnlineID <= ILegacyRuleset.MAX_LEGACY_RULESET_ID; } } diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index cf7d84c2b4..88e3988431 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -91,11 +91,6 @@ namespace osu.Game.Rulesets Available = Available }; - /// - /// Whether this ruleset's online ID is within the range that defines it as a legacy ruleset (ie. either osu!, osu!taiko, osu!catch or osu!mania). - /// - public bool IsLegacyRuleset => ((IRulesetInfo)this).IsLegacyRuleset; - public Ruleset CreateInstance() { if (!Available) diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index b877ee1c11..a935ce49eb 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using osu.Game.Beatmaps; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Online.Solo; @@ -31,7 +32,7 @@ namespace osu.Game.Screens.Play if (beatmapId <= 0) return null; - if (!Ruleset.Value.IsLegacyRuleset) + if (!Ruleset.Value.IsLegacyRuleset()) return null; return new CreateSoloScoreRequest(beatmapId, rulesetId, Game.VersionHash); diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 95910ed0aa..eb0addd377 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -11,6 +11,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Leaderboards; @@ -118,7 +119,7 @@ namespace osu.Game.Screens.Select.Leaderboards return null; } - if (!fetchRuleset.IsLegacyRuleset) + if (!fetchRuleset.IsLegacyRuleset()) { SetErrorState(LeaderboardState.RulesetUnavailable); return null; From 29bf7d0bde958ca2d7e30515ae4086a5bfddd2ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 14:35:52 +0900 Subject: [PATCH 0942/5199] Fix shocking grammar and typos in block comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Graphics/Containers/ScalingContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index dd611b0904..248bb8ca1f 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -190,9 +190,9 @@ namespace osu.Game.Graphics.Containers sizableContainer.MoveTo(targetRect.Location, duration, Easing.OutQuart); sizableContainer.ResizeTo(targetRect.Size, duration, Easing.OutQuart); - // Of note, this will not working great in the case of nested ScalingContainers where multiple are applying corner radius. - // There should likely only be masking and corner radius applied at one point in the full game stack to fix this. - // An example of how this can occur is it the skin editor is visible and the game screen scaling is set to "Everything". + // Of note, this will not work great in the case of nested ScalingContainers where multiple are applying corner radius. + // Masking and corner radius should likely only be applied at one point in the full game stack to fix this. + // An example of how this can occur is when the skin editor is visible and the game screen scaling is set to "Everything". sizableContainer.TransformTo(nameof(CornerRadius), requiresMasking ? corner_radius : 0, duration, requiresMasking ? Easing.OutQuart : Easing.None) .OnComplete(_ => { sizableContainer.Masking = requiresMasking; }); } From cb0d643f7047d0f594a4baf76f7751cb895166ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 14:38:20 +0900 Subject: [PATCH 0943/5199] Add parameter xmldoc to explain what a null rect does --- osu.Game/Graphics/Containers/ScalingContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 248bb8ca1f..d331b818a1 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -45,6 +45,8 @@ namespace osu.Game.Graphics.Containers /// /// Set a custom position and scale which overrides any user specification. /// + /// A rectangle with positional and sizing information for this container to conform to. null will clear the custom rect and revert to user settings. + /// Whether the position portion of the provided rect is in relative coordinate space or not. public void SetCustomRect(RectangleF? rect, bool relativePosition = false) { customRect = rect; From ab0ee265408ad406cdbd71de743c9ab1ef9e6d99 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 15:13:42 +0900 Subject: [PATCH 0944/5199] Remove padding from distribution graph bars to fix some bars becoming invisible at low sizes --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 93885b6e02..372c0d4849 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -160,8 +160,6 @@ namespace osu.Game.Screens.Ranking.Statistics RelativeSizeAxes = Axes.Both; - Padding = new MarginPadding { Horizontal = 1 }; - InternalChild = new Circle { RelativeSizeAxes = Axes.Both, From 464be6e64c52b129e36c0e412b00d5f29a4205a1 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 3 Mar 2022 14:37:39 +0800 Subject: [PATCH 0945/5199] Only call `IUpdatableByPlayfield.Update` if the playfield isn't nested --- osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs | 11 +++++++++++ osu.Game/Rulesets/UI/Playfield.cs | 9 ++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs b/osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs index 9baa252caf..7cf480a11b 100644 --- a/osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs +++ b/osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs @@ -5,8 +5,19 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mods { + /// + /// An interface for s that are updated every frame by a . + /// public interface IUpdatableByPlayfield : IApplicableMod { + /// + /// Update this . + /// + /// The main + /// + /// This method is called once per frame during gameplay by the main only. + /// To access nested s, use . + /// void Update(Playfield playfield); } } diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index d0bbf859af..30e71dde1c 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -79,6 +79,11 @@ namespace osu.Game.Rulesets.UI private readonly List nestedPlayfields = new List(); + /// + /// Whether this is nested in another . + /// + public bool IsNested { get; private set; } + /// /// Whether judgements should be displayed by this and and all nested s. /// @@ -206,6 +211,8 @@ namespace osu.Game.Rulesets.UI /// The to add. protected void AddNested(Playfield otherPlayfield) { + otherPlayfield.IsNested = true; + otherPlayfield.DisplayJudgements.BindTo(DisplayJudgements); otherPlayfield.NewResult += (d, r) => NewResult?.Invoke(d, r); @@ -229,7 +236,7 @@ namespace osu.Game.Rulesets.UI { base.Update(); - if (mods != null) + if (!IsNested && mods != null) { foreach (var mod in mods) { From a06d806fb911ddb1531eda8f5415c0e9a86dc7ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 15:43:01 +0900 Subject: [PATCH 0946/5199] Fix hit distribution graph midpoint rounding not looking great around zero Not sure this will be accepted and it's likely only ever going to show in tests, but seems to be a better approach to midpoint rounding for this case? --- .../TestSceneHitEventTimingDistributionGraph.cs | 6 ++++++ .../HitEventTimingDistributionGraph.cs | 16 ++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index 221001e40b..f31aec8975 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -23,6 +23,12 @@ namespace osu.Game.Tests.Visual.Ranking createTest(CreateDistributedHitEvents()); } + [Test] + public void TestManyDistributedEventsOffset() + { + createTest(CreateDistributedHitEvents(-3.5)); + } + [Test] public void TestAroundCentre() { diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 93885b6e02..3a4641cba9 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -64,10 +64,22 @@ namespace osu.Game.Screens.Ranking.Statistics // Prevent div-by-0 by enforcing a minimum bin size binSize = Math.Max(1, binSize); + bool roundUp = true; + foreach (var e in hitEvents) { - int binOffset = (int)Math.Round(e.TimeOffset / binSize, MidpointRounding.AwayFromZero); - bins[timing_distribution_centre_bin_index + binOffset]++; + double binOffset = e.TimeOffset / binSize; + + // .NET's round midpoint handling doesn't provide a behaviour that works amazingly for display + // purposes here. We want midpoint rounding to roughly distribute evenly to each adjacent bucket + // so the easiest way is to cycle between downwards and upwards rounding as we process events. + if (Math.Abs(binOffset - (int)binOffset) == 0.5) + { + binOffset += Math.Sign(binOffset) * (roundUp ? 1 : 0); + roundUp = !roundUp; + } + + bins[timing_distribution_centre_bin_index + (int)binOffset]++; } int maxCount = bins.Max(); From 9c43500ad358cc50ea399af79d47fa4f7f6ace5b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 16:23:30 +0900 Subject: [PATCH 0947/5199] Add ability for player loading screen settings to scroll As we add more items here this is going to become necessary. Until the design no doubt gets changed. --- osu.Game/Overlays/SettingsToolboxGroup.cs | 7 +++--- osu.Game/Screens/Play/PlayerLoader.cs | 29 +++++++++++++++-------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/SettingsToolboxGroup.cs b/osu.Game/Overlays/SettingsToolboxGroup.cs index 08321f68fe..b4178359a4 100644 --- a/osu.Game/Overlays/SettingsToolboxGroup.cs +++ b/osu.Game/Overlays/SettingsToolboxGroup.cs @@ -22,8 +22,9 @@ namespace osu.Game.Overlays { public class SettingsToolboxGroup : Container, IExpandable { + public const int CONTAINER_WIDTH = 270; + private const float transition_duration = 250; - private const int container_width = 270; private const int border_thickness = 2; private const int header_height = 30; private const int corner_radius = 5; @@ -49,7 +50,7 @@ namespace osu.Game.Overlays public SettingsToolboxGroup(string title) { AutoSizeAxes = Axes.Y; - Width = container_width; + Width = CONTAINER_WIDTH; Masking = true; CornerRadius = corner_radius; BorderColour = Color4.Black; @@ -201,7 +202,5 @@ namespace osu.Game.Overlays } protected override Container Content => content; - - protected override bool OnMouseDown(MouseDownEvent e) => true; } } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index f6d63a8ec5..41eb822e39 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -143,6 +143,8 @@ namespace osu.Game.Screens.Play muteWarningShownOnce = sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce); batteryWarningShownOnce = sessionStatics.GetBindable(Static.LowBatteryNotificationShownOnce); + const float padding = 25; + InternalChildren = new Drawable[] { (content = new LogoTrackingContainer @@ -158,20 +160,27 @@ namespace osu.Game.Screens.Play Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - PlayerSettings = new FillFlowContainer + new OsuScrollContainer { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 20), - Margin = new MarginPadding(25), - Children = new PlayerSettingsGroup[] + RelativeSizeAxes = Axes.Y, + Width = SettingsToolboxGroup.CONTAINER_WIDTH + padding * 2, + Padding = new MarginPadding { Vertical = padding }, + Masking = false, + Child = PlayerSettings = new FillFlowContainer { - VisualSettings = new VisualSettings(), - AudioSettings = new AudioSettings(), - new InputSettings() - } + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Padding = new MarginPadding { Horizontal = padding }, + Children = new PlayerSettingsGroup[] + { + VisualSettings = new VisualSettings(), + AudioSettings = new AudioSettings(), + new InputSettings() + } + }, }, idleTracker = new IdleTracker(750), }), From f09a4e9c5b0e5507d9d3a22b92682a2190030832 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 16:28:32 +0900 Subject: [PATCH 0948/5199] Fix potential crash in tests when attempting to lookup key bindings in cases the lookup is not available --- osu.Game/Configuration/DevelopmentOsuConfigManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Configuration/DevelopmentOsuConfigManager.cs b/osu.Game/Configuration/DevelopmentOsuConfigManager.cs index ff19dd874c..f1cb0731fe 100644 --- a/osu.Game/Configuration/DevelopmentOsuConfigManager.cs +++ b/osu.Game/Configuration/DevelopmentOsuConfigManager.cs @@ -14,6 +14,8 @@ namespace osu.Game.Configuration public DevelopmentOsuConfigManager(Storage storage) : base(storage) { + LookupKeyBindings = _ => "unknown"; + LookupSkinName = _ => "unknown"; } } } From 7ee30024e881387b8ab089d2b6153a901d7cafc8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 16:59:10 +0900 Subject: [PATCH 0949/5199] Restructure `OsuSliderBar` to allow for custom tooltips --- .../Graphics/UserInterface/OsuSliderBar.cs | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 333ae4f832..17dce10cf6 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box rightBox; private readonly Container nubContainer; - public virtual LocalisableString TooltipText { get; private set; } + public virtual LocalisableString TooltipText { get; protected set; } /// /// Whether to format the tooltip as a percentage or the actual value. @@ -148,7 +148,7 @@ namespace osu.Game.Graphics.UserInterface protected override void LoadComplete() { base.LoadComplete(); - CurrentNumber.BindValueChanged(current => updateTooltipText(current.NewValue), true); + CurrentNumber.BindValueChanged(current => TooltipText = GetTooltipText(current.NewValue), true); } protected override bool OnHover(HoverEvent e) @@ -178,7 +178,7 @@ namespace osu.Game.Graphics.UserInterface { base.OnUserChange(value); playSample(value); - updateTooltipText(value); + TooltipText = GetTooltipText(value); } private void playSample(T value) @@ -203,28 +203,22 @@ namespace osu.Game.Graphics.UserInterface channel.Play(); } - private void updateTooltipText(T value) + protected virtual LocalisableString GetTooltipText(T value) { if (CurrentNumber.IsInteger) - TooltipText = value.ToInt32(NumberFormatInfo.InvariantInfo).ToString("N0"); - else - { - double floatValue = value.ToDouble(NumberFormatInfo.InvariantInfo); + return value.ToInt32(NumberFormatInfo.InvariantInfo).ToString("N0"); - if (DisplayAsPercentage) - { - TooltipText = floatValue.ToString("0%"); - } - else - { - decimal decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits); + double floatValue = value.ToDouble(NumberFormatInfo.InvariantInfo); - // Find the number of significant digits (we could have less than 5 after normalize()) - int significantDigits = FormatUtils.FindPrecision(decimalPrecision); + if (DisplayAsPercentage) + return floatValue.ToString("0%"); - TooltipText = floatValue.ToString($"N{significantDigits}"); - } - } + decimal decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits); + + // Find the number of significant digits (we could have less than 5 after normalize()) + int significantDigits = FormatUtils.FindPrecision(decimalPrecision); + + return floatValue.ToString($"N{significantDigits}"); } protected override void UpdateAfterChildren() From 3848964faa4050465e8ff5db0156294e8d971336 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 17:07:46 +0900 Subject: [PATCH 0950/5199] Add tooltip text for offset adjustment slider --- .../BeatmapOffsetControlStrings.cs | 12 +++++++- .../PlayerSettings/BeatmapOffsetControl.cs | 28 ++++++++++++++++++- .../Play/PlayerSettings/PlayerSliderBar.cs | 12 ++++---- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/osu.Game/Localisation/BeatmapOffsetControlStrings.cs b/osu.Game/Localisation/BeatmapOffsetControlStrings.cs index 7b2a9e50b2..632a1ad0ea 100644 --- a/osu.Game/Localisation/BeatmapOffsetControlStrings.cs +++ b/osu.Game/Localisation/BeatmapOffsetControlStrings.cs @@ -29,6 +29,16 @@ namespace osu.Game.Localisation /// public static LocalisableString CalibrateUsingLastPlay => new TranslatableString(getKey(@"calibrate_using_last_play"), @"Calibrate using last play"); + /// + /// "(hit objects appear later)" + /// + public static LocalisableString HitObjectsAppearLater => new TranslatableString(getKey(@"hit_objects_appear_later"), @"(hit objects appear later)"); + + /// + /// "(hit objects appear earlier)" + /// + public static LocalisableString HitObjectsAppearEarlier => new TranslatableString(getKey(@"hit_objects_appear_earlier"), @"(hit objects appear earlier)"); + private static string getKey(string key) => $@"{prefix}:{key}"; } -} \ No newline at end of file +} diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index dc3e80d695..201e431367 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -3,12 +3,14 @@ using System; using System.ComponentModel; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Database; @@ -71,7 +73,7 @@ namespace osu.Game.Screens.Play.PlayerSettings Spacing = new Vector2(10), Children = new Drawable[] { - new PlayerSliderBar + new OffsetSliderBar { KeyboardStep = 5, LabelText = BeatmapOffsetControlStrings.BeatmapOffset, @@ -88,6 +90,30 @@ namespace osu.Game.Screens.Play.PlayerSettings }; } + public class OffsetSliderBar : PlayerSliderBar + { + protected override Drawable CreateControl() => new CustomSliderBar(); + + protected class CustomSliderBar : SliderBar + { + protected override LocalisableString GetTooltipText(double value) + { + return value == 0 + ? new TranslatableString("_", @"{0} ms", base.GetTooltipText(value)) + : new TranslatableString("_", @"{0} ms {1}", base.GetTooltipText(value), getEarlyLateText(value)); + } + + private LocalisableString getEarlyLateText(double value) + { + Debug.Assert(value != 0); + + return value > 0 + ? BeatmapOffsetControlStrings.HitObjectsAppearLater + : BeatmapOffsetControlStrings.HitObjectsAppearEarlier; + } + } + } + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs index 57ffe16f76..3f1a5bc0ac 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs @@ -15,13 +15,15 @@ namespace osu.Game.Screens.Play.PlayerSettings { public OsuSliderBar Bar => (OsuSliderBar)Control; - protected override Drawable CreateControl() => new SliderBar - { - RelativeSizeAxes = Axes.X - }; + protected override Drawable CreateControl() => new SliderBar(); - private class SliderBar : OsuSliderBar + protected class SliderBar : OsuSliderBar { + public SliderBar() + { + RelativeSizeAxes = Axes.X; + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { From 35f532fefa88662a0b536c9dbd4d1341586353be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 17:42:40 +0900 Subject: [PATCH 0951/5199] Add ability to watch properties via a `RealmAccess` helper method --- osu.Game/Database/RealmAccess.cs | 62 ++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index fb3052d850..b093c1ed56 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -3,9 +3,11 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; +using System.Linq.Expressions; using System.Reflection; using System.Threading; using System.Threading.Tasks; @@ -318,6 +320,66 @@ namespace osu.Game.Database } } + /// + /// Subscribe to the property of a realm object to watch for changes. + /// + /// + /// On subscribing, unless the does not match an object, an initial invocation of will occur immediately. + /// Further invocations will occur when the value changes, but may also fire on a realm recycle with no actual value change. + /// + /// A function to retrieve the relevant model from realm. + /// A function to traverse to the relevant property from the model. + /// A function to be invoked when a change of value occurs. + /// The type of the model. + /// The type of the property to be watched. + /// + /// A subscription token. It must be kept alive for as long as you want to receive change notifications. + /// To stop receiving notifications, call . + /// + public IDisposable SubscribeToPropertyChanged(Func modelAccessor, Expression> propertyLookup, Action onChanged) + where TModel : RealmObjectBase + { + return RegisterCustomSubscription(r => + { + string propertyName = getMemberName(propertyLookup); + + var model = Run(modelAccessor); + var propLookupCompiled = propertyLookup.Compile(); + + if (model == null) + return null; + + model.PropertyChanged += onPropertyChanged; + + // Update initial value immediately. + onChanged(propLookupCompiled(model)); + + return new InvokeOnDisposal(() => model.PropertyChanged -= onPropertyChanged); + + void onPropertyChanged(object sender, PropertyChangedEventArgs args) + { + if (args.PropertyName == propertyName) + onChanged(propLookupCompiled(model)); + } + }); + + static string getMemberName(Expression> expression) + { + if (!(expression is LambdaExpression lambda)) + throw new ArgumentException($"Outermost expression must be a lambda expression", nameof(expression)); + + if (!(lambda.Body is MemberExpression memberExpression)) + throw new ArgumentException($"Lambda body must be a member access expression", nameof(expression)); + + // TODO: nested access can be supported, with more iteration here + // (need to iteratively soft-cast `memberExpression.Expression` into `MemberExpression`s until `lambda.Parameters[0]` is hit) + if (memberExpression.Expression != lambda.Parameters[0]) + throw new ArgumentException($"Nested access expressions are not supported", nameof(expression)); + + return memberExpression.Member.Name; + } + } + /// /// Run work on realm that will be run every time the update thread realm instance gets recycled. /// From cecc746f9e3431ba2b1ae54e89072a262413bda9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 17:42:50 +0900 Subject: [PATCH 0952/5199] Update existing usages to use `SubscribeToPropertyChanged` --- .../Play/MasterGameplayClockContainer.cs | 24 +++--------------- .../PlayerSettings/BeatmapOffsetControl.cs | 25 ++++++------------- 2 files changed, 11 insertions(+), 38 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 2b6db5f59e..7febec5737 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -86,26 +86,10 @@ namespace osu.Game.Screens.Play userAudioOffset = config.GetBindable(OsuSetting.AudioOffset); userAudioOffset.BindValueChanged(offset => userGlobalOffsetClock.Offset = offset.NewValue, true); - beatmapOffsetSubscription = realm.RegisterCustomSubscription(r => - { - var userSettings = r.Find(beatmap.BeatmapInfo.ID)?.UserSettings; - - if (userSettings == null) // only the case for tests. - return null; - - void onUserSettingsOnPropertyChanged(object sender, PropertyChangedEventArgs args) - { - if (args.PropertyName == nameof(BeatmapUserSettings.Offset)) - updateOffset(); - } - - updateOffset(); - userSettings.PropertyChanged += onUserSettingsOnPropertyChanged; - - return new InvokeOnDisposal(() => userSettings.PropertyChanged -= onUserSettingsOnPropertyChanged); - - void updateOffset() => userBeatmapOffsetClock.Offset = userSettings.Offset; - }); + beatmapOffsetSubscription = realm.SubscribeToPropertyChanged( + r => r.Find(beatmap.BeatmapInfo.ID)?.UserSettings, + settings => settings.Offset, + val => userBeatmapOffsetClock.Offset = val); // sane default provided by ruleset. startOffset = gameplayStartTime; diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index dc3e80d695..23d7c626da 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -2,13 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.ComponentModel; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Database; @@ -94,24 +94,13 @@ namespace osu.Game.Screens.Play.PlayerSettings ReferenceScore.BindValueChanged(scoreChanged, true); - beatmapOffsetSubscription = realm.RegisterCustomSubscription(r => - { - var userSettings = r.Find(beatmap.Value.BeatmapInfo.ID)?.UserSettings; - - if (userSettings == null) // only the case for tests. - return null; - - Current.Value = userSettings.Offset; - userSettings.PropertyChanged += onUserSettingsOnPropertyChanged; - - return new InvokeOnDisposal(() => userSettings.PropertyChanged -= onUserSettingsOnPropertyChanged); - - void onUserSettingsOnPropertyChanged(object sender, PropertyChangedEventArgs args) + beatmapOffsetSubscription = realm.SubscribeToPropertyChanged( + realm => realm.Find(beatmap.Value.BeatmapInfo.ID)?.UserSettings, + settings => settings.Offset, + val => { - if (args.PropertyName == nameof(BeatmapUserSettings.Offset)) - Current.Value = userSettings.Offset; - } - }); + Current.Value = val; + }); Current.BindValueChanged(currentChanged); } From 1485a3a28a5ada0a04aedcb50ea9790331064b3e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 17:56:49 +0900 Subject: [PATCH 0953/5199] Add test coverage of proeprty changed subscriptions --- .../RealmSubscriptionRegistrationTests.cs | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs index 02d617d0e0..363a189f6e 100644 --- a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs +++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs @@ -47,6 +47,28 @@ namespace osu.Game.Tests.Database void onChanged(IRealmCollection sender, ChangeSet? changes, Exception error) => lastChanges = changes; } + [Test] + public void TestPropertyChangedSubscription() + { + RunTestWithRealm((realm, _) => + { + bool? receivedValue = null; + + realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); + + using (realm.SubscribeToPropertyChanged(r => r.All().First(), setInfo => setInfo.Protected, val => receivedValue = val)) + { + Assert.That(receivedValue, Is.False); + + realm.Write(r => r.All().First().Protected = true); + + realm.Run(r => r.Refresh()); + + Assert.That(receivedValue, Is.True); + } + }); + } + [Test] public void TestSubscriptionWithContextLoss() { @@ -163,5 +185,41 @@ namespace osu.Game.Tests.Database Assert.That(beatmapSetInfo, Is.Null); }); } + + [Test] + public void TestPropertyChangedSubscriptionWithContextLoss() + { + RunTestWithRealm((realm, _) => + { + bool? receivedValue = null; + + realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); + + var subscription = realm.SubscribeToPropertyChanged( + r => r.All().First(), + setInfo => setInfo.Protected, + val => receivedValue = val); + + Assert.That(receivedValue, Is.Not.Null); + receivedValue = null; + + using (realm.BlockAllOperations()) + { + } + + // re-registration after context restore. + realm.Run(r => r.Refresh()); + Assert.That(receivedValue, Is.Not.Null); + + subscription.Dispose(); + receivedValue = null; + + using (realm.BlockAllOperations()) + Assert.That(receivedValue, Is.Null); + + realm.Run(r => r.Refresh()); + Assert.That(receivedValue, Is.Null); + }); + } } } From 5cfa8b88211d819f70375e005d531843d1b1dac1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 21:31:56 +0900 Subject: [PATCH 0954/5199] Revert back to `private set` Co-authored-by: Salman Ahmed --- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 17dce10cf6..a5bc02246d 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box rightBox; private readonly Container nubContainer; - public virtual LocalisableString TooltipText { get; protected set; } + public virtual LocalisableString TooltipText { get; private set; } /// /// Whether to format the tooltip as a percentage or the actual value. From ffaf5b729f4da8af9b5061f2ea037c1e4ad5d640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Mar 2022 17:07:36 +0100 Subject: [PATCH 0955/5199] Move and reword docs of allowable rate range constants --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index b3a318e307..bde85c60ad 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -21,10 +21,6 @@ namespace osu.Game.Rulesets.Mods { public class ModAdaptiveSpeed : Mod, IApplicableToRate, IApplicableToDrawableHitObject, IApplicableToBeatmap, IUpdatableByPlayfield { - // use a wider range so there's still room for adjustment when the initial rate is extreme - private const double fastest_rate = 2.5f; - private const double slowest_rate = 0.4f; - /// /// Adjust track rate using the average speed of the last x hits /// @@ -61,10 +57,18 @@ namespace osu.Game.Rulesets.Mods public BindableNumber SpeedChange { get; } = new BindableDouble { + MinValue = min_allowable_rate, + MaxValue = max_allowable_rate, Default = 1, Value = 1 }; + // The two constants below denote the maximum allowable range of rates that `SpeedChange` can take. + // The range is purposefully wider than the range of values that `InitialRate` allows + // in order to give some leeway for change even when extreme initial rates are chosen. + private const double min_allowable_rate = 0.4f; + private const double max_allowable_rate = 2.5f; + private ITrack track; private double targetRate = 1d; @@ -122,7 +126,7 @@ namespace osu.Game.Rulesets.Mods double prevEndTime = previousEndTimes[result.HitObject]; - recentRates.Add(Math.Clamp((result.HitObject.GetEndTime() - prevEndTime) / (result.TimeAbsolute - prevEndTime) * SpeedChange.Value, slowest_rate, fastest_rate)); + recentRates.Add(Math.Clamp((result.HitObject.GetEndTime() - prevEndTime) / (result.TimeAbsolute - prevEndTime) * SpeedChange.Value, min_allowable_rate, max_allowable_rate)); dequeuedRates.Add(result.HitObject, recentRates[0]); recentRates.RemoveAt(0); From 3797871aa08aa836134a450fc03ab2431b3a47c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Mar 2022 17:25:49 +0100 Subject: [PATCH 0956/5199] Add extended documentation of adaptive speed mod machinations --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 73 ++++++++++++++++------ 1 file changed, 53 insertions(+), 20 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index bde85c60ad..78839ea692 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -21,11 +21,6 @@ namespace osu.Game.Rulesets.Mods { public class ModAdaptiveSpeed : Mod, IApplicableToRate, IApplicableToDrawableHitObject, IApplicableToBeatmap, IUpdatableByPlayfield { - /// - /// Adjust track rate using the average speed of the last x hits - /// - private const int average_count = 6; - public override string Name => "Adaptive Speed"; public override string Acronym => "AS"; @@ -55,6 +50,10 @@ namespace osu.Game.Rulesets.Mods Value = true }; + /// + /// The instantaneous rate of the track. + /// Every frame this mod will attempt to smoothly adjust this to meet . + /// public BindableNumber SpeedChange { get; } = new BindableDouble { MinValue = min_allowable_rate, @@ -72,18 +71,52 @@ namespace osu.Game.Rulesets.Mods private ITrack track; private double targetRate = 1d; - private readonly List recentRates = Enumerable.Repeat(1d, average_count).ToList(); + /// + /// The number of most recent track rates (approximated from how early/late each object was hit relative to the previous object) + /// which should be averaged to calculate the instantaneous value of . + /// + private const int recent_rate_count = 6; /// - /// Rate for a hit is calculated using the end time of another hit object earlier in time, - /// caching them here for easy access + /// Stores the most recent approximated track rates + /// which are averaged to calculate the instantaneous value of . /// - private readonly Dictionary previousEndTimes = new Dictionary(); + /// + /// This list is used as a double-ended queue with fixed capacity + /// (items can be enqueued/dequeued at either end of the list). + /// When time is elapsing forward, items are dequeued from the start and enqueued onto the end of the list. + /// When time is being rewound, items are dequeued from the end and enqueued onto the start of the list. + /// + private readonly List recentRates = Enumerable.Repeat(1d, recent_rate_count).ToList(); /// - /// Record the value removed from when an object is hit for rewind support + /// For each given in the map, this dictionary maps the object onto the latest end time of any other object + /// that precedes the end time of the given object. + /// This can be loosely interpreted as the end time of the preceding hit object in rulesets that do not have overlapping hit objects. /// - private readonly Dictionary dequeuedRates = new Dictionary(); + private readonly Dictionary precedingEndTimes = new Dictionary(); + + /// + /// For each given in the map, this dictionary maps the object onto the approximated track rate with which the user hit it. + /// + /// + /// + /// The approximation is calculated as follows: + /// + /// + /// Consider a hitobject which ends at 1000ms, and assume that its preceding hitobject ends at 500ms. + /// This gives a time difference of 1000 - 500 = 500ms. + /// + /// + /// Now assume that the user hit this object at 980ms rather than 1000ms. + /// When compared to the preceding hitobject, this gives 980 - 500 = 480ms. + /// + /// + /// With the above assumptions, the player is rushing / hitting early, which means that the track should speed up to match. + /// Therefore, the approximated target rate for this object would be equal to 500 / 480 * . + /// + /// + private readonly Dictionary approximatedRates = new Dictionary(); public ModAdaptiveSpeed() { @@ -102,7 +135,7 @@ namespace osu.Game.Rulesets.Mods InitialRate.TriggerChange(); AdjustPitch.TriggerChange(); recentRates.Clear(); - recentRates.AddRange(Enumerable.Repeat(InitialRate.Value, average_count)); + recentRates.AddRange(Enumerable.Repeat(InitialRate.Value, recent_rate_count)); } public void ApplyToSample(DrawableSample sample) @@ -121,26 +154,26 @@ namespace osu.Game.Rulesets.Mods { drawable.OnNewResult += (o, result) => { - if (dequeuedRates.ContainsKey(result.HitObject)) return; + if (approximatedRates.ContainsKey(result.HitObject)) return; if (!shouldProcessResult(result)) return; - double prevEndTime = previousEndTimes[result.HitObject]; + double prevEndTime = precedingEndTimes[result.HitObject]; recentRates.Add(Math.Clamp((result.HitObject.GetEndTime() - prevEndTime) / (result.TimeAbsolute - prevEndTime) * SpeedChange.Value, min_allowable_rate, max_allowable_rate)); - dequeuedRates.Add(result.HitObject, recentRates[0]); + approximatedRates.Add(result.HitObject, recentRates[0]); recentRates.RemoveAt(0); targetRate = recentRates.Average(); }; drawable.OnRevertResult += (o, result) => { - if (!dequeuedRates.ContainsKey(result.HitObject)) return; + if (!approximatedRates.ContainsKey(result.HitObject)) return; if (!shouldProcessResult(result)) return; - recentRates.Insert(0, dequeuedRates[result.HitObject]); + recentRates.Insert(0, approximatedRates[result.HitObject]); recentRates.RemoveAt(recentRates.Count - 1); - dequeuedRates.Remove(result.HitObject); + approximatedRates.Remove(result.HitObject); targetRate = recentRates.Average(); }; @@ -158,7 +191,7 @@ namespace osu.Game.Rulesets.Mods index -= 1; if (index >= 0) - previousEndTimes.Add(hitObject, endTimes[index]); + precedingEndTimes.Add(hitObject, endTimes[index]); } } @@ -188,7 +221,7 @@ namespace osu.Game.Rulesets.Mods { if (!result.IsHit) return false; if (!result.Type.AffectsAccuracy()) return false; - if (!previousEndTimes.ContainsKey(result.HitObject)) return false; + if (!precedingEndTimes.ContainsKey(result.HitObject)) return false; return true; } From fcefd3c72544ce153c870734de1b384b411ee1d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Mar 2022 17:39:47 +0100 Subject: [PATCH 0957/5199] Fix slightly wrong references in xmldocs --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 78839ea692..c28283d0bb 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -73,13 +73,13 @@ namespace osu.Game.Rulesets.Mods /// /// The number of most recent track rates (approximated from how early/late each object was hit relative to the previous object) - /// which should be averaged to calculate the instantaneous value of . + /// which should be averaged to calculate . /// private const int recent_rate_count = 6; /// /// Stores the most recent approximated track rates - /// which are averaged to calculate the instantaneous value of . + /// which are averaged to calculate the value of . /// /// /// This list is used as a double-ended queue with fixed capacity From 0fbc018a4267d55b63b44a1d185fae4f569229d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Mar 2022 20:21:09 +0100 Subject: [PATCH 0958/5199] Remove redundant string interpolation prefixes --- osu.Game/Database/RealmAccess.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index b093c1ed56..af7c485c57 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -366,15 +366,15 @@ namespace osu.Game.Database static string getMemberName(Expression> expression) { if (!(expression is LambdaExpression lambda)) - throw new ArgumentException($"Outermost expression must be a lambda expression", nameof(expression)); + throw new ArgumentException("Outermost expression must be a lambda expression", nameof(expression)); if (!(lambda.Body is MemberExpression memberExpression)) - throw new ArgumentException($"Lambda body must be a member access expression", nameof(expression)); + throw new ArgumentException("Lambda body must be a member access expression", nameof(expression)); // TODO: nested access can be supported, with more iteration here // (need to iteratively soft-cast `memberExpression.Expression` into `MemberExpression`s until `lambda.Parameters[0]` is hit) if (memberExpression.Expression != lambda.Parameters[0]) - throw new ArgumentException($"Nested access expressions are not supported", nameof(expression)); + throw new ArgumentException("Nested access expressions are not supported", nameof(expression)); return memberExpression.Member.Name; } From edd361d256a2428f9ce0f5a16aa2dc1ceaa2ba96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Mar 2022 20:21:46 +0100 Subject: [PATCH 0959/5199] Trim unused using directives --- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 1 - osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 7febec5737..af58e9d910 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; using osu.Framework; using osu.Framework.Allocation; diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 23d7c626da..72c2292624 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -8,7 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Logging; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Database; From 2e24e7ef56b61320f8516773b2cb5441bc6da3ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Mar 2022 20:28:00 +0100 Subject: [PATCH 0960/5199] Use property expression rather than block --- osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 72c2292624..d09e2cae92 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -96,10 +96,7 @@ namespace osu.Game.Screens.Play.PlayerSettings beatmapOffsetSubscription = realm.SubscribeToPropertyChanged( realm => realm.Find(beatmap.Value.BeatmapInfo.ID)?.UserSettings, settings => settings.Offset, - val => - { - Current.Value = val; - }); + val => Current.Value = val); Current.BindValueChanged(currentChanged); } From 15f65c7897da7f5e3102f00262c2bd8617877135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Mar 2022 20:28:19 +0100 Subject: [PATCH 0961/5199] Rename lambda param to avoid name shadowing --- osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index d09e2cae92..c05c5af10d 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -94,7 +94,7 @@ namespace osu.Game.Screens.Play.PlayerSettings ReferenceScore.BindValueChanged(scoreChanged, true); beatmapOffsetSubscription = realm.SubscribeToPropertyChanged( - realm => realm.Find(beatmap.Value.BeatmapInfo.ID)?.UserSettings, + r => r.Find(beatmap.Value.BeatmapInfo.ID)?.UserSettings, settings => settings.Offset, val => Current.Value = val); From 36263b4dbf09bcbcaed7559be689314d40434e34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Mar 2022 23:09:56 +0100 Subject: [PATCH 0962/5199] Replace remaining manual online ID check with extension method --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 1326395695..f0ead05280 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -7,9 +7,9 @@ using System.Linq; using System.Text; using osu.Framework.Extensions; using osu.Game.Beatmaps; +using osu.Game.Extensions; using osu.Game.IO.Legacy; using osu.Game.Replays.Legacy; -using osu.Game.Rulesets; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; using SharpCompress.Compressors.LZMA; @@ -48,7 +48,7 @@ namespace osu.Game.Scoring.Legacy if (beatmap == null && !score.Replay.Frames.All(f => f is LegacyReplayFrame)) throw new ArgumentException(@"Beatmap must be provided if frames are not already legacy frames.", nameof(beatmap)); - if (score.ScoreInfo.Ruleset.OnlineID < 0 || score.ScoreInfo.Ruleset.OnlineID > ILegacyRuleset.MAX_LEGACY_RULESET_ID) + if (!score.ScoreInfo.Ruleset.IsLegacyRuleset()) throw new ArgumentException(@"Only scores in the osu, taiko, catch, or mania rulesets can be encoded to the legacy score format.", nameof(score)); } From 17729f060582ed948ec6ba32a186549dc651f6c7 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 3 Mar 2022 14:53:49 -0800 Subject: [PATCH 0963/5199] Reword ide section of readme to always use latest version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7ace47a74f..67e28dad97 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Please make sure you have the following prerequisites: - A desktop platform with the [.NET 6.0 SDK](https://dotnet.microsoft.com/download) installed. - When developing with mobile, [Xamarin](https://docs.microsoft.com/en-us/xamarin/) is required, which is shipped together with Visual Studio or [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/). -- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/). +- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as the latest version of [Visual Studio](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/). - When running on Linux, please have a system-wide FFmpeg installation available to support video decoding. ### Downloading the source code From 53f23a429bcf56f426513a5625b98937193bd380 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 3 Mar 2022 15:01:21 -0800 Subject: [PATCH 0964/5199] Fix full stop being inside code backticks --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 67e28dad97..f64240f67a 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ git pull Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this is provided [below](#contributing). -- Visual Studio / Rider users should load the project via one of the platform-specific `.slnf` files, rather than the main `.sln.` This will allow access to template run configurations. +- Visual Studio / Rider users should load the project via one of the platform-specific `.slnf` files, rather than the main `.sln`. This will allow access to template run configurations. You can also build and run *osu!* from the command-line with a single command: From 18b207400d267d655621511ec89e7ef77a331c59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 11:34:25 +0900 Subject: [PATCH 0965/5199] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 526ce959a6..ab1bd553a8 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 7dfd099df1..a2739c527b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 80600655aa..14824a5af6 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 3a37e6e8b181389426cdcff800f35817ab842a00 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 12:00:02 +0900 Subject: [PATCH 0966/5199] Fix profile badges potentially showing on incorrect profile when switching users Closes https://github.com/ppy/osu/issues/17081. --- osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs index 5f513582e5..6333802549 100644 --- a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.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.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -63,11 +64,15 @@ namespace osu.Game.Overlays.Profile.Header }; } + private CancellationTokenSource cancellationTokenSource; + private void updateDisplay(APIUser user) { var badges = user.Badges; badgeFlowContainer.Clear(); + cancellationTokenSource?.Cancel(); + if (badges?.Length > 0) { Show(); @@ -79,7 +84,7 @@ namespace osu.Game.Overlays.Profile.Header { // load in stable order regardless of async load order. badgeFlowContainer.Insert(displayIndex, asyncBadge); - }); + }, (cancellationTokenSource = new CancellationTokenSource()).Token); } } else From b66af7edf419823ae63105bb48d78640d615c001 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 4 Mar 2022 11:03:57 +0800 Subject: [PATCH 0967/5199] Rename `approximatedRates` to `ratesForRewinding` and update xmldoc --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 45 ++++++++++++---------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index c28283d0bb..428d605a99 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -87,21 +87,9 @@ namespace osu.Game.Rulesets.Mods /// When time is elapsing forward, items are dequeued from the start and enqueued onto the end of the list. /// When time is being rewound, items are dequeued from the end and enqueued onto the start of the list. /// - private readonly List recentRates = Enumerable.Repeat(1d, recent_rate_count).ToList(); - - /// - /// For each given in the map, this dictionary maps the object onto the latest end time of any other object - /// that precedes the end time of the given object. - /// This can be loosely interpreted as the end time of the preceding hit object in rulesets that do not have overlapping hit objects. - /// - private readonly Dictionary precedingEndTimes = new Dictionary(); - - /// - /// For each given in the map, this dictionary maps the object onto the approximated track rate with which the user hit it. - /// /// /// - /// The approximation is calculated as follows: + /// The track rate approximation is calculated as follows: /// /// /// Consider a hitobject which ends at 1000ms, and assume that its preceding hitobject ends at 500ms. @@ -116,7 +104,21 @@ namespace osu.Game.Rulesets.Mods /// Therefore, the approximated target rate for this object would be equal to 500 / 480 * . /// /// - private readonly Dictionary approximatedRates = new Dictionary(); + private readonly List recentRates = Enumerable.Repeat(1d, recent_rate_count).ToList(); + + /// + /// For each given in the map, this dictionary maps the object onto the latest end time of any other object + /// that precedes the end time of the given object. + /// This can be loosely interpreted as the end time of the preceding hit object in rulesets that do not have overlapping hit objects. + /// + private readonly Dictionary precedingEndTimes = new Dictionary(); + + /// + /// For each given in the map, this dictionary maps the object onto the track rate dequeued from + /// (i.e. the oldest value in the queue) when the object is hit. If the hit is then reverted, + /// the mapped value can be re-introduced to to properly rewind the queue. + /// + private readonly Dictionary ratesForRewinding = new Dictionary(); public ModAdaptiveSpeed() { @@ -154,26 +156,27 @@ namespace osu.Game.Rulesets.Mods { drawable.OnNewResult += (o, result) => { - if (approximatedRates.ContainsKey(result.HitObject)) return; + if (ratesForRewinding.ContainsKey(result.HitObject)) return; if (!shouldProcessResult(result)) return; double prevEndTime = precedingEndTimes[result.HitObject]; - recentRates.Add(Math.Clamp((result.HitObject.GetEndTime() - prevEndTime) / (result.TimeAbsolute - prevEndTime) * SpeedChange.Value, min_allowable_rate, max_allowable_rate)); - - approximatedRates.Add(result.HitObject, recentRates[0]); + ratesForRewinding.Add(result.HitObject, recentRates[0]); recentRates.RemoveAt(0); + recentRates.Add(Math.Clamp((result.HitObject.GetEndTime() - prevEndTime) / (result.TimeAbsolute - prevEndTime) * SpeedChange.Value, min_allowable_rate, max_allowable_rate)); + targetRate = recentRates.Average(); }; drawable.OnRevertResult += (o, result) => { - if (!approximatedRates.ContainsKey(result.HitObject)) return; + if (!ratesForRewinding.ContainsKey(result.HitObject)) return; if (!shouldProcessResult(result)) return; - recentRates.Insert(0, approximatedRates[result.HitObject]); + recentRates.Insert(0, ratesForRewinding[result.HitObject]); + ratesForRewinding.Remove(result.HitObject); + recentRates.RemoveAt(recentRates.Count - 1); - approximatedRates.Remove(result.HitObject); targetRate = recentRates.Average(); }; From c38126ba9d708fe8250dbb76c5a8fe91cff464d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 12:05:02 +0900 Subject: [PATCH 0968/5199] Make mods argument optional for storyboard construction --- osu.Game/Storyboards/Drawables/DrawableStoryboard.cs | 5 +++-- osu.Game/Storyboards/Storyboard.cs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index 01e4dfca02..a0fb7b0b4a 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.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 System.Threading; @@ -60,10 +61,10 @@ namespace osu.Game.Storyboards.Drawables protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - public DrawableStoryboard(Storyboard storyboard, IReadOnlyList mods) + public DrawableStoryboard(Storyboard storyboard, IReadOnlyList mods = null) { Storyboard = storyboard; - Mods = mods; + Mods = mods ?? Array.Empty(); Size = new Vector2(640, 480); diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 844950336d..2faed98ae0 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -91,7 +91,7 @@ namespace osu.Game.Storyboards } } - public DrawableStoryboard CreateDrawable(IReadOnlyList mods) => + public DrawableStoryboard CreateDrawable(IReadOnlyList mods = null) => new DrawableStoryboard(this, mods); public Drawable CreateSpriteFromResourcePath(string path, TextureStore textureStore) From abba49fd8f8bbe15b93ace48809bc342b486488a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 12:16:05 +0900 Subject: [PATCH 0969/5199] Update all usages of `OsuSlider.TooltipText` overrides to instead implement `GetTooltipText` --- osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs | 2 +- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 2 +- osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs | 2 +- .../Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 2 +- osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs | 4 +++- osu.Game/Overlays/Settings/Sections/SizeSlider.cs | 2 +- .../Settings/Sections/UserInterface/GeneralSettings.cs | 2 +- .../Settings/Sections/UserInterface/SongSelectSettings.cs | 6 ++++-- osu.Game/Rulesets/Mods/ModMuted.cs | 2 +- osu.Game/Rulesets/Mods/ModNoScope.cs | 2 +- 10 files changed, 15 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index 36fa336d0c..ae3c279d98 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mania private class TimeSlider : OsuSliderBar { - public override LocalisableString TooltipText => Current.Value.ToString(@"N0") + "ms"; + protected override LocalisableString GetTooltipText(double value) => $"{value:N0} ms"; } } } diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index a5bc02246d..747a56f636 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box rightBox; private readonly Container nubContainer; - public virtual LocalisableString TooltipText { get; private set; } + public LocalisableString TooltipText { get; private set; } /// /// Whether to format the tooltip as a percentage or the actual value. diff --git a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs index 9345d3fcc7..1aaee5b540 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio private class OffsetSlider : OsuSliderBar { - public override LocalisableString TooltipText => Current.Value.ToString(@"0ms"); + protected override LocalisableString GetTooltipText(double value) => value.ToString(@"0ms"); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index adf1453d1a..b572f1c6a0 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -240,7 +240,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private class UIScaleSlider : OsuSliderBar { - public override LocalisableString TooltipText => base.TooltipText + "x"; + protected override LocalisableString GetTooltipText(float value) => $"{base.GetTooltipText(value)}x"; } private class ResolutionSettingsDropdown : SettingsDropdown diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index 4235dc0a05..971b19ca6c 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -135,7 +135,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input private class SensitivitySlider : OsuSliderBar { - public override LocalisableString TooltipText => Current.Disabled ? MouseSettingsStrings.EnableHighPrecisionForSensitivityAdjust : $"{base.TooltipText}x"; + protected override LocalisableString GetTooltipText(double value) => Current.Disabled + ? MouseSettingsStrings.EnableHighPrecisionForSensitivityAdjust + : $"{base.GetTooltipText(value)}x"; } } } diff --git a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs index 8aeb440be1..c80b330db6 100644 --- a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs +++ b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs @@ -11,6 +11,6 @@ namespace osu.Game.Overlays.Settings.Sections /// internal class SizeSlider : OsuSliderBar { - public override LocalisableString TooltipText => Current.Value.ToString(@"0.##x"); + protected override LocalisableString GetTooltipText(float value) => value.ToString(@"0.##x"); } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs index 0afbed5df5..ca59095875 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface private class TimeSlider : OsuSliderBar { - public override LocalisableString TooltipText => Current.Value.ToString(@"N0") + "ms"; + protected override LocalisableString GetTooltipText(float value) => $"{value:N0} ms"; } } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index 6290046987..c24513e7b4 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -64,12 +64,14 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface private class MaximumStarsSlider : StarsSlider { - public override LocalisableString TooltipText => Current.IsDefault ? UserInterfaceStrings.NoLimit : base.TooltipText; + protected override LocalisableString GetTooltipText(double value) => Current.IsDefault + ? UserInterfaceStrings.NoLimit + : base.GetTooltipText(value); } private class StarsSlider : OsuSliderBar { - public override LocalisableString TooltipText => Current.Value.ToString(@"0.## stars"); + protected override LocalisableString GetTooltipText(double value) => $"{value:0.##} stars"; } } } diff --git a/osu.Game/Rulesets/Mods/ModMuted.cs b/osu.Game/Rulesets/Mods/ModMuted.cs index 1d33b44812..e528d8214e 100644 --- a/osu.Game/Rulesets/Mods/ModMuted.cs +++ b/osu.Game/Rulesets/Mods/ModMuted.cs @@ -111,6 +111,6 @@ namespace osu.Game.Rulesets.Mods public class MuteComboSlider : OsuSliderBar { - public override LocalisableString TooltipText => Current.Value == 0 ? "always muted" : base.TooltipText; + protected override LocalisableString GetTooltipText(int value) => value == 0 ? "always muted" : base.GetTooltipText(value); } } diff --git a/osu.Game/Rulesets/Mods/ModNoScope.cs b/osu.Game/Rulesets/Mods/ModNoScope.cs index 7a935eb38f..c71239ea5a 100644 --- a/osu.Game/Rulesets/Mods/ModNoScope.cs +++ b/osu.Game/Rulesets/Mods/ModNoScope.cs @@ -57,6 +57,6 @@ namespace osu.Game.Rulesets.Mods public class HiddenComboSlider : OsuSliderBar { - public override LocalisableString TooltipText => Current.Value == 0 ? "always hidden" : base.TooltipText; + protected override LocalisableString GetTooltipText(int value) => value == 0 ? "always hidden" : base.GetTooltipText(value); } } From 7854a0a9132ddfd3f8deee5ee6c3a62dcb8000e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 12:21:05 +0900 Subject: [PATCH 0970/5199] Use `double` instead of `float` for `UIHoldActivationDelay` configuration value All times use double, so let's also use double here. --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/Graphics/Containers/HoldToConfirmContainer.cs | 4 ++-- .../Settings/Sections/UserInterface/GeneralSettings.cs | 8 ++++---- osu.Game/Screens/Menu/MainMenu.cs | 4 ++-- osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 07d2026c65..35638a025c 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -140,7 +140,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f); - SetDefault(OsuSetting.UIHoldActivationDelay, 200f, 0f, 500f, 50f); + SetDefault(OsuSetting.UIHoldActivationDelay, 200.0, 0.0, 500.0, 50.0); SetDefault(OsuSetting.IntroSequence, IntroSequence.Triangles); diff --git a/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs index fcf445a878..999dd183aa 100644 --- a/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs +++ b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs @@ -30,12 +30,12 @@ namespace osu.Game.Graphics.Containers public Bindable Progress = new BindableDouble(); - private Bindable holdActivationDelay; + private Bindable holdActivationDelay; [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - holdActivationDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); + holdActivationDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); } protected void BeginConfirm() diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs index ca59095875..c9d3b72add 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs @@ -35,18 +35,18 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface LabelText = UserInterfaceStrings.Parallax, Current = config.GetBindable(OsuSetting.MenuParallax) }, - new SettingsSlider + new SettingsSlider { LabelText = UserInterfaceStrings.HoldToConfirmActivationTime, - Current = config.GetBindable(OsuSetting.UIHoldActivationDelay), + Current = config.GetBindable(OsuSetting.UIHoldActivationDelay), KeyboardStep = 50 }, }; } - private class TimeSlider : OsuSliderBar + private class TimeSlider : OsuSliderBar { - protected override LocalisableString GetTooltipText(float value) => $"{value:N0} ms"; + protected override LocalisableString GetTooltipText(double value) => $"{value:N0} ms"; } } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index b0208a0ae8..e2d79b4015 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -66,7 +66,7 @@ namespace osu.Game.Screens.Menu protected override BackgroundScreen CreateBackground() => background; - private Bindable holdDelay; + private Bindable holdDelay; private Bindable loginDisplayed; private ExitConfirmOverlay exitConfirmOverlay; @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader(true)] private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics) { - holdDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); + holdDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); loginDisplayed = statics.GetBindable(Static.LoginOverlayDisplayed); if (host.CanExit) diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 430f001427..4087011933 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -63,11 +63,11 @@ namespace osu.Game.Screens.Play.HUD [Resolved] private OsuConfigManager config { get; set; } - private Bindable activationDelay; + private Bindable activationDelay; protected override void LoadComplete() { - activationDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); + activationDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); activationDelay.BindValueChanged(v => { text.Text = v.NewValue > 0 From 33862fc0dbc3bca229f2cf719cd3271cf5d131ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 12:25:19 +0900 Subject: [PATCH 0971/5199] Centralise implementation of slider bars which display millisecond time values --- .../ManiaSettingsSubsection.cs | 5 ----- osu.Game/Graphics/UserInterface/TimeSlider.cs | 15 +++++++++++++++ .../Settings/Sections/Audio/OffsetSettings.cs | 7 +------ .../Sections/UserInterface/GeneralSettings.cs | 5 ----- 4 files changed, 16 insertions(+), 16 deletions(-) create mode 100644 osu.Game/Graphics/UserInterface/TimeSlider.cs diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index ae3c279d98..bd3b8c3b10 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -45,10 +45,5 @@ namespace osu.Game.Rulesets.Mania } }; } - - private class TimeSlider : OsuSliderBar - { - protected override LocalisableString GetTooltipText(double value) => $"{value:N0} ms"; - } } } diff --git a/osu.Game/Graphics/UserInterface/TimeSlider.cs b/osu.Game/Graphics/UserInterface/TimeSlider.cs new file mode 100644 index 0000000000..e99345f147 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/TimeSlider.cs @@ -0,0 +1,15 @@ +// 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.Graphics.UserInterface +{ + /// + /// A slider bar which displays a millisecond time value. + /// + public class TimeSlider : OsuSliderBar + { + protected override LocalisableString GetTooltipText(double value) => $"{value:N0} ms"; + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs index 1aaee5b540..673252a99e 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio { Children = new Drawable[] { - new SettingsSlider + new SettingsSlider { LabelText = AudioSettingsStrings.AudioOffset, Current = config.GetBindable(OsuSetting.AudioOffset), @@ -35,10 +35,5 @@ namespace osu.Game.Overlays.Settings.Sections.Audio } }; } - - private class OffsetSlider : OsuSliderBar - { - protected override LocalisableString GetTooltipText(double value) => value.ToString(@"0ms"); - } } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs index c9d3b72add..59894cbcae 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs @@ -43,10 +43,5 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface }, }; } - - private class TimeSlider : OsuSliderBar - { - protected override LocalisableString GetTooltipText(double value) => $"{value:N0} ms"; - } } } From ac914878b80a5efef642d1158954258bcd4af42d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 12:31:57 +0900 Subject: [PATCH 0972/5199] Move default function specifications to `OsuConfigManager` This ensures that running tests in release configuration will not fail due to the same issue being fixed in this PR. --- osu.Game/Configuration/DevelopmentOsuConfigManager.cs | 2 -- osu.Game/Configuration/OsuConfigManager.cs | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Configuration/DevelopmentOsuConfigManager.cs b/osu.Game/Configuration/DevelopmentOsuConfigManager.cs index f1cb0731fe..ff19dd874c 100644 --- a/osu.Game/Configuration/DevelopmentOsuConfigManager.cs +++ b/osu.Game/Configuration/DevelopmentOsuConfigManager.cs @@ -14,8 +14,6 @@ namespace osu.Game.Configuration public DevelopmentOsuConfigManager(Storage storage) : base(storage) { - LookupKeyBindings = _ => "unknown"; - LookupSkinName = _ => "unknown"; } } } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 07d2026c65..1358b41ad2 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -240,9 +240,9 @@ namespace osu.Game.Configuration }; } - public Func LookupSkinName { private get; set; } + public Func LookupSkinName { private get; set; } = _ => @"unknown"; - public Func LookupKeyBindings { get; set; } + public Func LookupKeyBindings { get; set; } = _ => @"unknown"; } // IMPORTANT: These are used in user configuration files. From 8b504bb5ac2944efffcd3a7e0a378a26db83abda Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 12:42:07 +0900 Subject: [PATCH 0973/5199] Ensure rounding is still applied in non-midpoint cases --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 3a4641cba9..235eac7f78 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -75,11 +75,11 @@ namespace osu.Game.Screens.Ranking.Statistics // so the easiest way is to cycle between downwards and upwards rounding as we process events. if (Math.Abs(binOffset - (int)binOffset) == 0.5) { - binOffset += Math.Sign(binOffset) * (roundUp ? 1 : 0); + binOffset = (int)binOffset + Math.Sign(binOffset) * (roundUp ? 1 : 0); roundUp = !roundUp; } - bins[timing_distribution_centre_bin_index + (int)binOffset]++; + bins[timing_distribution_centre_bin_index + (int)Math.Round(binOffset, MidpointRounding.AwayFromZero)]++; } int maxCount = bins.Max(); From 8115a4bb8fd9e2d53c40b8607c7ad99f0f62e9a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 16:28:32 +0900 Subject: [PATCH 0974/5199] Fix potential crash in tests when attempting to lookup key bindings in cases the lookup is not available --- osu.Game/Configuration/DevelopmentOsuConfigManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Configuration/DevelopmentOsuConfigManager.cs b/osu.Game/Configuration/DevelopmentOsuConfigManager.cs index ff19dd874c..f1cb0731fe 100644 --- a/osu.Game/Configuration/DevelopmentOsuConfigManager.cs +++ b/osu.Game/Configuration/DevelopmentOsuConfigManager.cs @@ -14,6 +14,8 @@ namespace osu.Game.Configuration public DevelopmentOsuConfigManager(Storage storage) : base(storage) { + LookupKeyBindings = _ => "unknown"; + LookupSkinName = _ => "unknown"; } } } From 657f2ebb9dbd355428bb6b48845b0edd83155e13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 16:59:10 +0900 Subject: [PATCH 0975/5199] Restructure `OsuSliderBar` to allow for custom tooltips --- .../Graphics/UserInterface/OsuSliderBar.cs | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 333ae4f832..17dce10cf6 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box rightBox; private readonly Container nubContainer; - public virtual LocalisableString TooltipText { get; private set; } + public virtual LocalisableString TooltipText { get; protected set; } /// /// Whether to format the tooltip as a percentage or the actual value. @@ -148,7 +148,7 @@ namespace osu.Game.Graphics.UserInterface protected override void LoadComplete() { base.LoadComplete(); - CurrentNumber.BindValueChanged(current => updateTooltipText(current.NewValue), true); + CurrentNumber.BindValueChanged(current => TooltipText = GetTooltipText(current.NewValue), true); } protected override bool OnHover(HoverEvent e) @@ -178,7 +178,7 @@ namespace osu.Game.Graphics.UserInterface { base.OnUserChange(value); playSample(value); - updateTooltipText(value); + TooltipText = GetTooltipText(value); } private void playSample(T value) @@ -203,28 +203,22 @@ namespace osu.Game.Graphics.UserInterface channel.Play(); } - private void updateTooltipText(T value) + protected virtual LocalisableString GetTooltipText(T value) { if (CurrentNumber.IsInteger) - TooltipText = value.ToInt32(NumberFormatInfo.InvariantInfo).ToString("N0"); - else - { - double floatValue = value.ToDouble(NumberFormatInfo.InvariantInfo); + return value.ToInt32(NumberFormatInfo.InvariantInfo).ToString("N0"); - if (DisplayAsPercentage) - { - TooltipText = floatValue.ToString("0%"); - } - else - { - decimal decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits); + double floatValue = value.ToDouble(NumberFormatInfo.InvariantInfo); - // Find the number of significant digits (we could have less than 5 after normalize()) - int significantDigits = FormatUtils.FindPrecision(decimalPrecision); + if (DisplayAsPercentage) + return floatValue.ToString("0%"); - TooltipText = floatValue.ToString($"N{significantDigits}"); - } - } + decimal decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits); + + // Find the number of significant digits (we could have less than 5 after normalize()) + int significantDigits = FormatUtils.FindPrecision(decimalPrecision); + + return floatValue.ToString($"N{significantDigits}"); } protected override void UpdateAfterChildren() From cc4f89eef429160c4304841ef735a0b79d048820 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 17:07:46 +0900 Subject: [PATCH 0976/5199] Add tooltip text for offset adjustment slider --- .../BeatmapOffsetControlStrings.cs | 12 +++++++- .../PlayerSettings/BeatmapOffsetControl.cs | 28 ++++++++++++++++++- .../Play/PlayerSettings/PlayerSliderBar.cs | 12 ++++---- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/osu.Game/Localisation/BeatmapOffsetControlStrings.cs b/osu.Game/Localisation/BeatmapOffsetControlStrings.cs index 7b2a9e50b2..632a1ad0ea 100644 --- a/osu.Game/Localisation/BeatmapOffsetControlStrings.cs +++ b/osu.Game/Localisation/BeatmapOffsetControlStrings.cs @@ -29,6 +29,16 @@ namespace osu.Game.Localisation /// public static LocalisableString CalibrateUsingLastPlay => new TranslatableString(getKey(@"calibrate_using_last_play"), @"Calibrate using last play"); + /// + /// "(hit objects appear later)" + /// + public static LocalisableString HitObjectsAppearLater => new TranslatableString(getKey(@"hit_objects_appear_later"), @"(hit objects appear later)"); + + /// + /// "(hit objects appear earlier)" + /// + public static LocalisableString HitObjectsAppearEarlier => new TranslatableString(getKey(@"hit_objects_appear_earlier"), @"(hit objects appear earlier)"); + private static string getKey(string key) => $@"{prefix}:{key}"; } -} \ No newline at end of file +} diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index dc3e80d695..201e431367 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -3,12 +3,14 @@ using System; using System.ComponentModel; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Database; @@ -71,7 +73,7 @@ namespace osu.Game.Screens.Play.PlayerSettings Spacing = new Vector2(10), Children = new Drawable[] { - new PlayerSliderBar + new OffsetSliderBar { KeyboardStep = 5, LabelText = BeatmapOffsetControlStrings.BeatmapOffset, @@ -88,6 +90,30 @@ namespace osu.Game.Screens.Play.PlayerSettings }; } + public class OffsetSliderBar : PlayerSliderBar + { + protected override Drawable CreateControl() => new CustomSliderBar(); + + protected class CustomSliderBar : SliderBar + { + protected override LocalisableString GetTooltipText(double value) + { + return value == 0 + ? new TranslatableString("_", @"{0} ms", base.GetTooltipText(value)) + : new TranslatableString("_", @"{0} ms {1}", base.GetTooltipText(value), getEarlyLateText(value)); + } + + private LocalisableString getEarlyLateText(double value) + { + Debug.Assert(value != 0); + + return value > 0 + ? BeatmapOffsetControlStrings.HitObjectsAppearLater + : BeatmapOffsetControlStrings.HitObjectsAppearEarlier; + } + } + } + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs index 57ffe16f76..3f1a5bc0ac 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs @@ -15,13 +15,15 @@ namespace osu.Game.Screens.Play.PlayerSettings { public OsuSliderBar Bar => (OsuSliderBar)Control; - protected override Drawable CreateControl() => new SliderBar - { - RelativeSizeAxes = Axes.X - }; + protected override Drawable CreateControl() => new SliderBar(); - private class SliderBar : OsuSliderBar + protected class SliderBar : OsuSliderBar { + public SliderBar() + { + RelativeSizeAxes = Axes.X; + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { From 5dca0e3377fbb69667648709022db0440fe09394 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 21:31:56 +0900 Subject: [PATCH 0977/5199] Revert back to `private set` Co-authored-by: Salman Ahmed --- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 17dce10cf6..a5bc02246d 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box rightBox; private readonly Container nubContainer; - public virtual LocalisableString TooltipText { get; protected set; } + public virtual LocalisableString TooltipText { get; private set; } /// /// Whether to format the tooltip as a percentage or the actual value. From c1c9482077655d9315803cd5239aa66c16604928 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 00:14:24 +0900 Subject: [PATCH 0978/5199] Add note about how global audio offset is currently applied --- osu.Game/Configuration/OsuConfigManager.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 07d2026c65..5f9cd0470c 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -270,7 +270,13 @@ namespace osu.Game.Configuration MouseDisableButtons, MouseDisableWheel, ConfineMouseMode, + + /// + /// Globally applied audio offset. + /// This is added to the audio track's current time. Higher values will cause gameplay to occur earlier, relative to the audio track. + /// AudioOffset, + VolumeInactive, MenuMusic, MenuVoice, From f72c9a1f410de9b456567b2366b4dd4dce7eb624 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 4 Mar 2022 11:48:48 +0800 Subject: [PATCH 0979/5199] Cap speed change per hit and apply a speed decrease on miss --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 30 +++++++++++++++++----- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 428d605a99..e7ca1d3150 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -65,8 +65,16 @@ namespace osu.Game.Rulesets.Mods // The two constants below denote the maximum allowable range of rates that `SpeedChange` can take. // The range is purposefully wider than the range of values that `InitialRate` allows // in order to give some leeway for change even when extreme initial rates are chosen. - private const double min_allowable_rate = 0.4f; - private const double max_allowable_rate = 2.5f; + private const double min_allowable_rate = 0.4d; + private const double max_allowable_rate = 2.5d; + + // The two constants below denote the maximum allowable change in rate caused by a single hit + // This prevents sudden jolts caused by a badly-timed hit. + private const double min_allowable_rate_change = 0.8d; + private const double max_allowable_rate_change = 1.25d; + + // Apply a fixed rate change when missing, allowing the player to catch up when the rate is too fast. + private const double rate_change_on_miss = 0.95d; private ITrack track; private double targetRate = 1d; @@ -159,12 +167,10 @@ namespace osu.Game.Rulesets.Mods if (ratesForRewinding.ContainsKey(result.HitObject)) return; if (!shouldProcessResult(result)) return; - double prevEndTime = precedingEndTimes[result.HitObject]; - ratesForRewinding.Add(result.HitObject, recentRates[0]); recentRates.RemoveAt(0); - recentRates.Add(Math.Clamp((result.HitObject.GetEndTime() - prevEndTime) / (result.TimeAbsolute - prevEndTime) * SpeedChange.Value, min_allowable_rate, max_allowable_rate)); + recentRates.Add(Math.Clamp(getRelativeRateChange(result) * SpeedChange.Value, min_allowable_rate, max_allowable_rate)); targetRate = recentRates.Average(); }; @@ -222,11 +228,23 @@ namespace osu.Game.Rulesets.Mods private bool shouldProcessResult(JudgementResult result) { - if (!result.IsHit) return false; if (!result.Type.AffectsAccuracy()) return false; if (!precedingEndTimes.ContainsKey(result.HitObject)) return false; return true; } + + private double getRelativeRateChange(JudgementResult result) + { + if (!result.IsHit) + return rate_change_on_miss; + + double prevEndTime = precedingEndTimes[result.HitObject]; + return Math.Clamp( + (result.HitObject.GetEndTime() - prevEndTime) / (result.TimeAbsolute - prevEndTime), + min_allowable_rate_change, + max_allowable_rate_change + ); + } } } From e09dd7d8fe79ed1019f99b76946931db152eebc1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 12:55:35 +0900 Subject: [PATCH 0980/5199] Fix calibrating offset from previous non-zero offset not applying adjustment correctly --- .../Gameplay/TestSceneBeatmapOffsetControl.cs | 30 ++++++++++++++++++- .../PlayerSettings/BeatmapOffsetControl.cs | 6 ++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs index 67f5db548b..4b079cbb2c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestDisplay() + public void TestCalibrationFromZero() { const double average_error = -4.5; @@ -70,5 +70,33 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null); AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); } + + /// + /// When a beatmap offset was already set, the calibration should take it into account. + /// + [Test] + public void TestCalibrationFromNonZero() + { + const double average_error = -4.5; + const double initial_offset = -2; + + AddStep("Set offset non-neutral", () => offsetControl.Current.Value = initial_offset); + AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); + AddStep("Set reference score", () => + { + offsetControl.ReferenceScore.Value = new ScoreInfo + { + HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error) + }; + }); + + AddAssert("Has calibration button", () => offsetControl.ChildrenOfType().Any()); + AddStep("Press button", () => offsetControl.ChildrenOfType().Single().TriggerClick()); + AddAssert("Offset is adjusted", () => offsetControl.Current.Value == initial_offset - average_error); + + AddAssert("Button is disabled", () => !offsetControl.ChildrenOfType().Single().Enabled.Value); + AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null); + AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); + } } } diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 201e431367..1d6d0acb76 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -53,6 +53,7 @@ namespace osu.Game.Screens.Play.PlayerSettings private OsuColour colours { get; set; } = null!; private double lastPlayAverage; + private double lastPlayBeatmapOffset; private SettingsButton? useAverageButton; @@ -156,7 +157,7 @@ namespace osu.Game.Screens.Play.PlayerSettings } if (useAverageButton != null) - useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, -Current.Value, Current.Precision / 2); + useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, -Current.Value + lastPlayBeatmapOffset, Current.Precision / 2); realmWriteTask = realm.WriteAsync(r => { @@ -213,6 +214,7 @@ namespace osu.Game.Screens.Play.PlayerSettings } lastPlayAverage = average; + lastPlayBeatmapOffset = Current.Value; referenceScoreContainer.AddRange(new Drawable[] { @@ -225,7 +227,7 @@ namespace osu.Game.Screens.Play.PlayerSettings useAverageButton = new SettingsButton { Text = BeatmapOffsetControlStrings.CalibrateUsingLastPlay, - Action = () => Current.Value = -lastPlayAverage + Action = () => Current.Value = lastPlayBeatmapOffset - lastPlayAverage }, }); } From 010fa7ed0185dcce7192c50abb972fb864ac1e78 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 13:09:19 +0900 Subject: [PATCH 0981/5199] Allow an offset to be shown on the timing distribution graph --- .../PlayerSettings/BeatmapOffsetControl.cs | 17 ++++++-- .../HitEventTimingDistributionGraph.cs | 42 ++++++++++++++++--- 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 1d6d0acb76..4af2de6409 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -54,6 +54,7 @@ namespace osu.Game.Screens.Play.PlayerSettings private double lastPlayAverage; private double lastPlayBeatmapOffset; + private HitEventTimingDistributionGraph? lastPlayGraph; private SettingsButton? useAverageButton; @@ -109,8 +110,8 @@ namespace osu.Game.Screens.Play.PlayerSettings Debug.Assert(value != 0); return value > 0 - ? BeatmapOffsetControlStrings.HitObjectsAppearLater - : BeatmapOffsetControlStrings.HitObjectsAppearEarlier; + ? BeatmapOffsetControlStrings.HitObjectsAppearEarlier + : BeatmapOffsetControlStrings.HitObjectsAppearLater; } } } @@ -149,6 +150,12 @@ namespace osu.Game.Screens.Play.PlayerSettings void updateOffset() { + // the last play graph is relative to the offset at the point of the last play, so we need to factor that out. + double adjustmentSinceLastPlay = lastPlayBeatmapOffset - Current.Value; + + // Negative is applied here because the play graph is considering a hit offset, not track (as we currently use for clocks). + lastPlayGraph?.UpdateOffset(-adjustmentSinceLastPlay); + // ensure the previous write has completed. ignoring performance concerns, if we don't do this, the async writes could be out of sequence. if (realmWriteTask?.IsCompleted == false) { @@ -157,7 +164,9 @@ namespace osu.Game.Screens.Play.PlayerSettings } if (useAverageButton != null) - useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, -Current.Value + lastPlayBeatmapOffset, Current.Precision / 2); + { + useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, adjustmentSinceLastPlay, Current.Precision / 2); + } realmWriteTask = realm.WriteAsync(r => { @@ -218,7 +227,7 @@ namespace osu.Game.Screens.Play.PlayerSettings referenceScoreContainer.AddRange(new Drawable[] { - new HitEventTimingDistributionGraph(hitEvents) + lastPlayGraph = new HitEventTimingDistributionGraph(hitEvents) { RelativeSizeAxes = Axes.X, Height = 50, diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index b32b11c028..48c18deaec 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -40,6 +40,9 @@ namespace osu.Game.Screens.Ranking.Statistics /// private const float axis_points = 5; + /// + /// The currently displayed hit events. + /// private readonly IReadOnlyList hitEvents; /// @@ -51,24 +54,43 @@ namespace osu.Game.Screens.Ranking.Statistics this.hitEvents = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit()).ToList(); } + private int[] bins; + private double binSize; + private double hitOffset; + [BackgroundDependencyLoader] private void load() { if (hitEvents == null || hitEvents.Count == 0) return; - int[] bins = new int[total_timing_distribution_bins]; + bins = new int[total_timing_distribution_bins]; - double binSize = Math.Ceiling(hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins); + binSize = Math.Ceiling(hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins); // Prevent div-by-0 by enforcing a minimum bin size binSize = Math.Max(1, binSize); + Scheduler.AddOnce(updateDisplay); + } + + public void UpdateOffset(double hitOffset) + { + this.hitOffset = hitOffset; + Scheduler.AddOnce(updateDisplay); + } + + private void updateDisplay() + { bool roundUp = true; + Array.Clear(bins, 0, bins.Length); + foreach (var e in hitEvents) { - double binOffset = e.TimeOffset / binSize; + double time = e.TimeOffset + hitOffset; + + double binOffset = time / binSize; // .NET's round midpoint handling doesn't provide a behaviour that works amazingly for display // purposes here. We want midpoint rounding to roughly distribute evenly to each adjacent bucket @@ -79,13 +101,23 @@ namespace osu.Game.Screens.Ranking.Statistics roundUp = !roundUp; } - bins[timing_distribution_centre_bin_index + (int)Math.Round(binOffset, MidpointRounding.AwayFromZero)]++; + int index = timing_distribution_centre_bin_index + (int)Math.Round(binOffset, MidpointRounding.AwayFromZero); + + // may be out of range when applying an offset. for such cases we can just drop the results. + if (index >= 0 && index < bins.Length) + bins[index]++; } int maxCount = bins.Max(); var bars = new Drawable[total_timing_distribution_bins]; + for (int i = 0; i < bars.Length; i++) - bars[i] = new Bar { Height = Math.Max(0.05f, (float)bins[i] / maxCount) }; + { + bars[i] = new Bar + { + Height = Math.Max(0.05f, (float)bins[i] / maxCount) + }; + } Container axisFlow; From c063a73742c14494e17231b1d6d933a3cc17dbf9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 13:48:42 +0900 Subject: [PATCH 0982/5199] Fix autosize weirdness by specifying a constant size for the x axis --- .../Statistics/HitEventTimingDistributionGraph.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 48c18deaec..d83422a7b6 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -121,6 +121,8 @@ namespace osu.Game.Screens.Ranking.Statistics Container axisFlow; + const float axis_font_size = 12; + InternalChild = new GridContainer { Anchor = Anchor.Centre, @@ -142,7 +144,7 @@ namespace osu.Game.Screens.Ranking.Statistics axisFlow = new Container { RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y + Height = axis_font_size, } }, }, @@ -162,7 +164,7 @@ namespace osu.Game.Screens.Ranking.Statistics Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = "0", - Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) + Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) }); for (int i = 1; i <= axis_points; i++) @@ -179,7 +181,7 @@ namespace osu.Game.Screens.Ranking.Statistics X = -position / 2, Alpha = alpha, Text = axisValue.ToString("-0"), - Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) + Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) }); axisFlow.Add(new OsuSpriteText @@ -190,7 +192,7 @@ namespace osu.Game.Screens.Ranking.Statistics X = position / 2, Alpha = alpha, Text = axisValue.ToString("+0"), - Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) + Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) }); } } From d3e04fe594fe6e3a4c827aef0e9f16efd5ad9420 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 14:09:27 +0900 Subject: [PATCH 0983/5199] Colour centre bin in distribution graph differently --- .../Statistics/HitEventTimingDistributionGraph.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index d83422a7b6..72d53e5d1b 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Ranking.Statistics for (int i = 0; i < bars.Length; i++) { - bars[i] = new Bar + bars[i] = new Bar(i == timing_distribution_centre_bin_index) { Height = Math.Max(0.05f, (float)bins[i] / maxCount) }; @@ -199,17 +199,22 @@ namespace osu.Game.Screens.Ranking.Statistics private class Bar : CompositeDrawable { - public Bar() + public Bar(bool isCentre) { Anchor = Anchor.BottomCentre; Origin = Anchor.BottomCentre; RelativeSizeAxes = Axes.Both; + var colour = Color4Extensions.FromHex("#66FFCC"); + + if (isCentre) + colour = colour.Lighten(1); + InternalChild = new Circle { RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("#66FFCC") + Colour = colour }; } } From 540d7d0e2c1b58be41d03c6026df9077f17af440 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 14:34:33 +0900 Subject: [PATCH 0984/5199] Add the ability to set and show an offset value on timing distribution graph --- ...estSceneHitEventTimingDistributionGraph.cs | 5 +- .../HitEventTimingDistributionGraph.cs | 205 +++++++++++------- 2 files changed, 126 insertions(+), 84 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index f31aec8975..48ce6145c0 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -17,10 +17,13 @@ namespace osu.Game.Tests.Visual.Ranking { public class TestSceneHitEventTimingDistributionGraph : OsuTestScene { + private HitEventTimingDistributionGraph graph; + [Test] public void TestManyDistributedEvents() { createTest(CreateDistributedHitEvents()); + AddStep("add adjustment", () => graph.UpdateOffset(10)); } [Test] @@ -68,7 +71,7 @@ namespace osu.Game.Tests.Visual.Ranking RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#333") }, - new HitEventTimingDistributionGraph(events) + graph = new HitEventTimingDistributionGraph(events) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 72d53e5d1b..d510d995e2 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Scoring; +using osuTK.Graphics; namespace osu.Game.Screens.Ranking.Statistics { @@ -58,6 +59,8 @@ namespace osu.Game.Screens.Ranking.Statistics private double binSize; private double hitOffset; + private Bar[] barDrawables; + [BackgroundDependencyLoader] private void load() { @@ -108,115 +111,151 @@ namespace osu.Game.Screens.Ranking.Statistics bins[index]++; } - int maxCount = bins.Max(); - var bars = new Drawable[total_timing_distribution_bins]; - - for (int i = 0; i < bars.Length; i++) + if (barDrawables != null) { - bars[i] = new Bar(i == timing_distribution_centre_bin_index) + for (int i = 0; i < barDrawables.Length; i++) { - Height = Math.Max(0.05f, (float)bins[i] / maxCount) - }; - } - - Container axisFlow; - - const float axis_font_size = 12; - - InternalChild = new GridContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Width = 0.8f, - Content = new[] - { - new Drawable[] - { - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] { bars } - } - }, - new Drawable[] - { - axisFlow = new Container - { - RelativeSizeAxes = Axes.X, - Height = axis_font_size, - } - }, - }, - RowDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.AutoSize), + barDrawables[i].UpdateOffset(bins[i]); } - }; - - // Our axis will contain one centre element + 5 points on each side, each with a value depending on the number of bins * bin size. - double maxValue = timing_distribution_bins * binSize; - double axisValueStep = maxValue / axis_points; - - axisFlow.Add(new OsuSpriteText + } + else { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "0", - Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) - }); + int maxCount = bins.Max(); + barDrawables = new Bar[total_timing_distribution_bins]; - for (int i = 1; i <= axis_points; i++) - { - double axisValue = i * axisValueStep; - float position = (float)(axisValue / maxValue); - float alpha = 1f - position * 0.8f; + for (int i = 0; i < barDrawables.Length; i++) + barDrawables[i] = new Bar(bins[i], maxCount, i == timing_distribution_centre_bin_index); + + Container axisFlow; + + const float axis_font_size = 12; + + InternalChild = new GridContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Width = 0.8f, + Content = new[] + { + new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] { barDrawables } + } + }, + new Drawable[] + { + axisFlow = new Container + { + RelativeSizeAxes = Axes.X, + Height = axis_font_size, + } + }, + }, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + } + }; + + // Our axis will contain one centre element + 5 points on each side, each with a value depending on the number of bins * bin size. + double maxValue = timing_distribution_bins * binSize; + double axisValueStep = maxValue / axis_points; axisFlow.Add(new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, - RelativePositionAxes = Axes.X, - X = -position / 2, - Alpha = alpha, - Text = axisValue.ToString("-0"), + Text = "0", Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) }); - axisFlow.Add(new OsuSpriteText + for (int i = 1; i <= axis_points; i++) { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.X, - X = position / 2, - Alpha = alpha, - Text = axisValue.ToString("+0"), - Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) - }); + double axisValue = i * axisValueStep; + float position = (float)(axisValue / maxValue); + float alpha = 1f - position * 0.8f; + + axisFlow.Add(new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.X, + X = -position / 2, + Alpha = alpha, + Text = axisValue.ToString("-0"), + Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) + }); + + axisFlow.Add(new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.X, + X = position / 2, + Alpha = alpha, + Text = axisValue.ToString("+0"), + Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) + }); + } } } private class Bar : CompositeDrawable { - public Bar(bool isCentre) + private readonly float value; + private readonly float maxValue; + + private readonly Circle boxOriginal; + private readonly Circle boxAdjustment; + + public Bar(float value, float maxValue, bool isCentre) { - Anchor = Anchor.BottomCentre; - Origin = Anchor.BottomCentre; + this.value = value; + this.maxValue = maxValue; RelativeSizeAxes = Axes.Both; + Masking = true; - var colour = Color4Extensions.FromHex("#66FFCC"); - - if (isCentre) - colour = colour.Lighten(1); - - InternalChild = new Circle + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = colour + boxOriginal = new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Colour = isCentre ? Color4.White : Color4Extensions.FromHex("#66FFCC"), + Height = 0, + }, + boxAdjustment = new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Colour = Color4.Yellow, + Blending = BlendingParameters.Additive, + Alpha = 0.6f, + Height = 0, + }, }; } + + private const double duration = 300; + + protected override void LoadComplete() + { + base.LoadComplete(); + boxOriginal.ResizeHeightTo(Math.Clamp(value / maxValue, 0.05f, 1), duration, Easing.OutQuint); + } + + public void UpdateOffset(float adjustment) + { + boxAdjustment.ResizeHeightTo(Math.Clamp(adjustment / maxValue, 0.05f, 1), duration, Easing.OutQuint); + } } } } From 81a49057ec7e1fa089d40b103f55fbc2ae5c7f8f Mon Sep 17 00:00:00 2001 From: Riley Quinn Date: Thu, 3 Mar 2022 22:30:08 -0600 Subject: [PATCH 0985/5199] Fix wiki links containing locale not loading when opened from chat. --- osu.Game/Overlays/WikiOverlay.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index 44713d637d..63191900d8 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -7,6 +7,7 @@ using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -100,7 +101,16 @@ namespace osu.Game.Overlays cancellationToken?.Cancel(); request?.Cancel(); - request = new GetWikiRequest(e.NewValue); + string[] values = e.NewValue.Split('/', 2); + + if (values.Length > 1 && LanguageExtensions.TryParseCultureCode(values[0], out _)) + { + request = new GetWikiRequest(values[1], values[0]); + } + else + { + request = new GetWikiRequest(e.NewValue); + } Loading.Show(); From 92cd8ee29faf693bc6e02c87abc16b4e14ef40ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 14:56:46 +0900 Subject: [PATCH 0986/5199] Decrease overhead of hit event distribution tests --- .../TestSceneHitEventTimingDistributionGraph.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index 48ce6145c0..7471b6acf2 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Ranking.Statistics; @@ -19,6 +20,8 @@ namespace osu.Game.Tests.Visual.Ranking { private HitEventTimingDistributionGraph graph; + private static readonly HitObject placeholder_object = new HitCircle(); + [Test] public void TestManyDistributedEvents() { @@ -35,13 +38,13 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAroundCentre() { - createTest(Enumerable.Range(-150, 300).Select(i => new HitEvent(i / 50f, HitResult.Perfect, new HitCircle(), new HitCircle(), null)).ToList()); + createTest(Enumerable.Range(-150, 300).Select(i => new HitEvent(i / 50f, HitResult.Perfect, placeholder_object, placeholder_object, null)).ToList()); } [Test] public void TestZeroTimeOffset() { - createTest(Enumerable.Range(0, 100).Select(_ => new HitEvent(0, HitResult.Perfect, new HitCircle(), new HitCircle(), null)).ToList()); + createTest(Enumerable.Range(0, 100).Select(_ => new HitEvent(0, HitResult.Perfect, placeholder_object, placeholder_object, null)).ToList()); } [Test] @@ -56,9 +59,9 @@ namespace osu.Game.Tests.Visual.Ranking createTest(Enumerable.Range(0, 100).Select(i => { if (i % 2 == 0) - return new HitEvent(0, HitResult.Perfect, new HitCircle(), new HitCircle(), null); + return new HitEvent(0, HitResult.Perfect, placeholder_object, placeholder_object, null); - return new HitEvent(30, HitResult.Miss, new HitCircle(), new HitCircle(), null); + return new HitEvent(30, HitResult.Miss, placeholder_object, placeholder_object, null); }).ToList()); } @@ -86,10 +89,10 @@ namespace osu.Game.Tests.Visual.Ranking for (int i = 0; i < range * 2; i++) { - int count = (int)(Math.Pow(range - Math.Abs(i - range), 2)); + int count = (int)(Math.Pow(range - Math.Abs(i - range), 2)) / 10; for (int j = 0; j < count; j++) - hitEvents.Add(new HitEvent(centre + i - range, HitResult.Perfect, new HitCircle(), new HitCircle(), null)); + hitEvents.Add(new HitEvent(centre + i - range, HitResult.Perfect, placeholder_object, placeholder_object, null)); } return hitEvents; From 2785218b7932e93e61a654388e9dea6ccdaccac9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 14:59:53 +0900 Subject: [PATCH 0987/5199] Only apply animation if the bar is going to be larger than the minimum height --- .../Statistics/HitEventTimingDistributionGraph.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index d510d995e2..d475556c84 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -213,6 +213,8 @@ namespace osu.Game.Screens.Ranking.Statistics private readonly Circle boxOriginal; private readonly Circle boxAdjustment; + private const float minimum_height = 0.05f; + public Bar(float value, float maxValue, bool isCentre) { this.value = value; @@ -229,7 +231,7 @@ namespace osu.Game.Screens.Ranking.Statistics Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Colour = isCentre ? Color4.White : Color4Extensions.FromHex("#66FFCC"), - Height = 0, + Height = minimum_height, }, boxAdjustment = new Circle { @@ -249,7 +251,11 @@ namespace osu.Game.Screens.Ranking.Statistics protected override void LoadComplete() { base.LoadComplete(); - boxOriginal.ResizeHeightTo(Math.Clamp(value / maxValue, 0.05f, 1), duration, Easing.OutQuint); + + float height = Math.Clamp(value / maxValue, minimum_height, 1); + + if (height > minimum_height) + boxOriginal.ResizeHeightTo(height, duration, Easing.OutQuint); } public void UpdateOffset(float adjustment) From 8c7b1e0aa8dcaf5a48134d53ea4a3d23a2054111 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 15:01:54 +0900 Subject: [PATCH 0988/5199] Only construct the adjustment portion of bars when required --- .../HitEventTimingDistributionGraph.cs | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index d475556c84..c823ed1f4c 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -211,7 +211,7 @@ namespace osu.Game.Screens.Ranking.Statistics private readonly float maxValue; private readonly Circle boxOriginal; - private readonly Circle boxAdjustment; + private Circle boxAdjustment; private const float minimum_height = 0.05f; @@ -233,16 +233,6 @@ namespace osu.Game.Screens.Ranking.Statistics Colour = isCentre ? Color4.White : Color4Extensions.FromHex("#66FFCC"), Height = minimum_height, }, - boxAdjustment = new Circle - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Colour = Color4.Yellow, - Blending = BlendingParameters.Additive, - Alpha = 0.6f, - Height = 0, - }, }; } @@ -260,6 +250,20 @@ namespace osu.Game.Screens.Ranking.Statistics public void UpdateOffset(float adjustment) { + if (boxAdjustment == null) + { + AddInternal(boxAdjustment = new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Colour = Color4.Yellow, + Blending = BlendingParameters.Additive, + Alpha = 0.6f, + Height = 0, + }); + } + boxAdjustment.ResizeHeightTo(Math.Clamp(adjustment / maxValue, 0.05f, 1), duration, Easing.OutQuint); } } From e9e92b991e0de0921e0b040be63813c951911dae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 12:55:35 +0900 Subject: [PATCH 0989/5199] Fix calibrating offset from previous non-zero offset not applying adjustment correctly --- .../Gameplay/TestSceneBeatmapOffsetControl.cs | 30 ++++++++++++++++++- .../PlayerSettings/BeatmapOffsetControl.cs | 6 ++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs index 67f5db548b..4b079cbb2c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestDisplay() + public void TestCalibrationFromZero() { const double average_error = -4.5; @@ -70,5 +70,33 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null); AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); } + + /// + /// When a beatmap offset was already set, the calibration should take it into account. + /// + [Test] + public void TestCalibrationFromNonZero() + { + const double average_error = -4.5; + const double initial_offset = -2; + + AddStep("Set offset non-neutral", () => offsetControl.Current.Value = initial_offset); + AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); + AddStep("Set reference score", () => + { + offsetControl.ReferenceScore.Value = new ScoreInfo + { + HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error) + }; + }); + + AddAssert("Has calibration button", () => offsetControl.ChildrenOfType().Any()); + AddStep("Press button", () => offsetControl.ChildrenOfType().Single().TriggerClick()); + AddAssert("Offset is adjusted", () => offsetControl.Current.Value == initial_offset - average_error); + + AddAssert("Button is disabled", () => !offsetControl.ChildrenOfType().Single().Enabled.Value); + AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null); + AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); + } } } diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index dc3e80d695..fc5c50f32f 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -51,6 +51,7 @@ namespace osu.Game.Screens.Play.PlayerSettings private OsuColour colours { get; set; } = null!; private double lastPlayAverage; + private double lastPlayBeatmapOffset; private SettingsButton? useAverageButton; @@ -130,7 +131,7 @@ namespace osu.Game.Screens.Play.PlayerSettings } if (useAverageButton != null) - useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, -Current.Value, Current.Precision / 2); + useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, -Current.Value + lastPlayBeatmapOffset, Current.Precision / 2); realmWriteTask = realm.WriteAsync(r => { @@ -187,6 +188,7 @@ namespace osu.Game.Screens.Play.PlayerSettings } lastPlayAverage = average; + lastPlayBeatmapOffset = Current.Value; referenceScoreContainer.AddRange(new Drawable[] { @@ -199,7 +201,7 @@ namespace osu.Game.Screens.Play.PlayerSettings useAverageButton = new SettingsButton { Text = BeatmapOffsetControlStrings.CalibrateUsingLastPlay, - Action = () => Current.Value = -lastPlayAverage + Action = () => Current.Value = lastPlayBeatmapOffset - lastPlayAverage }, }); } From 5a164e4520aef238009bc95d398bb04c3ccaf8b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 15:19:55 +0900 Subject: [PATCH 0990/5199] Hide adjustment when no adjustment is applied --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index c823ed1f4c..bb42dda597 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -250,8 +250,13 @@ namespace osu.Game.Screens.Ranking.Statistics public void UpdateOffset(float adjustment) { + bool hasAdjustment = adjustment != value; + if (boxAdjustment == null) { + if (!hasAdjustment) + return; + AddInternal(boxAdjustment = new Circle { RelativeSizeAxes = Axes.Both, @@ -265,6 +270,7 @@ namespace osu.Game.Screens.Ranking.Statistics } boxAdjustment.ResizeHeightTo(Math.Clamp(adjustment / maxValue, 0.05f, 1), duration, Easing.OutQuint); + boxAdjustment.FadeTo(!hasAdjustment ? 0 : 1, duration, Easing.OutQuint); } } } From 8491bab77c47391f0f7ee386787befcb4d5dafbf Mon Sep 17 00:00:00 2001 From: Riley Quinn Date: Fri, 4 Mar 2022 00:57:13 -0600 Subject: [PATCH 0991/5199] Replace string locale with Language --- osu.Game/Online/API/Requests/GetWikiRequest.cs | 10 ++++++---- osu.Game/Overlays/WikiOverlay.cs | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetWikiRequest.cs b/osu.Game/Online/API/Requests/GetWikiRequest.cs index 248fcc03e3..09571ab0a8 100644 --- a/osu.Game/Online/API/Requests/GetWikiRequest.cs +++ b/osu.Game/Online/API/Requests/GetWikiRequest.cs @@ -1,6 +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.Game.Extensions; +using osu.Game.Localisation; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests @@ -8,14 +10,14 @@ namespace osu.Game.Online.API.Requests public class GetWikiRequest : APIRequest { private readonly string path; - private readonly string locale; + private readonly Language language; - public GetWikiRequest(string path, string locale = "en") + public GetWikiRequest(string path, Language language = Language.en) { this.path = path; - this.locale = locale; + this.language = language; } - protected override string Target => $"wiki/{locale}/{path}"; + protected override string Target => $"wiki/{language.ToCultureCode()}/{path}"; } } diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index 63191900d8..d6a379dc56 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -103,9 +103,9 @@ namespace osu.Game.Overlays string[] values = e.NewValue.Split('/', 2); - if (values.Length > 1 && LanguageExtensions.TryParseCultureCode(values[0], out _)) + if (values.Length > 1 && LanguageExtensions.TryParseCultureCode(values[0], out var language)) { - request = new GetWikiRequest(values[1], values[0]); + request = new GetWikiRequest(values[1], language); } else { From 76c293b9e982650e6b9b6edb576b37e427b95089 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 10:00:56 +0300 Subject: [PATCH 0992/5199] Fix cancellation token source recreated on every medal --- osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs index 6333802549..9341ad63fc 100644 --- a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs @@ -68,10 +68,12 @@ namespace osu.Game.Overlays.Profile.Header private void updateDisplay(APIUser user) { - var badges = user.Badges; + cancellationTokenSource?.Cancel(); + cancellationTokenSource = new CancellationTokenSource(); + badgeFlowContainer.Clear(); - cancellationTokenSource?.Cancel(); + var badges = user.Badges; if (badges?.Length > 0) { @@ -84,7 +86,7 @@ namespace osu.Game.Overlays.Profile.Header { // load in stable order regardless of async load order. badgeFlowContainer.Insert(displayIndex, asyncBadge); - }, (cancellationTokenSource = new CancellationTokenSource()).Token); + }, cancellationTokenSource.Token); } } else From 129c290ca07d3d23608615d84b0e80f3337ca96e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 10:01:07 +0300 Subject: [PATCH 0993/5199] Dispose cancellation token source on disposal --- osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs index 9341ad63fc..922f3832e4 100644 --- a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs @@ -94,5 +94,11 @@ namespace osu.Game.Overlays.Profile.Header Hide(); } } + + protected override void Dispose(bool isDisposing) + { + cancellationTokenSource?.Cancel(); + base.Dispose(isDisposing); + } } } From 3fdc7ed9d2b80533cda71a9e4829706eab1ded51 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 10:14:19 +0300 Subject: [PATCH 0994/5199] Remove brackets surrounding one-line statements --- osu.Game/Overlays/WikiOverlay.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index d6a379dc56..4015d8e196 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -104,13 +104,9 @@ namespace osu.Game.Overlays string[] values = e.NewValue.Split('/', 2); if (values.Length > 1 && LanguageExtensions.TryParseCultureCode(values[0], out var language)) - { request = new GetWikiRequest(values[1], language); - } else - { request = new GetWikiRequest(e.NewValue); - } Loading.Show(); From 1c40fcb79e65b31c46dbd7fb49cbde6ce2e389b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 17:54:04 +0900 Subject: [PATCH 0995/5199] Reorder math to be easier to pass Co-authored-by: Salman Ahmed --- osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index fc5c50f32f..8253c2e38e 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -131,7 +131,7 @@ namespace osu.Game.Screens.Play.PlayerSettings } if (useAverageButton != null) - useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, -Current.Value + lastPlayBeatmapOffset, Current.Precision / 2); + useAverageButton.Enabled.Value = !Precision.AlmostEquals(Current.Value, lastPlayBeatmapOffset - lastPlayAverage, Current.Precision / 2); realmWriteTask = realm.WriteAsync(r => { From 1e246bf5608676b0ca7f2c95eae5881421bc81f6 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 4 Mar 2022 20:14:14 +0900 Subject: [PATCH 0996/5199] Reduce 'cursor-tap' sample playback volume on MouseUp --- osu.Game/Graphics/Cursor/MenuCursor.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 0cc751ea21..03fad00e41 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -140,6 +140,7 @@ namespace osu.Game.Graphics.Cursor // Scale to [-0.75, 0.75] so that the sample isn't fully panned left or right (sounds weird) channel.Balance.Value = ((activeCursor.X / DrawWidth) * 2 - 1) * 0.75; channel.Frequency.Value = baseFrequency - (random_range / 2f) + RNG.NextDouble(random_range); + channel.Volume.Value = baseFrequency; channel.Play(); } From 76e64f501357db6a95a472c34a5389199c459aae Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 14:22:39 +0300 Subject: [PATCH 0997/5199] Use manual framed clock for lead-in player test scene --- osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index b195d2aa74..5a1fc1b1e5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Timing; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; @@ -107,7 +108,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create player", () => { - Beatmap.Value = CreateWorkingBeatmap(beatmap, storyboard); + Beatmap.Value = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), Audio); LoadScreen(player = new LeadInPlayer()); }); From c132fc19e75752191dda416d38474f2bac0a94dc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 22:59:33 +0900 Subject: [PATCH 0998/5199] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 526ce959a6..d418dcaccf 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 7dfd099df1..64785ab566 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 80600655aa..a7bffd28b5 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + From 524b8e02ef5a60ac17a030983095c46259e1d6b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 5 Mar 2022 22:37:36 +0900 Subject: [PATCH 0999/5199] Revert "Update all usages of `OsuSlider.TooltipText` overrides to instead implement `GetTooltipText`" This reverts commit abba49fd8f8bbe15b93ace48809bc342b486488a. --- osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs | 2 +- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 2 +- osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs | 2 +- .../Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 2 +- osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs | 4 +--- osu.Game/Overlays/Settings/Sections/SizeSlider.cs | 2 +- .../Settings/Sections/UserInterface/GeneralSettings.cs | 2 +- .../Settings/Sections/UserInterface/SongSelectSettings.cs | 6 ++---- osu.Game/Rulesets/Mods/ModMuted.cs | 2 +- osu.Game/Rulesets/Mods/ModNoScope.cs | 2 +- 10 files changed, 11 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index ae3c279d98..36fa336d0c 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mania private class TimeSlider : OsuSliderBar { - protected override LocalisableString GetTooltipText(double value) => $"{value:N0} ms"; + public override LocalisableString TooltipText => Current.Value.ToString(@"N0") + "ms"; } } } diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 747a56f636..a5bc02246d 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box rightBox; private readonly Container nubContainer; - public LocalisableString TooltipText { get; private set; } + public virtual LocalisableString TooltipText { get; private set; } /// /// Whether to format the tooltip as a percentage or the actual value. diff --git a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs index 1aaee5b540..9345d3fcc7 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio private class OffsetSlider : OsuSliderBar { - protected override LocalisableString GetTooltipText(double value) => value.ToString(@"0ms"); + public override LocalisableString TooltipText => Current.Value.ToString(@"0ms"); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index b572f1c6a0..adf1453d1a 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -240,7 +240,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private class UIScaleSlider : OsuSliderBar { - protected override LocalisableString GetTooltipText(float value) => $"{base.GetTooltipText(value)}x"; + public override LocalisableString TooltipText => base.TooltipText + "x"; } private class ResolutionSettingsDropdown : SettingsDropdown diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index 971b19ca6c..4235dc0a05 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -135,9 +135,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input private class SensitivitySlider : OsuSliderBar { - protected override LocalisableString GetTooltipText(double value) => Current.Disabled - ? MouseSettingsStrings.EnableHighPrecisionForSensitivityAdjust - : $"{base.GetTooltipText(value)}x"; + public override LocalisableString TooltipText => Current.Disabled ? MouseSettingsStrings.EnableHighPrecisionForSensitivityAdjust : $"{base.TooltipText}x"; } } } diff --git a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs index c80b330db6..8aeb440be1 100644 --- a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs +++ b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs @@ -11,6 +11,6 @@ namespace osu.Game.Overlays.Settings.Sections /// internal class SizeSlider : OsuSliderBar { - protected override LocalisableString GetTooltipText(float value) => value.ToString(@"0.##x"); + public override LocalisableString TooltipText => Current.Value.ToString(@"0.##x"); } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs index ca59095875..0afbed5df5 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface private class TimeSlider : OsuSliderBar { - protected override LocalisableString GetTooltipText(float value) => $"{value:N0} ms"; + public override LocalisableString TooltipText => Current.Value.ToString(@"N0") + "ms"; } } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index c24513e7b4..6290046987 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -64,14 +64,12 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface private class MaximumStarsSlider : StarsSlider { - protected override LocalisableString GetTooltipText(double value) => Current.IsDefault - ? UserInterfaceStrings.NoLimit - : base.GetTooltipText(value); + public override LocalisableString TooltipText => Current.IsDefault ? UserInterfaceStrings.NoLimit : base.TooltipText; } private class StarsSlider : OsuSliderBar { - protected override LocalisableString GetTooltipText(double value) => $"{value:0.##} stars"; + public override LocalisableString TooltipText => Current.Value.ToString(@"0.## stars"); } } } diff --git a/osu.Game/Rulesets/Mods/ModMuted.cs b/osu.Game/Rulesets/Mods/ModMuted.cs index e528d8214e..1d33b44812 100644 --- a/osu.Game/Rulesets/Mods/ModMuted.cs +++ b/osu.Game/Rulesets/Mods/ModMuted.cs @@ -111,6 +111,6 @@ namespace osu.Game.Rulesets.Mods public class MuteComboSlider : OsuSliderBar { - protected override LocalisableString GetTooltipText(int value) => value == 0 ? "always muted" : base.GetTooltipText(value); + public override LocalisableString TooltipText => Current.Value == 0 ? "always muted" : base.TooltipText; } } diff --git a/osu.Game/Rulesets/Mods/ModNoScope.cs b/osu.Game/Rulesets/Mods/ModNoScope.cs index c71239ea5a..7a935eb38f 100644 --- a/osu.Game/Rulesets/Mods/ModNoScope.cs +++ b/osu.Game/Rulesets/Mods/ModNoScope.cs @@ -57,6 +57,6 @@ namespace osu.Game.Rulesets.Mods public class HiddenComboSlider : OsuSliderBar { - protected override LocalisableString GetTooltipText(int value) => value == 0 ? "always hidden" : base.GetTooltipText(value); + public override LocalisableString TooltipText => Current.Value == 0 ? "always hidden" : base.TooltipText; } } From 1e34aca984061824993b490cd2b4b70e4242af5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 5 Mar 2022 14:38:15 +0100 Subject: [PATCH 1000/5199] Rename method to better fit purpose --- .../Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index a1688b87cc..602ace6dea 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -149,7 +149,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics windowModeDropdown.Current.BindValueChanged(mode => { - updateFullscreenDropdowns(); + updateDisplayModeDropdowns(); windowModeDropdown.WarningText = mode.NewValue != WindowMode.Fullscreen ? GraphicsSettingsStrings.NotFullscreenNote : default; }, true); @@ -175,7 +175,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics .Distinct()); } - updateFullscreenDropdowns(); + updateDisplayModeDropdowns(); }), true); scalingMode.BindValueChanged(mode => @@ -190,7 +190,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics // initial update bypasses transforms updateScalingModeVisibility(); - void updateFullscreenDropdowns() + void updateDisplayModeDropdowns() { if (resolutions.Count > 1 && windowModeDropdown.Current.Value == WindowMode.Fullscreen) resolutionDropdown.Show(); From ce51ce49cf1cf4cf109ec92a180314af93d41d63 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 5 Mar 2022 22:46:13 +0900 Subject: [PATCH 1001/5199] Revert changes to `GetTooltipText` and use `TooltipText` override directly --- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 6 +++--- .../Play/PlayerSettings/BeatmapOffsetControl.cs | 10 ++++------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index a5bc02246d..21c8dfcfa4 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -148,7 +148,7 @@ namespace osu.Game.Graphics.UserInterface protected override void LoadComplete() { base.LoadComplete(); - CurrentNumber.BindValueChanged(current => TooltipText = GetTooltipText(current.NewValue), true); + CurrentNumber.BindValueChanged(current => TooltipText = getTooltipText(current.NewValue), true); } protected override bool OnHover(HoverEvent e) @@ -178,7 +178,7 @@ namespace osu.Game.Graphics.UserInterface { base.OnUserChange(value); playSample(value); - TooltipText = GetTooltipText(value); + TooltipText = getTooltipText(value); } private void playSample(T value) @@ -203,7 +203,7 @@ namespace osu.Game.Graphics.UserInterface channel.Play(); } - protected virtual LocalisableString GetTooltipText(T value) + private LocalisableString getTooltipText(T value) { if (CurrentNumber.IsInteger) return value.ToInt32(NumberFormatInfo.InvariantInfo).ToString("N0"); diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 2e14480e2b..bb8dcf566d 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -97,12 +97,10 @@ namespace osu.Game.Screens.Play.PlayerSettings protected class CustomSliderBar : SliderBar { - protected override LocalisableString GetTooltipText(double value) - { - return value == 0 - ? new TranslatableString("_", @"{0} ms", base.GetTooltipText(value)) - : new TranslatableString("_", @"{0} ms {1}", base.GetTooltipText(value), getEarlyLateText(value)); - } + public override LocalisableString TooltipText => + Current.Value == 0 + ? new TranslatableString("_", @"{0} ms", base.TooltipText) + : new TranslatableString("_", @"{0} ms {1}", base.TooltipText, getEarlyLateText(Current.Value)); private LocalisableString getEarlyLateText(double value) { From 8b8b54b58ffc2f82c22e775cffc855964c1d77e3 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sat, 5 Mar 2022 21:48:57 +0800 Subject: [PATCH 1002/5199] Scale rate adjustments based on hit timing consistency and tweak some related numbers --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 29 ++++++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index e7ca1d3150..1115b95e6f 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -70,8 +70,8 @@ namespace osu.Game.Rulesets.Mods // The two constants below denote the maximum allowable change in rate caused by a single hit // This prevents sudden jolts caused by a badly-timed hit. - private const double min_allowable_rate_change = 0.8d; - private const double max_allowable_rate_change = 1.25d; + private const double min_allowable_rate_change = 0.9d; + private const double max_allowable_rate_change = 1.11d; // Apply a fixed rate change when missing, allowing the player to catch up when the rate is too fast. private const double rate_change_on_miss = 0.95d; @@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Mods /// The number of most recent track rates (approximated from how early/late each object was hit relative to the previous object) /// which should be averaged to calculate . /// - private const int recent_rate_count = 6; + private const int recent_rate_count = 8; /// /// Stores the most recent approximated track rates @@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.Mods recentRates.Add(Math.Clamp(getRelativeRateChange(result) * SpeedChange.Value, min_allowable_rate, max_allowable_rate)); - targetRate = recentRates.Average(); + updateTargetRate(); }; drawable.OnRevertResult += (o, result) => { @@ -184,7 +184,7 @@ namespace osu.Game.Rulesets.Mods recentRates.RemoveAt(recentRates.Count - 1); - targetRate = recentRates.Average(); + updateTargetRate(); }; } @@ -246,5 +246,24 @@ namespace osu.Game.Rulesets.Mods max_allowable_rate_change ); } + + /// + /// Update based on the values in . + /// + private void updateTargetRate() + { + // Compare values in recentRates to see how consistent the player's speed is + // If the player hits half of the notes too fast and the other half too slow: Abs(consistency) = 0 + // If the player hits all their notes too fast or too slow: Abs(consistency) = recent_rate_count - 1 + int consistency = 0; + + for (int i = 1; i < recentRates.Count; i++) + { + consistency += Math.Sign(recentRates[i] - recentRates[i - 1]); + } + + // Scale the rate adjustment based on consistency + targetRate = Interpolation.Lerp(targetRate, recentRates.Average(), Math.Abs(consistency) / (recent_rate_count - 1d)); + } } } From 08b3bc222d79a8d2a846ee11c1e300832d91a9d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 5 Mar 2022 16:42:51 +0100 Subject: [PATCH 1003/5199] Revert "Fix potential crash in tests when attempting to lookup key bindings in cases the lookup is not available" This reverts commit 8115a4bb8fd9e2d53c40b8607c7ad99f0f62e9a0. Commit was cherrypicked out to a separate pull on a different merge base, then reverted in that pull, so it should be reverted here too. --- osu.Game/Configuration/DevelopmentOsuConfigManager.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Configuration/DevelopmentOsuConfigManager.cs b/osu.Game/Configuration/DevelopmentOsuConfigManager.cs index f1cb0731fe..ff19dd874c 100644 --- a/osu.Game/Configuration/DevelopmentOsuConfigManager.cs +++ b/osu.Game/Configuration/DevelopmentOsuConfigManager.cs @@ -14,8 +14,6 @@ namespace osu.Game.Configuration public DevelopmentOsuConfigManager(Storage storage) : base(storage) { - LookupKeyBindings = _ => "unknown"; - LookupSkinName = _ => "unknown"; } } } From f8ef352306a133d935bd9c441a6419de62a5d825 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 6 Mar 2022 01:00:47 +0900 Subject: [PATCH 1004/5199] Don't consider judgements beneath the minimum height as being applicable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index bb42dda597..61b0cff8dd 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -250,7 +250,7 @@ namespace osu.Game.Screens.Ranking.Statistics public void UpdateOffset(float adjustment) { - bool hasAdjustment = adjustment != value; + bool hasAdjustment = adjustment != value && adjustment / maxValue >= minimum_height; if (boxAdjustment == null) { From 06512e8bd90d96ee62daa1866841e73499608505 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 6 Mar 2022 01:01:22 +0900 Subject: [PATCH 1005/5199] Use `const` for minimum height specification in final usage location MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 61b0cff8dd..f7c9d36cc4 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -269,7 +269,7 @@ namespace osu.Game.Screens.Ranking.Statistics }); } - boxAdjustment.ResizeHeightTo(Math.Clamp(adjustment / maxValue, 0.05f, 1), duration, Easing.OutQuint); + boxAdjustment.ResizeHeightTo(Math.Clamp(adjustment / maxValue, minimum_height, 1), duration, Easing.OutQuint); boxAdjustment.FadeTo(!hasAdjustment ? 0 : 1, duration, Easing.OutQuint); } } From 0e8ad4b143785b571206729846856a1512c2e4e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 6 Mar 2022 01:50:25 +0900 Subject: [PATCH 1006/5199] Switch step to `Until` steps due to `AddOnce` firing logic --- .../Visual/Gameplay/TestSceneBeatmapOffsetControl.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs index 4b079cbb2c..8ca49837da 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs @@ -62,11 +62,11 @@ namespace osu.Game.Tests.Visual.Gameplay }; }); - AddAssert("Has calibration button", () => offsetControl.ChildrenOfType().Any()); + AddUntilStep("Has calibration button", () => offsetControl.ChildrenOfType().Any()); AddStep("Press button", () => offsetControl.ChildrenOfType().Single().TriggerClick()); AddAssert("Offset is adjusted", () => offsetControl.Current.Value == -average_error); - AddAssert("Button is disabled", () => !offsetControl.ChildrenOfType().Single().Enabled.Value); + AddUntilStep("Button is disabled", () => !offsetControl.ChildrenOfType().Single().Enabled.Value); AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null); AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); } @@ -90,11 +90,11 @@ namespace osu.Game.Tests.Visual.Gameplay }; }); - AddAssert("Has calibration button", () => offsetControl.ChildrenOfType().Any()); + AddUntilStep("Has calibration button", () => offsetControl.ChildrenOfType().Any()); AddStep("Press button", () => offsetControl.ChildrenOfType().Single().TriggerClick()); AddAssert("Offset is adjusted", () => offsetControl.Current.Value == initial_offset - average_error); - AddAssert("Button is disabled", () => !offsetControl.ChildrenOfType().Single().Enabled.Value); + AddUntilStep("Button is disabled", () => !offsetControl.ChildrenOfType().Single().Enabled.Value); AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null); AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); } From 5b3ffb12b7c978f4ec1a4c8de8f346b854a5b9d3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 23:23:58 +0300 Subject: [PATCH 1007/5199] Refactor channel scrolling container to handle manual scrolls resiliently --- .../Online/TestSceneStandAloneChatDisplay.cs | 27 ++++++++++++++++--- .../Containers/UserTrackingScrollContainer.cs | 21 ++++++++++----- osu.Game/Overlays/Chat/DrawableChannel.cs | 19 ++++++------- 3 files changed, 49 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 779d72190d..9b9ee0e084 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -207,7 +207,28 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestUserScrollOverride() + public void TestOverrideChatScrolling() + { + fillChat(); + + sendMessage(); + checkScrolledToBottom(); + + AddStep("Scroll to start", () => chatDisplay.ScrollContainer.ScrollToStart()); + + checkNotScrolledToBottom(); + sendMessage(); + checkNotScrolledToBottom(); + + AddStep("Scroll to bottom", () => chatDisplay.ScrollContainer.ScrollToEnd()); + + checkScrolledToBottom(); + sendMessage(); + checkScrolledToBottom(); + } + + [Test] + public void TestOverrideChatScrollingByUser() { fillChat(); @@ -314,9 +335,9 @@ namespace osu.Game.Tests.Visual.Online { } - protected DrawableChannel DrawableChannel => InternalChildren.OfType().First(); + public DrawableChannel DrawableChannel => InternalChildren.OfType().First(); - protected UserTrackingScrollContainer ScrollContainer => (UserTrackingScrollContainer)((Container)DrawableChannel.Child).Child; + public UserTrackingScrollContainer ScrollContainer => (UserTrackingScrollContainer)((Container)DrawableChannel.Child).Child; public FillFlowContainer FillFlow => (FillFlowContainer)ScrollContainer.Child; diff --git a/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs b/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs index 0561051e35..988695c380 100644 --- a/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs +++ b/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs @@ -25,8 +25,6 @@ namespace osu.Game.Graphics.Containers /// public bool UserScrolling { get; private set; } - public void CancelUserScroll() => UserScrolling = false; - public UserTrackingScrollContainer() { } @@ -38,26 +36,37 @@ namespace osu.Game.Graphics.Containers protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = default) { - UserScrolling = true; base.OnUserScroll(value, animated, distanceDecay); + OnScrollChange(true); } public new void ScrollIntoView(Drawable target, bool animated = true) { - UserScrolling = false; base.ScrollIntoView(target, animated); + OnScrollChange(false); } public new void ScrollTo(float value, bool animated = true, double? distanceDecay = null) { - UserScrolling = false; base.ScrollTo(value, animated, distanceDecay); + OnScrollChange(false); + } + + public new void ScrollToStart(bool animated = true, bool allowDuringDrag = false) + { + base.ScrollToStart(animated, allowDuringDrag); + OnScrollChange(false); } public new void ScrollToEnd(bool animated = true, bool allowDuringDrag = false) { - UserScrolling = false; base.ScrollToEnd(animated, allowDuringDrag); + OnScrollChange(false); } + + /// + /// Invoked when any scroll has been performed either automatically or by user. + /// + protected virtual void OnScrollChange(bool byUser) => UserScrolling = byUser; } } diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 41e70bbfae..ccad55b809 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -249,31 +249,32 @@ namespace osu.Game.Overlays.Chat /// private const float auto_scroll_leniency = 10f; + private bool trackNewContent = true; private float? lastExtent; - protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = default) + protected override void OnScrollChange(bool byUser) { - base.OnUserScroll(value, animated, distanceDecay); - lastExtent = null; + base.OnScrollChange(byUser); + + if (byUser) + lastExtent = null; + + trackNewContent = IsScrolledToEnd(auto_scroll_leniency); } protected override void Update() { base.Update(); - // If the user has scrolled to the bottom of the container, we should resume tracking new content. - if (UserScrolling && IsScrolledToEnd(auto_scroll_leniency)) - CancelUserScroll(); - // If the user hasn't overridden our behaviour and there has been new content added to the container, we should update our scroll position to track it. - bool requiresScrollUpdate = !UserScrolling && (lastExtent == null || Precision.AlmostBigger(ScrollableExtent, lastExtent.Value)); + bool requiresScrollUpdate = trackNewContent && (lastExtent == null || Precision.AlmostBigger(ScrollableExtent, lastExtent.Value)); if (requiresScrollUpdate) { // Schedule required to allow FillFlow to be the correct size. Schedule(() => { - if (!UserScrolling) + if (trackNewContent) { if (Current < ScrollableExtent) ScrollToEnd(); From 634821e49f4485882539534e6e1df7fdb0259f1f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 6 Mar 2022 00:01:45 +0300 Subject: [PATCH 1008/5199] Use the term "programmatically" instead --- osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs b/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs index 988695c380..887aeb0c54 100644 --- a/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs +++ b/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs @@ -65,7 +65,7 @@ namespace osu.Game.Graphics.Containers } /// - /// Invoked when any scroll has been performed either automatically or by user. + /// Invoked when any scroll has been performed either programmatically or by user. /// protected virtual void OnScrollChange(bool byUser) => UserScrolling = byUser; } From e1eeb9c6bbaead86f6ce482a69700d6e3ec2144a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Mar 2022 01:43:56 +0100 Subject: [PATCH 1009/5199] Allow tabbing between textboxes in sample point popover --- osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs | 2 +- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 7 ++++++- .../Edit/Timing/IndeterminateSliderWithTextBoxInput.cs | 5 +++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index 4da8d6a554..fd64cc2056 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -35,7 +35,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 set => Component.Text = value; } - public Container TabbableContentContainer + public CompositeDrawable TabbableContentContainer { set => Component.TabbableContentContainer = value; } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 7d52645aa1..fc0952d4f0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -75,9 +75,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [BackgroundDependencyLoader] private void load() { + FillFlowContainer flow; + Children = new Drawable[] { - new FillFlowContainer + flow = new FillFlowContainer { Width = 200, Direction = FillDirection.Vertical, @@ -94,6 +96,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } }; + bank.TabbableContentContainer = flow; + volume.TabbableContentContainer = flow; + // if the piece belongs to a currently selected object, assume that the user wants to change all selected objects. // if the piece belongs to an unselected object, operate on that object alone, independently of the selection. var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray(); diff --git a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs index e25d83cfb0..0cf2cf6c54 100644 --- a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs @@ -32,6 +32,11 @@ namespace osu.Game.Screens.Edit.Timing set => slider.KeyboardStep = value; } + public CompositeDrawable TabbableContentContainer + { + set => textBox.TabbableContentContainer = value; + } + private readonly BindableWithCurrent current = new BindableWithCurrent(); public Bindable Current From df0617f34c33a31050c94cd113dd94819d064ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Feb 2022 15:03:21 +0100 Subject: [PATCH 1010/5199] Implement popup screen title component --- .../TestScenePopupScreenTitle.cs | 44 +++++ .../UserInterface/PopupScreenTitle.cs | 151 ++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestScenePopupScreenTitle.cs create mode 100644 osu.Game/Graphics/UserInterface/PopupScreenTitle.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePopupScreenTitle.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePopupScreenTitle.cs new file mode 100644 index 0000000000..c214c158a4 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePopupScreenTitle.cs @@ -0,0 +1,44 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestScenePopupScreenTitle : OsuTestScene + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + + [Test] + public void TestPopupScreenTitle() + { + AddStep("create content", () => + { + Child = new PopupScreenTitle + { + Title = "Popup Screen Title", + Description = "This is a description.", + Close = () => { } + }; + }); + } + + [Test] + public void TestDisabledExit() + { + AddStep("create content", () => + { + Child = new PopupScreenTitle + { + Title = "Popup Screen Title", + Description = "This is a description." + }; + }); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/PopupScreenTitle.cs b/osu.Game/Graphics/UserInterface/PopupScreenTitle.cs new file mode 100644 index 0000000000..8d5aef7427 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/PopupScreenTitle.cs @@ -0,0 +1,151 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Graphics.UserInterface +{ + public class PopupScreenTitle : CompositeDrawable + { + public LocalisableString Title + { + get => titleSpriteText.Text; + set => titleSpriteText.Text = value; + } + + public LocalisableString Description + { + get => descriptionSpriteText.Text; + set => descriptionSpriteText.Text = value; + } + + public Action? Close + { + get => closeButton.Action; + set => closeButton.Action = value; + } + + private const float corner_radius = 14; + private const float main_area_height = 70; + + private readonly Container underlayContainer; + private readonly Box underlayBackground; + private readonly Container contentContainer; + private readonly Box contentBackground; + private readonly OsuSpriteText titleSpriteText; + private readonly OsuSpriteText descriptionSpriteText; + private readonly IconButton closeButton; + + public PopupScreenTitle() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding + { + Horizontal = 70, + Top = -corner_radius + }, + Children = new Drawable[] + { + underlayContainer = new Container + { + RelativeSizeAxes = Axes.X, + Height = main_area_height + 2 * corner_radius, + CornerRadius = corner_radius, + Masking = true, + BorderThickness = 2, + Child = underlayBackground = new Box + { + RelativeSizeAxes = Axes.Both + } + }, + contentContainer = new Container + { + RelativeSizeAxes = Axes.X, + Height = main_area_height + corner_radius, + CornerRadius = corner_radius, + Masking = true, + BorderThickness = 2, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Colour4.Black.Opacity(0.1f), + Offset = new Vector2(0, 1), + Radius = 3 + }, + Children = new Drawable[] + { + contentBackground = new Box + { + RelativeSizeAxes = Axes.Both + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Top = corner_radius }, + Padding = new MarginPadding { Horizontal = 100 }, + Children = new Drawable[] + { + titleSpriteText = new OsuSpriteText + { + Font = OsuFont.TorusAlternate.With(size: 20) + }, + descriptionSpriteText = new OsuSpriteText + { + Font = OsuFont.Default.With(size: 12) + } + } + }, + closeButton = new IconButton + { + Icon = FontAwesome.Solid.Times, + Scale = new Vector2(0.6f), + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Margin = new MarginPadding + { + Right = 21, + Top = corner_radius + } + } + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + underlayContainer.BorderColour = ColourInfo.GradientVertical(Colour4.Black, colourProvider.Dark4); + underlayBackground.Colour = colourProvider.Dark4; + + contentContainer.BorderColour = ColourInfo.GradientVertical(colourProvider.Dark3, colourProvider.Dark1); + contentBackground.Colour = colourProvider.Dark3; + + closeButton.IconHoverColour = colourProvider.Highlight1; + } + } +} From 54275813b57e88d8709d1fbf1c3e33d329ed7cb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Feb 2022 18:10:30 +0100 Subject: [PATCH 1011/5199] Use text flow container in popup screen title --- .../UserInterface/TestScenePopupScreenTitle.cs | 3 ++- .../Graphics/UserInterface/PopupScreenTitle.cs | 15 +++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePopupScreenTitle.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePopupScreenTitle.cs index c214c158a4..22a8fa8a46 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePopupScreenTitle.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePopupScreenTitle.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.Allocation; using osu.Game.Graphics.UserInterface; @@ -22,7 +23,7 @@ namespace osu.Game.Tests.Visual.UserInterface Child = new PopupScreenTitle { Title = "Popup Screen Title", - Description = "This is a description.", + Description = string.Join(" ", Enumerable.Repeat("This is a description.", 20)), Close = () => { } }; }); diff --git a/osu.Game/Graphics/UserInterface/PopupScreenTitle.cs b/osu.Game/Graphics/UserInterface/PopupScreenTitle.cs index 8d5aef7427..5b7db09e77 100644 --- a/osu.Game/Graphics/UserInterface/PopupScreenTitle.cs +++ b/osu.Game/Graphics/UserInterface/PopupScreenTitle.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osuTK; @@ -22,14 +23,12 @@ namespace osu.Game.Graphics.UserInterface { public LocalisableString Title { - get => titleSpriteText.Text; set => titleSpriteText.Text = value; } public LocalisableString Description { - get => descriptionSpriteText.Text; - set => descriptionSpriteText.Text = value; + set => descriptionText.Text = value; } public Action? Close @@ -46,7 +45,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Container contentContainer; private readonly Box contentBackground; private readonly OsuSpriteText titleSpriteText; - private readonly OsuSpriteText descriptionSpriteText; + private readonly OsuTextFlowContainer descriptionText; private readonly IconButton closeButton; public PopupScreenTitle() @@ -112,9 +111,13 @@ namespace osu.Game.Graphics.UserInterface { Font = OsuFont.TorusAlternate.With(size: 20) }, - descriptionSpriteText = new OsuSpriteText + descriptionText = new OsuTextFlowContainer(t => { - Font = OsuFont.Default.With(size: 12) + t.Font = OsuFont.Default.With(size: 12); + }) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y } } }, From 9bc1f3f014178f19c5c174ba60bcff8d617762d7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 6 Mar 2022 23:34:12 +0300 Subject: [PATCH 1012/5199] Further refactor and simplify `ChannelScrollContainer` --- .../Online/TestSceneStandAloneChatDisplay.cs | 3 +- .../Overlays/Chat/ChannelScrollContainer.cs | 78 +++++++++++++++++++ osu.Game/Overlays/Chat/DrawableChannel.cs | 50 ------------ 3 files changed, 79 insertions(+), 52 deletions(-) create mode 100644 osu.Game/Overlays/Chat/ChannelScrollContainer.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 9b9ee0e084..a21647712d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -9,7 +9,6 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Chat; using osuTK.Input; @@ -337,7 +336,7 @@ namespace osu.Game.Tests.Visual.Online public DrawableChannel DrawableChannel => InternalChildren.OfType().First(); - public UserTrackingScrollContainer ScrollContainer => (UserTrackingScrollContainer)((Container)DrawableChannel.Child).Child; + public ChannelScrollContainer ScrollContainer => (ChannelScrollContainer)((Container)DrawableChannel.Child).Child; public FillFlowContainer FillFlow => (FillFlowContainer)ScrollContainer.Child; diff --git a/osu.Game/Overlays/Chat/ChannelScrollContainer.cs b/osu.Game/Overlays/Chat/ChannelScrollContainer.cs new file mode 100644 index 0000000000..23bc683661 --- /dev/null +++ b/osu.Game/Overlays/Chat/ChannelScrollContainer.cs @@ -0,0 +1,78 @@ +// 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.Utils; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Overlays.Chat +{ + /// + /// An with functionality to automatically scroll whenever the maximum scrollable distance increases. + /// + public class ChannelScrollContainer : OsuScrollContainer + { + /// + /// The chat will be automatically scrolled to end if and only if + /// the distance between the current scroll position and the end of the scroll + /// is less than this value. + /// + private const float auto_scroll_leniency = 10f; + + private bool trackNewContent = true; + + protected override void Update() + { + base.Update(); + + // If our behaviour hasn't been overriden and there has been new content added to the container, we should update our scroll position to track it. + bool requiresScrollUpdate = trackNewContent && !IsScrolledToEnd(); + + if (requiresScrollUpdate) + { + // Schedule required to allow FillFlow to be the correct size. + Schedule(() => + { + if (trackNewContent) + { + if (Current < ScrollableExtent) + ScrollToEnd(); + } + }); + } + } + + private void updateTrackState() => trackNewContent = IsScrolledToEnd(auto_scroll_leniency); + + // todo: we may eventually want this encapsulated in a "OnScrollChange" event handler method provided by ScrollContainer. + // important to note that this intentionally doesn't consider OffsetScrollPosition, but could make it do so with side changes. + + #region Scroll handling + + protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = null) + { + base.OnUserScroll(value, animated, distanceDecay); + updateTrackState(); + } + + public new void ScrollIntoView(Drawable d, bool animated = true) + { + base.ScrollIntoView(d, animated); + updateTrackState(); + } + + public new void ScrollToStart(bool animated = true, bool allowDuringDrag = false) + { + base.ScrollToStart(animated, allowDuringDrag); + updateTrackState(); + } + + public new void ScrollToEnd(bool animated = true, bool allowDuringDrag = false) + { + base.ScrollToEnd(animated, allowDuringDrag); + updateTrackState(); + } + + #endregion + } +} diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index ccad55b809..6220beeb82 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -11,9 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Utils; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; @@ -236,53 +234,5 @@ namespace osu.Game.Overlays.Chat }; } } - - /// - /// An with functionality to automatically scroll whenever the maximum scrollable distance increases. - /// - private class ChannelScrollContainer : UserTrackingScrollContainer - { - /// - /// The chat will be automatically scrolled to end if and only if - /// the distance between the current scroll position and the end of the scroll - /// is less than this value. - /// - private const float auto_scroll_leniency = 10f; - - private bool trackNewContent = true; - private float? lastExtent; - - protected override void OnScrollChange(bool byUser) - { - base.OnScrollChange(byUser); - - if (byUser) - lastExtent = null; - - trackNewContent = IsScrolledToEnd(auto_scroll_leniency); - } - - protected override void Update() - { - base.Update(); - - // If the user hasn't overridden our behaviour and there has been new content added to the container, we should update our scroll position to track it. - bool requiresScrollUpdate = trackNewContent && (lastExtent == null || Precision.AlmostBigger(ScrollableExtent, lastExtent.Value)); - - if (requiresScrollUpdate) - { - // Schedule required to allow FillFlow to be the correct size. - Schedule(() => - { - if (trackNewContent) - { - if (Current < ScrollableExtent) - ScrollToEnd(); - lastExtent = ScrollableExtent; - } - }); - } - } - } } } From 9ec0e7481332cc69e018f7c4ca6d60059cb4cd42 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 6 Mar 2022 23:50:57 +0300 Subject: [PATCH 1013/5199] Move scrolling to `UpdateAfterChildren` to avoid scheduling At least that's what I believe "let FillFlow update to new size" means. --- .../Overlays/Chat/ChannelScrollContainer.cs | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelScrollContainer.cs b/osu.Game/Overlays/Chat/ChannelScrollContainer.cs index 23bc683661..1d1d35dc34 100644 --- a/osu.Game/Overlays/Chat/ChannelScrollContainer.cs +++ b/osu.Game/Overlays/Chat/ChannelScrollContainer.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Framework.Utils; using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Chat @@ -21,25 +20,12 @@ namespace osu.Game.Overlays.Chat private bool trackNewContent = true; - protected override void Update() + protected override void UpdateAfterChildren() { - base.Update(); + base.UpdateAfterChildren(); - // If our behaviour hasn't been overriden and there has been new content added to the container, we should update our scroll position to track it. - bool requiresScrollUpdate = trackNewContent && !IsScrolledToEnd(); - - if (requiresScrollUpdate) - { - // Schedule required to allow FillFlow to be the correct size. - Schedule(() => - { - if (trackNewContent) - { - if (Current < ScrollableExtent) - ScrollToEnd(); - } - }); - } + if (trackNewContent && !IsScrolledToEnd()) + ScrollToEnd(); } private void updateTrackState() => trackNewContent = IsScrolledToEnd(auto_scroll_leniency); From a13a087f5dda13342fe9707a63d4421dbbdcdfb0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 6 Mar 2022 23:51:27 +0300 Subject: [PATCH 1014/5199] Add xmldoc to `trackNewContent` to explain its purpose --- osu.Game/Overlays/Chat/ChannelScrollContainer.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/Chat/ChannelScrollContainer.cs b/osu.Game/Overlays/Chat/ChannelScrollContainer.cs index 1d1d35dc34..58b2b9a075 100644 --- a/osu.Game/Overlays/Chat/ChannelScrollContainer.cs +++ b/osu.Game/Overlays/Chat/ChannelScrollContainer.cs @@ -18,6 +18,12 @@ namespace osu.Game.Overlays.Chat /// private const float auto_scroll_leniency = 10f; + /// + /// Whether to keep this container scrolled to end on new content. + /// + /// + /// This is specifically controlled by whether the latest scroll operation made the container scrolled to end. + /// private bool trackNewContent = true; protected override void UpdateAfterChildren() From 60334046e4dd7dface21e505d9ea929627993433 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 6 Mar 2022 23:57:51 +0300 Subject: [PATCH 1015/5199] Revert `UserTrackingScrollContainer` changes --- .../Containers/UserTrackingScrollContainer.cs | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs b/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs index 887aeb0c54..0561051e35 100644 --- a/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs +++ b/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs @@ -25,6 +25,8 @@ namespace osu.Game.Graphics.Containers /// public bool UserScrolling { get; private set; } + public void CancelUserScroll() => UserScrolling = false; + public UserTrackingScrollContainer() { } @@ -36,37 +38,26 @@ namespace osu.Game.Graphics.Containers protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = default) { + UserScrolling = true; base.OnUserScroll(value, animated, distanceDecay); - OnScrollChange(true); } public new void ScrollIntoView(Drawable target, bool animated = true) { + UserScrolling = false; base.ScrollIntoView(target, animated); - OnScrollChange(false); } public new void ScrollTo(float value, bool animated = true, double? distanceDecay = null) { + UserScrolling = false; base.ScrollTo(value, animated, distanceDecay); - OnScrollChange(false); - } - - public new void ScrollToStart(bool animated = true, bool allowDuringDrag = false) - { - base.ScrollToStart(animated, allowDuringDrag); - OnScrollChange(false); } public new void ScrollToEnd(bool animated = true, bool allowDuringDrag = false) { + UserScrolling = false; base.ScrollToEnd(animated, allowDuringDrag); - OnScrollChange(false); } - - /// - /// Invoked when any scroll has been performed either programmatically or by user. - /// - protected virtual void OnScrollChange(bool byUser) => UserScrolling = byUser; } } From d9be65ea39fc20af1f991f99940539da37b5e815 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 6 Mar 2022 23:58:06 +0300 Subject: [PATCH 1016/5199] Remove no longer necessary `CancelUserScroll` method --- osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs b/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs index 0561051e35..44afaf77ea 100644 --- a/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs +++ b/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs @@ -25,8 +25,6 @@ namespace osu.Game.Graphics.Containers /// public bool UserScrolling { get; private set; } - public void CancelUserScroll() => UserScrolling = false; - public UserTrackingScrollContainer() { } From 30b38345aad33a37419f32528cc1ff831e70699a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 23:21:29 +0300 Subject: [PATCH 1017/5199] Add ability to highlight chat lines --- osu.Game/Overlays/Chat/ChatLine.cs | 43 +++++++++++++++++------ osu.Game/Overlays/Chat/DrawableChannel.cs | 22 ++++++++++++ 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 87d1b1a3ad..e7b349b313 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -42,7 +42,9 @@ namespace osu.Game.Overlays.Chat protected virtual float TextSize => 20; - private Color4 customUsernameColour; + private Container lineBackground; + + private Color4 usernameColour; private OsuSpriteText timestamp; @@ -78,19 +80,22 @@ namespace osu.Game.Overlays.Chat } } - private bool senderHasBackground => !string.IsNullOrEmpty(message.Sender.Colour); + private bool senderHasColour => !string.IsNullOrEmpty(message.Sender.Colour); + + [Resolved] + private OsuColour colours { get; set; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { - customUsernameColour = colours.ChatBlue; - - bool hasBackground = senderHasBackground; + usernameColour = senderHasColour + ? Color4Extensions.FromHex(message.Sender.Colour) + : username_colours[message.Sender.Id % username_colours.Length]; Drawable effectedUsername = username = new OsuSpriteText { Shadow = false, - Colour = hasBackground ? customUsernameColour : username_colours[message.Sender.Id % username_colours.Length], + Colour = senderHasColour ? colours.ChatBlue : usernameColour, Truncate = true, EllipsisString = "… :", Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Bold, italics: true), @@ -99,7 +104,7 @@ namespace osu.Game.Overlays.Chat MaxWidth = MessagePadding - TimestampPadding }; - if (hasBackground) + if (senderHasColour) { // Background effect effectedUsername = new Container @@ -126,7 +131,7 @@ namespace osu.Game.Overlays.Chat new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex(message.Sender.Colour), + Colour = usernameColour, }, new Container { @@ -141,6 +146,14 @@ namespace osu.Game.Overlays.Chat InternalChildren = new Drawable[] { + lineBackground = new Container + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.0f), + CornerRadius = 2f, + Masking = true, + Child = new Box { RelativeSizeAxes = Axes.Both } + }, new Container { Size = new Vector2(MessagePadding, TextSize), @@ -177,7 +190,7 @@ namespace osu.Game.Overlays.Chat { t.Font = OsuFont.GetFont(italics: true); - if (senderHasBackground) + if (senderHasColour) t.Colour = Color4Extensions.FromHex(message.Sender.Colour); } @@ -200,13 +213,21 @@ namespace osu.Game.Overlays.Chat FinishTransforms(true); } + /// + /// Schedules a message highlight animation. + /// + /// + /// Scheduling is required to ensure the animation doesn't play until the chat line is in view and not scrolled away. + /// + public void ScheduleHighlight() => Schedule(() => lineBackground.FlashColour(usernameColour.Darken(1f).Opacity(0.5f), 1000, Easing.InQuint)); + private void updateMessageContent() { this.FadeTo(message is LocalEchoMessage ? 0.4f : 1.0f, 500, Easing.OutQuint); timestamp.FadeTo(message is LocalEchoMessage ? 0 : 1, 500, Easing.OutQuint); timestamp.Text = $@"{message.Timestamp.LocalDateTime:HH:mm:ss}"; - username.Text = $@"{message.Sender.Username}" + (senderHasBackground || message.IsAction ? "" : ":"); + username.Text = $@"{message.Sender.Username}" + (senderHasColour || message.IsAction ? "" : ":"); // remove non-existent channels from the link list message.Links.RemoveAll(link => link.Action == LinkAction.OpenChannel && chatManager?.AvailableChannels.Any(c => c.Name == link.Argument.ToString()) != true); diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 6220beeb82..cca089873f 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -79,6 +79,28 @@ namespace osu.Game.Overlays.Chat Channel.PendingMessageResolved += pendingMessageResolved; } + /// + /// Highlights a specific message in this drawable channel. + /// + /// The message to highlight. + public void HighlightMessage(Message message) + { + if (IsLoaded) + highlightMessage(message); + else + Schedule(highlightMessage, message); + } + + private void highlightMessage(Message message) + { + var chatLine = chatLines.SingleOrDefault(c => c.Message == message); + if (chatLine == null) + return; + + scroll.ScrollIntoView(chatLine); + chatLine.ScheduleHighlight(); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From cb2133944dfdd6cd212fbbfb8385f4e97fa51ce1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 23:27:09 +0300 Subject: [PATCH 1018/5199] Add test coverage for channel message highlighting --- .../Online/TestSceneStandAloneChatDisplay.cs | 147 +++++++++++++----- 1 file changed, 104 insertions(+), 43 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index a21647712d..89ee8b03e9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -9,6 +9,8 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Chat; using osuTK.Input; @@ -107,49 +109,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestManyMessages() { - AddStep("message from admin", () => testChannel.AddNewMessages(new Message(messageIdSequence++) - { - Sender = admin, - Content = "I am a wang!" - })); - - AddStep("message from team red", () => testChannel.AddNewMessages(new Message(messageIdSequence++) - { - Sender = redUser, - Content = "I am team red." - })); - - AddStep("message from team red", () => testChannel.AddNewMessages(new Message(messageIdSequence++) - { - Sender = redUser, - Content = "I plan to win!" - })); - - AddStep("message from team blue", () => testChannel.AddNewMessages(new Message(messageIdSequence++) - { - Sender = blueUser, - Content = "Not on my watch. Prepare to eat saaaaaaaaaand. Lots and lots of saaaaaaand." - })); - - AddStep("message from admin", () => testChannel.AddNewMessages(new Message(messageIdSequence++) - { - Sender = admin, - Content = "Okay okay, calm down guys. Let's do this!" - })); - - AddStep("message from long username", () => testChannel.AddNewMessages(new Message(messageIdSequence++) - { - Sender = longUsernameUser, - Content = "Hi guys, my new username is lit!" - })); - - AddStep("message with new date", () => testChannel.AddNewMessages(new Message(messageIdSequence++) - { - Sender = longUsernameUser, - Content = "Message from the future!", - Timestamp = DateTimeOffset.Now - })); - + sendRegularMessages(); checkScrolledToBottom(); const int messages_per_call = 10; @@ -182,6 +142,61 @@ namespace osu.Game.Tests.Visual.Online checkScrolledToBottom(); } + [Test] + public void TestMessageHighlighting() + { + Message highlighted = null; + + sendRegularMessages(); + + AddStep("highlight first message", () => + { + highlighted = testChannel.Messages[0]; + chatDisplay.DrawableChannel.HighlightMessage(highlighted); + }); + + AddUntilStep("chat scrolled to first message", () => + { + var line = chatDisplay.ChildrenOfType().Single(c => c.Message == highlighted); + return chatDisplay.ScrollContainer.ScreenSpaceDrawQuad.Contains(line.ScreenSpaceDrawQuad.Centre); + }); + + sendMessage(); + checkNotScrolledToBottom(); + + AddStep("highlight last message", () => + { + highlighted = testChannel.Messages[^1]; + chatDisplay.DrawableChannel.HighlightMessage(highlighted); + }); + + AddUntilStep("chat scrolled to last message", () => + { + var line = chatDisplay.ChildrenOfType().Single(c => c.Message == highlighted); + return chatDisplay.ScrollContainer.ScreenSpaceDrawQuad.Contains(line.ScreenSpaceDrawQuad.Centre); + }); + + sendMessage(); + checkScrolledToBottom(); + + AddRepeatStep("highlight other random messages", () => + { + highlighted = testChannel.Messages[RNG.Next(0, testChannel.Messages.Count - 1)]; + chatDisplay.DrawableChannel.HighlightMessage(highlighted); + }, 10); + } + + [Test] + public void TestMessageHighlightingOnFilledChat() + { + fillChat(); + + AddRepeatStep("highlight random messages", () => + { + chatDisplay.DrawableChannel.HighlightMessage(testChannel.Messages[RNG.Next(0, testChannel.Messages.Count - 1)]); + }, 10); + } + /// /// Tests that when a message gets wrapped by the chat display getting contracted while scrolled to bottom, the chat will still keep scrolling down. /// @@ -321,6 +336,52 @@ namespace osu.Game.Tests.Visual.Online })); } + private void sendRegularMessages() + { + AddStep("message from admin", () => testChannel.AddNewMessages(new Message(messageIdSequence++) + { + Sender = admin, + Content = "I am a wang!" + })); + + AddStep("message from team red", () => testChannel.AddNewMessages(new Message(messageIdSequence++) + { + Sender = redUser, + Content = "I am team red." + })); + + AddStep("message from team red", () => testChannel.AddNewMessages(new Message(messageIdSequence++) + { + Sender = redUser, + Content = "I plan to win!" + })); + + AddStep("message from team blue", () => testChannel.AddNewMessages(new Message(messageIdSequence++) + { + Sender = blueUser, + Content = "Not on my watch. Prepare to eat saaaaaaaaaand. Lots and lots of saaaaaaand." + })); + + AddStep("message from admin", () => testChannel.AddNewMessages(new Message(messageIdSequence++) + { + Sender = admin, + Content = "Okay okay, calm down guys. Let's do this!" + })); + + AddStep("message from long username", () => testChannel.AddNewMessages(new Message(messageIdSequence++) + { + Sender = longUsernameUser, + Content = "Hi guys, my new username is lit!" + })); + + AddStep("message with new date", () => testChannel.AddNewMessages(new Message(messageIdSequence++) + { + Sender = longUsernameUser, + Content = "Message from the future!", + Timestamp = DateTimeOffset.Now + })); + } + private void checkScrolledToBottom() => AddUntilStep("is scrolled to bottom", () => chatDisplay.ScrolledToBottom); From f4fa80c1e3bd9ae5056185a087f7ee4d65c071c8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 23:28:40 +0300 Subject: [PATCH 1019/5199] Add support to highlight messages in chat overlay --- osu.Game/Overlays/ChatOverlay.cs | 41 ++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index fde9d28b43..d642bf3bb2 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; +using JetBrains.Annotations; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; @@ -46,6 +47,8 @@ namespace osu.Game.Overlays private Container currentChannelContainer; + private DrawableChannel currentDrawableChannel => currentChannelContainer.SingleOrDefault(); + private readonly List loadedChannels = new List(); private LoadingSpinner loading; @@ -249,6 +252,9 @@ namespace osu.Game.Overlays private Bindable currentChannel; + [CanBeNull] + private Message messagePendingHighlight; + private void currentChannelChanged(ValueChangedEvent e) { if (e.NewValue == null) @@ -290,12 +296,24 @@ namespace osu.Game.Overlays currentChannelContainer.Clear(false); currentChannelContainer.Add(loaded); currentChannelContainer.FadeIn(500, Easing.OutQuint); + + if (messagePendingHighlight != null) + { + tryHighlightMessage(messagePendingHighlight); + messagePendingHighlight = null; + } }); } else { currentChannelContainer.Clear(false); currentChannelContainer.Add(loaded); + + if (messagePendingHighlight != null) + { + tryHighlightMessage(messagePendingHighlight); + messagePendingHighlight = null; + } } // mark channel as read when channel switched @@ -303,6 +321,29 @@ namespace osu.Game.Overlays channelManager.MarkChannelAsRead(e.NewValue); } + /// + /// Highlights a certain message in the specified channel. + /// + /// The message to highlight. + public void HighlightMessage(Message message) + { + if (currentDrawableChannel?.Channel.Id == message.ChannelId) + tryHighlightMessage(message); + else + { + messagePendingHighlight = message; + currentChannel.Value = channelManager.JoinedChannels.Single(c => c.Id == message.ChannelId); + } + } + + private void tryHighlightMessage(Message message) + { + if (message.ChannelId != currentChannel.Value.Id) + return; + + currentDrawableChannel.HighlightMessage(message); + } + private float startDragChatHeight; private bool isDragging; From 741702549b6d45e8e7abb52c7fbd28b52057148d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 23:31:03 +0300 Subject: [PATCH 1020/5199] Add test coverage for chat overlay message highlighting --- .../Visual/Online/TestSceneChatOverlay.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 14f32df653..a91cd53ec1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -412,6 +412,59 @@ namespace osu.Game.Tests.Visual.Online AddAssert("channel left", () => !channelManager.JoinedChannels.Contains(multiplayerChannel)); } + [Test] + public void TestHighlightOnCurrentChannel() + { + Message message = null; + + AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); + AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); + + AddStep("Send message in channel 1", () => + { + channel1.AddNewMessages(message = new Message + { + ChannelId = channel1.Id, + Content = "Message to highlight!", + Timestamp = DateTimeOffset.Now, + Sender = new APIUser + { + Id = 2, + Username = "Someone", + } + }); + }); + + AddStep("Highlight message", () => chatOverlay.HighlightMessage(message)); + } + + [Test] + public void TestHighlightOnAnotherChannel() + { + Message message = null; + + AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); + AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); + + AddStep("Join channel 2", () => channelManager.JoinChannel(channel2)); + AddStep("Send message in channel 2", () => + { + channel2.AddNewMessages(message = new Message + { + ChannelId = channel2.Id, + Content = "Message to highlight!", + Timestamp = DateTimeOffset.Now, + Sender = new APIUser + { + Id = 2, + Username = "Someone", + } + }); + }); + + AddStep("Highlight message", () => chatOverlay.HighlightMessage(message)); + } + private void pressChannelHotkey(int number) { var channelKey = Key.Number0 + number; From 32d242dd627412a38aff0340ae6ba8039e68d84f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 23:31:32 +0300 Subject: [PATCH 1021/5199] Hook up message notifications to chat message highlighting logic --- osu.Game/Online/Chat/MessageNotifier.cs | 28 ++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 2c99e9f9b9..de3b9f71ad 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -99,7 +99,7 @@ namespace osu.Game.Online.Chat if (checkForPMs(channel, message)) continue; - checkForMentions(channel, message); + checkForMentions(message); } } @@ -114,15 +114,15 @@ namespace osu.Game.Online.Chat if (!notifyOnPrivateMessage.Value || channel.Type != ChannelType.PM) return false; - notifications.Post(new PrivateMessageNotification(message.Sender.Username, channel)); + notifications.Post(new PrivateMessageNotification(message)); return true; } - private void checkForMentions(Channel channel, Message message) + private void checkForMentions(Message message) { if (!notifyOnUsername.Value || !CheckContainsUsername(message.Content, localUser.Value.Username)) return; - notifications.Post(new MentionNotification(message.Sender.Username, channel)); + notifications.Post(new MentionNotification(message)); } /// @@ -138,32 +138,32 @@ namespace osu.Game.Online.Chat public class PrivateMessageNotification : OpenChannelNotification { - public PrivateMessageNotification(string username, Channel channel) - : base(channel) + public PrivateMessageNotification(Message message) + : base(message) { Icon = FontAwesome.Solid.Envelope; - Text = $"You received a private message from '{username}'. Click to read it!"; + Text = $"You received a private message from '{message.Sender.Username}'. Click to read it!"; } } public class MentionNotification : OpenChannelNotification { - public MentionNotification(string username, Channel channel) - : base(channel) + public MentionNotification(Message message) + : base(message) { Icon = FontAwesome.Solid.At; - Text = $"Your name was mentioned in chat by '{username}'. Click to find out why!"; + Text = $"Your name was mentioned in chat by '{message.Sender.Username}'. Click to find out why!"; } } public abstract class OpenChannelNotification : SimpleNotification { - protected OpenChannelNotification(Channel channel) + protected OpenChannelNotification(Message message) { - this.channel = channel; + this.message = message; } - private readonly Channel channel; + private readonly Message message; public override bool IsImportant => false; @@ -175,8 +175,8 @@ namespace osu.Game.Online.Chat Activated = delegate { notificationOverlay.Hide(); + chatOverlay.HighlightMessage(message); chatOverlay.Show(); - channelManager.CurrentChannel.Value = channel; return true; }; From da29947ecd36fc3479d6e82ee8ca45aba4c1338a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Mar 2022 11:34:06 +0900 Subject: [PATCH 1022/5199] Disallow interaction with carousel set difficulty icons unless selected I kinda liked this flow, but from multiple reports from users it definitely seems in the way. We can revisit after the new design is applied to song select. Note that this means the tooltips also don't display. If it is preferred that they should (arguable from a UX perspective, since I'd expect to be able to click at that point) then the issue can be addressed using a slightly different path (a few more lines - nothing too complex). --- osu.Game/Screens/Select/Carousel/SetPanelContent.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index 760915b528..a000cfd5fc 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -16,6 +16,9 @@ namespace osu.Game.Screens.Select.Carousel { public class SetPanelContent : CompositeDrawable { + // Disallow interacting with difficulty icons on a panel until the panel has been selected. + public override bool PropagatePositionalInputSubTree => carouselSet.State.Value == CarouselItemState.Selected; + private readonly CarouselBeatmapSet carouselSet; public SetPanelContent(CarouselBeatmapSet carouselSet) From a02adfdbd4efb69dd44d0ed872da398bb8ba09df Mon Sep 17 00:00:00 2001 From: Siim Pender Date: Mon, 7 Mar 2022 20:36:09 +0200 Subject: [PATCH 1023/5199] Fix crash on super high bpm kiai sections --- osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs b/osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs index d49b1713f6..46e0030034 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.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 osu.Framework.Audio.Track; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; @@ -37,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default Child .FadeTo(flash_opacity, EarlyActivationMilliseconds, Easing.OutQuint) .Then() - .FadeOut(timingPoint.BeatLength - fade_length, Easing.OutSine); + .FadeOut(Math.Max(0, timingPoint.BeatLength - fade_length), Easing.OutSine); } } } From 78a3b5961e8590e85a9303fb362d3ab8d1993c52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Feb 2022 15:48:33 +0100 Subject: [PATCH 1024/5199] Implement basic difficulty multiplier display --- .../TestSceneDifficultyMultiplierDisplay.cs | 40 +++++ .../Mods/DifficultyMultiplierDisplay.cs | 166 ++++++++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneDifficultyMultiplierDisplay.cs create mode 100644 osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDifficultyMultiplierDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDifficultyMultiplierDisplay.cs new file mode 100644 index 0000000000..cd84f8b380 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDifficultyMultiplierDisplay.cs @@ -0,0 +1,40 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Overlays; +using osu.Game.Overlays.Mods; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestSceneDifficultyMultiplierDisplay : OsuTestScene + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + + [Test] + public void TestDifficultyMultiplierDisplay() + { + DifficultyMultiplierDisplay multiplierDisplay = null; + + AddStep("create content", () => Child = multiplierDisplay = new DifficultyMultiplierDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }); + + AddStep("set multiplier below 1", () => multiplierDisplay.Current.Value = 0.5); + AddStep("set multiplier to 1", () => multiplierDisplay.Current.Value = 1); + AddStep("set multiplier above 1", () => multiplierDisplay.Current.Value = 1.5); + + AddSliderStep("set multiplier", 0, 2, 1d, multiplier => + { + if (multiplierDisplay != null) + multiplierDisplay.Current.Value = multiplier; + }); + } + } +} diff --git a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs new file mode 100644 index 0000000000..1d4aa9e0f5 --- /dev/null +++ b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs @@ -0,0 +1,166 @@ +// 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.Extensions.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osuTK; + +namespace osu.Game.Overlays.Mods +{ + public class DifficultyMultiplierDisplay : CompositeDrawable, IHasCurrentValue + { + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private readonly BindableNumberWithCurrent current = new BindableNumberWithCurrent(1); + + private readonly Box underlayBackground; + private readonly Box contentBackground; + private readonly FillFlowContainer multiplierFlow; + private readonly OsuSpriteText multiplierText; + + [Resolved] + private OsuColour colours { get; set; } + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + private const float height = 42; + private const float transition_duration = 200; + + public DifficultyMultiplierDisplay() + { + Height = height; + AutoSizeAxes = Axes.X; + + InternalChild = new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + CornerRadius = ModPanel.CORNER_RADIUS, + Shear = new Vector2(ModPanel.SHEAR_X, 0), + Children = new Drawable[] + { + underlayBackground = new Box + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Y, + Width = height + ModPanel.CORNER_RADIUS + }, + new GridContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Absolute, height) + }, + Content = new[] + { + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + CornerRadius = ModPanel.CORNER_RADIUS, + Children = new Drawable[] + { + contentBackground = new Box + { + RelativeSizeAxes = Axes.Both + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Horizontal = 18 }, + Shear = new Vector2(-ModPanel.SHEAR_X, 0), + Text = "Difficulty Multiplier", + Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) + } + } + }, + multiplierFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Shear = new Vector2(-ModPanel.SHEAR_X, 0), + Direction = FillDirection.Horizontal, + Spacing = new Vector2(2, 0), + Children = new Drawable[] + { + multiplierText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) + }, + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.Times, + Size = new Vector2(7), + Margin = new MarginPadding { Top = 1 } + } + } + } + } + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load() + { + contentBackground.Colour = colourProvider.Background4; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + current.BindValueChanged(_ => updateState(), true); + FinishTransforms(true); + } + + private void updateState() + { + multiplierText.Text = current.Value.ToLocalisableString(@"N1"); + + if (Current.IsDefault) + { + underlayBackground.FadeColour(colourProvider.Background3, transition_duration, Easing.OutQuint); + multiplierFlow.FadeColour(Colour4.White, transition_duration, Easing.OutQuint); + } + else + { + var backgroundColour = Current.Value < 1 + ? colours.ForModType(ModType.DifficultyReduction) + : colours.ForModType(ModType.DifficultyIncrease); + + underlayBackground.FadeColour(backgroundColour, transition_duration, Easing.OutQuint); + multiplierFlow.FadeColour(colourProvider.Background5, transition_duration, Easing.OutQuint); + } + } + } +} From c25d7a1c75f0b964ff0434f58f621a2a22645d22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Feb 2022 16:02:46 +0100 Subject: [PATCH 1025/5199] Use rolling counter for multiplier display --- .../Mods/DifficultyMultiplierDisplay.cs | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs index 1d4aa9e0f5..3fbba0b8ed 100644 --- a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs +++ b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs @@ -9,8 +9,10 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Mods; using osuTK; @@ -24,12 +26,15 @@ namespace osu.Game.Overlays.Mods set => current.Current = value; } - private readonly BindableNumberWithCurrent current = new BindableNumberWithCurrent(1); + private readonly BindableNumberWithCurrent current = new BindableNumberWithCurrent(1) + { + Precision = 0.1 + }; private readonly Box underlayBackground; private readonly Box contentBackground; private readonly FillFlowContainer multiplierFlow; - private readonly OsuSpriteText multiplierText; + private readonly MultiplierCounter multiplierCounter; [Resolved] private OsuColour colours { get; set; } @@ -107,11 +112,11 @@ namespace osu.Game.Overlays.Mods Spacing = new Vector2(2, 0), Children = new Drawable[] { - multiplierText = new OsuSpriteText + multiplierCounter = new MultiplierCounter { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) + Current = { BindTarget = Current } }, new SpriteIcon { @@ -141,12 +146,11 @@ namespace osu.Game.Overlays.Mods base.LoadComplete(); current.BindValueChanged(_ => updateState(), true); FinishTransforms(true); + multiplierCounter.StopRolling(); } private void updateState() { - multiplierText.Text = current.Value.ToLocalisableString(@"N1"); - if (Current.IsDefault) { underlayBackground.FadeColour(colourProvider.Background3, transition_duration, Easing.OutQuint); @@ -162,5 +166,17 @@ namespace osu.Game.Overlays.Mods multiplierFlow.FadeColour(colourProvider.Background5, transition_duration, Easing.OutQuint); } } + + private class MultiplierCounter : RollingCounter + { + protected override double RollingDuration => 500; + + protected override LocalisableString FormatCount(double count) => count.ToLocalisableString(@"N1"); + + protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText + { + Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) + }; + } } } From 019f4d965de8165675aae9027105b2e54730f7b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 7 Mar 2022 22:55:55 +0100 Subject: [PATCH 1026/5199] Show two decimal digits on mod multiplier rather than one --- osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs index 3fbba0b8ed..e10edb1ebc 100644 --- a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs +++ b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Mods private readonly BindableNumberWithCurrent current = new BindableNumberWithCurrent(1) { - Precision = 0.1 + Precision = 0.01 }; private readonly Box underlayBackground; @@ -43,6 +43,7 @@ namespace osu.Game.Overlays.Mods private OverlayColourProvider colourProvider { get; set; } private const float height = 42; + private const float multiplier_value_area_width = 56; private const float transition_duration = 200; public DifficultyMultiplierDisplay() @@ -64,7 +65,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Y, - Width = height + ModPanel.CORNER_RADIUS + Width = multiplier_value_area_width + ModPanel.CORNER_RADIUS }, new GridContainer { @@ -73,7 +74,7 @@ namespace osu.Game.Overlays.Mods ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Absolute, height) + new Dimension(GridSizeMode.Absolute, multiplier_value_area_width) }, Content = new[] { @@ -171,7 +172,7 @@ namespace osu.Game.Overlays.Mods { protected override double RollingDuration => 500; - protected override LocalisableString FormatCount(double count) => count.ToLocalisableString(@"N1"); + protected override LocalisableString FormatCount(double count) => count.ToLocalisableString(@"N2"); protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText { From 643f68e8447d7cf946508c7632fab0e239303eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 7 Mar 2022 23:11:20 +0100 Subject: [PATCH 1027/5199] Better annotate initial rolling counter value set --- osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs index e10edb1ebc..4fc3a904fa 100644 --- a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs +++ b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs @@ -147,7 +147,9 @@ namespace osu.Game.Overlays.Mods base.LoadComplete(); current.BindValueChanged(_ => updateState(), true); FinishTransforms(true); - multiplierCounter.StopRolling(); + // required to prevent the counter initially rolling up from 0 to 1 + // due to `Current.Value` having a nonstandard default value of 1. + multiplierCounter.SetCountWithoutRolling(Current.Value); } private void updateState() From 22a2ef42c5e855e95df6970eb3659ac6c43ee0f0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 8 Mar 2022 01:22:47 +0300 Subject: [PATCH 1028/5199] Check channel ID on message highlight using `currentDrawableChannel` --- osu.Game/Overlays/ChatOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index d642bf3bb2..39513f7963 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -338,7 +338,7 @@ namespace osu.Game.Overlays private void tryHighlightMessage(Message message) { - if (message.ChannelId != currentChannel.Value.Id) + if (message.ChannelId != currentDrawableChannel.Channel.Id) return; currentDrawableChannel.HighlightMessage(message); From 5764c53c1789acbdf37e129ecd0a490c698c589d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 8 Mar 2022 01:22:58 +0300 Subject: [PATCH 1029/5199] OpenChannelNotification -> HighlightMessageNotification --- osu.Game/Online/Chat/MessageNotifier.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index de3b9f71ad..90b6d927c7 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -136,7 +136,7 @@ namespace osu.Game.Online.Chat return Regex.IsMatch(message, $@"(^|\W)({fullName}|{underscoreName})($|\W)", RegexOptions.IgnoreCase); } - public class PrivateMessageNotification : OpenChannelNotification + public class PrivateMessageNotification : HighlightMessageNotification { public PrivateMessageNotification(Message message) : base(message) @@ -146,7 +146,7 @@ namespace osu.Game.Online.Chat } } - public class MentionNotification : OpenChannelNotification + public class MentionNotification : HighlightMessageNotification { public MentionNotification(Message message) : base(message) @@ -156,9 +156,9 @@ namespace osu.Game.Online.Chat } } - public abstract class OpenChannelNotification : SimpleNotification + public abstract class HighlightMessageNotification : SimpleNotification { - protected OpenChannelNotification(Message message) + protected HighlightMessageNotification(Message message) { this.message = message; } @@ -168,7 +168,7 @@ namespace osu.Game.Online.Chat public override bool IsImportant => false; [BackgroundDependencyLoader] - private void load(OsuColour colours, ChatOverlay chatOverlay, NotificationOverlay notificationOverlay, ChannelManager channelManager) + private void load(OsuColour colours, ChatOverlay chatOverlay, NotificationOverlay notificationOverlay) { IconBackground.Colour = colours.PurpleDark; From 7f47be4680ee16ca16d6bd59185852623306c971 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 8 Mar 2022 03:19:35 +0300 Subject: [PATCH 1030/5199] Refactor message highlighting logic to rely on a `Channel` data bindable --- .../Online/TestSceneStandAloneChatDisplay.cs | 8 +- osu.Game/Online/Chat/Channel.cs | 7 ++ osu.Game/Overlays/Chat/DrawableChannel.cs | 95 ++++++++++--------- osu.Game/Overlays/ChatOverlay.cs | 33 +------ 4 files changed, 63 insertions(+), 80 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 89ee8b03e9..c9837be934 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -152,7 +152,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("highlight first message", () => { highlighted = testChannel.Messages[0]; - chatDisplay.DrawableChannel.HighlightMessage(highlighted); + chatDisplay.DrawableChannel.Channel.HighlightedMessage.Value = highlighted; }); AddUntilStep("chat scrolled to first message", () => @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("highlight last message", () => { highlighted = testChannel.Messages[^1]; - chatDisplay.DrawableChannel.HighlightMessage(highlighted); + chatDisplay.DrawableChannel.Channel.HighlightedMessage.Value = highlighted; }); AddUntilStep("chat scrolled to last message", () => @@ -182,7 +182,7 @@ namespace osu.Game.Tests.Visual.Online AddRepeatStep("highlight other random messages", () => { highlighted = testChannel.Messages[RNG.Next(0, testChannel.Messages.Count - 1)]; - chatDisplay.DrawableChannel.HighlightMessage(highlighted); + chatDisplay.DrawableChannel.Channel.HighlightedMessage.Value = highlighted; }, 10); } @@ -193,7 +193,7 @@ namespace osu.Game.Tests.Visual.Online AddRepeatStep("highlight random messages", () => { - chatDisplay.DrawableChannel.HighlightMessage(testChannel.Messages[RNG.Next(0, testChannel.Messages.Count - 1)]); + chatDisplay.DrawableChannel.Channel.HighlightedMessage.Value = testChannel.Messages[RNG.Next(0, testChannel.Messages.Count - 1)]; }, 10); } diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index 9cbb2f37e4..a37d3084f0 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -9,6 +9,7 @@ using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Framework.Lists; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays.Chat; namespace osu.Game.Online.Chat { @@ -89,6 +90,12 @@ namespace osu.Game.Online.Chat /// public Bindable Joined = new Bindable(); + /// + /// Signals if there is a message to highlight. + /// This is automatically cleared by the associated after highlighting. + /// + public Bindable HighlightedMessage = new Bindable(); + [JsonConstructor] public Channel() { diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index cca089873f..6ff7cccd65 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -48,6 +49,8 @@ namespace osu.Game.Overlays.Chat RelativeSizeAxes = Axes.Both; } + private Bindable highlightedMessage; + [BackgroundDependencyLoader] private void load() { @@ -71,34 +74,34 @@ namespace osu.Game.Overlays.Chat } }, }; + } - newMessagesArrived(Channel.Messages); + protected override void LoadComplete() + { + base.LoadComplete(); + + addChatLines(Channel.Messages); Channel.NewMessagesArrived += newMessagesArrived; Channel.MessageRemoved += messageRemoved; Channel.PendingMessageResolved += pendingMessageResolved; - } - /// - /// Highlights a specific message in this drawable channel. - /// - /// The message to highlight. - public void HighlightMessage(Message message) - { - if (IsLoaded) - highlightMessage(message); - else - Schedule(highlightMessage, message); - } + highlightedMessage = Channel.HighlightedMessage.GetBoundCopy(); + highlightedMessage.BindValueChanged(m => + { + if (m.NewValue == null) + return; - private void highlightMessage(Message message) - { - var chatLine = chatLines.SingleOrDefault(c => c.Message == message); - if (chatLine == null) - return; + var chatLine = chatLines.SingleOrDefault(c => c.Message == m.NewValue); - scroll.ScrollIntoView(chatLine); - chatLine.ScheduleHighlight(); + if (chatLine != null) + { + scroll.ScrollIntoView(chatLine); + chatLine.ScheduleHighlight(); + } + + highlightedMessage.Value = null; + }, true); } protected override void Dispose(bool isDisposing) @@ -118,18 +121,39 @@ namespace osu.Game.Overlays.Chat Colour = colours.ChatBlue.Lighten(0.7f), }; - private void newMessagesArrived(IEnumerable newMessages) => Schedule(() => + private void newMessagesArrived(IEnumerable newMessages) => Schedule(addChatLines, newMessages); + + private void pendingMessageResolved(Message existing, Message updated) => Schedule(() => { - if (newMessages.Min(m => m.Id) < chatLines.Max(c => c.Message.Id)) + var found = chatLines.LastOrDefault(c => c.Message == existing); + + if (found != null) + { + Trace.Assert(updated.Id.HasValue, "An updated message was returned with no ID."); + + ChatLineFlow.Remove(found); + found.Message = updated; + ChatLineFlow.Add(found); + } + }); + + private void messageRemoved(Message removed) => Schedule(() => + { + chatLines.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); + }); + + private void addChatLines(IEnumerable messages) + { + if (messages.Min(m => m.Id) < chatLines.Max(c => c.Message.Id)) { // there is a case (on initial population) that we may receive past messages and need to reorder. // easiest way is to just combine messages and recreate drawables (less worrying about day separators etc.) - newMessages = newMessages.Concat(chatLines.Select(c => c.Message)).OrderBy(m => m.Id).ToList(); + messages = messages.Concat(chatLines.Select(c => c.Message)).OrderBy(m => m.Id).ToList(); ChatLineFlow.Clear(); } // Add up to last Channel.MAX_HISTORY messages - var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY)); + var displayMessages = messages.Skip(Math.Max(0, messages.Count() - Channel.MAX_HISTORY)); Message lastMessage = chatLines.LastOrDefault()?.Message; @@ -168,28 +192,9 @@ namespace osu.Game.Overlays.Chat // due to the scroll adjusts from old messages removal above, a scroll-to-end must be enforced, // to avoid making the container think the user has scrolled back up and unwantedly disable auto-scrolling. - if (newMessages.Any(m => m is LocalMessage)) + if (messages.Any(m => m is LocalMessage)) scroll.ScrollToEnd(); - }); - - private void pendingMessageResolved(Message existing, Message updated) => Schedule(() => - { - var found = chatLines.LastOrDefault(c => c.Message == existing); - - if (found != null) - { - Trace.Assert(updated.Id.HasValue, "An updated message was returned with no ID."); - - ChatLineFlow.Remove(found); - found.Message = updated; - ChatLineFlow.Add(found); - } - }); - - private void messageRemoved(Message removed) => Schedule(() => - { - chatLines.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); - }); + } private IEnumerable chatLines => ChatLineFlow.Children.OfType(); diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 39513f7963..d5e8b209f0 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; -using JetBrains.Annotations; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; @@ -47,8 +46,6 @@ namespace osu.Game.Overlays private Container currentChannelContainer; - private DrawableChannel currentDrawableChannel => currentChannelContainer.SingleOrDefault(); - private readonly List loadedChannels = new List(); private LoadingSpinner loading; @@ -252,9 +249,6 @@ namespace osu.Game.Overlays private Bindable currentChannel; - [CanBeNull] - private Message messagePendingHighlight; - private void currentChannelChanged(ValueChangedEvent e) { if (e.NewValue == null) @@ -296,24 +290,12 @@ namespace osu.Game.Overlays currentChannelContainer.Clear(false); currentChannelContainer.Add(loaded); currentChannelContainer.FadeIn(500, Easing.OutQuint); - - if (messagePendingHighlight != null) - { - tryHighlightMessage(messagePendingHighlight); - messagePendingHighlight = null; - } }); } else { currentChannelContainer.Clear(false); currentChannelContainer.Add(loaded); - - if (messagePendingHighlight != null) - { - tryHighlightMessage(messagePendingHighlight); - messagePendingHighlight = null; - } } // mark channel as read when channel switched @@ -327,21 +309,10 @@ namespace osu.Game.Overlays /// The message to highlight. public void HighlightMessage(Message message) { - if (currentDrawableChannel?.Channel.Id == message.ChannelId) - tryHighlightMessage(message); - else - { - messagePendingHighlight = message; + if (currentChannel.Value.Id != message.ChannelId) currentChannel.Value = channelManager.JoinedChannels.Single(c => c.Id == message.ChannelId); - } - } - private void tryHighlightMessage(Message message) - { - if (message.ChannelId != currentDrawableChannel.Channel.Id) - return; - - currentDrawableChannel.HighlightMessage(message); + currentChannel.Value.HighlightedMessage.Value = message; } private float startDragChatHeight; From f8e5570e4152b2ac52c859f086a73abb8ec0f3f0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 8 Mar 2022 03:20:20 +0300 Subject: [PATCH 1031/5199] Fix `Message` equality not passing on equal references --- osu.Game/Online/Chat/Message.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index dcd15f9028..3db63c3fca 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -59,7 +59,13 @@ namespace osu.Game.Online.Chat return Id.Value.CompareTo(other.Id.Value); } - public virtual bool Equals(Message other) => Id.HasValue && Id == other?.Id; + public virtual bool Equals(Message other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return Id.HasValue && Id == other?.Id; + } // ReSharper disable once ImpureMethodCallOnReadonlyValueField public override int GetHashCode() => Id.GetHashCode(); From f64586995888b1b2d05a9e586d9dd888107954c1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 8 Mar 2022 03:42:17 +0300 Subject: [PATCH 1032/5199] Update `ChannelManager.CurrentChannel` directly to handle non-loaded chat scenario `currentChannel` gets instantiated once the chat overlay is open, while `HighlightMessage` could be called while the chat overlay has never been open. This will all be rewritten with the new chat overlay design anyways, so should be fine for now. --- osu.Game/Overlays/ChatOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index d5e8b209f0..d6a1b23c46 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -309,10 +309,10 @@ namespace osu.Game.Overlays /// The message to highlight. public void HighlightMessage(Message message) { - if (currentChannel.Value.Id != message.ChannelId) - currentChannel.Value = channelManager.JoinedChannels.Single(c => c.Id == message.ChannelId); + if (channelManager.CurrentChannel.Value.Id != message.ChannelId) + channelManager.CurrentChannel.Value = channelManager.JoinedChannels.Single(c => c.Id == message.ChannelId); - currentChannel.Value.HighlightedMessage.Value = message; + channelManager.CurrentChannel.Value.HighlightedMessage.Value = message; } private float startDragChatHeight; From d74064b94b93d229e39b4c2ebc6161bb8d5ff000 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 8 Mar 2022 03:56:27 +0300 Subject: [PATCH 1033/5199] Use `Equals` instead of reference equality operator --- osu.Game/Overlays/Chat/DrawableChannel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 6ff7cccd65..a73e61c52b 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -92,7 +92,7 @@ namespace osu.Game.Overlays.Chat if (m.NewValue == null) return; - var chatLine = chatLines.SingleOrDefault(c => c.Message == m.NewValue); + var chatLine = chatLines.SingleOrDefault(c => c.Message.Equals(m.NewValue)); if (chatLine != null) { From 5e0882df8de12a0229f6d0c39910ebd6ebb1b94b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 8 Mar 2022 04:00:11 +0300 Subject: [PATCH 1034/5199] Simplify message highlighting transforms --- osu.Game/Overlays/Chat/ChatLine.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index e7b349b313..97fda80492 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Chat protected virtual float TextSize => 20; - private Container lineBackground; + private Container lineHighlightBackground; private Color4 usernameColour; @@ -146,10 +146,11 @@ namespace osu.Game.Overlays.Chat InternalChildren = new Drawable[] { - lineBackground = new Container + lineHighlightBackground = new Container { + Alpha = 0f, RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.0f), + Colour = usernameColour.Darken(1f), CornerRadius = 2f, Masking = true, Child = new Box { RelativeSizeAxes = Axes.Both } @@ -219,7 +220,7 @@ namespace osu.Game.Overlays.Chat /// /// Scheduling is required to ensure the animation doesn't play until the chat line is in view and not scrolled away. /// - public void ScheduleHighlight() => Schedule(() => lineBackground.FlashColour(usernameColour.Darken(1f).Opacity(0.5f), 1000, Easing.InQuint)); + public void ScheduleHighlight() => Schedule(() => lineHighlightBackground.FadeTo(0.5f).FadeOut(1000, Easing.InQuint)); private void updateMessageContent() { From ded84cab3ff0f5a8dd0e79639fa77b828a70cc61 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 8 Mar 2022 11:45:16 +0800 Subject: [PATCH 1035/5199] Separate randomisation and object positioning logic --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 101 +++++++++++++-------- 1 file changed, 62 insertions(+), 39 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 9b9ebcad04..1d0442438e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => "It never gets boring!"; private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast; + private static readonly Vector2 playfield_centre = OsuPlayfield.BASE_SIZE / 2; /// /// Number of previous hitobjects to be shifted together when another object is being moved. @@ -44,20 +45,15 @@ namespace osu.Game.Rulesets.Osu.Mods rng = new Random((int)Seed.Value); - RandomObjectInfo previous = null; + var randomObjects = randomiseObjects(hitObjects); - float rateOfChangeMultiplier = 0; + RandomObjectInfo previous = null; for (int i = 0; i < hitObjects.Count; i++) { var hitObject = hitObjects[i]; - var current = new RandomObjectInfo(hitObject); - - // rateOfChangeMultiplier only changes every 5 iterations in a combo - // to prevent shaky-line-shaped streams - if (hitObject.IndexInCurrentCombo % 5 == 0) - rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; + var current = randomObjects[i]; if (hitObject is Spinner) { @@ -65,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Mods continue; } - applyRandomisation(rateOfChangeMultiplier, previous, current); + applyRandomisation(getAbsoluteAngle(hitObjects, i - 1), previous, current); // Move hit objects back into the playfield if they are outside of it Vector2 shift = Vector2.Zero; @@ -101,45 +97,72 @@ namespace osu.Game.Rulesets.Osu.Mods } } + private List randomiseObjects(IEnumerable hitObjects) + { + var randomObjects = new List(); + RandomObjectInfo previous = null; + float rateOfChangeMultiplier = 0; + + foreach (OsuHitObject hitObject in hitObjects) + { + var current = new RandomObjectInfo(hitObject); + randomObjects.Add(current); + + // rateOfChangeMultiplier only changes every 5 iterations in a combo + // to prevent shaky-line-shaped streams + if (hitObject.IndexInCurrentCombo % 5 == 0) + rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; + + if (previous == null) + { + current.Distance = (float)(rng.NextDouble() * OsuPlayfield.BASE_SIZE.X / 2); + current.RelativeAngle = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI); + } + else + { + current.Distance = Vector2.Distance(previous.EndPositionOriginal, current.PositionOriginal); + + // The max. angle (relative to the angle of the vector pointing from the 2nd last to the last hit object) + // is proportional to the distance between the last and the current hit object + // to allow jumps and prevent too sharp turns during streams. + + // Allow maximum jump angle when jump distance is more than half of playfield diagonal length + current.RelativeAngle = rateOfChangeMultiplier * 2 * (float)Math.PI * Math.Min(1f, current.Distance / (playfield_diagonal * 0.5f)); + } + + previous = current; + } + + return randomObjects; + } + + private float getAbsoluteAngle(IReadOnlyList hitObjects, int hitObjectIndex) + { + if (hitObjectIndex < 0) return 0; + + Vector2 previousPosition = hitObjectIndex == 0 ? playfield_centre : hitObjects[hitObjectIndex - 1].EndPosition; + Vector2 relativePosition = hitObjects[hitObjectIndex].Position - previousPosition; + return (float)Math.Atan2(relativePosition.Y, relativePosition.X); + } + /// /// Returns the final position of the hit object /// /// Final position of the hit object - private void applyRandomisation(float rateOfChangeMultiplier, RandomObjectInfo previous, RandomObjectInfo current) + private void applyRandomisation(float previousAbsoluteAngle, RandomObjectInfo previous, RandomObjectInfo current) { - if (previous == null) - { - var playfieldSize = OsuPlayfield.BASE_SIZE; - - current.AngleRad = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI); - current.PositionRandomised = new Vector2((float)rng.NextDouble() * playfieldSize.X, (float)rng.NextDouble() * playfieldSize.Y); - - return; - } - - float distanceToPrev = Vector2.Distance(previous.EndPositionOriginal, current.PositionOriginal); - - // The max. angle (relative to the angle of the vector pointing from the 2nd last to the last hit object) - // is proportional to the distance between the last and the current hit object - // to allow jumps and prevent too sharp turns during streams. - - // Allow maximum jump angle when jump distance is more than half of playfield diagonal length - double randomAngleRad = rateOfChangeMultiplier * 2 * Math.PI * Math.Min(1f, distanceToPrev / (playfield_diagonal * 0.5f)); - - current.AngleRad = (float)randomAngleRad + previous.AngleRad; - if (current.AngleRad < 0) - current.AngleRad += 2 * (float)Math.PI; + float absoluteAngle = previousAbsoluteAngle + current.RelativeAngle; var posRelativeToPrev = new Vector2( - distanceToPrev * (float)Math.Cos(current.AngleRad), - distanceToPrev * (float)Math.Sin(current.AngleRad) + current.Distance * (float)Math.Cos(absoluteAngle), + current.Distance * (float)Math.Sin(absoluteAngle) ); - posRelativeToPrev = OsuHitObjectGenerationUtils.RotateAwayFromEdge(previous.EndPositionRandomised, posRelativeToPrev); + Vector2 lastEndPosition = previous?.EndPositionRandomised ?? playfield_centre; - current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); + posRelativeToPrev = OsuHitObjectGenerationUtils.RotateAwayFromEdge(lastEndPosition, posRelativeToPrev); - current.PositionRandomised = previous.EndPositionRandomised + posRelativeToPrev; + current.PositionRandomised = lastEndPosition + posRelativeToPrev; } /// @@ -287,7 +310,8 @@ namespace osu.Game.Rulesets.Osu.Mods private class RandomObjectInfo { - public float AngleRad { get; set; } + public float RelativeAngle { get; set; } + public float Distance { get; set; } public Vector2 PositionOriginal { get; } public Vector2 PositionRandomised { get; set; } @@ -299,7 +323,6 @@ namespace osu.Game.Rulesets.Osu.Mods { PositionRandomised = PositionOriginal = hitObject.Position; EndPositionRandomised = EndPositionOriginal = hitObject.EndPosition; - AngleRad = 0; } } } From 8cfeffc085def6f9f063b3c6d7bf75e78e864bc7 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 8 Mar 2022 11:50:30 +0800 Subject: [PATCH 1036/5199] Extract a major part of `ApplyToBeatmap` to a new method --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 103 +++++++++++---------- 1 file changed, 54 insertions(+), 49 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 1d0442438e..76183fe0aa 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -47,54 +47,7 @@ namespace osu.Game.Rulesets.Osu.Mods var randomObjects = randomiseObjects(hitObjects); - RandomObjectInfo previous = null; - - for (int i = 0; i < hitObjects.Count; i++) - { - var hitObject = hitObjects[i]; - - var current = randomObjects[i]; - - if (hitObject is Spinner) - { - previous = null; - continue; - } - - applyRandomisation(getAbsoluteAngle(hitObjects, i - 1), previous, current); - - // Move hit objects back into the playfield if they are outside of it - Vector2 shift = Vector2.Zero; - - switch (hitObject) - { - case HitCircle circle: - shift = clampHitCircleToPlayfield(circle, current); - break; - - case Slider slider: - shift = clampSliderToPlayfield(slider, current); - break; - } - - if (shift != Vector2.Zero) - { - var toBeShifted = new List(); - - for (int j = i - 1; j >= i - preceding_hitobjects_to_shift && j >= 0; j--) - { - // only shift hit circles - if (!(hitObjects[j] is HitCircle)) break; - - toBeShifted.Add(hitObjects[j]); - } - - if (toBeShifted.Count > 0) - applyDecreasingShift(toBeShifted, shift); - } - - previous = current; - } + applyRandomisation(hitObjects, randomObjects); } private List randomiseObjects(IEnumerable hitObjects) @@ -136,6 +89,58 @@ namespace osu.Game.Rulesets.Osu.Mods return randomObjects; } + private void applyRandomisation(IReadOnlyList hitObjects, IReadOnlyList randomObjects) + { + RandomObjectInfo previous = null; + + for (int i = 0; i < hitObjects.Count; i++) + { + var hitObject = hitObjects[i]; + + var current = randomObjects[i]; + + if (hitObject is Spinner) + { + previous = null; + continue; + } + + computeRandomisedPosition(getAbsoluteAngle(hitObjects, i - 1), previous, current); + + // Move hit objects back into the playfield if they are outside of it + Vector2 shift = Vector2.Zero; + + switch (hitObject) + { + case HitCircle circle: + shift = clampHitCircleToPlayfield(circle, current); + break; + + case Slider slider: + shift = clampSliderToPlayfield(slider, current); + break; + } + + if (shift != Vector2.Zero) + { + var toBeShifted = new List(); + + for (int j = i - 1; j >= i - preceding_hitobjects_to_shift && j >= 0; j--) + { + // only shift hit circles + if (!(hitObjects[j] is HitCircle)) break; + + toBeShifted.Add(hitObjects[j]); + } + + if (toBeShifted.Count > 0) + applyDecreasingShift(toBeShifted, shift); + } + + previous = current; + } + } + private float getAbsoluteAngle(IReadOnlyList hitObjects, int hitObjectIndex) { if (hitObjectIndex < 0) return 0; @@ -149,7 +154,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// Returns the final position of the hit object /// /// Final position of the hit object - private void applyRandomisation(float previousAbsoluteAngle, RandomObjectInfo previous, RandomObjectInfo current) + private void computeRandomisedPosition(float previousAbsoluteAngle, RandomObjectInfo previous, RandomObjectInfo current) { float absoluteAngle = previousAbsoluteAngle + current.RelativeAngle; From ae1c65c38dfd48e57be107f31da80ac7be62db5f Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 8 Mar 2022 12:07:10 +0800 Subject: [PATCH 1037/5199] Add xmldoc --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 39 ++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 76183fe0aa..babc4311ee 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -50,6 +50,11 @@ namespace osu.Game.Rulesets.Osu.Mods applyRandomisation(hitObjects, randomObjects); } + /// + /// Randomise the position of each hit object and return a list of s describing how each hit object should be placed. + /// + /// A list of s to have their positions randomised. + /// A list of s describing how each hit object should be placed. private List randomiseObjects(IEnumerable hitObjects) { var randomObjects = new List(); @@ -89,6 +94,11 @@ namespace osu.Game.Rulesets.Osu.Mods return randomObjects; } + /// + /// Reposition the hit objects according to the information in . + /// + /// The hit objects to be repositioned. + /// A list of describing how each hit object should be placed. private void applyRandomisation(IReadOnlyList hitObjects, IReadOnlyList randomObjects) { RandomObjectInfo previous = null; @@ -141,6 +151,12 @@ namespace osu.Game.Rulesets.Osu.Mods } } + /// + /// Get the absolute angle of a vector pointing from the previous hit object to the one denoted by . + /// + /// A list of all hit objects in the beatmap. + /// The hit object that the vector should point to. + /// The absolute angle of the aforementioned vector. private float getAbsoluteAngle(IReadOnlyList hitObjects, int hitObjectIndex) { if (hitObjectIndex < 0) return 0; @@ -151,9 +167,11 @@ namespace osu.Game.Rulesets.Osu.Mods } /// - /// Returns the final position of the hit object + /// Compute the randomised position of a hit object while attempting to keep it inside the playfield. /// - /// Final position of the hit object + /// The direction of movement of the player's cursor before it starts to approach the current hit object. + /// The representing the hit object immediately preceding the current one. + /// The representing the hit object to have the randomised position computed for. private void computeRandomisedPosition(float previousAbsoluteAngle, RandomObjectInfo previous, RandomObjectInfo current) { float absoluteAngle = previousAbsoluteAngle + current.RelativeAngle; @@ -315,7 +333,24 @@ namespace osu.Game.Rulesets.Osu.Mods private class RandomObjectInfo { + /// + /// The jump angle from the previous hit object to this one, relative to the previous hit object's jump angle. + /// + /// + /// of the first hit object in a beatmap represents the absolute angle from playfield center to the object. + /// + /// + /// If is 0, the player's cursor doesn't need to change its direction of movement when passing + /// the previous object to reach this one. + /// public float RelativeAngle { get; set; } + + /// + /// The jump distance from the previous hit object to this one. + /// + /// + /// of the first hit object in a beatmap is relative to the playfield center. + /// public float Distance { get; set; } public Vector2 PositionOriginal { get; } From 2a55c5e02e6ddebe06ece01d067d037fc057a785 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Mar 2022 14:42:59 +0900 Subject: [PATCH 1038/5199] Add extension method to detect and isolate realm collection-level changes --- .../RealmSubscriptionRegistrationTests.cs | 59 ++++++++++++++++++- osu.Game/Database/RealmExtensions.cs | 11 ++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs index 363a189f6e..d99bcc092d 100644 --- a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs +++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs @@ -1,25 +1,80 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Tests.Resources; using Realms; -#nullable enable - namespace osu.Game.Tests.Database { [TestFixture] public class RealmSubscriptionRegistrationTests : RealmTest { + [Test] + public void TestSubscriptionCollectionAndPropertyChanges() + { + int collectionChanges = 0; + int propertyChanges = 0; + + ChangeSet? lastChanges = null; + + RunTestWithRealm((realm, _) => + { + var registration = realm.RegisterForNotifications(r => r.All(), onChanged); + + realm.Run(r => r.Refresh()); + + realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); + realm.Run(r => r.Refresh()); + + Assert.That(collectionChanges, Is.EqualTo(1)); + Assert.That(propertyChanges, Is.EqualTo(0)); + Assert.That(lastChanges?.InsertedIndices, Has.One.Items); + Assert.That(lastChanges?.ModifiedIndices, Is.Empty); + Assert.That(lastChanges?.NewModifiedIndices, Is.Empty); + + realm.Write(r => r.All().First().Beatmaps.First().CountdownOffset = 5); + realm.Run(r => r.Refresh()); + + Assert.That(collectionChanges, Is.EqualTo(1)); + Assert.That(propertyChanges, Is.EqualTo(1)); + Assert.That(lastChanges?.InsertedIndices, Is.Empty); + Assert.That(lastChanges?.ModifiedIndices, Has.One.Items); + Assert.That(lastChanges?.NewModifiedIndices, Has.One.Items); + + registration.Dispose(); + }); + + void onChanged(IRealmCollection sender, ChangeSet? changes, Exception error) + { + lastChanges = changes; + + if (changes == null) + return; + + if (changes.HasCollectionChanges()) + { + Interlocked.Increment(ref collectionChanges); + } + else + { + Interlocked.Increment(ref propertyChanges); + } + } + } + [Test] public void TestSubscriptionWithAsyncWrite() { diff --git a/osu.Game/Database/RealmExtensions.cs b/osu.Game/Database/RealmExtensions.cs index e6f3dba39f..551b84f7b6 100644 --- a/osu.Game/Database/RealmExtensions.cs +++ b/osu.Game/Database/RealmExtensions.cs @@ -4,6 +4,8 @@ using System; using Realms; +#nullable enable + namespace osu.Game.Database { public static class RealmExtensions @@ -22,5 +24,14 @@ namespace osu.Game.Database transaction.Commit(); return result; } + + /// + /// Whether the provided change set has changes to the top level collection. + /// + /// + /// Realm subscriptions fire on both collection and property changes (including *all* nested properties). + /// Quite often we only care about changes at a collection level. This can be used to guard and early-return when no such changes are in a callback. + /// + public static bool HasCollectionChanges(this ChangeSet changes) => changes.InsertedIndices.Length > 0 || changes.DeletedIndices.Length > 0 || changes.Moves.Length > 0; } } From 622ec531300f0369f37e711936b2dcec56601b72 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Mar 2022 14:43:14 +0900 Subject: [PATCH 1039/5199] Fix `BeatmapLeaderboard` refreshing on unrelated changes to a beatmap --- osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index eb0addd377..8d1654eb1d 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -191,6 +191,11 @@ namespace osu.Game.Screens.Select.Leaderboards if (cancellationToken.IsCancellationRequested) return; + // This subscription may fire from changes to linked beatmaps, which we don't care about. + // It's currently not possible for a score to be modified after insertion, so we can safely ignore callbacks with only modifications. + if (changes?.HasCollectionChanges() == false) + return; + var scores = sender.AsEnumerable(); if (filterMods && !mods.Value.Any()) From 589a40ca2d48cb14da5e859c3d6669337eb0910c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Mar 2022 17:58:37 +0900 Subject: [PATCH 1040/5199] Add `EnumMember` naming to `HitResult` to allow for correct json serialisation --- osu.Game/Rulesets/Scoring/HitResult.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index a254f9b760..68a03fa061 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -5,6 +5,7 @@ using System; using System.ComponentModel; using System.Diagnostics; using System.Linq; +using System.Runtime.Serialization; using osu.Framework.Utils; namespace osu.Game.Rulesets.Scoring @@ -16,6 +17,7 @@ namespace osu.Game.Rulesets.Scoring /// Indicates that the object has not been judged yet. /// [Description(@"")] + [EnumMember(Value = "none")] [Order(14)] None, @@ -27,32 +29,39 @@ namespace osu.Game.Rulesets.Scoring /// "too far in the future). It should also define when a forced miss should be triggered (as a result of no user input in time). /// [Description(@"Miss")] + [EnumMember(Value = "miss")] [Order(5)] Miss, [Description(@"Meh")] + [EnumMember(Value = "meh")] [Order(4)] Meh, [Description(@"OK")] + [EnumMember(Value = "ok")] [Order(3)] Ok, [Description(@"Good")] + [EnumMember(Value = "good")] [Order(2)] Good, [Description(@"Great")] + [EnumMember(Value = "great")] [Order(1)] Great, [Description(@"Perfect")] + [EnumMember(Value = "perfect")] [Order(0)] Perfect, /// /// Indicates small tick miss. /// + [EnumMember(Value = "small_tick_miss")] [Order(11)] SmallTickMiss, @@ -60,12 +69,14 @@ namespace osu.Game.Rulesets.Scoring /// Indicates a small tick hit. /// [Description(@"S Tick")] + [EnumMember(Value = "small_tick_hit")] [Order(7)] SmallTickHit, /// /// Indicates a large tick miss. /// + [EnumMember(Value = "large_tick_miss")] [Order(10)] LargeTickMiss, @@ -73,6 +84,7 @@ namespace osu.Game.Rulesets.Scoring /// Indicates a large tick hit. /// [Description(@"L Tick")] + [EnumMember(Value = "large_tick_hit")] [Order(6)] LargeTickHit, @@ -80,6 +92,7 @@ namespace osu.Game.Rulesets.Scoring /// Indicates a small bonus. /// [Description("S Bonus")] + [EnumMember(Value = "small_bonus")] [Order(9)] SmallBonus, @@ -87,18 +100,21 @@ namespace osu.Game.Rulesets.Scoring /// Indicates a large bonus. /// [Description("L Bonus")] + [EnumMember(Value = "large_bonus")] [Order(8)] LargeBonus, /// /// Indicates a miss that should be ignored for scoring purposes. /// + [EnumMember(Value = "ignore_miss")] [Order(13)] IgnoreMiss, /// /// Indicates a hit that should be ignored for scoring purposes. /// + [EnumMember(Value = "ignore_hit")] [Order(12)] IgnoreHit, } From 9a347af5c79e5f57decfa6be8a420a7f4ef6872e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Mar 2022 17:58:52 +0900 Subject: [PATCH 1041/5199] Add test coverage of `SubmittableScore` serialisation to (roughly) match spec --- .../TestSubmittableScoreJsonSerialization.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 osu.Game.Tests/Online/TestSubmittableScoreJsonSerialization.cs diff --git a/osu.Game.Tests/Online/TestSubmittableScoreJsonSerialization.cs b/osu.Game.Tests/Online/TestSubmittableScoreJsonSerialization.cs new file mode 100644 index 0000000000..662660bce4 --- /dev/null +++ b/osu.Game.Tests/Online/TestSubmittableScoreJsonSerialization.cs @@ -0,0 +1,40 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using NUnit.Framework; +using osu.Game.IO.Serialization; +using osu.Game.Online.Solo; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Online +{ + /// + /// Basic testing to ensure our attribute-based naming is correctly working. + /// + [TestFixture] + public class TestSubmittableScoreJsonSerialization + { + [Test] + public void TestScoreSerialisationViaExtensionMethod() + { + var score = new SubmittableScore(TestResources.CreateTestScoreInfo()); + + string serialised = score.Serialize(); + + Assert.That(serialised, Contains.Substring("large_tick_hit")); + Assert.That(serialised, Contains.Substring("\"rank\": \"S\"")); + } + + [Test] + public void TestScoreSerialisationWithoutSettings() + { + var score = new SubmittableScore(TestResources.CreateTestScoreInfo()); + + string serialised = JsonConvert.SerializeObject(score); + + Assert.That(serialised, Contains.Substring("large_tick_hit")); + Assert.That(serialised, Contains.Substring("\"rank\":\"S\"")); + } + } +} From 0718a55ad0d86e9c5715f67499b6f8b8e2b9f5d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Mar 2022 16:06:42 +0900 Subject: [PATCH 1042/5199] Add flow to allow recovery after running an older release (with a different realm database version) As brought up in https://github.com/ppy/osu/discussions/17148 --- osu.Game/Database/RealmAccess.cs | 86 +++++++++++++++++++++++++++++--- 1 file changed, 79 insertions(+), 7 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index af7c485c57..e7ca045702 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -105,6 +105,8 @@ namespace osu.Game.Database public Realm Realm => ensureUpdateRealm(); + private const string realm_extension = @".realm"; + private Realm ensureUpdateRealm() { if (isSendingNotificationResetEvents) @@ -149,11 +151,18 @@ namespace osu.Game.Database Filename = filename; - const string realm_extension = @".realm"; - if (!Filename.EndsWith(realm_extension, StringComparison.Ordinal)) Filename += realm_extension; + string newerVersionFilename = $"{Filename.Replace(realm_extension, string.Empty)}_newer_version{realm_extension}"; + + // Attempt to recover a newer database version if available. + if (storage.Exists(newerVersionFilename)) + { + Logger.Log(@"A newer realm database has been found, attempting recovery...", LoggingTarget.Database); + attemptRecoverFromFile(newerVersionFilename); + } + try { // This method triggers the first `getRealmInstance` call, which will implicitly run realm migrations and bring the schema up-to-date. @@ -161,15 +170,78 @@ namespace osu.Game.Database } catch (Exception e) { - Logger.Error(e, "Realm startup failed with unrecoverable error; starting with a fresh database. A backup of your database has been made."); + // See https://github.com/realm/realm-core/blob/master/src%2Frealm%2Fobject-store%2Fobject_store.cpp#L1016-L1022 + // This is the best way we can detect a schema version downgrade. + if (e.Message.StartsWith(@"Provided schema version", StringComparison.Ordinal)) + { + Logger.Error(e, "Your local database is too new to work with this version of osu!. Please close osu! and install the latest release to recover your data."); - CreateBackup($"{Filename.Replace(realm_extension, string.Empty)}_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}_corrupt{realm_extension}"); - storage.Delete(Filename); + // If a newer version database already exists, don't backup again. We can presume that the first backup is the one we care about. + if (!storage.Exists(newerVersionFilename)) + CreateBackup(newerVersionFilename); + + storage.Delete(Filename); + } + else + { + Logger.Error(e, "Realm startup failed with unrecoverable error; starting with a fresh database. A backup of your database has been made."); + CreateBackup($"{Filename.Replace(realm_extension, string.Empty)}_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}_corrupt{realm_extension}"); + storage.Delete(Filename); + } cleanupPendingDeletions(); } } + private void attemptRecoverFromFile(string recoveryFilename) + { + Logger.Log($@"Performing recovery from {recoveryFilename}", LoggingTarget.Database); + + // First check the user hasn't started to use the database that is in place.. + try + { + using (var realm = Realm.GetInstance(getConfiguration())) + { + if (realm.All().Any()) + { + Logger.Log(@"Recovery aborted as the existing database has scores set already.", LoggingTarget.Database); + Logger.Log(@"To perform recovery, delete client.realm while osu! is not running.", LoggingTarget.Database); + return; + } + } + } + catch + { + // Even if reading the in place database fails, still attempt to recover. + } + + // Then check that the database we are about to attempt recovery can actually be recovered on this version.. + try + { + using (var realm = Realm.GetInstance(getConfiguration(recoveryFilename))) + { + // Don't need to do anything, just check that opening the realm works correctly. + } + } + catch + { + Logger.Log(@"Recovery aborted as the newer version could not be loaded by this osu! version.", LoggingTarget.Database); + return; + } + + // For extra safety, also store the temporarily-used database which we are about to replace. + CreateBackup($"{Filename.Replace(realm_extension, string.Empty)}_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}_newer_version_before_recovery{realm_extension}"); + + storage.Delete(Filename); + + using (var inputStream = storage.GetStream(recoveryFilename)) + using (var outputStream = storage.GetStream(Filename, FileAccess.Write, FileMode.Create)) + inputStream.CopyTo(outputStream); + + storage.Delete(recoveryFilename); + Logger.Log(@"Recovery complete!", LoggingTarget.Database); + } + private void cleanupPendingDeletions() { using (var realm = getRealmInstance()) @@ -476,7 +548,7 @@ namespace osu.Game.Database } } - private RealmConfiguration getConfiguration() + private RealmConfiguration getConfiguration(string? filename = null) { // This is currently the only usage of temporary files at the osu! side. // If we use the temporary folder in more situations in the future, this should be moved to a higher level (helper method or OsuGameBase). @@ -484,7 +556,7 @@ namespace osu.Game.Database if (!Directory.Exists(tempPathLocation)) Directory.CreateDirectory(tempPathLocation); - return new RealmConfiguration(storage.GetFullPath(Filename, true)) + return new RealmConfiguration(storage.GetFullPath(filename ?? Filename, true)) { SchemaVersion = schema_version, MigrationCallback = onMigration, From a172fc6cb8eb212a17a586279656ceacfac4efd5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Mar 2022 18:19:11 +0900 Subject: [PATCH 1043/5199] Add IsBasic() and IsTick() extensions on HitResult --- osu.Game/Rulesets/Scoring/HitResult.cs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index a254f9b760..ddddf70cc6 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -133,6 +133,30 @@ namespace osu.Game.Rulesets.Scoring public static bool AffectsAccuracy(this HitResult result) => IsScorable(result) && !IsBonus(result); + /// + /// Whether a is a non-tick and non-bonus result. + /// + public static bool IsBasic(this HitResult result) + => IsScorable(result) && !IsTick(result) && !IsBonus(result); + + /// + /// Whether a should be counted as a tick. + /// + public static bool IsTick(this HitResult result) + { + switch (result) + { + case HitResult.LargeTickHit: + case HitResult.LargeTickMiss: + case HitResult.SmallTickHit: + case HitResult.SmallTickMiss: + return true; + + default: + return false; + } + } + /// /// Whether a should be counted as bonus score. /// From 6565c95b176a8808cd3d8fd1987e31bdbae3952a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Mar 2022 18:19:54 +0900 Subject: [PATCH 1044/5199] Remove unused variable --- osu.Game/Database/RealmAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index e7ca045702..f0d4011ab8 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -218,7 +218,7 @@ namespace osu.Game.Database // Then check that the database we are about to attempt recovery can actually be recovered on this version.. try { - using (var realm = Realm.GetInstance(getConfiguration(recoveryFilename))) + using (Realm.GetInstance(getConfiguration(recoveryFilename))) { // Don't need to do anything, just check that opening the realm works correctly. } From b0f40d9e4562898a488cdd3b643553aa7099a4e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Mar 2022 18:38:24 +0900 Subject: [PATCH 1045/5199] Remove `user` from `SubmittableScore` This wasn't being used by osu-web, and included far too much unnecessary data. Of note, `pp` and `ruleset_id` are also not strictly required, but there's no harm in sending them so I've left them be for now. --- osu.Game/Online/Solo/SubmittableScore.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Online/Solo/SubmittableScore.cs b/osu.Game/Online/Solo/SubmittableScore.cs index 4e4dae5157..9b6da1844a 100644 --- a/osu.Game/Online/Solo/SubmittableScore.cs +++ b/osu.Game/Online/Solo/SubmittableScore.cs @@ -46,9 +46,6 @@ namespace osu.Game.Online.Solo [JsonProperty("mods")] public APIMod[] Mods { get; set; } - [JsonProperty("user")] - public APIUser User { get; set; } - [JsonProperty("statistics")] public Dictionary Statistics { get; set; } @@ -67,7 +64,6 @@ namespace osu.Game.Online.Solo RulesetID = score.RulesetID; Passed = score.Passed; Mods = score.APIMods; - User = score.User; Statistics = score.Statistics; } } From f5cd9676353c9bdcd6c6a30d0069612e36528cc2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Mar 2022 19:07:39 +0900 Subject: [PATCH 1046/5199] Fix scores not being recalculated in beatmap listing --- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 3 ++- osu.Game/Scoring/ScoreManager.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 00dedc892b..6f07b20049 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -79,7 +79,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores var beatmapInfo = new BeatmapInfo { MaxCombo = apiBeatmap.MaxCombo, - Status = apiBeatmap.Status + Status = apiBeatmap.Status, + MD5Hash = apiBeatmap.MD5Hash }; scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.CreateScoreInfo(rulesets, beatmapInfo)).ToArray(), loadCancellationSource.Token) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 963c4a77ca..87345a06fb 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -132,7 +132,7 @@ namespace osu.Game.Scoring public async Task GetTotalScoreAsync([NotNull] ScoreInfo score, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default) { // TODO: This is required for playlist aggregate scores. They should likely not be getting here in the first place. - if (string.IsNullOrEmpty(score.BeatmapInfo.Hash)) + if (string.IsNullOrEmpty(score.BeatmapInfo.MD5Hash)) return score.TotalScore; int beatmapMaxCombo; From daa42584f43214c8e0939b512bf00d59b785704b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Mar 2022 19:36:08 +0900 Subject: [PATCH 1047/5199] Fix feedback from realm writes causing offset slider to jump around --- .../Play/PlayerSettings/BeatmapOffsetControl.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index c00b2f56dc..d4babe3e9f 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using System.Diagnostics; using System.Linq; @@ -23,8 +25,6 @@ using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; using osuTK; -#nullable enable - namespace osu.Game.Screens.Play.PlayerSettings { public class BeatmapOffsetControl : CompositeDrawable @@ -122,7 +122,16 @@ namespace osu.Game.Screens.Play.PlayerSettings beatmapOffsetSubscription = realm.SubscribeToPropertyChanged( r => r.Find(beatmap.Value.BeatmapInfo.ID)?.UserSettings, settings => settings.Offset, - val => Current.Value = val); + val => + { + // At the point we reach here, it's not guaranteed that all realm writes have taken place (there may be some in-flight). + // We are only aware of writes that originated from our own flow, so if we do see one that's active we can avoid handling the feedback value arriving. + if (realmWriteTask == null) + Current.Value = val; + + // we can also mark any in-flight write that is managed locally as "seen" and start handling any incoming changes again. + realmWriteTask = null; + }); Current.BindValueChanged(currentChanged); } From 960b6528cad6209f20180f84586940ec9d8cc79f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Mar 2022 19:36:23 +0900 Subject: [PATCH 1048/5199] Ensure the value used during realm async write is the same as whe compared for equality --- .../Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index d4babe3e9f..9e280fdca2 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -167,10 +167,12 @@ namespace osu.Game.Screens.Play.PlayerSettings if (settings == null) // only the case for tests. return; - if (settings.Offset == Current.Value) + double val = Current.Value; + + if (settings.Offset == val) return; - settings.Offset = Current.Value; + settings.Offset = val; }); } } From d13a66a96cb147a7dba1fdbfa1be75f8533440e7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Mar 2022 20:11:56 +0900 Subject: [PATCH 1049/5199] Rework test scene by only relying on OnlineID --- .../TestScenePlaylistsResultsScreen.cs | 76 +++++-------------- 1 file changed, 21 insertions(+), 55 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 161624413d..67894bab38 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -33,16 +33,25 @@ namespace osu.Game.Tests.Visual.Playlists private TestResultsScreen resultsScreen; - private int currentScoreId; + private int lowestScoreId; // Score ID of the lowest score in the list. + private int highestScoreId; // Score ID of the highest score in the list. + private bool requestComplete; private int totalCount; + private ScoreInfo userScore; [SetUp] public void Setup() => Schedule(() => { - currentScoreId = 1; + lowestScoreId = 1; + highestScoreId = 1; requestComplete = false; totalCount = 0; + + userScore = TestResources.CreateTestScoreInfo(); + userScore.TotalScore = 0; + userScore.Statistics = new Dictionary(); + bindHandler(); // beatmap is required to be an actual beatmap so the scores can get their scores correctly calculated for standardised scoring. @@ -53,15 +62,7 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestShowWithUserScore() { - ScoreInfo userScore = null; - - AddStep("bind user score info handler", () => - { - userScore = TestResources.CreateTestScoreInfo(); - userScore.OnlineID = currentScoreId++; - - bindHandler(userScore: userScore); - }); + AddStep("bind user score info handler", () => bindHandler(userScore: userScore)); createResults(() => userScore); @@ -81,15 +82,7 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestShowUserScoreWithDelay() { - ScoreInfo userScore = null; - - AddStep("bind user score info handler", () => - { - userScore = TestResources.CreateTestScoreInfo(); - userScore.OnlineID = currentScoreId++; - - bindHandler(true, userScore); - }); + AddStep("bind user score info handler", () => bindHandler(true, userScore)); createResults(() => userScore); @@ -124,7 +117,7 @@ namespace osu.Game.Tests.Visual.Playlists AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible); waitForDisplay(); - AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType().Count() == beforePanelCount + scores_per_result); + AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType().Count() >= beforePanelCount + scores_per_result); AddAssert("right loading spinner hidden", () => resultsScreen.RightSpinner.State.Value == Visibility.Hidden); } } @@ -132,15 +125,7 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestFetchWhenScrolledToTheLeft() { - ScoreInfo userScore = null; - - AddStep("bind user score info handler", () => - { - userScore = TestResources.CreateTestScoreInfo(); - userScore.OnlineID = currentScoreId++; - - bindHandler(userScore: userScore); - }); + AddStep("bind user score info handler", () => bindHandler(userScore: userScore)); createResults(() => userScore); @@ -156,7 +141,7 @@ namespace osu.Game.Tests.Visual.Playlists AddAssert("left loading spinner shown", () => resultsScreen.LeftSpinner.State.Value == Visibility.Visible); waitForDisplay(); - AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType().Count() == beforePanelCount + scores_per_result); + AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType().Count() >= beforePanelCount + scores_per_result); AddAssert("left loading spinner hidden", () => resultsScreen.LeftSpinner.State.Value == Visibility.Hidden); } } @@ -245,16 +230,13 @@ namespace osu.Game.Tests.Visual.Playlists { var multiplayerUserScore = new MultiplayerScore { - ID = (int)(userScore.OnlineID > 0 ? userScore.OnlineID : currentScoreId++), + ID = highestScoreId, Accuracy = userScore.Accuracy, - EndedAt = userScore.Date, Passed = userScore.Passed, Rank = userScore.Rank, Position = real_user_position, MaxCombo = userScore.MaxCombo, - TotalScore = userScore.TotalScore, User = userScore.User, - Statistics = userScore.Statistics, ScoresAround = new MultiplayerScoresAround { Higher = new MultiplayerScores(), @@ -268,38 +250,32 @@ namespace osu.Game.Tests.Visual.Playlists { multiplayerUserScore.ScoresAround.Lower.Scores.Add(new MultiplayerScore { - ID = currentScoreId++, + ID = --highestScoreId, Accuracy = userScore.Accuracy, - EndedAt = userScore.Date, Passed = true, Rank = userScore.Rank, MaxCombo = userScore.MaxCombo, - TotalScore = userScore.TotalScore - i, User = new APIUser { Id = 2, Username = $"peppy{i}", CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }, - Statistics = userScore.Statistics }); multiplayerUserScore.ScoresAround.Higher.Scores.Add(new MultiplayerScore { - ID = currentScoreId++, + ID = ++lowestScoreId, Accuracy = userScore.Accuracy, - EndedAt = userScore.Date, Passed = true, Rank = userScore.Rank, MaxCombo = userScore.MaxCombo, - TotalScore = userScore.TotalScore + i, User = new APIUser { Id = 2, Username = $"peppy{i}", CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }, - Statistics = userScore.Statistics }); totalCount += 2; @@ -315,33 +291,23 @@ namespace osu.Game.Tests.Visual.Playlists { var result = new IndexedMultiplayerScores(); - long startTotalScore = req.Cursor?.Properties["total_score"].ToObject() ?? 1000000; string sort = req.IndexParams?.Properties["sort"].ToObject() ?? "score_desc"; for (int i = 1; i <= scores_per_result; i++) { result.Scores.Add(new MultiplayerScore { - ID = currentScoreId++, + ID = sort == "score_asc" ? --highestScoreId : ++lowestScoreId, Accuracy = 1, - EndedAt = DateTimeOffset.Now, Passed = true, Rank = ScoreRank.X, MaxCombo = 1000, - TotalScore = startTotalScore + (sort == "score_asc" ? i : -i), User = new APIUser { Id = 2, Username = $"peppy{i}", CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }, - Statistics = new Dictionary - { - { HitResult.Miss, 1 }, - { HitResult.Meh, 50 }, - { HitResult.Good, 100 }, - { HitResult.Great, 300 } - } }); totalCount++; @@ -367,7 +333,7 @@ namespace osu.Game.Tests.Visual.Playlists { Properties = new Dictionary { - { "sort", JToken.FromObject(scores.Scores[^1].TotalScore > scores.Scores[^2].TotalScore ? "score_asc" : "score_desc") } + { "sort", JToken.FromObject(scores.Scores[^1].ID > scores.Scores[^2].ID ? "score_asc" : "score_desc") } } }; } From c867068cae9fbf589327ed4616939b1e91c1a0a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Mar 2022 21:15:14 +0900 Subject: [PATCH 1050/5199] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 5b26b8f36e..c2788f9a48 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d86fbc693e..14cb751958 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index c37692f0d8..e485c69096 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 2c382bd1d9acdad5fa0e9ba4d80f996c36d5044e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Mar 2022 11:26:07 +0900 Subject: [PATCH 1051/5199] Rename GetImmediateScore() as overload of GetScore() --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 2 +- .../PerformanceBreakdownCalculator.cs | 2 +- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 46 +++++++++---------- .../HUD/MultiplayerGameplayLeaderboard.cs | 2 +- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index c6e7988543..e96ff1f7f1 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -300,7 +300,7 @@ namespace osu.Game.Tests.Rulesets.Scoring HitObjects = { new TestHitObject(result) } }); - Assert.That(scoreProcessor.GetImmediateScore(ScoringMode.Standardised, result.AffectsCombo() ? 1 : 0, statistic), Is.EqualTo(expectedScore).Within(0.5d)); + Assert.That(scoreProcessor.GetScore(ScoringMode.Standardised, result.AffectsCombo() ? 1 : 0, statistic), Is.EqualTo(expectedScore).Within(0.5d)); } private class TestJudgement : Judgement diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs index 3d384f5914..dfb8066b28 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Difficulty ScoreProcessor scoreProcessor = ruleset.CreateScoreProcessor(); scoreProcessor.HighestCombo.Value = perfectPlay.MaxCombo; scoreProcessor.Mods.Value = perfectPlay.Mods; - perfectPlay.TotalScore = (long)scoreProcessor.GetImmediateScore(ScoringMode.Standardised, perfectPlay.MaxCombo, statistics); + perfectPlay.TotalScore = (long)scoreProcessor.GetScore(ScoringMode.Standardised, perfectPlay.MaxCombo, statistics); // compute rank achieved // default to SS, then adjust the rank with mods diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index d5a5aa4592..af9b4db5ca 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -218,6 +218,29 @@ namespace osu.Game.Rulesets.Scoring scoreResultCounts); } + /// + /// Given a minimal set of inputs, return the computed score for the tracked beatmap / mods combination, at the current point in time. + /// + /// The to compute the total score in. + /// The maximum combo achievable in the beatmap. + /// Statistics to be used for calculating accuracy, bonus score, etc. + /// The computed score for provided inputs. + public double GetScore(ScoringMode mode, int maxCombo, Dictionary statistics) + { + // calculate base score from statistics pairs + int computedBaseScore = 0; + + foreach (var pair in statistics) + { + if (!pair.Key.AffectsAccuracy()) + continue; + + computedBaseScore += Judgement.ToNumericResult(pair.Key) * pair.Value; + } + + return GetScore(mode, calculateAccuracyRatio(computedBaseScore), calculateComboRatio(maxCombo), statistics); + } + /// /// Computes the total score. /// @@ -250,29 +273,6 @@ namespace osu.Game.Rulesets.Scoring } } - /// - /// Given a minimal set of inputs, return the computed score for the tracked beatmap / mods combination, at the current point in time. - /// - /// The to compute the total score in. - /// The maximum combo achievable in the beatmap. - /// Statistics to be used for calculating accuracy, bonus score, etc. - /// The computed score for provided inputs. - public double GetImmediateScore(ScoringMode mode, int maxCombo, Dictionary statistics) - { - // calculate base score from statistics pairs - int computedBaseScore = 0; - - foreach (var pair in statistics) - { - if (!pair.Key.AffectsAccuracy()) - continue; - - computedBaseScore += Judgement.ToNumericResult(pair.Key) * pair.Value; - } - - return GetScore(mode, calculateAccuracyRatio(computedBaseScore), calculateComboRatio(maxCombo), statistics); - } - /// /// Get the accuracy fraction for the provided base score. /// diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index 83c73e5a70..0516e00b8b 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -244,7 +244,7 @@ namespace osu.Game.Screens.Play.HUD { var header = frame.Header; - Score.Value = ScoreProcessor.GetImmediateScore(ScoringMode.Value, header.MaxCombo, header.Statistics); + Score.Value = ScoreProcessor.GetScore(ScoringMode.Value, header.MaxCombo, header.Statistics); Accuracy.Value = header.Accuracy; CurrentCombo.Value = header.Combo; } From 6654977a7b73c19d2250b030e519055c8be7cbae Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Mar 2022 11:28:14 +0900 Subject: [PATCH 1052/5199] Add GetScore() overload with total hitobject count --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index af9b4db5ca..67b78d5230 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -250,6 +250,17 @@ namespace osu.Game.Rulesets.Scoring /// Any statistics to be factored in. /// The total score. public double GetScore(ScoringMode mode, double accuracyRatio, double comboRatio, Dictionary statistics) + { + int totalHitObjects = statistics.Where(k => k.Key >= HitResult.Miss && k.Key <= HitResult.Perfect).Sum(k => k.Value); + + // If there are no hitobjects then the beatmap can be composed of only ticks or spinners, so ensure we don't multiply by 0 at all times. + if (totalHitObjects == 0) + totalHitObjects = 1; + + return GetScore(mode, accuracyRatio, comboRatio, statistics, totalHitObjects); + } + + public double GetScore(ScoringMode mode, double accuracyRatio, double comboRatio, Dictionary statistics, int totalHitObjects) { switch (mode) { @@ -260,12 +271,6 @@ namespace osu.Game.Rulesets.Scoring return (max_score * (accuracyScore + comboScore) + getBonusScore(statistics)) * scoreMultiplier; case ScoringMode.Classic: - int totalHitObjects = statistics.Where(k => k.Key >= HitResult.Miss && k.Key <= HitResult.Perfect).Sum(k => k.Value); - - // If there are no hitobjects then the beatmap can be composed of only ticks or spinners, so ensure we don't multiply by 0 at all times. - if (totalHitObjects == 0) - totalHitObjects = 1; - // This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring. // The invariant is important to ensure that scores don't get re-ordered on leaderboards between the two scoring modes. double scaledStandardised = GetScore(ScoringMode.Standardised, accuracyRatio, comboRatio, statistics) / max_score; From 5b6b8d1fa95c32f10500b838b95cf116dbe59abf Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Mar 2022 11:29:47 +0900 Subject: [PATCH 1053/5199] Remove GetStandardisedScore() proxy method --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 14 +++----------- .../OnlinePlay/Playlists/PlaylistsPlayer.cs | 3 ++- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 67b78d5230..422b29601a 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -207,16 +207,10 @@ namespace osu.Game.Rulesets.Scoring if (rollingMaxBaseScore != 0) Accuracy.Value = calculateAccuracyRatio(baseScore, true); - TotalScore.Value = getScore(Mode.Value); + TotalScore.Value = GetScore(Mode.Value); } - private double getScore(ScoringMode mode) - { - return GetScore(mode, - calculateAccuracyRatio(baseScore), - calculateComboRatio(HighestCombo.Value), - scoreResultCounts); - } + public double GetScore(ScoringMode mode) => GetScore(mode, calculateAccuracyRatio(baseScore), calculateComboRatio(HighestCombo.Value), scoreResultCounts); /// /// Given a minimal set of inputs, return the computed score for the tracked beatmap / mods combination, at the current point in time. @@ -316,8 +310,6 @@ namespace osu.Game.Rulesets.Scoring public int GetStatistic(HitResult result) => scoreResultCounts.GetValueOrDefault(result); - public double GetStandardisedScore() => getScore(ScoringMode.Standardised); - /// /// Resets this ScoreProcessor to a default state. /// @@ -351,7 +343,7 @@ namespace osu.Game.Rulesets.Scoring /// public virtual void PopulateScore(ScoreInfo score) { - score.TotalScore = (long)Math.Round(GetStandardisedScore()); + score.TotalScore = (long)Math.Round(GetScore(ScoringMode.Standardised)); score.Combo = Combo.Value; score.MaxCombo = HighestCombo.Value; score.Accuracy = Accuracy.Value; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 7efeae8129..77eaf133c7 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -11,6 +11,7 @@ using osu.Framework.Screens; using osu.Game.Extensions; using osu.Game.Online.Rooms; using osu.Game.Rulesets; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; @@ -64,7 +65,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { await base.PrepareScoreForResultsAsync(score).ConfigureAwait(false); - Score.ScoreInfo.TotalScore = (int)Math.Round(ScoreProcessor.GetStandardisedScore()); + Score.ScoreInfo.TotalScore = (int)Math.Round(ScoreProcessor.GetScore(ScoringMode.Standardised)); } protected override void Dispose(bool isDisposing) From a8e99f1a9521bdc528aaf66817183bc5f1e6b085 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Mar 2022 11:45:04 +0900 Subject: [PATCH 1054/5199] Calculate classic score using total basic hitobject count --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 93 +++++++++++++++------ 1 file changed, 66 insertions(+), 27 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 422b29601a..b0656e270e 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Utils; +using osu.Game.Beatmaps; using osu.Game.Extensions; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; @@ -86,9 +87,22 @@ namespace osu.Game.Rulesets.Scoring /// private double maxBaseScore; + /// + /// The maximum number of basic (non-tick and non-bonus) hitobjects. + /// + private int maxBasicHitObjects; + + /// + /// Maximum for a normal hit (i.e. not tick/bonus) for this ruleset. + /// Only populated via . + /// + private HitResult? maxBasicHitResult; + private double rollingMaxBaseScore; private double baseScore; + private int basicHitObjects; + private readonly Dictionary scoreResultCounts = new Dictionary(); private readonly List hitEvents = new List(); private HitObject lastHitObject; @@ -122,8 +136,6 @@ namespace osu.Game.Rulesets.Scoring }; } - private readonly Dictionary scoreResultCounts = new Dictionary(); - protected sealed override void ApplyResultInternal(JudgementResult result) { result.ComboAtJudgement = Combo.Value; @@ -160,6 +172,9 @@ namespace osu.Game.Rulesets.Scoring rollingMaxBaseScore += result.Judgement.MaxNumericResult; } + if (result.Type.IsBasic()) + basicHitObjects++; + hitEvents.Add(CreateHitEvent(result)); lastHitObject = result.HitObject; @@ -195,6 +210,9 @@ namespace osu.Game.Rulesets.Scoring rollingMaxBaseScore -= result.Judgement.MaxNumericResult; } + if (result.Type.IsBasic()) + basicHitObjects--; + Debug.Assert(hitEvents.Count > 0); lastHitObject = hitEvents[^1].LastHitObject; hitEvents.RemoveAt(hitEvents.Count - 1); @@ -210,15 +228,30 @@ namespace osu.Game.Rulesets.Scoring TotalScore.Value = GetScore(Mode.Value); } - public double GetScore(ScoringMode mode) => GetScore(mode, calculateAccuracyRatio(baseScore), calculateComboRatio(HighestCombo.Value), scoreResultCounts); + /// + /// Computes the total score from judgements that have been applied to this + /// through and . + /// + /// + /// Requires an to have been applied via before use. + /// + /// The to represent the score as. + /// The total score in the given . + public double GetScore(ScoringMode mode) + { + return GetScore(mode, calculateAccuracyRatio(baseScore), calculateComboRatio(HighestCombo.Value), scoreResultCounts); + } /// - /// Given a minimal set of inputs, return the computed score for the tracked beatmap / mods combination, at the current point in time. + /// Computes the total score from judgements counts in a statistics dictionary. /// - /// The to compute the total score in. + /// + /// Requires an to have been applied via before use. + /// + /// The to represent the score as. /// The maximum combo achievable in the beatmap. - /// Statistics to be used for calculating accuracy, bonus score, etc. - /// The computed score for provided inputs. + /// The statistics to compute the score for. + /// The total score computed from judgements in the statistics dictionary. public double GetScore(ScoringMode mode, int maxCombo, Dictionary statistics) { // calculate base score from statistics pairs @@ -236,25 +269,34 @@ namespace osu.Game.Rulesets.Scoring } /// - /// Computes the total score. + /// Computes the total score from given scoring component ratios. /// - /// The to compute the total score in. + /// + /// Requires an to have been applied via before use. + /// + /// The to represent the score as. /// The accuracy percentage achieved by the player. - /// The proportion of the max combo achieved by the player. - /// Any statistics to be factored in. - /// The total score. + /// The portion of the max combo achieved by the player. + /// Any additional statistics to be factored in. + /// The total score computed from the given scoring component ratios. public double GetScore(ScoringMode mode, double accuracyRatio, double comboRatio, Dictionary statistics) { - int totalHitObjects = statistics.Where(k => k.Key >= HitResult.Miss && k.Key <= HitResult.Perfect).Sum(k => k.Value); - - // If there are no hitobjects then the beatmap can be composed of only ticks or spinners, so ensure we don't multiply by 0 at all times. - if (totalHitObjects == 0) - totalHitObjects = 1; - - return GetScore(mode, accuracyRatio, comboRatio, statistics, totalHitObjects); + return GetScore(mode, accuracyRatio, comboRatio, maxBasicHitObjects, statistics); } - public double GetScore(ScoringMode mode, double accuracyRatio, double comboRatio, Dictionary statistics, int totalHitObjects) + /// + /// Computes the total score from given scoring component ratios. + /// + /// + /// Does not require an to have been applied via before use. + /// + /// The to represent the score as. + /// The accuracy percentage achieved by the player. + /// The portion of the max combo achieved by the player. + /// Any additional statistics to be factored in. + /// The total number of basic (non-tick and non-bonus) hitobjects in the beatmap. + /// The total score computed from the given scoring component ratios. + public double GetScore(ScoringMode mode, double accuracyRatio, double comboRatio, int totalBasicHitObjects, Dictionary statistics) { switch (mode) { @@ -268,7 +310,7 @@ namespace osu.Game.Rulesets.Scoring // This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring. // The invariant is important to ensure that scores don't get re-ordered on leaderboards between the two scoring modes. double scaledStandardised = GetScore(ScoringMode.Standardised, accuracyRatio, comboRatio, statistics) / max_score; - return Math.Pow(scaledStandardised * totalHitObjects, 2) * 36; + return Math.Pow(scaledStandardised * totalBasicHitObjects, 2) * 36; } } @@ -326,10 +368,12 @@ namespace osu.Game.Rulesets.Scoring { maxAchievableCombo = HighestCombo.Value; maxBaseScore = baseScore; + maxBasicHitObjects = basicHitObjects; } baseScore = 0; rollingMaxBaseScore = 0; + basicHitObjects = 0; TotalScore.Value = 0; Accuracy.Value = 1; @@ -355,11 +399,6 @@ namespace osu.Game.Rulesets.Scoring score.HitEvents = hitEvents; } - /// - /// Maximum for a normal hit (i.e. not tick/bonus) for this ruleset. Only populated via . - /// - private HitResult? maxNormalResult; - public override void ResetFromReplayFrame(Ruleset ruleset, ReplayFrame frame) { base.ResetFromReplayFrame(ruleset, frame); @@ -394,7 +433,7 @@ namespace osu.Game.Rulesets.Scoring break; default: - maxResult = maxNormalResult ??= ruleset.GetHitResults().OrderByDescending(kvp => Judgement.ToNumericResult(kvp.result)).First().result; + maxResult = maxBasicHitResult ??= ruleset.GetHitResults().OrderByDescending(kvp => Judgement.ToNumericResult(kvp.result)).First().result; break; } From f1c40bd9eda6dfda1bcd6669c548b8e078c8396f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Mar 2022 12:57:59 +0900 Subject: [PATCH 1055/5199] Rework GetScore() method signatures + implementations Rename legacy-facing overload to mention as much --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 25 ++- .../Visual/Gameplay/TestSceneFailJudgement.cs | 2 +- .../TestSceneMultiSpectatorLeaderboard.cs | 5 +- ...TestSceneMultiplayerGameplayLeaderboard.cs | 2 +- ...ceneMultiplayerGameplayLeaderboardTeams.cs | 2 +- .../PerformanceBreakdownCalculator.cs | 3 +- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 190 ++++++++++-------- osu.Game/Scoring/ScoreManager.cs | 15 +- .../Multiplayer/MultiplayerPlayer.cs | 2 +- .../Spectate/MultiSpectatorLeaderboard.cs | 11 +- .../Spectate/MultiSpectatorScreen.cs | 2 +- .../OnlinePlay/Playlists/PlaylistsPlayer.cs | 2 +- .../HUD/MultiplayerGameplayLeaderboard.cs | 25 ++- 13 files changed, 173 insertions(+), 113 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index e96ff1f7f1..d327f4844e 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -6,11 +6,15 @@ using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Rulesets.Scoring @@ -300,7 +304,26 @@ namespace osu.Game.Tests.Rulesets.Scoring HitObjects = { new TestHitObject(result) } }); - Assert.That(scoreProcessor.GetScore(ScoringMode.Standardised, result.AffectsCombo() ? 1 : 0, statistic), Is.EqualTo(expectedScore).Within(0.5d)); + Assert.That(scoreProcessor.ComputeFinalScore(ScoringMode.Standardised, new ScoreInfo + { + Ruleset = new TestRuleset().RulesetInfo, + MaxCombo = result.AffectsCombo() ? 1 : 0, + Statistics = statistic + }), Is.EqualTo(expectedScore).Within(0.5d)); + } + + private class TestRuleset : Ruleset + { + public override IEnumerable GetModsFor(ModType type) => throw new System.NotImplementedException(); + + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new System.NotImplementedException(); + + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new System.NotImplementedException(); + + public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => throw new System.NotImplementedException(); + + public override string Description { get; } + public override string ShortName { get; } } private class TestJudgement : Judgement diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs index 6430c29dfa..79d7bb366d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for multiple judgements", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits > 1); AddAssert("total number of results == 1", () => { - var score = new ScoreInfo(); + var score = new ScoreInfo { Ruleset = Ruleset.Value }; ((FailPlayer)Player).ScoreProcessor.PopulateScore(score); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 42bb99de24..f57a54d84c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -46,7 +46,10 @@ namespace osu.Game.Tests.Visual.Multiplayer var scoreProcessor = new OsuScoreProcessor(); scoreProcessor.ApplyBeatmap(playable); - LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, clocks.Keys.Select(id => new MultiplayerRoomUser(id)).ToArray()) { Expanded = { Value = true } }, Add); + LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(Ruleset.Value, scoreProcessor, clocks.Keys.Select(id => new MultiplayerRoomUser(id)).ToArray()) + { + Expanded = { Value = true } + }, Add); }); AddUntilStep("wait for load", () => leaderboard.IsLoaded); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index cfac5da4ff..bcd4474876 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Multiplayer scoreProcessor.ApplyBeatmap(playableBeatmap); - LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, multiplayerUsers.ToArray()) + LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(Ruleset.Value, scoreProcessor, multiplayerUsers.ToArray()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs index f751b162d1..7f5aced925 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Multiplayer scoreProcessor.ApplyBeatmap(playableBeatmap); - LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, multiplayerUsers.ToArray()) + LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(Ruleset.Value, scoreProcessor, multiplayerUsers.ToArray()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs index dfb8066b28..fc38ed0298 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -63,9 +63,8 @@ namespace osu.Game.Rulesets.Difficulty // calculate total score ScoreProcessor scoreProcessor = ruleset.CreateScoreProcessor(); - scoreProcessor.HighestCombo.Value = perfectPlay.MaxCombo; scoreProcessor.Mods.Value = perfectPlay.Mods; - perfectPlay.TotalScore = (long)scoreProcessor.GetScore(ScoringMode.Standardised, perfectPlay.MaxCombo, statistics); + perfectPlay.TotalScore = (long)scoreProcessor.ComputeFinalScore(ScoringMode.Standardised, perfectPlay); // compute rank achieved // default to SS, then adjust the rank with mods diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index b0656e270e..3004464df7 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -7,7 +7,6 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Utils; -using osu.Game.Beatmaps; using osu.Game.Extensions; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; @@ -93,10 +92,10 @@ namespace osu.Game.Rulesets.Scoring private int maxBasicHitObjects; /// - /// Maximum for a normal hit (i.e. not tick/bonus) for this ruleset. - /// Only populated via . + /// The maximum of a basic (non-tick and non-bonus) hitobject. + /// Only populated via or . /// - private HitResult? maxBasicHitResult; + private HitResult? maxBasicResult; private double rollingMaxBaseScore; private double baseScore; @@ -222,81 +221,110 @@ namespace osu.Game.Rulesets.Scoring private void updateScore() { - if (rollingMaxBaseScore != 0) - Accuracy.Value = calculateAccuracyRatio(baseScore, true); + double rollingAccuracyRatio = rollingMaxBaseScore > 0 ? baseScore / rollingMaxBaseScore : 1; + double accuracyRatio = maxBaseScore > 0 ? baseScore / maxBaseScore : 1; + double comboRatio = maxAchievableCombo > 0 ? (double)HighestCombo.Value / maxAchievableCombo : 1; - TotalScore.Value = GetScore(Mode.Value); + Accuracy.Value = rollingAccuracyRatio; + TotalScore.Value = ComputeScore(Mode.Value, accuracyRatio, comboRatio, getBonusScore(scoreResultCounts), maxBasicHitObjects); } /// - /// Computes the total score from judgements that have been applied to this - /// through and . + /// Computes the total score of a given finalised . This should be used when a score is known to be complete. /// /// - /// Requires an to have been applied via before use. + /// Does not require to have been called before use. /// /// The to represent the score as. + /// The to compute the total score of. /// The total score in the given . - public double GetScore(ScoringMode mode) + public double ComputeFinalScore(ScoringMode mode, ScoreInfo scoreInfo) { - return GetScore(mode, calculateAccuracyRatio(baseScore), calculateComboRatio(HighestCombo.Value), scoreResultCounts); + extractFromStatistics(scoreInfo.Ruleset.CreateInstance(), + scoreInfo.Statistics, + out double extractedBaseScore, + out double extractedMaxBaseScore, + out int extractedMaxCombo, + out int extractedBasicHitObjects); + + double accuracyRatio = extractedMaxBaseScore > 0 ? extractedBaseScore / extractedMaxBaseScore : 1; + double comboRatio = extractedMaxCombo > 0 ? (double)scoreInfo.MaxCombo / extractedMaxCombo : 1; + + return ComputeScore(mode, accuracyRatio, comboRatio, getBonusScore(scoreInfo.Statistics), extractedBasicHitObjects); } /// - /// Computes the total score from judgements counts in a statistics dictionary. + /// Computes the total score of a partially-completed . This should be used when it is unknown whether a score is complete. /// /// - /// Requires an to have been applied via before use. + /// Requires to have been called before use. /// /// The to represent the score as. - /// The maximum combo achievable in the beatmap. - /// The statistics to compute the score for. - /// The total score computed from judgements in the statistics dictionary. - public double GetScore(ScoringMode mode, int maxCombo, Dictionary statistics) + /// The to compute the total score of. + /// The total score in the given . + public double ComputePartialScore(ScoringMode mode, ScoreInfo scoreInfo) { - // calculate base score from statistics pairs - int computedBaseScore = 0; + extractFromStatistics(scoreInfo.Ruleset.CreateInstance(), + scoreInfo.Statistics, + out double extractedBaseScore, + out _, + out _, + out _); - foreach (var pair in statistics) + double accuracyRatio = maxBaseScore > 0 ? extractedBaseScore / maxBaseScore : 1; + double comboRatio = maxAchievableCombo > 0 ? (double)scoreInfo.MaxCombo / maxAchievableCombo : 1; + + return ComputeScore(mode, accuracyRatio, comboRatio, getBonusScore(scoreInfo.Statistics), maxBasicHitObjects); + } + + /// + /// Computes the total score of a given with a given custom max achievable combo. + /// + /// + /// This is useful for processing legacy scores in which the maximum achievable combo can be more accurately determined via external means (e.g. database values or difficulty calculation). + ///

Does not require to have been called before use.

+ ///
+ /// The to represent the score as. + /// The to compute the total score of. + /// The maximum achievable combo for the provided beatmap. + /// The total score in the given . + public double ComputeFinalLegacyScore(ScoringMode mode, ScoreInfo scoreInfo, int maxAchievableCombo) + { + double accuracyRatio = scoreInfo.Accuracy; + double comboRatio = maxAchievableCombo > 0 ? (double)scoreInfo.MaxCombo / maxAchievableCombo : 1; + + // For legacy osu!mania scores, a full-GREAT score has 100% accuracy. If combined with a full-combo, the score becomes indistinguishable from a full-PERFECT score. + // To get around this, the accuracy ratio is always recalculated based on the hit statistics rather than trusting the score. + // Note: This cannot be applied universally to all legacy scores, as some rulesets (e.g. catch) group multiple judgements together. + if (scoreInfo.IsLegacyScore && scoreInfo.Ruleset.OnlineID == 3) { - if (!pair.Key.AffectsAccuracy()) - continue; + extractFromStatistics( + scoreInfo.Ruleset.CreateInstance(), + scoreInfo.Statistics, + out double computedBaseScore, + out double computedMaxBaseScore, + out _, + out _); - computedBaseScore += Judgement.ToNumericResult(pair.Key) * pair.Value; + if (computedMaxBaseScore > 0) + accuracyRatio = computedBaseScore / computedMaxBaseScore; } - return GetScore(mode, calculateAccuracyRatio(computedBaseScore), calculateComboRatio(maxCombo), statistics); + int computedBasicHitObjects = scoreInfo.Statistics.Where(kvp => kvp.Key.IsBasic()).Select(kvp => kvp.Value).Sum(); + + return ComputeScore(mode, accuracyRatio, comboRatio, getBonusScore(scoreInfo.Statistics), computedBasicHitObjects); } /// - /// Computes the total score from given scoring component ratios. + /// Computes the total score from individual scoring components. /// - /// - /// Requires an to have been applied via before use. - /// /// The to represent the score as. /// The accuracy percentage achieved by the player. /// The portion of the max combo achieved by the player. - /// Any additional statistics to be factored in. - /// The total score computed from the given scoring component ratios. - public double GetScore(ScoringMode mode, double accuracyRatio, double comboRatio, Dictionary statistics) - { - return GetScore(mode, accuracyRatio, comboRatio, maxBasicHitObjects, statistics); - } - - /// - /// Computes the total score from given scoring component ratios. - /// - /// - /// Does not require an to have been applied via before use. - /// - /// The to represent the score as. - /// The accuracy percentage achieved by the player. - /// The portion of the max combo achieved by the player. - /// Any additional statistics to be factored in. + /// The total bonus score. /// The total number of basic (non-tick and non-bonus) hitobjects in the beatmap. /// The total score computed from the given scoring component ratios. - public double GetScore(ScoringMode mode, double accuracyRatio, double comboRatio, int totalBasicHitObjects, Dictionary statistics) + public double ComputeScore(ScoringMode mode, double accuracyRatio, double comboRatio, double bonusScore, int totalBasicHitObjects) { switch (mode) { @@ -304,33 +332,22 @@ namespace osu.Game.Rulesets.Scoring case ScoringMode.Standardised: double accuracyScore = accuracyPortion * accuracyRatio; double comboScore = comboPortion * comboRatio; - return (max_score * (accuracyScore + comboScore) + getBonusScore(statistics)) * scoreMultiplier; + return (max_score * (accuracyScore + comboScore) + bonusScore) * scoreMultiplier; case ScoringMode.Classic: // This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring. // The invariant is important to ensure that scores don't get re-ordered on leaderboards between the two scoring modes. - double scaledStandardised = GetScore(ScoringMode.Standardised, accuracyRatio, comboRatio, statistics) / max_score; - return Math.Pow(scaledStandardised * totalBasicHitObjects, 2) * 36; + double scaledStandardised = ComputeScore(ScoringMode.Standardised, accuracyRatio, comboRatio, bonusScore, totalBasicHitObjects) / max_score; + return Math.Pow(scaledStandardised * Math.Max(1, totalBasicHitObjects), 2) * 36; } } /// - /// Get the accuracy fraction for the provided base score. + /// Calculates the total bonus score from score statistics. /// - /// The score to be used for accuracy calculation. - /// Whether the rolling base score should be used (ie. for the current point in time based on Apply/Reverted results). - /// The computed accuracy. - private double calculateAccuracyRatio(double baseScore, bool preferRolling = false) - { - if (preferRolling && rollingMaxBaseScore != 0) - return baseScore / rollingMaxBaseScore; - - return maxBaseScore > 0 ? baseScore / maxBaseScore : 1; - } - - private double calculateComboRatio(int maxCombo) => maxAchievableCombo > 0 ? (double)maxCombo / maxAchievableCombo : 1; - - private double getBonusScore(Dictionary statistics) + /// The score statistics. + /// The total bonus score. + private double getBonusScore(IReadOnlyDictionary statistics) => statistics.GetValueOrDefault(HitResult.SmallBonus) * Judgement.SMALL_BONUS_SCORE + statistics.GetValueOrDefault(HitResult.LargeBonus) * Judgement.LARGE_BONUS_SCORE; @@ -387,16 +404,17 @@ namespace osu.Game.Rulesets.Scoring ///
public virtual void PopulateScore(ScoreInfo score) { - score.TotalScore = (long)Math.Round(GetScore(ScoringMode.Standardised)); score.Combo = Combo.Value; score.MaxCombo = HighestCombo.Value; score.Accuracy = Accuracy.Value; score.Rank = Rank.Value; + score.HitEvents = hitEvents; foreach (var result in HitResultExtensions.ALL_TYPES) score.Statistics[result] = GetStatistic(result); - score.HitEvents = hitEvents; + // Populate total score after everything else. + score.TotalScore = (long)Math.Round(ComputeFinalScore(ScoringMode.Standardised, score)); } public override void ResetFromReplayFrame(Ruleset ruleset, ReplayFrame frame) @@ -406,11 +424,26 @@ namespace osu.Game.Rulesets.Scoring if (frame.Header == null) return; - baseScore = 0; - rollingMaxBaseScore = 0; + extractFromStatistics(ruleset, frame.Header.Statistics, out baseScore, out rollingMaxBaseScore, out _, out _); HighestCombo.Value = frame.Header.MaxCombo; - foreach ((HitResult result, int count) in frame.Header.Statistics) + scoreResultCounts.Clear(); + scoreResultCounts.AddRange(frame.Header.Statistics); + + updateScore(); + + OnResetFromReplayFrame?.Invoke(); + } + + private void extractFromStatistics(Ruleset ruleset, IReadOnlyDictionary statistics, out double baseScore, out double maxBaseScore, out int maxCombo, + out int basicHitObjects) + { + baseScore = 0; + maxBaseScore = 0; + maxCombo = 0; + basicHitObjects = 0; + + foreach ((HitResult result, int count) in statistics) { // Bonus scores are counted separately directly from the statistics dictionary later on. if (!result.IsScorable() || result.IsBonus()) @@ -433,20 +466,19 @@ namespace osu.Game.Rulesets.Scoring break; default: - maxResult = maxBasicHitResult ??= ruleset.GetHitResults().OrderByDescending(kvp => Judgement.ToNumericResult(kvp.result)).First().result; + maxResult = maxBasicResult ??= ruleset.GetHitResults().OrderByDescending(kvp => Judgement.ToNumericResult(kvp.result)).First().result; break; } baseScore += count * Judgement.ToNumericResult(result); - rollingMaxBaseScore += count * Judgement.ToNumericResult(maxResult); + maxBaseScore += count * Judgement.ToNumericResult(maxResult); + + if (result.AffectsCombo()) + maxCombo += count; + + if (result.IsBasic()) + basicHitObjects += count; } - - scoreResultCounts.Clear(); - scoreResultCounts.AddRange(frame.Header.Statistics); - - updateScore(); - - OnResetFromReplayFrame?.Invoke(); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 963c4a77ca..6e49e3c9af 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -18,7 +18,6 @@ using osu.Game.Database; using osu.Game.IO.Archives; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; -using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; namespace osu.Game.Scoring @@ -136,21 +135,9 @@ namespace osu.Game.Scoring return score.TotalScore; int beatmapMaxCombo; - double accuracy = score.Accuracy; if (score.IsLegacyScore) { - if (score.RulesetID == 3) - { - // In osu!stable, a full-GREAT score has 100% accuracy in mania. Along with a full combo, the score becomes indistinguishable from a full-PERFECT score. - // To get around this, recalculate accuracy based on the hit statistics. - // Note: This cannot be applied universally to all legacy scores, as some rulesets (e.g. catch) group multiple judgements together. - double maxBaseScore = score.Statistics.Select(kvp => kvp.Value).Sum() * Judgement.ToNumericResult(HitResult.Perfect); - double baseScore = score.Statistics.Select(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value).Sum(); - if (maxBaseScore > 0) - accuracy = baseScore / maxBaseScore; - } - // This score is guaranteed to be an osu!stable score. // The combo must be determined through either the beatmap's max combo value or the difficulty calculator, as lazer's scoring has changed and the score statistics cannot be used. if (score.BeatmapInfo.MaxCombo != null) @@ -184,7 +171,7 @@ namespace osu.Game.Scoring var scoreProcessor = ruleset.CreateScoreProcessor(); scoreProcessor.Mods.Value = score.Mods; - return (long)Math.Round(scoreProcessor.GetScore(mode, accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics)); + return (long)Math.Round(scoreProcessor.ComputeFinalLegacyScore(mode, score, beatmapMaxCombo)); } /// diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index bd2f49a9e5..d8fb448437 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -76,7 +76,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer }); // todo: this should be implemented via a custom HUD implementation, and correctly masked to the main content area. - LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(ScoreProcessor, users), l => + LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(GameplayState.Ruleset.RulesetInfo, ScoreProcessor, users), l => { if (!LoadedBeatmapSuccessfully) return; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs index 1614828a78..4545913db8 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs @@ -5,6 +5,7 @@ using System; using JetBrains.Annotations; using osu.Framework.Timing; using osu.Game.Online.Multiplayer; +using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; @@ -12,8 +13,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { public class MultiSpectatorLeaderboard : MultiplayerGameplayLeaderboard { - public MultiSpectatorLeaderboard([NotNull] ScoreProcessor scoreProcessor, MultiplayerRoomUser[] users) - : base(scoreProcessor, users) + public MultiSpectatorLeaderboard(RulesetInfo ruleset, [NotNull] ScoreProcessor scoreProcessor, MultiplayerRoomUser[] users) + : base(ruleset, scoreProcessor, users) { } @@ -33,7 +34,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate ((SpectatingTrackedUserData)data).Clock = null; } - protected override TrackedUserData CreateUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor) => new SpectatingTrackedUserData(user, scoreProcessor); + protected override TrackedUserData CreateUserData(MultiplayerRoomUser user, RulesetInfo ruleset, ScoreProcessor scoreProcessor) => new SpectatingTrackedUserData(user, ruleset, scoreProcessor); protected override void Update() { @@ -48,8 +49,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate [CanBeNull] public IClock Clock; - public SpectatingTrackedUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor) - : base(user, scoreProcessor) + public SpectatingTrackedUserData(MultiplayerRoomUser user, RulesetInfo ruleset, ScoreProcessor scoreProcessor) + : base(user, ruleset, scoreProcessor) { } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 3bb76c4a76..6747b8fc66 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -133,7 +133,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate var scoreProcessor = Ruleset.Value.CreateInstance().CreateScoreProcessor(); scoreProcessor.ApplyBeatmap(playableBeatmap); - LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, users) + LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(Ruleset.Value, scoreProcessor, users) { Expanded = { Value = true }, }, l => diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 77eaf133c7..5a7762a3d8 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -65,7 +65,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { await base.PrepareScoreForResultsAsync(score).ConfigureAwait(false); - Score.ScoreInfo.TotalScore = (int)Math.Round(ScoreProcessor.GetScore(ScoringMode.Standardised)); + Score.ScoreInfo.TotalScore = (int)Math.Round(ScoreProcessor.ComputeFinalScore(ScoringMode.Standardised, Score.ScoreInfo)); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index 0516e00b8b..71998622ef 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -17,7 +17,9 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; using osu.Game.Online.Spectator; +using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD @@ -41,6 +43,7 @@ namespace osu.Game.Screens.Play.HUD [Resolved] private UserLookupCache userLookupCache { get; set; } + private readonly RulesetInfo ruleset; private readonly ScoreProcessor scoreProcessor; private readonly MultiplayerRoomUser[] playingUsers; private Bindable scoringMode; @@ -52,11 +55,13 @@ namespace osu.Game.Screens.Play.HUD /// /// Construct a new leaderboard. /// + /// The ruleset. /// A score processor instance to handle score calculation for scores of users in the match. /// IDs of all users in this match. - public MultiplayerGameplayLeaderboard(ScoreProcessor scoreProcessor, MultiplayerRoomUser[] users) + public MultiplayerGameplayLeaderboard(RulesetInfo ruleset, ScoreProcessor scoreProcessor, MultiplayerRoomUser[] users) { // todo: this will eventually need to be created per user to support different mod combinations. + this.ruleset = ruleset; this.scoreProcessor = scoreProcessor; playingUsers = users; @@ -69,7 +74,7 @@ namespace osu.Game.Screens.Play.HUD foreach (var user in playingUsers) { - var trackedUser = CreateUserData(user, scoreProcessor); + var trackedUser = CreateUserData(user, ruleset, scoreProcessor); trackedUser.ScoringMode.BindTo(scoringMode); UserScores[user.UserID] = trackedUser; @@ -119,7 +124,7 @@ namespace osu.Game.Screens.Play.HUD spectatorClient.OnNewFrames += handleIncomingFrames; } - protected virtual TrackedUserData CreateUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor) => new TrackedUserData(user, scoreProcessor); + protected virtual TrackedUserData CreateUserData(MultiplayerRoomUser user, RulesetInfo ruleset, ScoreProcessor scoreProcessor) => new TrackedUserData(user, ruleset, scoreProcessor); protected override GameplayLeaderboardScore CreateLeaderboardScoreDrawable(APIUser user, bool isTracked) { @@ -222,8 +227,12 @@ namespace osu.Game.Screens.Play.HUD public int? Team => (User.MatchState as TeamVersusUserState)?.TeamID; - public TrackedUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor) + private readonly RulesetInfo ruleset; + + public TrackedUserData(MultiplayerRoomUser user, RulesetInfo ruleset, ScoreProcessor scoreProcessor) { + this.ruleset = ruleset; + User = user; ScoreProcessor = scoreProcessor; @@ -244,7 +253,13 @@ namespace osu.Game.Screens.Play.HUD { var header = frame.Header; - Score.Value = ScoreProcessor.GetScore(ScoringMode.Value, header.MaxCombo, header.Statistics); + Score.Value = ScoreProcessor.ComputePartialScore(ScoringMode.Value, new ScoreInfo + { + Ruleset = ruleset, + MaxCombo = header.MaxCombo, + Statistics = header.Statistics + }); + Accuracy.Value = header.Accuracy; CurrentCombo.Value = header.Combo; } From 6fd8b4d8918f9095c9651cf88047c5467c10b06e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Mar 2022 22:14:58 +0900 Subject: [PATCH 1056/5199] Safeguard method against invalid invocation --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 3004464df7..5e11d4da72 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Utils; +using osu.Game.Beatmaps; using osu.Game.Extensions; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; @@ -100,6 +101,7 @@ namespace osu.Game.Rulesets.Scoring private double rollingMaxBaseScore; private double baseScore; private int basicHitObjects; + private bool beatmapApplied; private readonly Dictionary scoreResultCounts = new Dictionary(); private readonly List hitEvents = new List(); @@ -135,6 +137,12 @@ namespace osu.Game.Rulesets.Scoring }; } + public override void ApplyBeatmap(IBeatmap beatmap) + { + base.ApplyBeatmap(beatmap); + beatmapApplied = true; + } + protected sealed override void ApplyResultInternal(JudgementResult result) { result.ComboAtJudgement = Combo.Value; @@ -264,6 +272,9 @@ namespace osu.Game.Rulesets.Scoring /// The total score in the given . public double ComputePartialScore(ScoringMode mode, ScoreInfo scoreInfo) { + if (!beatmapApplied) + throw new InvalidOperationException($"Cannot compute partial score without calling {nameof(ApplyBeatmap)}."); + extractFromStatistics(scoreInfo.Ruleset.CreateInstance(), scoreInfo.Statistics, out double extractedBaseScore, From 512536f5fe3465b329c1ccb6f7a2a7e23d8d6a65 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Mar 2022 23:25:51 +0900 Subject: [PATCH 1057/5199] Fix unconditional null in `Equals` implementation --- osu.Game/Online/Chat/Message.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index 3db63c3fca..ad004e2881 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -64,7 +64,7 @@ namespace osu.Game.Online.Chat if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return Id.HasValue && Id == other?.Id; + return Id.HasValue && Id == other.Id; } // ReSharper disable once ImpureMethodCallOnReadonlyValueField From e9a2d235420c87ef3c45d06e82997fc7248a426f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 8 Mar 2022 23:35:35 +0900 Subject: [PATCH 1058/5199] Fix score order related test failure --- .../Visual/Ranking/TestSceneScorePanelList.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index e786b85f78..c65587d433 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -2,12 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Models; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Tests.Resources; @@ -208,13 +210,19 @@ namespace osu.Game.Tests.Visual.Ranking public void TestKeyboardNavigation() { var lowestScore = TestResources.CreateTestScoreInfo(); - lowestScore.MaxCombo = 100; + lowestScore.OnlineID = 3; + lowestScore.TotalScore = 0; + lowestScore.Statistics = new Dictionary(); var middleScore = TestResources.CreateTestScoreInfo(); - middleScore.MaxCombo = 200; + middleScore.OnlineID = 2; + middleScore.TotalScore = 0; + middleScore.Statistics = new Dictionary(); var highestScore = TestResources.CreateTestScoreInfo(); - highestScore.MaxCombo = 300; + highestScore.OnlineID = 1; + highestScore.TotalScore = 0; + highestScore.Statistics = new Dictionary(); createListStep(() => new ScorePanelList()); From 94d5e2f2642a531a08e5f31c932bf9be779d2afb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 9 Mar 2022 00:48:03 +0900 Subject: [PATCH 1059/5199] Fix test failure ripple through entire TestScene --- .../Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 35992a0b38..5490c1e056 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -43,8 +43,6 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); - - beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); } [SetUp] @@ -52,8 +50,10 @@ namespace osu.Game.Tests.Visual.Multiplayer { AvailabilityTracker.SelectedItem.BindTo(selectedItem); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); importedSet = beatmaps.GetAllUsableBeatmapSets().First(); Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); + selectedItem.Value = new PlaylistItem(Beatmap.Value.BeatmapInfo) { RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID From 729af28a64c866f5eed51e9335ec151f5238d4b6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 9 Mar 2022 00:48:12 +0900 Subject: [PATCH 1060/5199] Fix intermittent test failure --- .../Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 5490c1e056..c86d5e482a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -92,16 +92,16 @@ namespace osu.Game.Tests.Visual.Multiplayer { OsuButton readyButton = null; - AddAssert("ensure ready button enabled", () => + AddUntilStep("ensure ready button enabled", () => { readyButton = button.ChildrenOfType().Single(); return readyButton.Enabled.Value; }); AddStep("delete beatmap", () => beatmaps.Delete(importedSet)); - AddAssert("ready button disabled", () => !readyButton.Enabled.Value); + AddUntilStep("ready button disabled", () => !readyButton.Enabled.Value); AddStep("undelete beatmap", () => beatmaps.Undelete(importedSet)); - AddAssert("ready button enabled back", () => readyButton.Enabled.Value); + AddUntilStep("ready button enabled back", () => readyButton.Enabled.Value); } [Test] From a2ef086c1f23ebf9873ef55b6c4766983d9be455 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Mar 2022 01:08:11 +0900 Subject: [PATCH 1061/5199] Fix potential crash on rare incorrect firing of skin dropdown update methods As brought to light by https://gist.github.com/smoogipoo/56eda7ab56b9d1966556f2ca7a80a847. There's a chance that the dropdown is not populated by the time `updateSelectedSkinFromConfig` is fired via an external means (config changes). --- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 1dfe49945f..475c4bff8d 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -134,6 +134,9 @@ namespace osu.Game.Overlays.Settings.Sections private void updateSelectedSkinFromConfig() { + if (!skinDropdown.Items.Any()) + return; + Live skin = null; if (Guid.TryParse(configBindable.Value, out var configId)) From 56ad684f5b72271476ed430e78d45fb3e0c8e40a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Mar 2022 01:11:34 +0900 Subject: [PATCH 1062/5199] Fix potential test failure on slow realm callback in `TestSceneBeatmapDownloadButton` As brought to light by https://gist.github.com/smoogipoo/56eda7ab56b9d1966556f2ca7a80a847. --- .../Visual/Online/TestSceneBeatmapDownloadButton.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapDownloadButton.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapDownloadButton.cs index d9f01622da..8d8879490d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapDownloadButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapDownloadButton.cs @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("ensure manager loaded", () => beatmaps != null); ensureSoleilyRemoved(); createButtonWithBeatmap(createSoleily()); - AddAssert("button state not downloaded", () => downloadButton.DownloadState == DownloadState.NotDownloaded); + AddUntilStep("button state not downloaded", () => downloadButton.DownloadState == DownloadState.NotDownloaded); AddStep("import soleily", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport())); AddUntilStep("wait for beatmap import", () => beatmaps.GetAllUsableBeatmapSets().Any(b => b.OnlineID == 241526)); @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Online createButtonWithBeatmap(createSoleily()); AddUntilStep("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable); ensureSoleilyRemoved(); - AddAssert("button state not downloaded", () => downloadButton.DownloadState == DownloadState.NotDownloaded); + AddUntilStep("button state not downloaded", () => downloadButton.DownloadState == DownloadState.NotDownloaded); } private void ensureSoleilyRemoved() From 286bafe326cf1f9b5bf35cd1e9c1f1867ad09781 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Mar 2022 01:18:53 +0900 Subject: [PATCH 1063/5199] Refactor multiple `TestScenePlaySongSelect` test methods to be resilient to slow realm callbacks --- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index e2b50e38c2..c0c1e6b7a4 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -284,14 +284,13 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestDummy() { createSongSelect(); - AddAssert("dummy selected", () => songSelect.CurrentBeatmap == defaultBeatmap); + AddUntilStep("dummy selected", () => songSelect.CurrentBeatmap == defaultBeatmap); AddUntilStep("dummy shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap == defaultBeatmap); addManyTestMaps(); - AddWaitStep("wait for select", 3); - AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); + AddUntilStep("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); } [Test] @@ -299,9 +298,8 @@ namespace osu.Game.Tests.Visual.SongSelect { createSongSelect(); addManyTestMaps(); - AddWaitStep("wait for add", 3); - AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); + AddUntilStep("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); AddStep(@"Sort by Artist", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Artist)); AddStep(@"Sort by Title", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Title)); @@ -571,6 +569,8 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); + AddUntilStep("wait for selection", () => !Beatmap.IsDefault); + AddStep("press ctrl+enter", () => { InputManager.PressKey(Key.ControlLeft); @@ -605,6 +605,8 @@ namespace osu.Game.Tests.Visual.SongSelect addRulesetImportStep(0); createSongSelect(); + AddUntilStep("wait for selection", () => !Beatmap.IsDefault); + DrawableCarouselBeatmapSet set = null; AddStep("Find the DrawableCarouselBeatmapSet", () => { @@ -844,6 +846,8 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); + AddUntilStep("wait for selection", () => !Beatmap.IsDefault); + AddStep("present score", () => { // this beatmap change should be overridden by the present. From 233c8232d3c782f22f5352a442ed6c76de25a73c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Mar 2022 01:21:46 +0900 Subject: [PATCH 1064/5199] Fix `TestSceneTopLocalRank.TestHighScoreSet` not waiting for potentially slow realm callback As brought to light by https://gist.github.com/smoogipoo/56eda7ab56b9d1966556f2ca7a80a847. --- osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs index 39680d157b..7bef7c8fce 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Visual.SongSelect }); AddUntilStep("Became present", () => topLocalRank.IsPresent); - AddAssert("Correct rank", () => topLocalRank.Rank == ScoreRank.B); + AddUntilStep("Correct rank", () => topLocalRank.Rank == ScoreRank.B); AddStep("Add higher score for current user", () => { @@ -137,7 +137,7 @@ namespace osu.Game.Tests.Visual.SongSelect scoreManager.Import(testScoreInfo2); }); - AddAssert("Correct rank", () => topLocalRank.Rank == ScoreRank.S); + AddUntilStep("Correct rank", () => topLocalRank.Rank == ScoreRank.S); } } } From e91226f5786e1d481c7bffcbe778a77056e6eed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 8 Mar 2022 21:41:10 +0100 Subject: [PATCH 1065/5199] Fix 'auto-property never assigned to' inspections --- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index d327f4844e..2a19d51c9d 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -322,8 +322,8 @@ namespace osu.Game.Tests.Rulesets.Scoring public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => throw new System.NotImplementedException(); - public override string Description { get; } - public override string ShortName { get; } + public override string Description => string.Empty; + public override string ShortName => string.Empty; } private class TestJudgement : Judgement From e2001148d570ca139df57cf7cbd606adf8f50363 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 8 Mar 2022 21:47:54 +0000 Subject: [PATCH 1066/5199] Implement strict tracking mod --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 3 + .../Mods/OsuModStrictTracking.cs | 59 +++++++++++++++++++ .../Objects/SliderTailCircle.cs | 4 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 1 + 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index e04a30d06c..f46573c494 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.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.Linq; using osu.Framework.Bindables; using osu.Game.Configuration; @@ -16,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModClassic : ModClassic, IApplicableToHitObject, IApplicableToDrawableHitObject, IApplicableToDrawableRuleset { + public override Type[] IncompatibleMods => new[] { typeof(OsuModStrictTracking) }; + [SettingSource("No slider head accuracy requirement", "Scores sliders proportionally to the number of ticks hit.")] public Bindable NoSliderHeadAccuracy { get; } = new BindableBool(true); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs new file mode 100644 index 0000000000..13da422049 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs @@ -0,0 +1,59 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModStrictTracking : Mod, IApplicableToDifficulty, IApplicableToDrawableHitObject, IApplicableToHitObject + { + public override string Name => @"Strict Tracking"; + public override string Acronym => @"ST"; + public override IconUsage? Icon => FontAwesome.Solid.PenFancy; + public override ModType Type => ModType.DifficultyIncrease; + public override string Description => @"Follow circles just got serious..."; + public override double ScoreMultiplier => 1.0; + public override Type[] IncompatibleMods => new[] { typeof(ModClassic) }; + + public void ApplyToDifficulty(BeatmapDifficulty difficulty) + { + difficulty.SliderTickRate = 0.0; + } + + public void ApplyToDrawableHitObject(DrawableHitObject drawable) + { + if (drawable is DrawableSlider slider) + { + slider.Tracking.ValueChanged += e => + { + if (e.NewValue || slider.Judged) return; + + slider.MissForcefully(); + + foreach (var o in slider.NestedHitObjects) + { + if (o is DrawableOsuHitObject h && !o.Judged) + h.MissForcefully(); + } + }; + } + } + + public void ApplyToHitObject(HitObject hitObject) + { + switch (hitObject) + { + case Slider slider: + slider.TailCircle.JudgeAsSliderTick = true; + break; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index f9450062f4..70459dc432 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -14,12 +14,14 @@ namespace osu.Game.Rulesets.Osu.Objects /// public class SliderTailCircle : SliderEndCircle { + public bool JudgeAsSliderTick = false; + public SliderTailCircle(Slider slider) : base(slider) { } - public override Judgement CreateJudgement() => new SliderTailJudgement(); + public override Judgement CreateJudgement() => JudgeAsSliderTick ? (OsuJudgement)new SliderTickJudgement() : new SliderTailJudgement(); public class SliderTailJudgement : OsuJudgement { diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 5ade164566..faaab1a8e2 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -159,6 +159,7 @@ namespace osu.Game.Rulesets.Osu new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()), new OsuModHidden(), new MultiMod(new OsuModFlashlight(), new OsuModBlinds()), + new OsuModStrictTracking() }; case ModType.Conversion: From a3477c3841d4c85f81a5fe5546e695c8ebbd836c Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 8 Mar 2022 22:30:58 +0000 Subject: [PATCH 1067/5199] Implement `ChannelListing` for new chat design Adds components `ChannelListing` and `ChannelListing` item with visual test. Essentially a more simplified version of the existing `ChannelSelectionOverlay` component. Correctly implements `IFilterable` behaviour to filter child channel items. Channel joined state is based on the underlying `Joined` bindable of the `Channel` class. Channel join/leave events are exposed via `OnRequestJoin` and `OnRequestLeave` events which should be handled by parent component. Requires a cached `OverlayColourScheme` instance to be provided by the parent overlay component when added. --- .../Online/NewChat/TestSceneChannelListing.cs | 90 +++++++++ osu.Game/Overlays/NewChat/ChannelListing.cs | 85 +++++++++ .../Overlays/NewChat/ChannelListingItem.cs | 175 ++++++++++++++++++ 3 files changed, 350 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs create mode 100644 osu.Game/Overlays/NewChat/ChannelListing.cs create mode 100644 osu.Game/Overlays/NewChat/ChannelListingItem.cs diff --git a/osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs b/osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs new file mode 100644 index 0000000000..cbec23a305 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs @@ -0,0 +1,90 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Chat; +using osu.Game.Overlays; +using osu.Game.Overlays.NewChat; +using osuTK; + +namespace osu.Game.Tests.Visual.Online.NewChat +{ + [TestFixture] + public class TestSceneChannelListing : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider overlayColours = new OverlayColourProvider(OverlayColourScheme.Pink); + + private SearchTextBox search; + private ChannelListing listing; + + [SetUp] + public void SetUp() + { + Schedule(() => + { + Children = new Drawable[] + { + search = new SearchTextBox + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Width = 300, + Margin = new MarginPadding { Top = 100 }, + }, + listing = new ChannelListing + { + Size = new Vector2(800, 400), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + }; + listing.Show(); + search.Current.ValueChanged += term => listing.SearchTerm = term.NewValue; + }); + } + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Add Join/Leave callbacks", () => + { + listing.OnRequestJoin += channel => channel.Joined.Value = true; + listing.OnRequestLeave += channel => channel.Joined.Value = false; + }); + } + + [Test] + public void TestAddRandomChannels() + { + AddStep("Add Random Channels", () => + { + listing.UpdateAvailableChannels(createRandomChannels(20)); + }); + } + + private Channel createRandomChannel() + { + var id = RNG.Next(0, 10000); + return new Channel + { + Name = $"#channel-{id}", + Topic = RNG.Next(4) < 3 ? $"We talk about the number {id} here" : null, + Type = ChannelType.Public, + Id = id, + }; + } + + private List createRandomChannels(int num) + => Enumerable.Range(0, num) + .Select(_ => createRandomChannel()) + .ToList(); + } +} diff --git a/osu.Game/Overlays/NewChat/ChannelListing.cs b/osu.Game/Overlays/NewChat/ChannelListing.cs new file mode 100644 index 0000000000..79371d0fed --- /dev/null +++ b/osu.Game/Overlays/NewChat/ChannelListing.cs @@ -0,0 +1,85 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; +using osu.Game.Online.Chat; +using osuTK; + +namespace osu.Game.Overlays.NewChat +{ + public class ChannelListing : VisibilityContainer + { + public event Action? OnRequestJoin; + public event Action? OnRequestLeave; + + public string SearchTerm + { + get => flow.SearchTerm; + set => flow.SearchTerm = value; + } + + private SearchContainer flow = null!; + + [Resolved] + private OverlayColourProvider overlayColours { get; set; } = null!; + + public ChannelListing() + { + Masking = true; + } + + protected override void PopIn() => this.FadeIn(); + protected override void PopOut() => this.FadeOut(); + + public void UpdateAvailableChannels(IEnumerable newChannels) + { + flow.ChildrenEnumerable = newChannels.Where(c => c.Type == ChannelType.Public) + .Select(c => new ChannelListingItem(c)); + + foreach (var item in flow.Children) + { + item.OnRequestJoin += channel => OnRequestJoin?.Invoke(channel); + item.OnRequestLeave += channel => OnRequestLeave?.Invoke(channel); + } + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = overlayColours.Background4, + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + ScrollbarAnchor = Anchor.TopRight, + Child = flow = new SearchContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(3), + Padding = new MarginPadding + { + Vertical = 13, + Horizontal = 15, + }, + }, + }, + }; + } + } +} diff --git a/osu.Game/Overlays/NewChat/ChannelListingItem.cs b/osu.Game/Overlays/NewChat/ChannelListingItem.cs new file mode 100644 index 0000000000..845db4e802 --- /dev/null +++ b/osu.Game/Overlays/NewChat/ChannelListingItem.cs @@ -0,0 +1,175 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; +using osuTK; + +namespace osu.Game.Overlays.NewChat +{ + public class ChannelListingItem : OsuClickableContainer, IFilterable + { + public event Action? OnRequestJoin; + public event Action? OnRequestLeave; + + public bool FilteringActive { get; set; } + public IEnumerable FilterTerms => new[] { channel.Name, channel.Topic ?? string.Empty }; + public bool MatchingFilter + { + set => this.FadeTo(value ? 1f : 0f, 100); + } + + private readonly float TEXT_SIZE = 18; + private readonly float ICON_SIZE = 14; + private readonly Channel channel; + + private Colour4 selectedColour; + private Colour4 normalColour; + + private Box hoverBox = null!; + private SpriteIcon checkbox = null!; + private OsuSpriteText channelText = null!; + private IBindable channelJoined = null!; + + [Resolved] + private OverlayColourProvider overlayColours { get; set; } = null!; + + public ChannelListingItem(Channel channel) + { + this.channel = channel; + + Masking = true; + CornerRadius = 5; + RelativeSizeAxes = Axes.X; + Height = 20; + } + + protected override bool OnHover(HoverEvent e) + { + hoverBox.Show(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hoverBox.Hide(); + base.OnHoverLost(e); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // Set colours + normalColour = overlayColours.Light3; + selectedColour = Colour4.White; + + // Set handlers for state display + channelJoined = channel.Joined.GetBoundCopy(); + channelJoined.BindValueChanged(change => + { + if (change.NewValue) + { + checkbox.Show(); + channelText.Colour = selectedColour; + } + else + { + checkbox.Hide(); + channelText.Colour = normalColour; + } + }, true); + + // Set action on click + Action = () => (channelJoined.Value ? OnRequestLeave : OnRequestJoin)?.Invoke(channel); + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + hoverBox = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = overlayColours.Background3, + Alpha = 0f, + }, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new Dimension[] + { + new Dimension(GridSizeMode.Absolute, 40), + new Dimension(GridSizeMode.Absolute, 200), + new Dimension(GridSizeMode.Absolute, 400), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + }, + Content = new[] + { + new Drawable[] + { + checkbox = new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Left = 15 }, + Icon = FontAwesome.Solid.Check, + Size = new Vector2(ICON_SIZE), + }, + channelText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = $"# {channel.Name.Substring(1)}", + Font = OsuFont.Torus.With(size: TEXT_SIZE, weight: FontWeight.Medium), + Margin = new MarginPadding { Bottom = 2 }, + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = channel.Topic, + Font = OsuFont.Torus.With(size: TEXT_SIZE), + Margin = new MarginPadding { Bottom = 2 }, + Colour = Colour4.White, + }, + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.User, + Size = new Vector2(ICON_SIZE), + Margin = new MarginPadding { Right = 5 }, + Colour = overlayColours.Light3, + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = "0", + Font = OsuFont.Numeric.With(size: TEXT_SIZE, weight: FontWeight.Medium), + Margin = new MarginPadding { Bottom = 2 }, + Colour = overlayColours.Light3, + }, + }, + }, + }, + }; + } + } +} From 7dd51a9c4a69ab6b6ef2618bc25c4eec08689002 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 9 Mar 2022 00:12:44 +0000 Subject: [PATCH 1068/5199] Reorder class attributes --- osu.Game/Overlays/NewChat/ChannelListing.cs | 40 +++---- .../Overlays/NewChat/ChannelListingItem.cs | 110 +++++++++--------- 2 files changed, 72 insertions(+), 78 deletions(-) diff --git a/osu.Game/Overlays/NewChat/ChannelListing.cs b/osu.Game/Overlays/NewChat/ChannelListing.cs index 79371d0fed..3de6364303 100644 --- a/osu.Game/Overlays/NewChat/ChannelListing.cs +++ b/osu.Game/Overlays/NewChat/ChannelListing.cs @@ -30,27 +30,7 @@ namespace osu.Game.Overlays.NewChat private SearchContainer flow = null!; [Resolved] - private OverlayColourProvider overlayColours { get; set; } = null!; - - public ChannelListing() - { - Masking = true; - } - - protected override void PopIn() => this.FadeIn(); - protected override void PopOut() => this.FadeOut(); - - public void UpdateAvailableChannels(IEnumerable newChannels) - { - flow.ChildrenEnumerable = newChannels.Where(c => c.Type == ChannelType.Public) - .Select(c => new ChannelListingItem(c)); - - foreach (var item in flow.Children) - { - item.OnRequestJoin += channel => OnRequestJoin?.Invoke(channel); - item.OnRequestLeave += channel => OnRequestLeave?.Invoke(channel); - } - } + private OverlayColourProvider colourProvider { get; set; } = null!; [BackgroundDependencyLoader] private void load() @@ -60,7 +40,7 @@ namespace osu.Game.Overlays.NewChat new Box { RelativeSizeAxes = Axes.Both, - Colour = overlayColours.Background4, + Colour = colourProvider.Background4, }, new OsuScrollContainer { @@ -81,5 +61,21 @@ namespace osu.Game.Overlays.NewChat }, }; } + + public void UpdateAvailableChannels(IEnumerable newChannels) + { + flow.ChildrenEnumerable = newChannels.Where(c => c.Type == ChannelType.Public) + .Select(c => new ChannelListingItem(c)); + + foreach (var item in flow.Children) + { + item.OnRequestJoin += channel => OnRequestJoin?.Invoke(channel); + item.OnRequestLeave += channel => OnRequestLeave?.Invoke(channel); + } + } + + protected override void PopIn() => this.FadeIn(); + + protected override void PopOut() => this.FadeOut(); } } diff --git a/osu.Game/Overlays/NewChat/ChannelListingItem.cs b/osu.Game/Overlays/NewChat/ChannelListingItem.cs index 845db4e802..7e7cf36e91 100644 --- a/osu.Game/Overlays/NewChat/ChannelListingItem.cs +++ b/osu.Game/Overlays/NewChat/ChannelListingItem.cs @@ -27,15 +27,13 @@ namespace osu.Game.Overlays.NewChat public bool FilteringActive { get; set; } public IEnumerable FilterTerms => new[] { channel.Name, channel.Topic ?? string.Empty }; - public bool MatchingFilter - { - set => this.FadeTo(value ? 1f : 0f, 100); - } + public bool MatchingFilter { set => this.FadeTo(value ? 1f : 0f, 100); } - private readonly float TEXT_SIZE = 18; - private readonly float ICON_SIZE = 14; private readonly Channel channel; + private const float TEXT_SIZE = 18; + private const float ICON_SIZE = 14; + private Colour4 selectedColour; private Colour4 normalColour; @@ -45,73 +43,33 @@ namespace osu.Game.Overlays.NewChat private IBindable channelJoined = null!; [Resolved] - private OverlayColourProvider overlayColours { get; set; } = null!; + private OverlayColourProvider colourProvider { get; set; } = null!; public ChannelListingItem(Channel channel) { this.channel = channel; - - Masking = true; - CornerRadius = 5; - RelativeSizeAxes = Axes.X; - Height = 20; - } - - protected override bool OnHover(HoverEvent e) - { - hoverBox.Show(); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - hoverBox.Hide(); - base.OnHoverLost(e); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - // Set colours - normalColour = overlayColours.Light3; - selectedColour = Colour4.White; - - // Set handlers for state display - channelJoined = channel.Joined.GetBoundCopy(); - channelJoined.BindValueChanged(change => - { - if (change.NewValue) - { - checkbox.Show(); - channelText.Colour = selectedColour; - } - else - { - checkbox.Hide(); - channelText.Colour = normalColour; - } - }, true); - - // Set action on click - Action = () => (channelJoined.Value ? OnRequestLeave : OnRequestJoin)?.Invoke(channel); } [BackgroundDependencyLoader] private void load() { + Masking = true; + CornerRadius = 5; + RelativeSizeAxes = Axes.X; + Height = 20; + Children = new Drawable[] { hoverBox = new Box { RelativeSizeAxes = Axes.Both, - Colour = overlayColours.Background3, + Colour = colourProvider.Background3, Alpha = 0f, }, new GridContainer { RelativeSizeAxes = Axes.Both, - ColumnDimensions = new Dimension[] + ColumnDimensions = new[] { new Dimension(GridSizeMode.Absolute, 40), new Dimension(GridSizeMode.Absolute, 200), @@ -155,7 +113,7 @@ namespace osu.Game.Overlays.NewChat Icon = FontAwesome.Solid.User, Size = new Vector2(ICON_SIZE), Margin = new MarginPadding { Right = 5 }, - Colour = overlayColours.Light3, + Colour = colourProvider.Light3, }, new OsuSpriteText { @@ -164,12 +122,52 @@ namespace osu.Game.Overlays.NewChat Text = "0", Font = OsuFont.Numeric.With(size: TEXT_SIZE, weight: FontWeight.Medium), Margin = new MarginPadding { Bottom = 2 }, - Colour = overlayColours.Light3, + Colour = colourProvider.Light3, }, }, }, }, }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // Set colours + normalColour = colourProvider.Light3; + selectedColour = Colour4.White; + + // Set handlers for state display + channelJoined = channel.Joined.GetBoundCopy(); + channelJoined.BindValueChanged(change => + { + if (change.NewValue) + { + checkbox.Show(); + channelText.Colour = selectedColour; + } + else + { + checkbox.Hide(); + channelText.Colour = normalColour; + } + }, true); + + // Set action on click + Action = () => (channelJoined.Value ? OnRequestLeave : OnRequestJoin)?.Invoke(channel); + } + + protected override bool OnHover(HoverEvent e) + { + hoverBox.Show(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hoverBox.Hide(); + base.OnHoverLost(e); + } } } From 7daa2d0ea4dd67f4aea73274ee2db9198f8b1871 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 9 Mar 2022 00:33:46 +0000 Subject: [PATCH 1069/5199] Use correct fonts and colours in ChannelListingItem --- .../Overlays/NewChat/ChannelListingItem.cs | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/NewChat/ChannelListingItem.cs b/osu.Game/Overlays/NewChat/ChannelListingItem.cs index 7e7cf36e91..77d5599c9b 100644 --- a/osu.Game/Overlays/NewChat/ChannelListingItem.cs +++ b/osu.Game/Overlays/NewChat/ChannelListingItem.cs @@ -31,20 +31,18 @@ namespace osu.Game.Overlays.NewChat private readonly Channel channel; - private const float TEXT_SIZE = 18; - private const float ICON_SIZE = 14; - - private Colour4 selectedColour; - private Colour4 normalColour; - private Box hoverBox = null!; private SpriteIcon checkbox = null!; private OsuSpriteText channelText = null!; + private OsuSpriteText topicText = null!; private IBindable channelJoined = null!; [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; + private const float text_size = 18; + private const float icon_size = 14; + public ChannelListingItem(Channel channel) { this.channel = channel; @@ -87,31 +85,30 @@ namespace osu.Game.Overlays.NewChat Origin = Anchor.CentreLeft, Margin = new MarginPadding { Left = 15 }, Icon = FontAwesome.Solid.Check, - Size = new Vector2(ICON_SIZE), + Size = new Vector2(icon_size), }, channelText = new OsuSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Text = $"# {channel.Name.Substring(1)}", - Font = OsuFont.Torus.With(size: TEXT_SIZE, weight: FontWeight.Medium), + Text = channel.Name, + Font = OsuFont.Torus.With(size: text_size, weight: FontWeight.SemiBold), Margin = new MarginPadding { Bottom = 2 }, }, - new OsuSpriteText + topicText = new OsuSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Text = channel.Topic, - Font = OsuFont.Torus.With(size: TEXT_SIZE), + Font = OsuFont.Torus.With(size: text_size), Margin = new MarginPadding { Bottom = 2 }, - Colour = Colour4.White, }, new SpriteIcon { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Icon = FontAwesome.Solid.User, - Size = new Vector2(ICON_SIZE), + Size = new Vector2(icon_size), Margin = new MarginPadding { Right = 5 }, Colour = colourProvider.Light3, }, @@ -120,7 +117,7 @@ namespace osu.Game.Overlays.NewChat Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Text = "0", - Font = OsuFont.Numeric.With(size: TEXT_SIZE, weight: FontWeight.Medium), + Font = OsuFont.Torus.With(size: text_size), Margin = new MarginPadding { Bottom = 2 }, Colour = colourProvider.Light3, }, @@ -134,27 +131,23 @@ namespace osu.Game.Overlays.NewChat { base.LoadComplete(); - // Set colours - normalColour = colourProvider.Light3; - selectedColour = Colour4.White; - - // Set handlers for state display channelJoined = channel.Joined.GetBoundCopy(); channelJoined.BindValueChanged(change => { if (change.NewValue) { checkbox.Show(); - channelText.Colour = selectedColour; + channelText.Colour = Colour4.White; + topicText.Colour = Colour4.White; } else { checkbox.Hide(); - channelText.Colour = normalColour; + channelText.Colour = colourProvider.Light3; + topicText.Colour = colourProvider.Content2; } }, true); - // Set action on click Action = () => (channelJoined.Value ? OnRequestLeave : OnRequestJoin)?.Invoke(channel); } From 9720a06b16105f23fc9f151e67d3af0cce63004d Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 9 Mar 2022 00:52:33 +0000 Subject: [PATCH 1070/5199] Use `int` in ChannelListing test --- osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs b/osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs index cbec23a305..8f955c0520 100644 --- a/osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs +++ b/osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Online.NewChat private Channel createRandomChannel() { - var id = RNG.Next(0, 10000); + int id = RNG.Next(0, 10000); return new Channel { Name = $"#channel-{id}", From 6bf436cd62d0c844d74964837fb2387ebe112725 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Mar 2022 13:52:58 +0900 Subject: [PATCH 1071/5199] Only null the realm write task if it actually completed --- .../Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 9e280fdca2..42091c521f 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -129,8 +129,11 @@ namespace osu.Game.Screens.Play.PlayerSettings if (realmWriteTask == null) Current.Value = val; - // we can also mark any in-flight write that is managed locally as "seen" and start handling any incoming changes again. - realmWriteTask = null; + if (realmWriteTask?.IsCompleted == true) + { + // we can also mark any in-flight write that is managed locally as "seen" and start handling any incoming changes again. + realmWriteTask = null; + } }); Current.BindValueChanged(currentChanged); From 3ced5e7904877e7b989984bff414b1ce2c83dd47 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 9 Mar 2022 13:09:33 +0800 Subject: [PATCH 1072/5199] Rename `Distance` to `DistanceFromPrevious` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index babc4311ee..e450127488 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -73,19 +73,19 @@ namespace osu.Game.Rulesets.Osu.Mods if (previous == null) { - current.Distance = (float)(rng.NextDouble() * OsuPlayfield.BASE_SIZE.X / 2); + current.DistanceFromPrevious = (float)(rng.NextDouble() * OsuPlayfield.BASE_SIZE.X / 2); current.RelativeAngle = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI); } else { - current.Distance = Vector2.Distance(previous.EndPositionOriginal, current.PositionOriginal); + current.DistanceFromPrevious = Vector2.Distance(previous.EndPositionOriginal, current.PositionOriginal); // The max. angle (relative to the angle of the vector pointing from the 2nd last to the last hit object) // is proportional to the distance between the last and the current hit object // to allow jumps and prevent too sharp turns during streams. // Allow maximum jump angle when jump distance is more than half of playfield diagonal length - current.RelativeAngle = rateOfChangeMultiplier * 2 * (float)Math.PI * Math.Min(1f, current.Distance / (playfield_diagonal * 0.5f)); + current.RelativeAngle = rateOfChangeMultiplier * 2 * (float)Math.PI * Math.Min(1f, current.DistanceFromPrevious / (playfield_diagonal * 0.5f)); } previous = current; @@ -177,8 +177,8 @@ namespace osu.Game.Rulesets.Osu.Mods float absoluteAngle = previousAbsoluteAngle + current.RelativeAngle; var posRelativeToPrev = new Vector2( - current.Distance * (float)Math.Cos(absoluteAngle), - current.Distance * (float)Math.Sin(absoluteAngle) + current.DistanceFromPrevious * (float)Math.Cos(absoluteAngle), + current.DistanceFromPrevious * (float)Math.Sin(absoluteAngle) ); Vector2 lastEndPosition = previous?.EndPositionRandomised ?? playfield_centre; @@ -349,9 +349,9 @@ namespace osu.Game.Rulesets.Osu.Mods /// The jump distance from the previous hit object to this one. ///
/// - /// of the first hit object in a beatmap is relative to the playfield center. + /// of the first hit object in a beatmap is relative to the playfield center. /// - public float Distance { get; set; } + public float DistanceFromPrevious { get; set; } public Vector2 PositionOriginal { get; } public Vector2 PositionRandomised { get; set; } From e3cf2c6acd3e0aecd307e458acbe5f251258f488 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 9 Mar 2022 13:18:57 +0800 Subject: [PATCH 1073/5199] Merge `getAbsoluteAngle` into `computeRandomisedPosition` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 36 ++++++++++------------ 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index e450127488..895dc9a506 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Graphics.Primitives; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -115,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Mods continue; } - computeRandomisedPosition(getAbsoluteAngle(hitObjects, i - 1), previous, current); + computeRandomisedPosition(current, previous, i > 1 ? randomObjects[i - 2] : null); // Move hit objects back into the playfield if they are outside of it Vector2 shift = Vector2.Zero; @@ -151,29 +152,23 @@ namespace osu.Game.Rulesets.Osu.Mods } } - /// - /// Get the absolute angle of a vector pointing from the previous hit object to the one denoted by . - /// - /// A list of all hit objects in the beatmap. - /// The hit object that the vector should point to. - /// The absolute angle of the aforementioned vector. - private float getAbsoluteAngle(IReadOnlyList hitObjects, int hitObjectIndex) - { - if (hitObjectIndex < 0) return 0; - - Vector2 previousPosition = hitObjectIndex == 0 ? playfield_centre : hitObjects[hitObjectIndex - 1].EndPosition; - Vector2 relativePosition = hitObjects[hitObjectIndex].Position - previousPosition; - return (float)Math.Atan2(relativePosition.Y, relativePosition.X); - } - /// /// Compute the randomised position of a hit object while attempting to keep it inside the playfield. /// - /// The direction of movement of the player's cursor before it starts to approach the current hit object. - /// The representing the hit object immediately preceding the current one. /// The representing the hit object to have the randomised position computed for. - private void computeRandomisedPosition(float previousAbsoluteAngle, RandomObjectInfo previous, RandomObjectInfo current) + /// The representing the hit object immediately preceding the current one. + /// The representing the hit object immediately preceding the one. + private void computeRandomisedPosition(RandomObjectInfo current, [CanBeNull] RandomObjectInfo previous, [CanBeNull] RandomObjectInfo beforePrevious) { + float previousAbsoluteAngle = 0f; + + if (previous != null) + { + Vector2 earliestPosition = beforePrevious == null ? playfield_centre : beforePrevious.HitObject.EndPosition; + Vector2 relativePosition = previous.HitObject.Position - earliestPosition; + previousAbsoluteAngle = (float)Math.Atan2(relativePosition.Y, relativePosition.X); + } + float absoluteAngle = previousAbsoluteAngle + current.RelativeAngle; var posRelativeToPrev = new Vector2( @@ -359,10 +354,13 @@ namespace osu.Game.Rulesets.Osu.Mods public Vector2 EndPositionOriginal { get; } public Vector2 EndPositionRandomised { get; set; } + public OsuHitObject HitObject { get; } + public RandomObjectInfo(OsuHitObject hitObject) { PositionRandomised = PositionOriginal = hitObject.Position; EndPositionRandomised = EndPositionOriginal = hitObject.EndPosition; + HitObject = hitObject; } } } From ad0ca5673a445f5a1e7fb38da44af8050ae7fa38 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 9 Mar 2022 14:39:02 +0900 Subject: [PATCH 1074/5199] Fix avatar not clickable after watching replay --- osu.Game/Users/Drawables/ClickableAvatar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index e3cfaf1d14..34c87568a1 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -64,7 +64,7 @@ namespace osu.Game.Users.Drawables private void openProfile() { - if (user?.Id > 1) + if (user?.Id > 1 || !string.IsNullOrEmpty(user?.Username)) game?.ShowUser(user); } From b07a1e8d09b50bd8511dabcbc75c49a55439687c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 9 Mar 2022 15:38:00 +0900 Subject: [PATCH 1075/5199] Fix unable to copy playlist rooms without first opening --- osu.Game/Online/Rooms/Room.cs | 19 +-------- .../OnlinePlay/Lounge/DrawableLoungeRoom.cs | 2 +- .../OnlinePlay/Lounge/LoungeSubScreen.cs | 39 +++++++++++++++++++ 3 files changed, 41 insertions(+), 19 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index a33150fe08..543b176b51 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -10,12 +10,11 @@ using osu.Game.IO.Serialization.Converters; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms.RoomStatuses; -using osu.Game.Utils; namespace osu.Game.Online.Rooms { [JsonObject(MemberSerialization.OptIn)] - public class Room : IDeepCloneable + public class Room { [Cached] [JsonProperty("id")] @@ -153,22 +152,6 @@ namespace osu.Game.Online.Rooms Password.BindValueChanged(p => HasPassword.Value = !string.IsNullOrEmpty(p.NewValue)); } - /// - /// Create a copy of this room without online information. - /// Should be used to create a local copy of a room for submitting in the future. - /// - public Room DeepClone() - { - var copy = new Room(); - - copy.CopyFrom(this); - - // ID must be unset as we use this as a marker for whether this is a client-side (not-yet-created) room or not. - copy.RoomID.Value = null; - - return copy; - } - public void CopyFrom(Room other) { RoomID.Value = other.RoomID.Value; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 27743e709f..7baa346c6f 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -128,7 +128,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { new OsuMenuItem("Create copy", MenuItemType.Standard, () => { - lounge?.Open(Room.DeepClone()); + lounge?.OpenCopy(Room); }) }; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index cd1c8a0a64..08b4ae9db2 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -20,6 +20,7 @@ using osu.Framework.Threading; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input; +using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Rulesets; @@ -63,6 +64,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge [Resolved] private IBindable ruleset { get; set; } + [Resolved] + private IAPIProvider api { get; set; } + [CanBeNull] private IDisposable joiningRoomOperation { get; set; } @@ -310,6 +314,41 @@ namespace osu.Game.Screens.OnlinePlay.Lounge }); }); + /// + /// Copies a room and opens it as a fresh (not-yet-created) one. + /// + /// The room to copy. + public void OpenCopy(Room room) + { + Debug.Assert(room.RoomID.Value != null); + + if (joiningRoomOperation != null) + return; + + joiningRoomOperation = ongoingOperationTracker?.BeginOperation(); + + var req = new GetRoomRequest(room.RoomID.Value.Value); + + req.Success += r => + { + // ID must be unset as we use this as a marker for whether this is a client-side (not-yet-created) room or not. + r.RoomID.Value = null; + + Open(r); + + joiningRoomOperation?.Dispose(); + joiningRoomOperation = null; + }; + + req.Failure += _ => + { + joiningRoomOperation?.Dispose(); + joiningRoomOperation = null; + }; + + api.Queue(req); + } + /// /// Push a room as a new subscreen. /// From 520d2d6cfa4b7e3206083e5c68ea8c04879d10b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Mar 2022 16:06:39 +0900 Subject: [PATCH 1076/5199] Fix beatmap carousel panels accepting input while marked as not-visible This is an issue as carousel panels manage their own animated state. If they are marked as not-visible (done at a higher level, from filtering or update pathways) but clicked while fading out, they will animate back to a visible state but not be marked as visible. No tests for this one as it's probably not worthwhile to test (and hard to do so). Manual testing can be done with the following patch: ```diff diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c3d340ac61..3372242acc 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -255,7 +255,7 @@ private void beatmapSetsChanged(IRealmCollection sender, ChangeS } foreach (int i in changes.NewModifiedIndices) - UpdateBeatmapSet(sender[i].Detach()); + Scheduler.AddDelayed(() => UpdateBeatmapSet(sender[i].Detach()), 100, true); foreach (int i in changes.InsertedIndices) UpdateBeatmapSet(sender[i].Detach()); ``` - Enter gameplay and adjust beatmap offset then return to song select and click the flashing panel. OR - Enter editor and save then return to song select and click the flashing panel. Closes https://github.com/ppy/osu/discussions/17171. --- osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index cde3edad39..75bcdedec4 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -17,6 +17,9 @@ namespace osu.Game.Screens.Select.Carousel public override bool IsPresent => base.IsPresent || Item?.Visible == true; + public override bool HandlePositionalInput => Item?.Visible == true; + public override bool PropagatePositionalInputSubTree => Item?.Visible == true; + public readonly CarouselHeader Header; /// From 4839bd804466fb410884ee1f94eec40a84c86785 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 9 Mar 2022 16:47:47 +0900 Subject: [PATCH 1077/5199] Notify if copying room fails Co-authored-by: Dean Herbert --- osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index 08b4ae9db2..fb8647284f 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -340,8 +340,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge joiningRoomOperation = null; }; - req.Failure += _ => + req.Failure += exception => { + Logger.Error(exception, "Couldn't create a copy of this room."); joiningRoomOperation?.Dispose(); joiningRoomOperation = null; }; From 75c6a676b42b4d51bd15b74a2254e0a559269169 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Mar 2022 16:58:36 +0900 Subject: [PATCH 1078/5199] Apply `nullable` to `OsuModRandom` rather than using jetbrains annotations --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 895dc9a506..8ccfbf0da5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Graphics.Primitives; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -33,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// private const int preceding_hitobjects_to_shift = 10; - private Random rng; + private Random? rng; public void ApplyToBeatmap(IBeatmap beatmap) { @@ -58,8 +60,10 @@ namespace osu.Game.Rulesets.Osu.Mods /// A list of s describing how each hit object should be placed. private List randomiseObjects(IEnumerable hitObjects) { + Debug.Assert(rng != null, $"{nameof(ApplyToBeatmap)} was not called before randomising objects"); + var randomObjects = new List(); - RandomObjectInfo previous = null; + RandomObjectInfo? previous = null; float rateOfChangeMultiplier = 0; foreach (OsuHitObject hitObject in hitObjects) @@ -102,7 +106,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// A list of describing how each hit object should be placed. private void applyRandomisation(IReadOnlyList hitObjects, IReadOnlyList randomObjects) { - RandomObjectInfo previous = null; + RandomObjectInfo? previous = null; for (int i = 0; i < hitObjects.Count; i++) { @@ -158,7 +162,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// The representing the hit object to have the randomised position computed for. /// The representing the hit object immediately preceding the current one. /// The representing the hit object immediately preceding the one. - private void computeRandomisedPosition(RandomObjectInfo current, [CanBeNull] RandomObjectInfo previous, [CanBeNull] RandomObjectInfo beforePrevious) + private void computeRandomisedPosition(RandomObjectInfo current, RandomObjectInfo? previous, RandomObjectInfo? beforePrevious) { float previousAbsoluteAngle = 0f; From 353b251d3887e6a71dd82ada40a8205fdf6611a4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 9 Mar 2022 17:46:41 +0900 Subject: [PATCH 1079/5199] Attempt to merge conditional expression Hoping to fix CI error, caused by older R# version. --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 8ccfbf0da5..7479c3120a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -168,7 +168,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (previous != null) { - Vector2 earliestPosition = beforePrevious == null ? playfield_centre : beforePrevious.HitObject.EndPosition; + Vector2 earliestPosition = beforePrevious?.HitObject.EndPosition ?? playfield_centre; Vector2 relativePosition = previous.HitObject.Position - earliestPosition; previousAbsoluteAngle = (float)Math.Atan2(relativePosition.Y, relativePosition.X); } From 0267aed846cdccaf08b9b382cf7363248a44c659 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Mar 2022 17:13:51 +0900 Subject: [PATCH 1080/5199] Change `ToMod` to return an `UnknownMod` rather than throw if a mod isn't available As seen by this kind of crash, having the `.ToMod` method throw can be very problematic and also hidden (as it is used inside of models in places where exceptions are not expected to occur). Given there are tens of usages of this method, returning a placeholder mod seems like a better idea than outright throwing. ``` An unhandled has occurred. System.InvalidOperationException: There is no mod in the ruleset (osu) matching the acronym AS. at osu.Game.Online.API.APIMod.ToMod(Ruleset ruleset) in /Users/dean/Projects/osu/osu.Game/Online/API/APIMod.cs:line 54 at osu.Game.Scoring.ScoreInfo.b__117_0(APIMod m) in /Users/dean/Projects/osu/osu.Game/Scoring/ScoreInfo.cs:line 193 at System.Linq.Enumerable.SelectArrayIterator`2.ToArray() at osu.Game.Scoring.ScoreInfo.get_Mods() in /Users/dean/Projects/osu/osu.Game/Scoring/ScoreInfo.cs:line 193 at osu.Game.Screens.Select.Leaderboards.BeatmapLeaderboard.<>c.b__40_2(ScoreInfo s) in /Users/dean/Projects/osu/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs:line 199 at System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext() at osu.Game.Database.RealmObjectExtensions.Detach[T](IEnumerable`1 items) in /Users/dean/Projects/osu/osu.Game/Database/RealmObjectExtensions.cs:line 180 at osu.Game.Screens.Select.Leaderboards.BeatmapLeaderboard.<>c__DisplayClass40_0.g__localScoresChanged|1(IRealmCollection`1 sender, ChangeSet changes, Exception exception) in /Users/dean/Projects/osu/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs:line 209 at Realms.RealmCollectionBase`1.Realms.INotifiable.NotifyCallbacks(Nullable`1 changes, Nullable`1 exception) at Realms.NotifiableObjectHandleBase.NotifyObjectChanged(IntPtr managedHandle, IntPtr changes, IntPtr exception) ``` --- .../Online/TestAPIModJsonSerialization.cs | 14 ++++++++++ osu.Game/Online/API/APIMod.cs | 7 ++++- osu.Game/Rulesets/Mods/UnknownMod.cs | 27 +++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Rulesets/Mods/UnknownMod.cs diff --git a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs index d5ea3e492c..e98ea98bb2 100644 --- a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs @@ -24,6 +24,20 @@ namespace osu.Game.Tests.Online [TestFixture] public class TestAPIModJsonSerialization { + [Test] + public void TestUnknownMod() + { + var apiMod = new APIMod { Acronym = "WNG" }; + + var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(apiMod)); + + var converted = deserialized?.ToMod(new TestRuleset()); + + Assert.That(converted, Is.TypeOf(typeof(UnknownMod))); + Assert.That(converted?.Type, Is.EqualTo(ModType.System)); + Assert.That(converted?.Acronym, Is.EqualTo("WNG??")); + } + [Test] public void TestAcronymIsPreserved() { diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index 67041cae07..ef8637b8ed 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -8,6 +8,7 @@ using Humanizer; using MessagePack; using Newtonsoft.Json; using osu.Framework.Bindables; +using osu.Framework.Logging; using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -51,7 +52,10 @@ namespace osu.Game.Online.API Mod resultMod = ruleset.CreateModFromAcronym(Acronym); if (resultMod == null) - throw new InvalidOperationException($"There is no mod in the ruleset ({ruleset.ShortName}) matching the acronym {Acronym}."); + { + Logger.Log($"There is no mod in the ruleset ({ruleset.ShortName}) matching the acronym {Acronym}."); + return new UnknownMod(Acronym); + } if (Settings.Count > 0) { @@ -98,4 +102,5 @@ namespace osu.Game.Online.API public int GetHashCode(KeyValuePair obj) => HashCode.Combine(obj.Key, ModUtils.GetSettingUnderlyingValue(obj.Value)); } } + } diff --git a/osu.Game/Rulesets/Mods/UnknownMod.cs b/osu.Game/Rulesets/Mods/UnknownMod.cs new file mode 100644 index 0000000000..013725e227 --- /dev/null +++ b/osu.Game/Rulesets/Mods/UnknownMod.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Mods +{ + public class UnknownMod : Mod + { + /// + /// The acronym of the mod which could not be resolved. + /// + public readonly string OriginalAcronym; + + public override string Name => $"Unknown mod ({OriginalAcronym})"; + public override string Acronym => $"{OriginalAcronym}??"; + public override string Description => "This mod could not be resolved by the game."; + public override double ScoreMultiplier => 0; + + public override ModType Type => ModType.System; + + public UnknownMod(string acronym) + { + OriginalAcronym = acronym; + } + + public override Mod DeepClone() => new UnknownMod(OriginalAcronym); + } +} From 1ee0be5e39c613e7bff13cac6bf65dc50dcd02b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Mar 2022 17:38:56 +0900 Subject: [PATCH 1081/5199] Ensure gameplay can't start when an `UnknownMod` is present --- .../Visual/Gameplay/UnknownModTestScene.cs | 26 +++++++++++++++++++ osu.Game/Screens/Play/Player.cs | 6 +++++ 2 files changed, 32 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/UnknownModTestScene.cs diff --git a/osu.Game.Tests/Visual/Gameplay/UnknownModTestScene.cs b/osu.Game.Tests/Visual/Gameplay/UnknownModTestScene.cs new file mode 100644 index 0000000000..d3e52a0ed4 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/UnknownModTestScene.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneUnknownMod : ModTestScene + { + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); + + [Test] + public void TestUnknownModDoesntEnterGameplay() + { + CreateModTest(new ModTestData + { + Beatmap = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).Beatmap, + Mod = new UnknownMod("WNG"), + PassCondition = () => Player.IsLoaded && !Player.LoadedBeatmapSuccessfully + }); + } + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b6f576ff2b..875a96c810 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -185,6 +185,12 @@ namespace osu.Game.Screens.Play { var gameplayMods = Mods.Value.Select(m => m.DeepClone()).ToArray(); + if (gameplayMods.Any(m => m is UnknownMod)) + { + Logger.Log("Gameplay was started with an unknown mod applied.", level: LogLevel.Important); + return; + } + if (Beatmap.Value is DummyWorkingBeatmap) return; From 2eb3365f463618a8679a2ba4a06418bc44b1e4c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Mar 2022 17:50:05 +0900 Subject: [PATCH 1082/5199] Fix regressing issues when attempting to exit `Player` after an unsuccessful beatmap load --- .../Visual/Gameplay/UnknownModTestScene.cs | 3 ++ osu.Game/Screens/Play/Player.cs | 29 ++++++++++--------- osu.Game/Screens/Play/SubmittingPlayer.cs | 3 +- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/UnknownModTestScene.cs b/osu.Game.Tests/Visual/Gameplay/UnknownModTestScene.cs index d3e52a0ed4..c0f1112905 100644 --- a/osu.Game.Tests/Visual/Gameplay/UnknownModTestScene.cs +++ b/osu.Game.Tests/Visual/Gameplay/UnknownModTestScene.cs @@ -12,6 +12,9 @@ namespace osu.Game.Tests.Visual.Gameplay { protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); + /// + /// This test also covers the scenario of exiting Player after an unsuccessful beatmap load. + /// [Test] public void TestUnknownModDoesntEnterGameplay() { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 875a96c810..cb8f4b6020 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -994,24 +994,27 @@ namespace osu.Game.Screens.Play public override bool OnExiting(IScreen next) { - if (!GameplayState.HasPassed && !GameplayState.HasFailed) - GameplayState.HasQuit = true; - screenSuspension?.RemoveAndDisposeImmediately(); failAnimationLayer?.RemoveFilters(); - // if arriving here and the results screen preparation task hasn't run, it's safe to say the user has not completed the beatmap. - if (prepareScoreForDisplayTask == null) + if (LoadedBeatmapSuccessfully) { - Score.ScoreInfo.Passed = false; - // potentially should be ScoreRank.F instead? this is the best alternative for now. - Score.ScoreInfo.Rank = ScoreRank.D; - } + if (!GameplayState.HasPassed && !GameplayState.HasFailed) + GameplayState.HasQuit = true; - // EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous. - // To resolve test failures, forcefully end playing synchronously when this screen exits. - // Todo: Replace this with a more permanent solution once osu-framework has a synchronous cleanup method. - spectatorClient.EndPlaying(GameplayState); + // if arriving here and the results screen preparation task hasn't run, it's safe to say the user has not completed the beatmap. + if (prepareScoreForDisplayTask == null) + { + Score.ScoreInfo.Passed = false; + // potentially should be ScoreRank.F instead? this is the best alternative for now. + Score.ScoreInfo.Rank = ScoreRank.D; + } + + // EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous. + // To resolve test failures, forcefully end playing synchronously when this screen exits. + // Todo: Replace this with a more permanent solution once osu-framework has a synchronous cleanup method. + spectatorClient.EndPlaying(GameplayState); + } // GameplayClockContainer performs seeks / start / stop operations on the beatmap's track. // as we are no longer the current screen, we cannot guarantee the track is still usable. diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 2cf56be659..b1f2bccddf 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -119,7 +119,8 @@ namespace osu.Game.Screens.Play { bool exiting = base.OnExiting(next); - submitScore(Score.DeepClone()); + if (LoadedBeatmapSuccessfully) + submitScore(Score.DeepClone()); return exiting; } From 2e350a91ccc40487678c6b7ceff7d7c31aa93241 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Mar 2022 18:07:37 +0900 Subject: [PATCH 1083/5199] Fix non-matching filename --- .../Gameplay/{UnknownModTestScene.cs => TestSceneUnknownMod.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Tests/Visual/Gameplay/{UnknownModTestScene.cs => TestSceneUnknownMod.cs} (100%) diff --git a/osu.Game.Tests/Visual/Gameplay/UnknownModTestScene.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneUnknownMod.cs similarity index 100% rename from osu.Game.Tests/Visual/Gameplay/UnknownModTestScene.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneUnknownMod.cs From 5fb51b578f52adf7a426f5871de0c1f0a833cc3c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Mar 2022 19:09:50 +0900 Subject: [PATCH 1084/5199] Update dependencies Mainly for a `Clowd.Squirrel` bump to fix https://github.com/ppy/osu/discussions/17190. --- osu.Desktop/osu.Desktop.csproj | 2 +- .../osu.Game.Rulesets.Osu.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game/osu.Game.csproj | 14 +++++++------- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 32ead231c7..a06484214b 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index bd4c3d3345..4ce29ab5c7 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index acf1e8470b..0bcf533653 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -8,7 +8,7 @@ - + WinExe diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d86fbc693e..c106c373c4 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -19,13 +19,13 @@ - + - - - + + + @@ -35,10 +35,10 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - + From 8539f619c50eb06dc320fc1902e2c3cd0afd23f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Mar 2022 21:16:40 +0900 Subject: [PATCH 1085/5199] Update framework --- osu.Android.props | 2 +- osu.iOS.props | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 5b26b8f36e..c2788f9a48 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index c37692f0d8..e485c69096 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 37328f8d245fc9d50e3a14bfe57a3d9c73d0f1d1 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 9 Mar 2022 20:36:31 +0800 Subject: [PATCH 1086/5199] Extract hit object positioning logic to a separate class It is intentional to not rename the identifiers at this point to produce a cleaner diff. --- .../Mods/OsuHitObjectPositionModifier.cs | 346 ++++++++++++++++++ osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 325 +--------------- 2 files changed, 354 insertions(+), 317 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs b/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs new file mode 100644 index 0000000000..3242b99755 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs @@ -0,0 +1,346 @@ +// 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.Graphics.Primitives; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Osu.Utils; +using osuTK; + +#nullable enable + +namespace osu.Game.Rulesets.Osu.Mods +{ + /// + /// Places hit objects according to information in while keeping objects inside the playfield. + /// + public class OsuHitObjectPositionModifier + { + /// + /// Number of previous hitobjects to be shifted together when an object is being moved. + /// + private const int preceding_hitobjects_to_shift = 10; + + private static readonly Vector2 playfield_centre = OsuPlayfield.BASE_SIZE / 2; + + private readonly List hitObjects; + + private readonly List randomObjects = new List(); + + /// + /// Contains information specifying how each hit object should be placed. + /// The default values correspond to how objects are originally placed in the beatmap. + /// + public IReadOnlyList RandomObjects => randomObjects; + + public OsuHitObjectPositionModifier(List hitObjects) + { + this.hitObjects = hitObjects; + populateHitObjectPositions(); + } + + private void populateHitObjectPositions() + { + Vector2 previousPosition = playfield_centre; + float previousAngle = 0; + + foreach (OsuHitObject hitObject in hitObjects) + { + Vector2 relativePosition = hitObject.Position - previousPosition; + float absoluteAngle = (float)Math.Atan2(relativePosition.Y, relativePosition.X); + float relativeAngle = absoluteAngle - previousAngle; + + randomObjects.Add(new RandomObjectInfo(hitObject) + { + RelativeAngle = relativeAngle, + DistanceFromPrevious = relativePosition.Length + }); + + previousPosition = hitObject.EndPosition; + previousAngle = absoluteAngle; + } + } + + /// + /// Reposition the hit objects according to the information in . + /// + public void ApplyRandomisation() + { + RandomObjectInfo? previous = null; + + for (int i = 0; i < hitObjects.Count; i++) + { + var hitObject = hitObjects[i]; + + var current = randomObjects[i]; + + if (hitObject is Spinner) + { + previous = null; + continue; + } + + computeRandomisedPosition(current, previous, i > 1 ? randomObjects[i - 2] : null); + + // Move hit objects back into the playfield if they are outside of it + Vector2 shift = Vector2.Zero; + + switch (hitObject) + { + case HitCircle circle: + shift = clampHitCircleToPlayfield(circle, current); + break; + + case Slider slider: + shift = clampSliderToPlayfield(slider, current); + break; + } + + if (shift != Vector2.Zero) + { + var toBeShifted = new List(); + + for (int j = i - 1; j >= i - preceding_hitobjects_to_shift && j >= 0; j--) + { + // only shift hit circles + if (!(hitObjects[j] is HitCircle)) break; + + toBeShifted.Add(hitObjects[j]); + } + + if (toBeShifted.Count > 0) + applyDecreasingShift(toBeShifted, shift); + } + + previous = current; + } + } + + /// + /// Compute the randomised position of a hit object while attempting to keep it inside the playfield. + /// + /// The representing the hit object to have the randomised position computed for. + /// The representing the hit object immediately preceding the current one. + /// The representing the hit object immediately preceding the one. + private void computeRandomisedPosition(RandomObjectInfo current, RandomObjectInfo? previous, RandomObjectInfo? beforePrevious) + { + float previousAbsoluteAngle = 0f; + + if (previous != null) + { + Vector2 earliestPosition = beforePrevious?.HitObject.EndPosition ?? playfield_centre; + Vector2 relativePosition = previous.HitObject.Position - earliestPosition; + previousAbsoluteAngle = (float)Math.Atan2(relativePosition.Y, relativePosition.X); + } + + float absoluteAngle = previousAbsoluteAngle + current.RelativeAngle; + + var posRelativeToPrev = new Vector2( + current.DistanceFromPrevious * (float)Math.Cos(absoluteAngle), + current.DistanceFromPrevious * (float)Math.Sin(absoluteAngle) + ); + + Vector2 lastEndPosition = previous?.EndPositionRandomised ?? playfield_centre; + + posRelativeToPrev = OsuHitObjectGenerationUtils.RotateAwayFromEdge(lastEndPosition, posRelativeToPrev); + + current.PositionRandomised = lastEndPosition + posRelativeToPrev; + } + + /// + /// Move the randomised position of a hit circle so that it fits inside the playfield. + /// + /// The deviation from the original randomised position in order to fit within the playfield. + private Vector2 clampHitCircleToPlayfield(HitCircle circle, RandomObjectInfo objectInfo) + { + var previousPosition = objectInfo.PositionRandomised; + objectInfo.EndPositionRandomised = objectInfo.PositionRandomised = clampToPlayfieldWithPadding( + objectInfo.PositionRandomised, + (float)circle.Radius + ); + + circle.Position = objectInfo.PositionRandomised; + + return objectInfo.PositionRandomised - previousPosition; + } + + /// + /// Moves the and all necessary nested s into the if they aren't already. + /// + /// The deviation from the original randomised position in order to fit within the playfield. + private Vector2 clampSliderToPlayfield(Slider slider, RandomObjectInfo objectInfo) + { + var possibleMovementBounds = calculatePossibleMovementBounds(slider); + + var previousPosition = objectInfo.PositionRandomised; + + // Clamp slider position to the placement area + // If the slider is larger than the playfield, force it to stay at the original position + float newX = possibleMovementBounds.Width < 0 + ? objectInfo.PositionOriginal.X + : Math.Clamp(previousPosition.X, possibleMovementBounds.Left, possibleMovementBounds.Right); + + float newY = possibleMovementBounds.Height < 0 + ? objectInfo.PositionOriginal.Y + : Math.Clamp(previousPosition.Y, possibleMovementBounds.Top, possibleMovementBounds.Bottom); + + slider.Position = objectInfo.PositionRandomised = new Vector2(newX, newY); + objectInfo.EndPositionRandomised = slider.EndPosition; + + shiftNestedObjects(slider, objectInfo.PositionRandomised - objectInfo.PositionOriginal); + + return objectInfo.PositionRandomised - previousPosition; + } + + /// + /// Decreasingly shift a list of s by a specified amount. + /// The first item in the list is shifted by the largest amount, while the last item is shifted by the smallest amount. + /// + /// The list of hit objects to be shifted. + /// The amount to be shifted. + private void applyDecreasingShift(IList hitObjects, Vector2 shift) + { + for (int i = 0; i < hitObjects.Count; i++) + { + var hitObject = hitObjects[i]; + // The first object is shifted by a vector slightly smaller than shift + // The last object is shifted by a vector slightly larger than zero + Vector2 position = hitObject.Position + shift * ((hitObjects.Count - i) / (float)(hitObjects.Count + 1)); + + hitObject.Position = clampToPlayfieldWithPadding(position, (float)hitObject.Radius); + } + } + + /// + /// Calculates a which contains all of the possible movements of the slider (in relative X/Y coordinates) + /// such that the entire slider is inside the playfield. + /// + /// + /// If the slider is larger than the playfield, the returned may have negative width/height. + /// + private RectangleF calculatePossibleMovementBounds(Slider slider) + { + var pathPositions = new List(); + slider.Path.GetPathToProgress(pathPositions, 0, 1); + + float minX = float.PositiveInfinity; + float maxX = float.NegativeInfinity; + + float minY = float.PositiveInfinity; + float maxY = float.NegativeInfinity; + + // Compute the bounding box of the slider. + foreach (var pos in pathPositions) + { + minX = MathF.Min(minX, pos.X); + maxX = MathF.Max(maxX, pos.X); + + minY = MathF.Min(minY, pos.Y); + maxY = MathF.Max(maxY, pos.Y); + } + + // Take the circle radius into account. + float radius = (float)slider.Radius; + + minX -= radius; + minY -= radius; + + maxX += radius; + maxY += radius; + + // Given the bounding box of the slider (via min/max X/Y), + // the amount that the slider can move to the left is minX (with the sign flipped, since positive X is to the right), + // and the amount that it can move to the right is WIDTH - maxX. + // Same calculation applies for the Y axis. + float left = -minX; + float right = OsuPlayfield.BASE_SIZE.X - maxX; + float top = -minY; + float bottom = OsuPlayfield.BASE_SIZE.Y - maxY; + + return new RectangleF(left, top, right - left, bottom - top); + } + + /// + /// Shifts all nested s and s by the specified shift. + /// + /// whose nested s and s should be shifted + /// The the 's nested s and s should be shifted by + private void shiftNestedObjects(Slider slider, Vector2 shift) + { + foreach (var hitObject in slider.NestedHitObjects.Where(o => o is SliderTick || o is SliderRepeat)) + { + if (!(hitObject is OsuHitObject osuHitObject)) + continue; + + osuHitObject.Position += shift; + } + } + + /// + /// Clamp a position to playfield, keeping a specified distance from the edges. + /// + /// The position to be clamped. + /// The minimum distance allowed from playfield edges. + /// The clamped position. + private Vector2 clampToPlayfieldWithPadding(Vector2 position, float padding) + { + return new Vector2( + Math.Clamp(position.X, padding, OsuPlayfield.BASE_SIZE.X - padding), + Math.Clamp(position.Y, padding, OsuPlayfield.BASE_SIZE.Y - padding) + ); + } + + public interface IRandomObjectInfo + { + /// + /// The jump angle from the previous hit object to this one, relative to the previous hit object's jump angle. + /// + /// + /// of the first hit object in a beatmap represents the absolute angle from playfield center to the object. + /// + /// + /// If is 0, the player's cursor doesn't need to change its direction of movement when passing + /// the previous object to reach this one. + /// + float RelativeAngle { get; set; } + + /// + /// The jump distance from the previous hit object to this one. + /// + /// + /// of the first hit object in a beatmap is relative to the playfield center. + /// + float DistanceFromPrevious { get; set; } + + /// + /// The hit object associated with this . + /// + OsuHitObject HitObject { get; } + } + + private class RandomObjectInfo : IRandomObjectInfo + { + public float RelativeAngle { get; set; } + + public float DistanceFromPrevious { get; set; } + + public Vector2 PositionOriginal { get; } + public Vector2 PositionRandomised { get; set; } + + public Vector2 EndPositionOriginal { get; } + public Vector2 EndPositionRandomised { get; set; } + + public OsuHitObject HitObject { get; } + + public RandomObjectInfo(OsuHitObject hitObject) + { + PositionRandomised = PositionOriginal = hitObject.Position; + EndPositionRandomised = EndPositionOriginal = hitObject.EndPosition; + HitObject = hitObject; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 7479c3120a..2c38be6c16 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -4,19 +4,13 @@ #nullable enable using System; -using System.Collections.Generic; -using System.Diagnostics; using System.Linq; -using osu.Framework.Graphics.Primitives; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; -using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.Osu.Utils; -using osuTK; namespace osu.Game.Rulesets.Osu.Mods { @@ -28,12 +22,6 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => "It never gets boring!"; private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast; - private static readonly Vector2 playfield_centre = OsuPlayfield.BASE_SIZE / 2; - - /// - /// Number of previous hitobjects to be shifted together when another object is being moved. - /// - private const int preceding_hitobjects_to_shift = 10; private Random? rng; @@ -42,330 +30,33 @@ namespace osu.Game.Rulesets.Osu.Mods if (!(beatmap is OsuBeatmap osuBeatmap)) return; - var hitObjects = osuBeatmap.HitObjects; - Seed.Value ??= RNG.Next(); rng = new Random((int)Seed.Value); - var randomObjects = randomiseObjects(hitObjects); + var positionModifier = new OsuHitObjectPositionModifier(osuBeatmap.HitObjects); - applyRandomisation(hitObjects, randomObjects); - } - - /// - /// Randomise the position of each hit object and return a list of s describing how each hit object should be placed. - /// - /// A list of s to have their positions randomised. - /// A list of s describing how each hit object should be placed. - private List randomiseObjects(IEnumerable hitObjects) - { - Debug.Assert(rng != null, $"{nameof(ApplyToBeatmap)} was not called before randomising objects"); - - var randomObjects = new List(); - RandomObjectInfo? previous = null; float rateOfChangeMultiplier = 0; - foreach (OsuHitObject hitObject in hitObjects) + foreach (var positionInfo in positionModifier.RandomObjects) { - var current = new RandomObjectInfo(hitObject); - randomObjects.Add(current); - // rateOfChangeMultiplier only changes every 5 iterations in a combo // to prevent shaky-line-shaped streams - if (hitObject.IndexInCurrentCombo % 5 == 0) + if (positionInfo.HitObject.IndexInCurrentCombo % 5 == 0) rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; - if (previous == null) + if (positionInfo == positionModifier.RandomObjects.First()) { - current.DistanceFromPrevious = (float)(rng.NextDouble() * OsuPlayfield.BASE_SIZE.X / 2); - current.RelativeAngle = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI); + positionInfo.DistanceFromPrevious = (float)rng.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2; + positionInfo.RelativeAngle = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI); } else { - current.DistanceFromPrevious = Vector2.Distance(previous.EndPositionOriginal, current.PositionOriginal); - - // The max. angle (relative to the angle of the vector pointing from the 2nd last to the last hit object) - // is proportional to the distance between the last and the current hit object - // to allow jumps and prevent too sharp turns during streams. - - // Allow maximum jump angle when jump distance is more than half of playfield diagonal length - current.RelativeAngle = rateOfChangeMultiplier * 2 * (float)Math.PI * Math.Min(1f, current.DistanceFromPrevious / (playfield_diagonal * 0.5f)); + positionInfo.RelativeAngle = (float)(rateOfChangeMultiplier * 2 * Math.PI * Math.Min(1f, positionInfo.DistanceFromPrevious / (playfield_diagonal * 0.5f))); } - - previous = current; } - return randomObjects; - } - - /// - /// Reposition the hit objects according to the information in . - /// - /// The hit objects to be repositioned. - /// A list of describing how each hit object should be placed. - private void applyRandomisation(IReadOnlyList hitObjects, IReadOnlyList randomObjects) - { - RandomObjectInfo? previous = null; - - for (int i = 0; i < hitObjects.Count; i++) - { - var hitObject = hitObjects[i]; - - var current = randomObjects[i]; - - if (hitObject is Spinner) - { - previous = null; - continue; - } - - computeRandomisedPosition(current, previous, i > 1 ? randomObjects[i - 2] : null); - - // Move hit objects back into the playfield if they are outside of it - Vector2 shift = Vector2.Zero; - - switch (hitObject) - { - case HitCircle circle: - shift = clampHitCircleToPlayfield(circle, current); - break; - - case Slider slider: - shift = clampSliderToPlayfield(slider, current); - break; - } - - if (shift != Vector2.Zero) - { - var toBeShifted = new List(); - - for (int j = i - 1; j >= i - preceding_hitobjects_to_shift && j >= 0; j--) - { - // only shift hit circles - if (!(hitObjects[j] is HitCircle)) break; - - toBeShifted.Add(hitObjects[j]); - } - - if (toBeShifted.Count > 0) - applyDecreasingShift(toBeShifted, shift); - } - - previous = current; - } - } - - /// - /// Compute the randomised position of a hit object while attempting to keep it inside the playfield. - /// - /// The representing the hit object to have the randomised position computed for. - /// The representing the hit object immediately preceding the current one. - /// The representing the hit object immediately preceding the one. - private void computeRandomisedPosition(RandomObjectInfo current, RandomObjectInfo? previous, RandomObjectInfo? beforePrevious) - { - float previousAbsoluteAngle = 0f; - - if (previous != null) - { - Vector2 earliestPosition = beforePrevious?.HitObject.EndPosition ?? playfield_centre; - Vector2 relativePosition = previous.HitObject.Position - earliestPosition; - previousAbsoluteAngle = (float)Math.Atan2(relativePosition.Y, relativePosition.X); - } - - float absoluteAngle = previousAbsoluteAngle + current.RelativeAngle; - - var posRelativeToPrev = new Vector2( - current.DistanceFromPrevious * (float)Math.Cos(absoluteAngle), - current.DistanceFromPrevious * (float)Math.Sin(absoluteAngle) - ); - - Vector2 lastEndPosition = previous?.EndPositionRandomised ?? playfield_centre; - - posRelativeToPrev = OsuHitObjectGenerationUtils.RotateAwayFromEdge(lastEndPosition, posRelativeToPrev); - - current.PositionRandomised = lastEndPosition + posRelativeToPrev; - } - - /// - /// Move the randomised position of a hit circle so that it fits inside the playfield. - /// - /// The deviation from the original randomised position in order to fit within the playfield. - private Vector2 clampHitCircleToPlayfield(HitCircle circle, RandomObjectInfo objectInfo) - { - var previousPosition = objectInfo.PositionRandomised; - objectInfo.EndPositionRandomised = objectInfo.PositionRandomised = clampToPlayfieldWithPadding( - objectInfo.PositionRandomised, - (float)circle.Radius - ); - - circle.Position = objectInfo.PositionRandomised; - - return objectInfo.PositionRandomised - previousPosition; - } - - /// - /// Moves the and all necessary nested s into the if they aren't already. - /// - /// The deviation from the original randomised position in order to fit within the playfield. - private Vector2 clampSliderToPlayfield(Slider slider, RandomObjectInfo objectInfo) - { - var possibleMovementBounds = calculatePossibleMovementBounds(slider); - - var previousPosition = objectInfo.PositionRandomised; - - // Clamp slider position to the placement area - // If the slider is larger than the playfield, force it to stay at the original position - float newX = possibleMovementBounds.Width < 0 - ? objectInfo.PositionOriginal.X - : Math.Clamp(previousPosition.X, possibleMovementBounds.Left, possibleMovementBounds.Right); - - float newY = possibleMovementBounds.Height < 0 - ? objectInfo.PositionOriginal.Y - : Math.Clamp(previousPosition.Y, possibleMovementBounds.Top, possibleMovementBounds.Bottom); - - slider.Position = objectInfo.PositionRandomised = new Vector2(newX, newY); - objectInfo.EndPositionRandomised = slider.EndPosition; - - shiftNestedObjects(slider, objectInfo.PositionRandomised - objectInfo.PositionOriginal); - - return objectInfo.PositionRandomised - previousPosition; - } - - /// - /// Decreasingly shift a list of s by a specified amount. - /// The first item in the list is shifted by the largest amount, while the last item is shifted by the smallest amount. - /// - /// The list of hit objects to be shifted. - /// The amount to be shifted. - private void applyDecreasingShift(IList hitObjects, Vector2 shift) - { - for (int i = 0; i < hitObjects.Count; i++) - { - var hitObject = hitObjects[i]; - // The first object is shifted by a vector slightly smaller than shift - // The last object is shifted by a vector slightly larger than zero - Vector2 position = hitObject.Position + shift * ((hitObjects.Count - i) / (float)(hitObjects.Count + 1)); - - hitObject.Position = clampToPlayfieldWithPadding(position, (float)hitObject.Radius); - } - } - - /// - /// Calculates a which contains all of the possible movements of the slider (in relative X/Y coordinates) - /// such that the entire slider is inside the playfield. - /// - /// - /// If the slider is larger than the playfield, the returned may have negative width/height. - /// - private RectangleF calculatePossibleMovementBounds(Slider slider) - { - var pathPositions = new List(); - slider.Path.GetPathToProgress(pathPositions, 0, 1); - - float minX = float.PositiveInfinity; - float maxX = float.NegativeInfinity; - - float minY = float.PositiveInfinity; - float maxY = float.NegativeInfinity; - - // Compute the bounding box of the slider. - foreach (var pos in pathPositions) - { - minX = MathF.Min(minX, pos.X); - maxX = MathF.Max(maxX, pos.X); - - minY = MathF.Min(minY, pos.Y); - maxY = MathF.Max(maxY, pos.Y); - } - - // Take the circle radius into account. - float radius = (float)slider.Radius; - - minX -= radius; - minY -= radius; - - maxX += radius; - maxY += radius; - - // Given the bounding box of the slider (via min/max X/Y), - // the amount that the slider can move to the left is minX (with the sign flipped, since positive X is to the right), - // and the amount that it can move to the right is WIDTH - maxX. - // Same calculation applies for the Y axis. - float left = -minX; - float right = OsuPlayfield.BASE_SIZE.X - maxX; - float top = -minY; - float bottom = OsuPlayfield.BASE_SIZE.Y - maxY; - - return new RectangleF(left, top, right - left, bottom - top); - } - - /// - /// Shifts all nested s and s by the specified shift. - /// - /// whose nested s and s should be shifted - /// The the 's nested s and s should be shifted by - private void shiftNestedObjects(Slider slider, Vector2 shift) - { - foreach (var hitObject in slider.NestedHitObjects.Where(o => o is SliderTick || o is SliderRepeat)) - { - if (!(hitObject is OsuHitObject osuHitObject)) - continue; - - osuHitObject.Position += shift; - } - } - - /// - /// Clamp a position to playfield, keeping a specified distance from the edges. - /// - /// The position to be clamped. - /// The minimum distance allowed from playfield edges. - /// The clamped position. - private Vector2 clampToPlayfieldWithPadding(Vector2 position, float padding) - { - return new Vector2( - Math.Clamp(position.X, padding, OsuPlayfield.BASE_SIZE.X - padding), - Math.Clamp(position.Y, padding, OsuPlayfield.BASE_SIZE.Y - padding) - ); - } - - private class RandomObjectInfo - { - /// - /// The jump angle from the previous hit object to this one, relative to the previous hit object's jump angle. - /// - /// - /// of the first hit object in a beatmap represents the absolute angle from playfield center to the object. - /// - /// - /// If is 0, the player's cursor doesn't need to change its direction of movement when passing - /// the previous object to reach this one. - /// - public float RelativeAngle { get; set; } - - /// - /// The jump distance from the previous hit object to this one. - /// - /// - /// of the first hit object in a beatmap is relative to the playfield center. - /// - public float DistanceFromPrevious { get; set; } - - public Vector2 PositionOriginal { get; } - public Vector2 PositionRandomised { get; set; } - - public Vector2 EndPositionOriginal { get; } - public Vector2 EndPositionRandomised { get; set; } - - public OsuHitObject HitObject { get; } - - public RandomObjectInfo(OsuHitObject hitObject) - { - PositionRandomised = PositionOriginal = hitObject.Position; - EndPositionRandomised = EndPositionOriginal = hitObject.EndPosition; - HitObject = hitObject; - } + positionModifier.ApplyRandomisation(); } } } From b59117caf643d04e1f35d8814811dc7c3bce2791 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Mar 2022 21:42:02 +0900 Subject: [PATCH 1087/5199] Update silly iOS dependencies --- osu.iOS.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.iOS.props b/osu.iOS.props index e485c69096..86cf1b229c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -79,7 +79,7 @@ - + @@ -89,6 +89,6 @@ - + From 6a507ca11bdf4c95cba876be61bacbc4a9e1a9d5 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 9 Mar 2022 20:52:11 +0800 Subject: [PATCH 1088/5199] Rename identifiers to remove references to random mod --- .../Mods/OsuHitObjectPositionModifier.cs | 86 +++++++++---------- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 6 +- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs b/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs index 3242b99755..84ad198951 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Mods { /// - /// Places hit objects according to information in while keeping objects inside the playfield. + /// Places hit objects according to information in while keeping objects inside the playfield. /// public class OsuHitObjectPositionModifier { @@ -28,21 +28,21 @@ namespace osu.Game.Rulesets.Osu.Mods private readonly List hitObjects; - private readonly List randomObjects = new List(); + private readonly List objectPositionInfos = new List(); /// /// Contains information specifying how each hit object should be placed. /// The default values correspond to how objects are originally placed in the beatmap. /// - public IReadOnlyList RandomObjects => randomObjects; + public IReadOnlyList ObjectPositionInfos => objectPositionInfos; public OsuHitObjectPositionModifier(List hitObjects) { this.hitObjects = hitObjects; - populateHitObjectPositions(); + populateObjectPositionInfos(); } - private void populateHitObjectPositions() + private void populateObjectPositionInfos() { Vector2 previousPosition = playfield_centre; float previousAngle = 0; @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Mods float absoluteAngle = (float)Math.Atan2(relativePosition.Y, relativePosition.X); float relativeAngle = absoluteAngle - previousAngle; - randomObjects.Add(new RandomObjectInfo(hitObject) + objectPositionInfos.Add(new ObjectPositionInfo(hitObject) { RelativeAngle = relativeAngle, DistanceFromPrevious = relativePosition.Length @@ -65,17 +65,17 @@ namespace osu.Game.Rulesets.Osu.Mods } /// - /// Reposition the hit objects according to the information in . + /// Reposition the hit objects according to the information in . /// - public void ApplyRandomisation() + public void ApplyModifications() { - RandomObjectInfo? previous = null; + ObjectPositionInfo? previous = null; for (int i = 0; i < hitObjects.Count; i++) { var hitObject = hitObjects[i]; - var current = randomObjects[i]; + var current = objectPositionInfos[i]; if (hitObject is Spinner) { @@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Mods continue; } - computeRandomisedPosition(current, previous, i > 1 ? randomObjects[i - 2] : null); + computeModifiedPosition(current, previous, i > 1 ? objectPositionInfos[i - 2] : null); // Move hit objects back into the playfield if they are outside of it Vector2 shift = Vector2.Zero; @@ -120,12 +120,12 @@ namespace osu.Game.Rulesets.Osu.Mods } /// - /// Compute the randomised position of a hit object while attempting to keep it inside the playfield. + /// Compute the modified position of a hit object while attempting to keep it inside the playfield. /// - /// The representing the hit object to have the randomised position computed for. - /// The representing the hit object immediately preceding the current one. - /// The representing the hit object immediately preceding the one. - private void computeRandomisedPosition(RandomObjectInfo current, RandomObjectInfo? previous, RandomObjectInfo? beforePrevious) + /// The representing the hit object to have the modified position computed for. + /// The representing the hit object immediately preceding the current one. + /// The representing the hit object immediately preceding the one. + private void computeModifiedPosition(ObjectPositionInfo current, ObjectPositionInfo? previous, ObjectPositionInfo? beforePrevious) { float previousAbsoluteAngle = 0f; @@ -143,56 +143,56 @@ namespace osu.Game.Rulesets.Osu.Mods current.DistanceFromPrevious * (float)Math.Sin(absoluteAngle) ); - Vector2 lastEndPosition = previous?.EndPositionRandomised ?? playfield_centre; + Vector2 lastEndPosition = previous?.EndPositionModified ?? playfield_centre; posRelativeToPrev = OsuHitObjectGenerationUtils.RotateAwayFromEdge(lastEndPosition, posRelativeToPrev); - current.PositionRandomised = lastEndPosition + posRelativeToPrev; + current.PositionModified = lastEndPosition + posRelativeToPrev; } /// - /// Move the randomised position of a hit circle so that it fits inside the playfield. + /// Move the modified position of a hit circle so that it fits inside the playfield. /// - /// The deviation from the original randomised position in order to fit within the playfield. - private Vector2 clampHitCircleToPlayfield(HitCircle circle, RandomObjectInfo objectInfo) + /// The deviation from the original modified position in order to fit within the playfield. + private Vector2 clampHitCircleToPlayfield(HitCircle circle, ObjectPositionInfo objectPositionInfo) { - var previousPosition = objectInfo.PositionRandomised; - objectInfo.EndPositionRandomised = objectInfo.PositionRandomised = clampToPlayfieldWithPadding( - objectInfo.PositionRandomised, + var previousPosition = objectPositionInfo.PositionModified; + objectPositionInfo.EndPositionModified = objectPositionInfo.PositionModified = clampToPlayfieldWithPadding( + objectPositionInfo.PositionModified, (float)circle.Radius ); - circle.Position = objectInfo.PositionRandomised; + circle.Position = objectPositionInfo.PositionModified; - return objectInfo.PositionRandomised - previousPosition; + return objectPositionInfo.PositionModified - previousPosition; } /// /// Moves the and all necessary nested s into the if they aren't already. /// - /// The deviation from the original randomised position in order to fit within the playfield. - private Vector2 clampSliderToPlayfield(Slider slider, RandomObjectInfo objectInfo) + /// The deviation from the original modified position in order to fit within the playfield. + private Vector2 clampSliderToPlayfield(Slider slider, ObjectPositionInfo objectPositionInfo) { var possibleMovementBounds = calculatePossibleMovementBounds(slider); - var previousPosition = objectInfo.PositionRandomised; + var previousPosition = objectPositionInfo.PositionModified; // Clamp slider position to the placement area // If the slider is larger than the playfield, force it to stay at the original position float newX = possibleMovementBounds.Width < 0 - ? objectInfo.PositionOriginal.X + ? objectPositionInfo.PositionOriginal.X : Math.Clamp(previousPosition.X, possibleMovementBounds.Left, possibleMovementBounds.Right); float newY = possibleMovementBounds.Height < 0 - ? objectInfo.PositionOriginal.Y + ? objectPositionInfo.PositionOriginal.Y : Math.Clamp(previousPosition.Y, possibleMovementBounds.Top, possibleMovementBounds.Bottom); - slider.Position = objectInfo.PositionRandomised = new Vector2(newX, newY); - objectInfo.EndPositionRandomised = slider.EndPosition; + slider.Position = objectPositionInfo.PositionModified = new Vector2(newX, newY); + objectPositionInfo.EndPositionModified = slider.EndPosition; - shiftNestedObjects(slider, objectInfo.PositionRandomised - objectInfo.PositionOriginal); + shiftNestedObjects(slider, objectPositionInfo.PositionModified - objectPositionInfo.PositionOriginal); - return objectInfo.PositionRandomised - previousPosition; + return objectPositionInfo.PositionModified - previousPosition; } /// @@ -293,7 +293,7 @@ namespace osu.Game.Rulesets.Osu.Mods ); } - public interface IRandomObjectInfo + public interface IObjectPositionInfo { /// /// The jump angle from the previous hit object to this one, relative to the previous hit object's jump angle. @@ -316,29 +316,29 @@ namespace osu.Game.Rulesets.Osu.Mods float DistanceFromPrevious { get; set; } /// - /// The hit object associated with this . + /// The hit object associated with this . /// OsuHitObject HitObject { get; } } - private class RandomObjectInfo : IRandomObjectInfo + private class ObjectPositionInfo : IObjectPositionInfo { public float RelativeAngle { get; set; } public float DistanceFromPrevious { get; set; } public Vector2 PositionOriginal { get; } - public Vector2 PositionRandomised { get; set; } + public Vector2 PositionModified { get; set; } public Vector2 EndPositionOriginal { get; } - public Vector2 EndPositionRandomised { get; set; } + public Vector2 EndPositionModified { get; set; } public OsuHitObject HitObject { get; } - public RandomObjectInfo(OsuHitObject hitObject) + public ObjectPositionInfo(OsuHitObject hitObject) { - PositionRandomised = PositionOriginal = hitObject.Position; - EndPositionRandomised = EndPositionOriginal = hitObject.EndPosition; + PositionModified = PositionOriginal = hitObject.Position; + EndPositionModified = EndPositionOriginal = hitObject.EndPosition; HitObject = hitObject; } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 2c38be6c16..59abc73ed9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -38,14 +38,14 @@ namespace osu.Game.Rulesets.Osu.Mods float rateOfChangeMultiplier = 0; - foreach (var positionInfo in positionModifier.RandomObjects) + foreach (var positionInfo in positionModifier.ObjectPositionInfos) { // rateOfChangeMultiplier only changes every 5 iterations in a combo // to prevent shaky-line-shaped streams if (positionInfo.HitObject.IndexInCurrentCombo % 5 == 0) rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; - if (positionInfo == positionModifier.RandomObjects.First()) + if (positionInfo == positionModifier.ObjectPositionInfos.First()) { positionInfo.DistanceFromPrevious = (float)rng.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2; positionInfo.RelativeAngle = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI); @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Mods } } - positionModifier.ApplyRandomisation(); + positionModifier.ApplyModifications(); } } } From 8e12a067dfb1695c984d1d4705aff15c5af18b8b Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 9 Mar 2022 21:04:35 +0800 Subject: [PATCH 1089/5199] Remove an unused property --- osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs b/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs index 84ad198951..16ec25f389 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs @@ -329,8 +329,6 @@ namespace osu.Game.Rulesets.Osu.Mods public Vector2 PositionOriginal { get; } public Vector2 PositionModified { get; set; } - - public Vector2 EndPositionOriginal { get; } public Vector2 EndPositionModified { get; set; } public OsuHitObject HitObject { get; } @@ -338,7 +336,7 @@ namespace osu.Game.Rulesets.Osu.Mods public ObjectPositionInfo(OsuHitObject hitObject) { PositionModified = PositionOriginal = hitObject.Position; - EndPositionModified = EndPositionOriginal = hitObject.EndPosition; + EndPositionModified = hitObject.EndPosition; HitObject = hitObject; } } From e8dbed738e4b819f5a68bf9b1df6a2d1b2a824a7 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Wed, 9 Mar 2022 21:52:15 +0800 Subject: [PATCH 1090/5199] Move `OsuHitObjectPositionModifier` to `Utils/` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 1 + .../{Mods => Utils}/OsuHitObjectPositionModifier.cs | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game.Rulesets.Osu/{Mods => Utils}/OsuHitObjectPositionModifier.cs (99%) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 59abc73ed9..cdaa8fa3d5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Osu.Utils; namespace osu.Game.Rulesets.Osu.Mods { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectPositionModifier.cs similarity index 99% rename from osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs rename to osu.Game.Rulesets.Osu/Utils/OsuHitObjectPositionModifier.cs index 16ec25f389..428866623f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuHitObjectPositionModifier.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectPositionModifier.cs @@ -7,12 +7,11 @@ using System.Linq; using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.Osu.Utils; using osuTK; #nullable enable -namespace osu.Game.Rulesets.Osu.Mods +namespace osu.Game.Rulesets.Osu.Utils { /// /// Places hit objects according to information in while keeping objects inside the playfield. From b67f9269f919d47647be5a72d71f1667500194bb Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 9 Mar 2022 18:13:56 +0000 Subject: [PATCH 1091/5199] Remove `NewChat` namespace --- .../Visual/Online/{NewChat => }/TestSceneChannelListing.cs | 4 ++-- osu.Game/Overlays/{NewChat => Chat/Listing}/ChannelListing.cs | 2 +- .../Overlays/{NewChat => Chat/Listing}/ChannelListingItem.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game.Tests/Visual/Online/{NewChat => }/TestSceneChannelListing.cs (97%) rename osu.Game/Overlays/{NewChat => Chat/Listing}/ChannelListing.cs (98%) rename osu.Game/Overlays/{NewChat => Chat/Listing}/ChannelListingItem.cs (99%) diff --git a/osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelListing.cs similarity index 97% rename from osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs rename to osu.Game.Tests/Visual/Online/TestSceneChannelListing.cs index 8f955c0520..e521db1c9d 100644 --- a/osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelListing.cs @@ -11,10 +11,10 @@ using osu.Framework.Utils; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Chat; using osu.Game.Overlays; -using osu.Game.Overlays.NewChat; +using osu.Game.Overlays.Chat.Listing; using osuTK; -namespace osu.Game.Tests.Visual.Online.NewChat +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestSceneChannelListing : OsuTestScene diff --git a/osu.Game/Overlays/NewChat/ChannelListing.cs b/osu.Game/Overlays/Chat/Listing/ChannelListing.cs similarity index 98% rename from osu.Game/Overlays/NewChat/ChannelListing.cs rename to osu.Game/Overlays/Chat/Listing/ChannelListing.cs index 3de6364303..9b8b45c85f 100644 --- a/osu.Game/Overlays/NewChat/ChannelListing.cs +++ b/osu.Game/Overlays/Chat/Listing/ChannelListing.cs @@ -14,7 +14,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Online.Chat; using osuTK; -namespace osu.Game.Overlays.NewChat +namespace osu.Game.Overlays.Chat.Listing { public class ChannelListing : VisibilityContainer { diff --git a/osu.Game/Overlays/NewChat/ChannelListingItem.cs b/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs similarity index 99% rename from osu.Game/Overlays/NewChat/ChannelListingItem.cs rename to osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs index 77d5599c9b..9586730c6b 100644 --- a/osu.Game/Overlays/NewChat/ChannelListingItem.cs +++ b/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs @@ -18,7 +18,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; using osuTK; -namespace osu.Game.Overlays.NewChat +namespace osu.Game.Overlays.Chat.Listing { public class ChannelListingItem : OsuClickableContainer, IFilterable { From d2983b74d53714176600b02387a8f2370b2d264f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 9 Mar 2022 20:15:34 +0100 Subject: [PATCH 1092/5199] Bump Moq in mobile test projects for parity --- osu.Game.Tests.Android/osu.Game.Tests.Android.csproj | 2 +- osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj index b45a3249ff..afafec6b1f 100644 --- a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj +++ b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj @@ -79,7 +79,7 @@ - + 5.0.0 diff --git a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj index 97df9b2cd5..05b3cad6da 100644 --- a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj +++ b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj @@ -48,7 +48,7 @@ - + From 5cb058a17d636ef70e2ea833a9401fd6dd41d332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 9 Mar 2022 20:17:46 +0100 Subject: [PATCH 1093/5199] Bump Realm in Android test project for parity --- osu.Android.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android.props b/osu.Android.props index c2788f9a48..839f7882bf 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -56,6 +56,6 @@ - + From 2d8983383a02a830b4e1d84af34c795d077eb562 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 8 Mar 2022 04:43:40 +0300 Subject: [PATCH 1094/5199] Revert `newMessagesArrived` changes and always schedule highlight In the case a message arrives and the chat overlay is hidden, clicking on the mention notification will not work as the `HighlightedMessage` bindable callback will execute before the scheduled `newMessagesArrived` logic (which was hanging since the message arrived until the chat overlay became open because of the notification). Simplify things by always scheduling the `HighlightedMessage` bindable callback. --- osu.Game/Overlays/Chat/DrawableChannel.cs | 82 ++++++++++++----------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index a73e61c52b..0cc3260e60 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -74,33 +74,37 @@ namespace osu.Game.Overlays.Chat } }, }; + + newMessagesArrived(Channel.Messages); + + Channel.NewMessagesArrived += newMessagesArrived; + Channel.MessageRemoved += messageRemoved; + Channel.PendingMessageResolved += pendingMessageResolved; } protected override void LoadComplete() { base.LoadComplete(); - addChatLines(Channel.Messages); - - Channel.NewMessagesArrived += newMessagesArrived; - Channel.MessageRemoved += messageRemoved; - Channel.PendingMessageResolved += pendingMessageResolved; - highlightedMessage = Channel.HighlightedMessage.GetBoundCopy(); highlightedMessage.BindValueChanged(m => { if (m.NewValue == null) return; - var chatLine = chatLines.SingleOrDefault(c => c.Message.Equals(m.NewValue)); - - if (chatLine != null) + // schedule highlight to ensure it performs after any pending "newMessagesArrived" calls. + Schedule(() => { - scroll.ScrollIntoView(chatLine); - chatLine.ScheduleHighlight(); - } + var chatLine = chatLines.SingleOrDefault(c => c.Message.Equals(m.NewValue)); - highlightedMessage.Value = null; + if (chatLine != null) + { + scroll.ScrollIntoView(chatLine); + chatLine.ScheduleHighlight(); + } + + highlightedMessage.Value = null; + }); }, true); } @@ -121,39 +125,18 @@ namespace osu.Game.Overlays.Chat Colour = colours.ChatBlue.Lighten(0.7f), }; - private void newMessagesArrived(IEnumerable newMessages) => Schedule(addChatLines, newMessages); - - private void pendingMessageResolved(Message existing, Message updated) => Schedule(() => + private void newMessagesArrived(IEnumerable newMessages) => Schedule(() => { - var found = chatLines.LastOrDefault(c => c.Message == existing); - - if (found != null) - { - Trace.Assert(updated.Id.HasValue, "An updated message was returned with no ID."); - - ChatLineFlow.Remove(found); - found.Message = updated; - ChatLineFlow.Add(found); - } - }); - - private void messageRemoved(Message removed) => Schedule(() => - { - chatLines.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); - }); - - private void addChatLines(IEnumerable messages) - { - if (messages.Min(m => m.Id) < chatLines.Max(c => c.Message.Id)) + if (newMessages.Min(m => m.Id) < chatLines.Max(c => c.Message.Id)) { // there is a case (on initial population) that we may receive past messages and need to reorder. // easiest way is to just combine messages and recreate drawables (less worrying about day separators etc.) - messages = messages.Concat(chatLines.Select(c => c.Message)).OrderBy(m => m.Id).ToList(); + newMessages = newMessages.Concat(chatLines.Select(c => c.Message)).OrderBy(m => m.Id).ToList(); ChatLineFlow.Clear(); } // Add up to last Channel.MAX_HISTORY messages - var displayMessages = messages.Skip(Math.Max(0, messages.Count() - Channel.MAX_HISTORY)); + var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY)); Message lastMessage = chatLines.LastOrDefault()?.Message; @@ -192,9 +175,28 @@ namespace osu.Game.Overlays.Chat // due to the scroll adjusts from old messages removal above, a scroll-to-end must be enforced, // to avoid making the container think the user has scrolled back up and unwantedly disable auto-scrolling. - if (messages.Any(m => m is LocalMessage)) + if (newMessages.Any(m => m is LocalMessage)) scroll.ScrollToEnd(); - } + }); + + private void pendingMessageResolved(Message existing, Message updated) => Schedule(() => + { + var found = chatLines.LastOrDefault(c => c.Message == existing); + + if (found != null) + { + Trace.Assert(updated.Id.HasValue, "An updated message was returned with no ID."); + + ChatLineFlow.Remove(found); + found.Message = updated; + ChatLineFlow.Add(found); + } + }); + + private void messageRemoved(Message removed) => Schedule(() => + { + chatLines.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); + }); private IEnumerable chatLines => ChatLineFlow.Children.OfType(); From 93cf93943f5ff339443e7cffba97c09f51113ec9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 02:48:33 +0300 Subject: [PATCH 1095/5199] Schedule chat line highlight after children to handle non-loaded lines --- osu.Game/Overlays/Chat/DrawableChannel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 0cc3260e60..d5df6102f2 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -93,7 +93,8 @@ namespace osu.Game.Overlays.Chat return; // schedule highlight to ensure it performs after any pending "newMessagesArrived" calls. - Schedule(() => + // also schedule after children to ensure the scroll flow is updated with any new chat lines. + ScheduleAfterChildren(() => { var chatLine = chatLines.SingleOrDefault(c => c.Message.Equals(m.NewValue)); From 80c0df6af578962b91757d36e8a9de53664acfc3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 02:49:23 +0300 Subject: [PATCH 1096/5199] Scroll chat line to channel center We may eventually want that encapsulated within `ScrollIntoView`, as it would also come in handy for `GameplayLeaderboard`. --- .../Online/TestSceneStandAloneChatDisplay.cs | 17 ++++++++++------- .../Overlays/Chat/ChannelScrollContainer.cs | 6 ++++++ osu.Game/Overlays/Chat/DrawableChannel.cs | 3 ++- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index c9837be934..17c818963e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -152,7 +152,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("highlight first message", () => { highlighted = testChannel.Messages[0]; - chatDisplay.DrawableChannel.Channel.HighlightedMessage.Value = highlighted; + testChannel.HighlightedMessage.Value = highlighted; }); AddUntilStep("chat scrolled to first message", () => @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("highlight last message", () => { highlighted = testChannel.Messages[^1]; - chatDisplay.DrawableChannel.Channel.HighlightedMessage.Value = highlighted; + testChannel.HighlightedMessage.Value = highlighted; }); AddUntilStep("chat scrolled to last message", () => @@ -182,19 +182,22 @@ namespace osu.Game.Tests.Visual.Online AddRepeatStep("highlight other random messages", () => { highlighted = testChannel.Messages[RNG.Next(0, testChannel.Messages.Count - 1)]; - chatDisplay.DrawableChannel.Channel.HighlightedMessage.Value = highlighted; + testChannel.HighlightedMessage.Value = highlighted; }, 10); } [Test] public void TestMessageHighlightingOnFilledChat() { + int index = 0; + fillChat(); - AddRepeatStep("highlight random messages", () => - { - chatDisplay.DrawableChannel.Channel.HighlightedMessage.Value = testChannel.Messages[RNG.Next(0, testChannel.Messages.Count - 1)]; - }, 10); + AddStep("highlight first message", () => testChannel.HighlightedMessage.Value = testChannel.Messages[index = 0]); + AddStep("highlight next message", () => testChannel.HighlightedMessage.Value = testChannel.Messages[index = Math.Min(index + 1, testChannel.Messages.Count - 1)]); + AddStep("highlight last message", () => testChannel.HighlightedMessage.Value = testChannel.Messages[index = testChannel.Messages.Count - 1]); + AddStep("highlight previous message", () => testChannel.HighlightedMessage.Value = testChannel.Messages[index = Math.Max(index - 1, 0)]); + AddRepeatStep("highlight random messages", () => testChannel.HighlightedMessage.Value = testChannel.Messages[index = RNG.Next(0, testChannel.Messages.Count - 1)], 10); } /// diff --git a/osu.Game/Overlays/Chat/ChannelScrollContainer.cs b/osu.Game/Overlays/Chat/ChannelScrollContainer.cs index 58b2b9a075..139c091f03 100644 --- a/osu.Game/Overlays/Chat/ChannelScrollContainer.cs +++ b/osu.Game/Overlays/Chat/ChannelScrollContainer.cs @@ -47,6 +47,12 @@ namespace osu.Game.Overlays.Chat updateTrackState(); } + public new void ScrollTo(float value, bool animated = true, double? distanceDecay = null) + { + base.ScrollTo(value, animated, distanceDecay); + updateTrackState(); + } + public new void ScrollIntoView(Drawable d, bool animated = true) { base.ScrollIntoView(d, animated); diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index d5df6102f2..433fb2ced7 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -100,7 +100,8 @@ namespace osu.Game.Overlays.Chat if (chatLine != null) { - scroll.ScrollIntoView(chatLine); + float center = scroll.GetChildPosInContent(chatLine, chatLine.DrawSize / 2) - scroll.DisplayableContent / 2; + scroll.ScrollTo(Math.Clamp(center, 0, scroll.ScrollableExtent)); chatLine.ScheduleHighlight(); } From cf9671cafbe9f85dd8612a5a545360b534906fa8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 02:50:07 +0300 Subject: [PATCH 1097/5199] Increase highlight delay to 1500ms --- osu.Game/Overlays/Chat/ChatLine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 97fda80492..9ae80a4e2b 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -220,7 +220,7 @@ namespace osu.Game.Overlays.Chat /// /// Scheduling is required to ensure the animation doesn't play until the chat line is in view and not scrolled away. /// - public void ScheduleHighlight() => Schedule(() => lineHighlightBackground.FadeTo(0.5f).FadeOut(1000, Easing.InQuint)); + public void ScheduleHighlight() => Schedule(() => lineHighlightBackground.FadeTo(0.5f).FadeOut(1500, Easing.InQuint)); private void updateMessageContent() { From d4de435eb2f228696d29147d351764114c1745ed Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 02:50:27 +0300 Subject: [PATCH 1098/5199] Add test case for highlighting while chat overlay is hidden --- .../Visual/Online/TestSceneChatOverlay.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index a91cd53ec1..5ee17f80bd 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -465,6 +465,38 @@ namespace osu.Game.Tests.Visual.Online AddStep("Highlight message", () => chatOverlay.HighlightMessage(message)); } + [Test] + public void TestHighlightWhileChatHidden() + { + Message message = null; + + AddStep("hide chat", () => chatOverlay.Hide()); + + AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); + AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); + + AddStep("Send message in channel 1", () => + { + channel1.AddNewMessages(message = new Message + { + ChannelId = channel1.Id, + Content = "Message to highlight!", + Timestamp = DateTimeOffset.Now, + Sender = new APIUser + { + Id = 2, + Username = "Someone", + } + }); + }); + + AddStep("Highlight message and show chat", () => + { + chatOverlay.HighlightMessage(message); + chatOverlay.Show(); + }); + } + private void pressChannelHotkey(int number) { var channelKey = Key.Number0 + number; From b25c37ce62d96a3cb2640480cb203ff6d7741280 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 03:47:48 +0300 Subject: [PATCH 1099/5199] Instantiate highlight background container on animation Also removes the necessity of scheduling as it actually never worked as intended, `Scheduler` will still update even when the chat line is masked away, and the animation will never be held anyways. The new duration of the animation should be enough for long scrolls either way. --- osu.Game/Overlays/Chat/ChatLine.cs | 37 +++++++++++++---------- osu.Game/Overlays/Chat/DrawableChannel.cs | 2 +- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 9ae80a4e2b..a1d8cd5d38 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -42,8 +42,6 @@ namespace osu.Game.Overlays.Chat protected virtual float TextSize => 20; - private Container lineHighlightBackground; - private Color4 usernameColour; private OsuSpriteText timestamp; @@ -146,15 +144,6 @@ namespace osu.Game.Overlays.Chat InternalChildren = new Drawable[] { - lineHighlightBackground = new Container - { - Alpha = 0f, - RelativeSizeAxes = Axes.Both, - Colour = usernameColour.Darken(1f), - CornerRadius = 2f, - Masking = true, - Child = new Box { RelativeSizeAxes = Axes.Both } - }, new Container { Size = new Vector2(MessagePadding, TextSize), @@ -214,13 +203,29 @@ namespace osu.Game.Overlays.Chat FinishTransforms(true); } + private Container highlight; + /// - /// Schedules a message highlight animation. + /// Performs a highlight animation on this . /// - /// - /// Scheduling is required to ensure the animation doesn't play until the chat line is in view and not scrolled away. - /// - public void ScheduleHighlight() => Schedule(() => lineHighlightBackground.FadeTo(0.5f).FadeOut(1500, Easing.InQuint)); + public void Highlight() + { + if (highlight?.IsAlive != true) + { + AddInternal(highlight = new Container + { + CornerRadius = 2f, + Masking = true, + RelativeSizeAxes = Axes.Both, + Colour = usernameColour.Darken(1f), + Depth = float.MaxValue, + Child = new Box { RelativeSizeAxes = Axes.Both } + }); + } + + highlight.FadeTo(0.5f).FadeOut(1500, Easing.InQuint); + highlight.Expire(); + } private void updateMessageContent() { diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 433fb2ced7..5086d08e91 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -102,7 +102,7 @@ namespace osu.Game.Overlays.Chat { float center = scroll.GetChildPosInContent(chatLine, chatLine.DrawSize / 2) - scroll.DisplayableContent / 2; scroll.ScrollTo(Math.Clamp(center, 0, scroll.ScrollableExtent)); - chatLine.ScheduleHighlight(); + chatLine.Highlight(); } highlightedMessage.Value = null; From f229447453821c387da151c73eeee15cb3a47601 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 04:01:07 +0300 Subject: [PATCH 1100/5199] Add too many messages to better test long scrolls --- .../Visual/Online/TestSceneStandAloneChatDisplay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 17c818963e..860ef5d565 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -191,7 +191,7 @@ namespace osu.Game.Tests.Visual.Online { int index = 0; - fillChat(); + fillChat(100); AddStep("highlight first message", () => testChannel.HighlightedMessage.Value = testChannel.Messages[index = 0]); AddStep("highlight next message", () => testChannel.HighlightedMessage.Value = testChannel.Messages[index = Math.Min(index + 1, testChannel.Messages.Count - 1)]); @@ -304,11 +304,11 @@ namespace osu.Game.Tests.Visual.Online checkScrolledToBottom(); } - private void fillChat() + private void fillChat(int count = 10) { AddStep("fill chat", () => { - for (int i = 0; i < 10; i++) + for (int i = 0; i < count; i++) { testChannel.AddNewMessages(new Message(messageIdSequence++) { From c36badab4bf9c636c9c5e516c805923d96ba5b31 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 10 Mar 2022 10:26:09 +0900 Subject: [PATCH 1101/5199] Add per-ruleset score multipliers for classic scoring --- osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs | 1 + osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs | 2 ++ osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs | 2 ++ osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs | 2 ++ osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 7 ++++++- 5 files changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 2cc05826b4..9cd03dc869 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -7,5 +7,6 @@ namespace osu.Game.Rulesets.Catch.Scoring { public class CatchScoreProcessor : ScoreProcessor { + protected override double ClassicScoreMultiplier => 28; } } diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 48b377c794..e0b19d87e8 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -10,5 +10,7 @@ namespace osu.Game.Rulesets.Mania.Scoring protected override double DefaultAccuracyPortion => 0.99; protected override double DefaultComboPortion => 0.01; + + protected override double ClassicScoreMultiplier => 16; } } diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 44118227d9..df38f0204a 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Osu.Scoring { public class OsuScoreProcessor : ScoreProcessor { + protected override double ClassicScoreMultiplier => 36; + protected override HitEvent CreateHitEvent(JudgementResult result) => base.CreateHitEvent(result).With((result as OsuHitCircleJudgementResult)?.CursorPositionAtHit); diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index 1829ea2513..849b9c14bd 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -10,5 +10,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring protected override double DefaultAccuracyPortion => 0.75; protected override double DefaultComboPortion => 0.25; + + protected override double ClassicScoreMultiplier => 22; } } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index d5a5aa4592..7ca8a0fecf 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -76,6 +76,11 @@ namespace osu.Game.Rulesets.Scoring /// protected virtual double DefaultComboPortion => 0.7; + /// + /// An arbitrary multiplier to scale scores in the scoring mode. + /// + protected virtual double ClassicScoreMultiplier => 36; + private readonly double accuracyPortion; private readonly double comboPortion; @@ -246,7 +251,7 @@ namespace osu.Game.Rulesets.Scoring // This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring. // The invariant is important to ensure that scores don't get re-ordered on leaderboards between the two scoring modes. double scaledStandardised = GetScore(ScoringMode.Standardised, accuracyRatio, comboRatio, statistics) / max_score; - return Math.Pow(scaledStandardised * totalHitObjects, 2) * 36; + return Math.Pow(scaledStandardised * totalHitObjects, 2) * ClassicScoreMultiplier; } } From b38de6e580e61cc125d3305fbccdd4bf7b4da40c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 05:51:54 +0300 Subject: [PATCH 1102/5199] Add skinning support for welcome sprite text --- osu.Game/Screens/Menu/IntroWelcome.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index 639591cfef..f85dbe9abe 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -100,13 +100,13 @@ namespace osu.Game.Screens.Menu private class WelcomeIntroSequence : Container { - private Sprite welcomeText; + private Drawable welcomeText; private Container scaleContainer; public LogoVisualisation LogoVisualisation { get; private set; } [BackgroundDependencyLoader] - private void load(TextureStore textures) + private void load(TextureStore textures, IAPIProvider api) { Origin = Anchor.Centre; Anchor = Anchor.Centre; @@ -135,15 +135,17 @@ namespace osu.Game.Screens.Menu Size = new Vector2(480), Colour = Color4.Black }, - welcomeText = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Texture = textures.Get(@"Intro/Welcome/welcome_text") - }, } }, }; + + if (api.LocalUser.Value.IsSupporter) + scaleContainer.Add(welcomeText = new SkinnableSprite(@"Intro/Welcome/welcome_text")); + else + scaleContainer.Add(welcomeText = new Sprite { Texture = textures.Get(@"Intro/Welcome/welcome_text") }); + + welcomeText.Anchor = Anchor.Centre; + welcomeText.Origin = Anchor.Centre; } protected override void LoadComplete() From b8ee786d774f2c504491ef41875f79b01c3636e0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 05:52:12 +0300 Subject: [PATCH 1103/5199] Add skinning support for "welcome" sample --- osu.Game/Screens/Menu/IntroWelcome.cs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index f85dbe9abe..27eaa7eb3a 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -13,7 +13,10 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Game.Audio; +using osu.Game.Online.API; using osu.Game.Screens.Backgrounds; +using osu.Game.Skinning; using osuTK.Graphics; namespace osu.Game.Screens.Menu @@ -23,8 +26,11 @@ namespace osu.Game.Screens.Menu protected override string BeatmapHash => "64e00d7022195959bfa3109d09c2e2276c8f12f486b91fcf6175583e973b48f2"; protected override string BeatmapFile => "welcome.osz"; private const double delay_step_two = 2142; - private Sample welcome; - private Sample pianoReverb; + + private SkinnableSound skinnableWelcome; + private ISample welcome; + + private ISample pianoReverb; protected override string SeeyaSampleName => "Intro/Welcome/seeya"; protected override BackgroundScreen CreateBackground() => background = new BackgroundScreenDefault(false) @@ -40,10 +46,15 @@ namespace osu.Game.Screens.Menu } [BackgroundDependencyLoader] - private void load(AudioManager audio) + private void load(AudioManager audio, IAPIProvider api) { if (MenuVoice.Value) - welcome = audio.Samples.Get(@"Intro/Welcome/welcome"); + { + if (api.LocalUser.Value.IsSupporter) + AddInternal(skinnableWelcome = new SkinnableSound(new SampleInfo(@"Intro/Welcome/welcome"))); + else + welcome = audio.Samples.Get(@"Intro/Welcome/welcome"); + } pianoReverb = audio.Samples.Get(@"Intro/Welcome/welcome_piano"); } @@ -65,7 +76,10 @@ namespace osu.Game.Screens.Menu AddInternal(intro); - welcome?.Play(); + if (skinnableWelcome != null) + skinnableWelcome.Play(); + else + welcome?.Play(); var reverbChannel = pianoReverb?.Play(); if (reverbChannel != null) From 2c1589e068dcc0af8d40ece788ed248fed782c8a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 05:52:20 +0300 Subject: [PATCH 1104/5199] Add skinning support for "seeya" sample --- osu.Game/Screens/Menu/IntroScreen.cs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index afe75c5ef7..65f9dde1f9 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -13,14 +13,17 @@ using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Framework.Utils; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.IO.Archives; +using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Screens.Backgrounds; +using osu.Game.Skinning; using osuTK; using osuTK.Graphics; using Realms; @@ -55,7 +58,8 @@ namespace osu.Game.Screens.Menu private const int exit_delay = 3000; - private Sample seeya; + private SkinnableSound skinnableSeeya; + private ISample seeya; protected virtual string SeeyaSampleName => "Intro/seeya"; @@ -90,14 +94,18 @@ namespace osu.Game.Screens.Menu private BeatmapManager beatmaps { get; set; } [BackgroundDependencyLoader] - private void load(OsuConfigManager config, Framework.Game game, RealmAccess realm) + private void load(OsuConfigManager config, Framework.Game game, RealmAccess realm, IAPIProvider api) { // prevent user from changing beatmap while the intro is still running. beatmap = Beatmap.BeginLease(false); MenuVoice = config.GetBindable(OsuSetting.MenuVoice); MenuMusic = config.GetBindable(OsuSetting.MenuMusic); - seeya = audio.Samples.Get(SeeyaSampleName); + + if (api.LocalUser.Value.IsSupporter) + AddInternal(skinnableSeeya = new SkinnableSound(new SampleInfo(SeeyaSampleName))); + else + seeya = audio.Samples.Get(SeeyaSampleName); // if the user has requested not to play theme music, we should attempt to find a random beatmap from their collection. if (!MenuMusic.Value) @@ -201,7 +209,14 @@ namespace osu.Game.Screens.Menu // we also handle the exit transition. if (MenuVoice.Value) { - seeya.Play(); + // ensure samples have been updated after resume before playing. + ScheduleAfterChildren(() => + { + if (skinnableSeeya != null) + skinnableSeeya.Play(); + else + seeya.Play(); + }); // if playing the outro voice, we have more time to have fun with the background track. // initially fade to almost silent then ramp out over the remaining time. From ede838c4b3ebaa2fa2471de68ec11bbf7fbd21e5 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 10 Mar 2022 11:23:52 +0800 Subject: [PATCH 1105/5199] Use `ObjectPositionInfo.HitObject` --- osu.Game.Rulesets.Osu/Utils/OsuHitObjectPositionModifier.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectPositionModifier.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectPositionModifier.cs index 428866623f..32f547dfe7 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectPositionModifier.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectPositionModifier.cs @@ -70,11 +70,10 @@ namespace osu.Game.Rulesets.Osu.Utils { ObjectPositionInfo? previous = null; - for (int i = 0; i < hitObjects.Count; i++) + for (int i = 0; i < objectPositionInfos.Count; i++) { - var hitObject = hitObjects[i]; - var current = objectPositionInfos[i]; + var hitObject = current.HitObject; if (hitObject is Spinner) { From 3a71d817758e8387568a0a8f974f6c8ac22e43e7 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 10 Mar 2022 11:53:03 +0800 Subject: [PATCH 1106/5199] Convert the position modifier to stateless methods --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 8 +-- .../Utils/OsuHitObjectGenerationUtils.cs | 2 +- ...OsuHitObjectGenerationUtils_Reposition.cs} | 65 +++++++++---------- 3 files changed, 35 insertions(+), 40 deletions(-) rename osu.Game.Rulesets.Osu/Utils/{OsuHitObjectPositionModifier.cs => OsuHitObjectGenerationUtils_Reposition.cs} (84%) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index cdaa8fa3d5..3c2c5d7759 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -35,18 +35,18 @@ namespace osu.Game.Rulesets.Osu.Mods rng = new Random((int)Seed.Value); - var positionModifier = new OsuHitObjectPositionModifier(osuBeatmap.HitObjects); + var positionInfos = OsuHitObjectGenerationUtils.GeneratePositionInfos(osuBeatmap.HitObjects); float rateOfChangeMultiplier = 0; - foreach (var positionInfo in positionModifier.ObjectPositionInfos) + foreach (var positionInfo in positionInfos) { // rateOfChangeMultiplier only changes every 5 iterations in a combo // to prevent shaky-line-shaped streams if (positionInfo.HitObject.IndexInCurrentCombo % 5 == 0) rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; - if (positionInfo == positionModifier.ObjectPositionInfos.First()) + if (positionInfo == positionInfos.First()) { positionInfo.DistanceFromPrevious = (float)rng.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2; positionInfo.RelativeAngle = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI); @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Mods } } - positionModifier.ApplyModifications(); + osuBeatmap.HitObjects = OsuHitObjectGenerationUtils.RepositionHitObjects(positionInfos); } } } diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index 97a4b14a62..da73c2addb 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -11,7 +11,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Utils { - public static class OsuHitObjectGenerationUtils + public static partial class OsuHitObjectGenerationUtils { // The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle. // The closer the hit objects draw to the border, the sharper the turn diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectPositionModifier.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs similarity index 84% rename from osu.Game.Rulesets.Osu/Utils/OsuHitObjectPositionModifier.cs rename to osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index 32f547dfe7..2a735c89d9 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectPositionModifier.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -13,10 +13,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Utils { - /// - /// Places hit objects according to information in while keeping objects inside the playfield. - /// - public class OsuHitObjectPositionModifier + public static partial class OsuHitObjectGenerationUtils { /// /// Number of previous hitobjects to be shifted together when an object is being moved. @@ -25,24 +22,15 @@ namespace osu.Game.Rulesets.Osu.Utils private static readonly Vector2 playfield_centre = OsuPlayfield.BASE_SIZE / 2; - private readonly List hitObjects; - - private readonly List objectPositionInfos = new List(); - /// - /// Contains information specifying how each hit object should be placed. - /// The default values correspond to how objects are originally placed in the beatmap. + /// Generate a list of s containing information for how the given list of + /// s are positioned. /// - public IReadOnlyList ObjectPositionInfos => objectPositionInfos; - - public OsuHitObjectPositionModifier(List hitObjects) - { - this.hitObjects = hitObjects; - populateObjectPositionInfos(); - } - - private void populateObjectPositionInfos() + /// A list of s to process. + /// A list of s describing how each hit object is positioned relative to the previous one. + public static List GeneratePositionInfos(IEnumerable hitObjects) { + var positionInfos = new List(); Vector2 previousPosition = playfield_centre; float previousAngle = 0; @@ -52,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Utils float absoluteAngle = (float)Math.Atan2(relativePosition.Y, relativePosition.X); float relativeAngle = absoluteAngle - previousAngle; - objectPositionInfos.Add(new ObjectPositionInfo(hitObject) + positionInfos.Add(new ObjectPositionInfo(hitObject) { RelativeAngle = relativeAngle, DistanceFromPrevious = relativePosition.Length @@ -61,18 +49,23 @@ namespace osu.Game.Rulesets.Osu.Utils previousPosition = hitObject.EndPosition; previousAngle = absoluteAngle; } + + return positionInfos; } /// - /// Reposition the hit objects according to the information in . + /// Reposition the hit objects according to the information in . /// - public void ApplyModifications() + /// + /// The repositioned hit objects. + public static List RepositionHitObjects(IEnumerable objectPositionInfos) { + List positionInfos = objectPositionInfos.Cast().ToList(); ObjectPositionInfo? previous = null; - for (int i = 0; i < objectPositionInfos.Count; i++) + for (int i = 0; i < positionInfos.Count; i++) { - var current = objectPositionInfos[i]; + var current = positionInfos[i]; var hitObject = current.HitObject; if (hitObject is Spinner) @@ -81,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.Utils continue; } - computeModifiedPosition(current, previous, i > 1 ? objectPositionInfos[i - 2] : null); + computeModifiedPosition(current, previous, i > 1 ? positionInfos[i - 2] : null); // Move hit objects back into the playfield if they are outside of it Vector2 shift = Vector2.Zero; @@ -104,9 +97,9 @@ namespace osu.Game.Rulesets.Osu.Utils for (int j = i - 1; j >= i - preceding_hitobjects_to_shift && j >= 0; j--) { // only shift hit circles - if (!(hitObjects[j] is HitCircle)) break; + if (!(positionInfos[j].HitObject is HitCircle)) break; - toBeShifted.Add(hitObjects[j]); + toBeShifted.Add(positionInfos[j].HitObject); } if (toBeShifted.Count > 0) @@ -115,6 +108,8 @@ namespace osu.Game.Rulesets.Osu.Utils previous = current; } + + return positionInfos.Select(p => p.HitObject).ToList(); } /// @@ -123,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// The representing the hit object to have the modified position computed for. /// The representing the hit object immediately preceding the current one. /// The representing the hit object immediately preceding the one. - private void computeModifiedPosition(ObjectPositionInfo current, ObjectPositionInfo? previous, ObjectPositionInfo? beforePrevious) + private static void computeModifiedPosition(ObjectPositionInfo current, ObjectPositionInfo? previous, ObjectPositionInfo? beforePrevious) { float previousAbsoluteAngle = 0f; @@ -143,7 +138,7 @@ namespace osu.Game.Rulesets.Osu.Utils Vector2 lastEndPosition = previous?.EndPositionModified ?? playfield_centre; - posRelativeToPrev = OsuHitObjectGenerationUtils.RotateAwayFromEdge(lastEndPosition, posRelativeToPrev); + posRelativeToPrev = RotateAwayFromEdge(lastEndPosition, posRelativeToPrev); current.PositionModified = lastEndPosition + posRelativeToPrev; } @@ -152,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// Move the modified position of a hit circle so that it fits inside the playfield. /// /// The deviation from the original modified position in order to fit within the playfield. - private Vector2 clampHitCircleToPlayfield(HitCircle circle, ObjectPositionInfo objectPositionInfo) + private static Vector2 clampHitCircleToPlayfield(HitCircle circle, ObjectPositionInfo objectPositionInfo) { var previousPosition = objectPositionInfo.PositionModified; objectPositionInfo.EndPositionModified = objectPositionInfo.PositionModified = clampToPlayfieldWithPadding( @@ -169,7 +164,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// Moves the and all necessary nested s into the if they aren't already. /// /// The deviation from the original modified position in order to fit within the playfield. - private Vector2 clampSliderToPlayfield(Slider slider, ObjectPositionInfo objectPositionInfo) + private static Vector2 clampSliderToPlayfield(Slider slider, ObjectPositionInfo objectPositionInfo) { var possibleMovementBounds = calculatePossibleMovementBounds(slider); @@ -199,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// /// The list of hit objects to be shifted. /// The amount to be shifted. - private void applyDecreasingShift(IList hitObjects, Vector2 shift) + private static void applyDecreasingShift(IList hitObjects, Vector2 shift) { for (int i = 0; i < hitObjects.Count; i++) { @@ -219,7 +214,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// /// If the slider is larger than the playfield, the returned may have negative width/height. /// - private RectangleF calculatePossibleMovementBounds(Slider slider) + private static RectangleF calculatePossibleMovementBounds(Slider slider) { var pathPositions = new List(); slider.Path.GetPathToProgress(pathPositions, 0, 1); @@ -266,7 +261,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// /// whose nested s and s should be shifted /// The the 's nested s and s should be shifted by - private void shiftNestedObjects(Slider slider, Vector2 shift) + private static void shiftNestedObjects(Slider slider, Vector2 shift) { foreach (var hitObject in slider.NestedHitObjects.Where(o => o is SliderTick || o is SliderRepeat)) { @@ -283,7 +278,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// The position to be clamped. /// The minimum distance allowed from playfield edges. /// The clamped position. - private Vector2 clampToPlayfieldWithPadding(Vector2 position, float padding) + private static Vector2 clampToPlayfieldWithPadding(Vector2 position, float padding) { return new Vector2( Math.Clamp(position.X, padding, OsuPlayfield.BASE_SIZE.X - padding), From 5e36383258b8a5cb01ab6298d6d2283ef86e6c4f Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 10 Mar 2022 12:02:25 +0800 Subject: [PATCH 1107/5199] Convert `IObjectPositionInfo` to a class --- .../OsuHitObjectGenerationUtils_Reposition.cs | 69 ++++++++++--------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index 2a735c89d9..37a12b20b4 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -23,14 +23,14 @@ namespace osu.Game.Rulesets.Osu.Utils private static readonly Vector2 playfield_centre = OsuPlayfield.BASE_SIZE / 2; /// - /// Generate a list of s containing information for how the given list of + /// Generate a list of s containing information for how the given list of /// s are positioned. /// /// A list of s to process. - /// A list of s describing how each hit object is positioned relative to the previous one. - public static List GeneratePositionInfos(IEnumerable hitObjects) + /// A list of s describing how each hit object is positioned relative to the previous one. + public static List GeneratePositionInfos(IEnumerable hitObjects) { - var positionInfos = new List(); + var positionInfos = new List(); Vector2 previousPosition = playfield_centre; float previousAngle = 0; @@ -56,12 +56,12 @@ namespace osu.Game.Rulesets.Osu.Utils /// /// Reposition the hit objects according to the information in . /// - /// + /// Position information for each hit object. /// The repositioned hit objects. - public static List RepositionHitObjects(IEnumerable objectPositionInfos) + public static List RepositionHitObjects(IEnumerable objectPositionInfos) { - List positionInfos = objectPositionInfos.Cast().ToList(); - ObjectPositionInfo? previous = null; + List positionInfos = objectPositionInfos.Select(o => new ObjectPositionInfoInternal(o)).ToList(); + ObjectPositionInfoInternal? previous = null; for (int i = 0; i < positionInfos.Count; i++) { @@ -115,10 +115,10 @@ namespace osu.Game.Rulesets.Osu.Utils /// /// Compute the modified position of a hit object while attempting to keep it inside the playfield. /// - /// The representing the hit object to have the modified position computed for. - /// The representing the hit object immediately preceding the current one. - /// The representing the hit object immediately preceding the one. - private static void computeModifiedPosition(ObjectPositionInfo current, ObjectPositionInfo? previous, ObjectPositionInfo? beforePrevious) + /// The representing the hit object to have the modified position computed for. + /// The representing the hit object immediately preceding the current one. + /// The representing the hit object immediately preceding the one. + private static void computeModifiedPosition(ObjectPositionInfoInternal current, ObjectPositionInfoInternal? previous, ObjectPositionInfoInternal? beforePrevious) { float previousAbsoluteAngle = 0f; @@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// Move the modified position of a hit circle so that it fits inside the playfield. /// /// The deviation from the original modified position in order to fit within the playfield. - private static Vector2 clampHitCircleToPlayfield(HitCircle circle, ObjectPositionInfo objectPositionInfo) + private static Vector2 clampHitCircleToPlayfield(HitCircle circle, ObjectPositionInfoInternal objectPositionInfo) { var previousPosition = objectPositionInfo.PositionModified; objectPositionInfo.EndPositionModified = objectPositionInfo.PositionModified = clampToPlayfieldWithPadding( @@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// Moves the and all necessary nested s into the if they aren't already. ///
/// The deviation from the original modified position in order to fit within the playfield. - private static Vector2 clampSliderToPlayfield(Slider slider, ObjectPositionInfo objectPositionInfo) + private static Vector2 clampSliderToPlayfield(Slider slider, ObjectPositionInfoInternal objectPositionInfo) { var possibleMovementBounds = calculatePossibleMovementBounds(slider); @@ -286,7 +286,7 @@ namespace osu.Game.Rulesets.Osu.Utils ); } - public interface IObjectPositionInfo + public class ObjectPositionInfo { /// /// The jump angle from the previous hit object to this one, relative to the previous hit object's jump angle. @@ -298,7 +298,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// If is 0, the player's cursor doesn't need to change its direction of movement when passing /// the previous object to reach this one. /// - float RelativeAngle { get; set; } + public float RelativeAngle { get; set; } /// /// The jump distance from the previous hit object to this one. @@ -306,32 +306,33 @@ namespace osu.Game.Rulesets.Osu.Utils /// /// of the first hit object in a beatmap is relative to the playfield center. /// - float DistanceFromPrevious { get; set; } - - /// - /// The hit object associated with this . - /// - OsuHitObject HitObject { get; } - } - - private class ObjectPositionInfo : IObjectPositionInfo - { - public float RelativeAngle { get; set; } - public float DistanceFromPrevious { get; set; } - public Vector2 PositionOriginal { get; } - public Vector2 PositionModified { get; set; } - public Vector2 EndPositionModified { get; set; } - + /// + /// The hit object associated with this . + /// public OsuHitObject HitObject { get; } public ObjectPositionInfo(OsuHitObject hitObject) { - PositionModified = PositionOriginal = hitObject.Position; - EndPositionModified = hitObject.EndPosition; HitObject = hitObject; } } + + private class ObjectPositionInfoInternal : ObjectPositionInfo + { + public Vector2 PositionOriginal { get; } + public Vector2 PositionModified { get; set; } + public Vector2 EndPositionModified { get; set; } + + public ObjectPositionInfoInternal(ObjectPositionInfo original) + : base(original.HitObject) + { + RelativeAngle = original.RelativeAngle; + DistanceFromPrevious = original.DistanceFromPrevious; + PositionModified = PositionOriginal = HitObject.Position; + EndPositionModified = HitObject.EndPosition; + } + } } } From eaef27595c71e8412f449345e9b81528fc6b60e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Mar 2022 15:33:50 +0900 Subject: [PATCH 1108/5199] Also mark `UnknownMod` as not user-playable --- osu.Game/Rulesets/Mods/UnknownMod.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Rulesets/Mods/UnknownMod.cs b/osu.Game/Rulesets/Mods/UnknownMod.cs index 013725e227..b426386d7a 100644 --- a/osu.Game/Rulesets/Mods/UnknownMod.cs +++ b/osu.Game/Rulesets/Mods/UnknownMod.cs @@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Mods public override string Description => "This mod could not be resolved by the game."; public override double ScoreMultiplier => 0; + public override bool UserPlayable => false; + public override ModType Type => ModType.System; public UnknownMod(string acronym) From 1a187d4dec553a0563e6baa75a5c15e282c34a7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Mar 2022 15:49:18 +0900 Subject: [PATCH 1109/5199] Add animation to checkbox when joning/leaving a channel --- .../Overlays/Chat/Listing/ChannelListingItem.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs b/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs index 9586730c6b..83362bc79d 100644 --- a/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs +++ b/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs @@ -81,9 +81,9 @@ namespace osu.Game.Overlays.Chat.Listing { checkbox = new SpriteIcon { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Margin = new MarginPadding { Left = 15 }, + Alpha = 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Icon = FontAwesome.Solid.Check, Size = new Vector2(icon_size), }, @@ -134,15 +134,19 @@ namespace osu.Game.Overlays.Chat.Listing channelJoined = channel.Joined.GetBoundCopy(); channelJoined.BindValueChanged(change => { + const double duration = 500; + if (change.NewValue) { - checkbox.Show(); + checkbox.FadeIn(duration, Easing.OutQuint); + checkbox.ScaleTo(1f, duration, Easing.OutElastic); channelText.Colour = Colour4.White; topicText.Colour = Colour4.White; } else { - checkbox.Hide(); + checkbox.FadeOut(duration, Easing.OutQuint); + checkbox.ScaleTo(0.8f, duration, Easing.OutQuint); channelText.Colour = colourProvider.Light3; topicText.Colour = colourProvider.Content2; } @@ -159,7 +163,7 @@ namespace osu.Game.Overlays.Chat.Listing protected override void OnHoverLost(HoverLostEvent e) { - hoverBox.Hide(); + hoverBox.FadeOut(300, Easing.OutQuint); base.OnHoverLost(e); } } From 46f2db1712beda4ae0a33d2c5fe54b6b10f19b0a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Mar 2022 15:58:19 +0900 Subject: [PATCH 1110/5199] Move `ChannelListingItem` spacing into item so input is always handled by an item in the list Without this change, there would be a couple of pixels between each list item where nothing would be hovered. This is a pretty annoying UX which we should be avoiding we possible. --- osu.Game/Overlays/Chat/Listing/ChannelListing.cs | 2 -- osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs | 5 ++++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/Listing/ChannelListing.cs b/osu.Game/Overlays/Chat/Listing/ChannelListing.cs index 9b8b45c85f..732c78de15 100644 --- a/osu.Game/Overlays/Chat/Listing/ChannelListing.cs +++ b/osu.Game/Overlays/Chat/Listing/ChannelListing.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; using osu.Game.Online.Chat; -using osuTK; namespace osu.Game.Overlays.Chat.Listing { @@ -51,7 +50,6 @@ namespace osu.Game.Overlays.Chat.Listing Direction = FillDirection.Vertical, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Spacing = new Vector2(3), Padding = new MarginPadding { Vertical = 13, diff --git a/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs b/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs index 83362bc79d..526cbcda87 100644 --- a/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs +++ b/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs @@ -43,6 +43,8 @@ namespace osu.Game.Overlays.Chat.Listing private const float text_size = 18; private const float icon_size = 14; + private const float vertical_margin = 1.5f; + public ChannelListingItem(Channel channel) { this.channel = channel; @@ -54,7 +56,7 @@ namespace osu.Game.Overlays.Chat.Listing Masking = true; CornerRadius = 5; RelativeSizeAxes = Axes.X; - Height = 20; + Height = 20 + (vertical_margin * 2); Children = new Drawable[] { @@ -62,6 +64,7 @@ namespace osu.Game.Overlays.Chat.Listing { RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background3, + Margin = new MarginPadding { Vertical = vertical_margin }, Alpha = 0f, }, new GridContainer From c61397cc22c9ba9a1a78ab8ec1ba0ab64526a799 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 10 Mar 2022 16:22:27 +0900 Subject: [PATCH 1111/5199] Fix whitespace/inspection --- osu.Game/Online/API/APIMod.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index ef8637b8ed..44b1c460d5 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -102,5 +102,4 @@ namespace osu.Game.Online.API public int GetHashCode(KeyValuePair obj) => HashCode.Combine(obj.Key, ModUtils.GetSettingUnderlyingValue(obj.Value)); } } - } From a1b7bf3986666e7ec07a226988386a3b84b75507 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Mar 2022 16:47:02 +0900 Subject: [PATCH 1112/5199] Use a minimum fade length for clamping rather than zero MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs b/osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs index 46e0030034..506f679836 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/KiaiFlash.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default Child .FadeTo(flash_opacity, EarlyActivationMilliseconds, Easing.OutQuint) .Then() - .FadeOut(Math.Max(0, timingPoint.BeatLength - fade_length), Easing.OutSine); + .FadeOut(Math.Max(fade_length, timingPoint.BeatLength - fade_length), Easing.OutSine); } } } From a25b6e6a099233f02ec88121de7f9b508d32d378 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Thu, 10 Mar 2022 00:42:58 -0800 Subject: [PATCH 1113/5199] Proof of Concept draft for Taiko touch input --- osu.Game.Rulesets.Taiko/TaikoInputManager.cs | 2 + osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 - .../UI/DrawableTaikoRuleset.cs | 6 + .../UI/DrumTouchInputArea.cs | 141 ++++++++++++++++++ osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 5 +- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 1 + 6 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs diff --git a/osu.Game.Rulesets.Taiko/TaikoInputManager.cs b/osu.Game.Rulesets.Taiko/TaikoInputManager.cs index 058bec5111..20766029bb 100644 --- a/osu.Game.Rulesets.Taiko/TaikoInputManager.cs +++ b/osu.Game.Rulesets.Taiko/TaikoInputManager.cs @@ -2,11 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.ComponentModel; +using osu.Framework.Allocation; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Taiko { + [Cached] // Used for touch input, see DrumTouchInputArea. public class TaikoInputManager : RulesetInputManager { public TaikoInputManager(RulesetInfo ruleset) diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index e56aabaf9d..4a22619f06 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -48,8 +48,6 @@ namespace osu.Game.Rulesets.Taiko public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { - new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre), - new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim), new KeyBinding(InputKey.D, TaikoAction.LeftRim), new KeyBinding(InputKey.F, TaikoAction.LeftCentre), new KeyBinding(InputKey.J, TaikoAction.RightCentre), diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 824b95639b..cad134067f 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -52,6 +52,12 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.X, Depth = float.MaxValue }); + + DrumTouchInputArea touchInput = new DrumTouchInputArea(Playfield) { + RelativePositionAxes = Axes.Both, + RelativeSizeAxes = Axes.Both, + }; + Overlays.Add(touchInput); } protected override void UpdateAfterChildren() diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs new file mode 100644 index 0000000000..647187226a --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -0,0 +1,141 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Framework.Logging; +using osu.Game.Graphics; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Taiko.UI +{ + /// + /// An overlay that captures and displays Taiko mouse and touch input. + /// The boundaries of this overlay defines the interactable area for touch input. + /// A secondary InputDrum is attached by this overlay, which defines the circulary boundary which distinguishes "centre" from "rim" hits, and also displays input. + /// + public class DrumTouchInputArea : Container + { + // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) + private const float overhangPercent = 0.33f; + private readonly InputDrum touchInputDrum; + + [Resolved(canBeNull: true)] + private TaikoInputManager taikoInputManager { get; set; } + + private KeyBindingContainer keyBindingContainer; + + // Which Taiko action was pressed by the last OnMouseDown event, so that the corresponding action can be released OnMouseUp even if the cursor position moved + private TaikoAction mouseAction; + + // A map of (Finger Index OnTouchDown -> Which Taiko action was pressed), so that the corresponding action can be released OnTouchUp is released even if the touch position moved + private Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); + + private Playfield playfield; + + public DrumTouchInputArea(Playfield playfield) { + this.playfield = playfield; + + RelativeSizeAxes = Axes.Both; + RelativePositionAxes = Axes.Both; + Children = new Drawable[] + { + new Box() { + Alpha = 0.0f, + Colour = new OsuColour().Blue, + + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + }, + new Container() { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + + Children = new Drawable[] + { + touchInputDrum = new InputDrum(playfield.HitObjectContainer) { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + }, + }; + + } + protected override void LoadComplete() + { + keyBindingContainer = taikoInputManager?.KeyBindingContainer; + + Padding = new MarginPadding { + Top = playfield.ScreenSpaceDrawQuad.BottomLeft.Y, + Bottom = -DrawHeight * overhangPercent, + }; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + mouseAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); + keyBindingContainer?.TriggerPressed(mouseAction); + return base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseUpEvent e) + { + keyBindingContainer?.TriggerReleased(mouseAction); + base.OnMouseUp(e); + } + + protected override bool OnTouchDown(TouchDownEvent e) + { + TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); + if (touchActions.ContainsKey(e.Touch.Source)) { + touchActions[e.Touch.Source] = taikoAction; + } + else { + touchActions.Add(e.Touch.Source, taikoAction); + } + keyBindingContainer?.TriggerPressed(touchActions[e.Touch.Source]); + + return base.OnTouchDown(e); + } + + protected override void OnTouchUp(TouchUpEvent e) + { + keyBindingContainer?.TriggerReleased(touchActions[e.Touch.Source]); + base.OnTouchUp(e); + } + + private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { + bool leftSide = inputIsOnLeftSide(inputPosition); + bool centreHit = inputIsCenterHit(inputPosition); + + return leftSide ? + (centreHit ? TaikoAction.LeftCentre : TaikoAction.LeftRim) : + (centreHit ? TaikoAction.RightCentre : TaikoAction.RightRim); + } + + private bool inputIsOnLeftSide(Vector2 inputPosition) { + Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; + return inputPositionToDrumCentreDelta.X < 0f; + } + + private bool inputIsCenterHit(Vector2 inputPosition) { + Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; + + float inputDrumRadius = Math.Max(touchInputDrum.Width, touchInputDrum.DrawHeight) / 2f; + float centreRadius = (inputDrumRadius * InputDrum.centre_size); + return inputPositionToDrumCentreDelta.Length <= centreRadius; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 16be20f7f3..1949d09de1 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Taiko.UI /// internal class InputDrum : Container { + public const float centre_size = 0.7f; private const float middle_split = 0.025f; [Cached] @@ -122,14 +123,14 @@ namespace osu.Game.Rulesets.Taiko.UI Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.7f) + Size = new Vector2(centre_size) }, centreHit = new Sprite { Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.7f), + Size = new Vector2(centre_size), Alpha = 0, Blending = BlendingParameters.Additive } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index d650cab729..2f7e0dc08f 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -144,6 +144,7 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, }, drumRollHitContainer.CreateProxy(), + // new DrumTouchInputArea(this), }; RegisterPool(50); From 671d614c92bde69e6465cc718fd69e561b34c42d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 10 Mar 2022 17:54:31 +0900 Subject: [PATCH 1114/5199] Fix beatmap wedge mutating transforms incorrectly --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 40 +++++++++++---------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index ea531e89c8..59a48f4f9d 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -152,7 +152,6 @@ namespace osu.Game.Screens.Select public OsuSpriteText VersionLabel { get; private set; } public OsuSpriteText TitleLabel { get; private set; } public OsuSpriteText ArtistLabel { get; private set; } - public BeatmapSetOnlineStatusPill StatusPill { get; private set; } public FillFlowContainer MapperContainer { get; private set; } private Container difficultyColourBar; @@ -169,6 +168,12 @@ namespace osu.Game.Screens.Select [Resolved] private IBindable> mods { get; set; } + [Resolved] + private BeatmapDifficultyCache difficultyCache { get; set; } + + [Resolved] + private OsuColour colours { get; set; } + private ModSettingChangeTracker settingChangeTracker; public WedgeInfoText(WorkingBeatmap working, RulesetInfo userRuleset) @@ -181,7 +186,7 @@ namespace osu.Game.Screens.Select private IBindable starDifficulty; [BackgroundDependencyLoader] - private void load(OsuColour colours, LocalisationManager localisation, BeatmapDifficultyCache difficultyCache) + private void load(LocalisationManager localisation) { var beatmapInfo = working.BeatmapInfo; var metadata = beatmapInfo.Metadata; @@ -255,7 +260,7 @@ namespace osu.Game.Screens.Select Shear = -wedged_container_shear, Alpha = 0f, }, - StatusPill = new BeatmapSetOnlineStatusPill + new BeatmapSetOnlineStatusPill { AutoSizeAxes = Axes.Both, Anchor = Anchor.TopRight, @@ -264,6 +269,7 @@ namespace osu.Game.Screens.Select TextSize = 11, TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, Status = beatmapInfo.Status, + Alpha = string.IsNullOrEmpty(beatmapInfo.DifficultyName) ? 0 : 1 } } }, @@ -311,22 +317,6 @@ namespace osu.Game.Screens.Select titleBinding.BindValueChanged(_ => setMetadata(metadata.Source)); artistBinding.BindValueChanged(_ => setMetadata(metadata.Source), true); - starRatingDisplay.DisplayedStars.BindValueChanged(s => - { - difficultyColourBar.Colour = colours.ForStarDifficulty(s.NewValue); - }, true); - - starDifficulty = difficultyCache.GetBindableDifficulty(beatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); - starDifficulty.BindValueChanged(s => - { - starRatingDisplay.FadeIn(transition_duration); - starRatingDisplay.Current.Value = s.NewValue ?? default; - }); - - // no difficulty means it can't have a status to show - if (string.IsNullOrEmpty(beatmapInfo.DifficultyName)) - StatusPill.Hide(); - addInfoLabels(); } @@ -334,6 +324,18 @@ namespace osu.Game.Screens.Select { base.LoadComplete(); + starRatingDisplay.DisplayedStars.BindValueChanged(s => + { + difficultyColourBar.Colour = colours.ForStarDifficulty(s.NewValue); + }, true); + + starDifficulty = difficultyCache.GetBindableDifficulty(working.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); + starDifficulty.BindValueChanged(s => + { + starRatingDisplay.FadeIn(transition_duration); + starRatingDisplay.Current.Value = s.NewValue ?? default; + }); + mods.BindValueChanged(m => { settingChangeTracker?.Dispose(); From d5eca16b69eb01ec42a3b747e1367a3510f4775f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Mar 2022 18:08:07 +0900 Subject: [PATCH 1115/5199] Fix potential test failures due to slow realm refresh in `TestSceneScreenNavigation` As seen at https://github.com/ppy/osu/runs/5492345983?check_suite_focus=true. --- osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 8debb95f38..f2e6aa1e16 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -133,7 +133,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("confirm deletion", () => InputManager.Key(Key.Number1)); AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get().CurrentDialog == null); - AddAssert("ensure score is pending deletion", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == true)); + AddUntilStep("ensure score is pending deletion", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == true)); AddUntilStep("wait for score panel removal", () => scorePanel.Parent == null); } @@ -178,7 +178,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("confirm deletion", () => InputManager.Key(Key.Number1)); AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get().CurrentDialog == null); - AddAssert("ensure score is pending deletion", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == true)); + AddUntilStep("ensure score is pending deletion", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == true)); AddUntilStep("wait for score panel removal", () => scorePanel.Parent == null); } From 45d537ef727e787afeaf044b9cfc12af50c29bf9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 10 Mar 2022 18:50:53 +0900 Subject: [PATCH 1116/5199] Fix potential multiplayer crash with async disposal --- osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index d8fb448437..b49f373c7b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -175,7 +175,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer base.StartGameplay(); }); - private void onResultsReady() => resultsReady.SetResult(true); + private void onResultsReady() => Scheduler.Add(() => + { + resultsReady.SetResult(true); + }); protected override async Task PrepareScoreForResultsAsync(Score score) { From 2d135e5be61e2615fe045747a34c834d31d3c7fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Mar 2022 19:52:08 +0900 Subject: [PATCH 1117/5199] Explain purpose of `Schedule` call in `MultiplayerPlayer.onResultsReady` --- .../Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index b49f373c7b..78e7f3d987 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -175,10 +175,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer base.StartGameplay(); }); - private void onResultsReady() => Scheduler.Add(() => + private void onResultsReady() { - resultsReady.SetResult(true); - }); + // Schedule is required to ensure that `TaskCompletionSource.SetResult` is not called more than once. + // A scenario where this can occur is if this instance is not immediately disposed (ie. async disposal queue). + Schedule(() => resultsReady.SetResult(true)); + } protected override async Task PrepareScoreForResultsAsync(Score score) { From 2de779584482cb5a0795a338ad7666982ca6335a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 10 Mar 2022 19:59:05 +0900 Subject: [PATCH 1118/5199] Fix always rolling up from zero Co-authored-by: Dean Herbert --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 59a48f4f9d..1e0aaf9c27 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -332,8 +332,13 @@ namespace osu.Game.Screens.Select starDifficulty = difficultyCache.GetBindableDifficulty(working.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); starDifficulty.BindValueChanged(s => { - starRatingDisplay.FadeIn(transition_duration); starRatingDisplay.Current.Value = s.NewValue ?? default; + + // Don't roll the counter on initial display (but still allow it to roll on applying mods etc.) + if (!starRatingDisplay.IsPresent) + starRatingDisplay.FinishTransforms(true); + + starRatingDisplay.FadeIn(transition_duration); }); mods.BindValueChanged(m => From 885cb3ce5bc0768a3e434436a5e540bfaff4a5b4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 10 Mar 2022 20:02:58 +0900 Subject: [PATCH 1119/5199] Add current screen checks for added safety --- .../OnlinePlay/Multiplayer/MultiplayerPlayer.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index 78e7f3d987..043315c790 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; +using osu.Framework.Screens; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; @@ -171,6 +172,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private void onMatchStarted() => Scheduler.Add(() => { + if (!this.IsCurrentScreen()) + return; + loadingDisplay.Hide(); base.StartGameplay(); }); @@ -179,7 +183,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { // Schedule is required to ensure that `TaskCompletionSource.SetResult` is not called more than once. // A scenario where this can occur is if this instance is not immediately disposed (ie. async disposal queue). - Schedule(() => resultsReady.SetResult(true)); + Schedule(() => + { + if (!this.IsCurrentScreen()) + return; + + resultsReady.SetResult(true); + }); } protected override async Task PrepareScoreForResultsAsync(Score score) From e4ef540b5b321c2eef6932d44d6508055a851e52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Mar 2022 20:33:14 +0900 Subject: [PATCH 1120/5199] Fix intermittent failures on `TestBeatmapDownloadingFlow` due to slow realm refresh --- .../Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 2521d570f2..db988a544d 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -118,7 +118,7 @@ namespace osu.Game.Tests.Online [Test] public void TestBeatmapDownloadingFlow() { - AddAssert("ensure beatmap unavailable", () => !beatmaps.IsAvailableLocally(testBeatmapSet)); + AddUntilStep("ensure beatmap unavailable", () => !beatmaps.IsAvailableLocally(testBeatmapSet)); addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded); AddStep("start downloading", () => beatmapDownloader.Download(testBeatmapSet)); @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Online AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); AddUntilStep("wait for import", () => beatmaps.CurrentImport != null); - AddAssert("ensure beatmap available", () => beatmaps.IsAvailableLocally(testBeatmapSet)); + AddUntilStep("ensure beatmap available", () => beatmaps.IsAvailableLocally(testBeatmapSet)); addAvailabilityCheckStep("state is locally available", BeatmapAvailability.LocallyAvailable); } From 317869078f470889330702b7b99b6146569b4f75 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Thu, 10 Mar 2022 05:09:07 -0800 Subject: [PATCH 1121/5199] Basic functionality for Taiko touch input now complete --- .../UI/DrawableTaikoRuleset.cs | 12 ++-- .../UI/DrumSamplePlayer.cs | 55 ++++++++++++++++ .../UI/DrumTouchInputArea.cs | 62 ++++++++----------- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 19 +----- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 4 +- 5 files changed, 93 insertions(+), 59 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index cad134067f..9cd5b0ca7f 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects; @@ -35,6 +36,8 @@ namespace osu.Game.Rulesets.Taiko.UI private SkinnableDrawable scroller; + private DrumTouchInputArea drumTouchInputArea; + public DrawableTaikoRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset, beatmap, mods) { @@ -53,11 +56,8 @@ namespace osu.Game.Rulesets.Taiko.UI Depth = float.MaxValue }); - DrumTouchInputArea touchInput = new DrumTouchInputArea(Playfield) { - RelativePositionAxes = Axes.Both, - RelativeSizeAxes = Axes.Both, - }; - Overlays.Add(touchInput); + if ((drumTouchInputArea = CreateDrumTouchInputArea()) != null) + KeyBindingInputManager.Add(drumTouchInputArea); } protected override void UpdateAfterChildren() @@ -76,6 +76,8 @@ namespace osu.Game.Rulesets.Taiko.UI return ControlPoints[result]; } + public DrumTouchInputArea CreateDrumTouchInputArea() => new DrumTouchInputArea(); + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer(); protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs new file mode 100644 index 0000000000..458dbc6ca1 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs @@ -0,0 +1,55 @@ +// 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.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Taiko.UI +{ + internal class DrumSamplePlayer : Container, IKeyBindingHandler { + private DrumSampleTriggerSource leftRimSampleTriggerSource; + private DrumSampleTriggerSource leftCentreSampleTriggerSource; + private DrumSampleTriggerSource rightCentreSampleTriggerSource; + private DrumSampleTriggerSource rightRimSampleTriggerSource; + + public DrumSamplePlayer(HitObjectContainer hitObjectContainer) { + Children = new Drawable[] + { + leftRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), + leftCentreSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), + rightCentreSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), + rightRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), + }; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action == TaikoAction.LeftRim) + { + leftRimSampleTriggerSource.Play(HitType.Rim); + } + else if (e.Action == TaikoAction.LeftCentre) + { + leftCentreSampleTriggerSource.Play(HitType.Centre); + } + else if (e.Action == TaikoAction.RightCentre) + { + rightCentreSampleTriggerSource.Play(HitType.Centre); + } + else if (e.Action == TaikoAction.RightRim) + { + rightRimSampleTriggerSource.Play(HitType.Rim); + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 647187226a..d610e84c8d 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -10,9 +10,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Logging; using osu.Game.Graphics; -using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Taiko.UI @@ -25,11 +23,9 @@ namespace osu.Game.Rulesets.Taiko.UI public class DrumTouchInputArea : Container { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float overhangPercent = 0.33f; - private readonly InputDrum touchInputDrum; - - [Resolved(canBeNull: true)] - private TaikoInputManager taikoInputManager { get; set; } + private const float overhangPercent = 0.35f; + private InputDrum touchInputDrum; + private Circle drumBackground; private KeyBindingContainer keyBindingContainer; @@ -39,55 +35,55 @@ namespace osu.Game.Rulesets.Taiko.UI // A map of (Finger Index OnTouchDown -> Which Taiko action was pressed), so that the corresponding action can be released OnTouchUp is released even if the touch position moved private Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); - private Playfield playfield; - - public DrumTouchInputArea(Playfield playfield) { - this.playfield = playfield; - + public DrumTouchInputArea() { RelativeSizeAxes = Axes.Both; RelativePositionAxes = Axes.Both; Children = new Drawable[] { - new Box() { - Alpha = 0.0f, - Colour = new OsuColour().Blue, - - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, - }, new Container() { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, - Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Children = new Drawable[] { - touchInputDrum = new InputDrum(playfield.HitObjectContainer) { + drumBackground = new Circle() { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fit, + Alpha = 0.9f, + }, + touchInputDrum = new InputDrum() { Anchor = Anchor.Centre, Origin = Anchor.Centre, }, } }, }; - } + protected override void LoadComplete() { - keyBindingContainer = taikoInputManager?.KeyBindingContainer; - Padding = new MarginPadding { - Top = playfield.ScreenSpaceDrawQuad.BottomLeft.Y, - Bottom = -DrawHeight * overhangPercent, + Top = TaikoPlayfield.DEFAULT_HEIGHT * 2f, // Visual elements should start right below the playfield + Bottom = -touchInputDrum.DrawHeight * overhangPercent, // The drum should go past the bottom of the screen so that it can be wider }; } + [BackgroundDependencyLoader] + private void load(TaikoInputManager taikoInputManager, OsuColour colours) + { + keyBindingContainer = taikoInputManager?.KeyBindingContainer; + drumBackground.Colour = colours.Gray0; + } + protected override bool OnMouseDown(MouseDownEvent e) { mouseAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); keyBindingContainer?.TriggerPressed(mouseAction); - return base.OnMouseDown(e); + return true; } protected override void OnMouseUp(MouseUpEvent e) @@ -99,20 +95,16 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnTouchDown(TouchDownEvent e) { TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); - if (touchActions.ContainsKey(e.Touch.Source)) { - touchActions[e.Touch.Source] = taikoAction; - } - else { - touchActions.Add(e.Touch.Source, taikoAction); - } + touchActions.Add(e.Touch.Source, taikoAction); keyBindingContainer?.TriggerPressed(touchActions[e.Touch.Source]); - return base.OnTouchDown(e); + return true; } protected override void OnTouchUp(TouchUpEvent e) { keyBindingContainer?.TriggerReleased(touchActions[e.Touch.Source]); + touchActions.Remove(e.Touch.Source); base.OnTouchUp(e); } diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 1949d09de1..76594b86c1 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -10,8 +10,6 @@ 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; using osu.Game.Skinning; using osuTK; @@ -25,13 +23,8 @@ namespace osu.Game.Rulesets.Taiko.UI public const float centre_size = 0.7f; private const float middle_split = 0.025f; - [Cached] - private DrumSampleTriggerSource sampleTriggerSource; - - public InputDrum(HitObjectContainer hitObjectContainer) + public InputDrum() { - sampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer); - RelativeSizeAxes = Axes.Both; } @@ -70,8 +63,7 @@ namespace osu.Game.Rulesets.Taiko.UI CentreAction = TaikoAction.RightCentre } } - }), - sampleTriggerSource + }) }; } @@ -95,9 +87,6 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Sprite centre; private readonly Sprite centreHit; - [Resolved] - private DrumSampleTriggerSource sampleTriggerSource { get; set; } - public TaikoHalfDrum(bool flipped) { Masking = true; @@ -158,15 +147,11 @@ namespace osu.Game.Rulesets.Taiko.UI { target = centreHit; back = centre; - - sampleTriggerSource.Play(HitType.Centre); } else if (e.Action == RimAction) { target = rimHit; back = rim; - - sampleTriggerSource.Play(HitType.Rim); } if (target != null) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 2f7e0dc08f..71aee107d2 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -123,7 +123,7 @@ namespace osu.Game.Rulesets.Taiko.UI Children = new Drawable[] { new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()), - new InputDrum(HitObjectContainer) + new InputDrum() { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -144,7 +144,7 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, }, drumRollHitContainer.CreateProxy(), - // new DrumTouchInputArea(this), + new DrumSamplePlayer(HitObjectContainer), }; RegisterPool(50); From 717ddbba4e0eb4e4a1f3a96ea83dfc17273d48f9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 16:30:20 +0300 Subject: [PATCH 1122/5199] Fix skin components toolbox not autosizing properly --- osu.Game/Overlays/SettingsToolboxGroup.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SettingsToolboxGroup.cs b/osu.Game/Overlays/SettingsToolboxGroup.cs index b4178359a4..b9e5283a44 100644 --- a/osu.Game/Overlays/SettingsToolboxGroup.cs +++ b/osu.Game/Overlays/SettingsToolboxGroup.cs @@ -162,7 +162,9 @@ namespace osu.Game.Overlays Expanded.BindValueChanged(v => { - content.ClearTransforms(); + // clearing transforms can break autosizing, see: https://github.com/ppy/osu-framework/issues/5064 + if (v.NewValue != v.OldValue) + content.ClearTransforms(); if (v.NewValue) content.AutoSizeAxes = Axes.Y; From 5ce57fa34a70a23971e56495a96ee86081395166 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Thu, 10 Mar 2022 06:17:06 -0800 Subject: [PATCH 1123/5199] Improve readability for getTaikoActionFromInput --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index d610e84c8d..a2c33a85e8 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -109,12 +109,12 @@ namespace osu.Game.Rulesets.Taiko.UI } private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { - bool leftSide = inputIsOnLeftSide(inputPosition); bool centreHit = inputIsCenterHit(inputPosition); + bool leftSide = inputIsOnLeftSide(inputPosition); - return leftSide ? - (centreHit ? TaikoAction.LeftCentre : TaikoAction.LeftRim) : - (centreHit ? TaikoAction.RightCentre : TaikoAction.RightRim); + return centreHit ? + (leftSide ? TaikoAction.LeftCentre : TaikoAction.RightCentre) : + (leftSide ? TaikoAction.LeftRim : TaikoAction.RightRim); } private bool inputIsOnLeftSide(Vector2 inputPosition) { From 68aedd63a7c9e0234de549988d876f1a2b8817d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Mar 2022 20:22:36 +0900 Subject: [PATCH 1124/5199] Move `SelectionHandler`'s `SelectedItems` binding to the base implementation Until now it was up to each implementation to connect `BlueprintContainer` to its `SelectionHandler`, which didn't make much sense at all. --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 1 + .../Edit/Compose/Components/EditorSelectionHandler.cs | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 130d7a015f..6dc6f20cfe 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -73,6 +73,7 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectionHandler = CreateSelectionHandler(); SelectionHandler.DeselectAll = deselectAll; + SelectionHandler.SelectedItems.BindTo(SelectedItems); AddRangeInternal(new[] { diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 5f6e8de557..7f693996a4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -29,11 +29,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // bring in updates from selection changes EditorBeatmap.HitObjectUpdated += _ => Scheduler.AddOnce(UpdateTernaryStates); - SelectedItems.BindTo(EditorBeatmap.SelectedHitObjects); - SelectedItems.CollectionChanged += (sender, args) => - { - Scheduler.AddOnce(UpdateTernaryStates); - }; + SelectedItems.CollectionChanged += (sender, args) => Scheduler.AddOnce(UpdateTernaryStates); } protected override void DeleteItems(IEnumerable items) => EditorBeatmap.RemoveRange(items); From 8cbc8b944f1ec39622cabc2388db53bdacea3e9f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 17:25:13 +0300 Subject: [PATCH 1125/5199] Rescope scheduling and improve comment --- osu.Game/Screens/Menu/IntroScreen.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 65f9dde1f9..a6b54dd1f2 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -209,14 +209,15 @@ namespace osu.Game.Screens.Menu // we also handle the exit transition. if (MenuVoice.Value) { - // ensure samples have been updated after resume before playing. - ScheduleAfterChildren(() => + if (skinnableSeeya != null) { - if (skinnableSeeya != null) - skinnableSeeya.Play(); - else - seeya.Play(); - }); + // resuming a screen (i.e. calling OnResume) happens before the screen itself becomes alive, + // therefore skinnable samples may not be updated yet with the recently selected skin. + // schedule after children to ensure skinnable samples have processed skin changes before playing. + ScheduleAfterChildren(() => skinnableSeeya.Play()); + } + else + seeya.Play(); // if playing the outro voice, we have more time to have fun with the background track. // initially fade to almost silent then ramp out over the remaining time. From c72e8a8b5e37d3e959a491e926422ded94dd931a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 21:28:38 +0300 Subject: [PATCH 1126/5199] Add functionality to switch to successfully joined channel --- osu.Game/Online/Chat/ChannelManager.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 47e45e67d1..1404c0f333 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -420,10 +420,11 @@ namespace osu.Game.Online.Chat /// Joins a channel if it has not already been joined. Must be called from the update thread. /// /// The channel to join. + /// Switch to the joined channel on successful request. /// The joined channel. Note that this may not match the parameter channel as it is a backed object. - public Channel JoinChannel(Channel channel) => joinChannel(channel, true); + public Channel JoinChannel(Channel channel, bool switchToJoined = false) => joinChannel(channel, switchToJoined, true); - private Channel joinChannel(Channel channel, bool fetchInitialMessages = false) + private Channel joinChannel(Channel channel, bool switchToJoined = false, bool fetchInitialMessages = false) { if (channel == null) return null; @@ -439,7 +440,7 @@ namespace osu.Game.Online.Chat case ChannelType.Multiplayer: // join is implicit. happens when you join a multiplayer game. // this will probably change in the future. - joinChannel(channel, fetchInitialMessages); + joinChannel(channel, switchToJoined, fetchInitialMessages); return channel; case ChannelType.PM: @@ -460,7 +461,7 @@ namespace osu.Game.Online.Chat default: var req = new JoinChannelRequest(channel); - req.Success += () => joinChannel(channel, fetchInitialMessages); + req.Success += () => joinChannel(channel, switchToJoined, fetchInitialMessages); req.Failure += ex => LeaveChannel(channel); api.Queue(req); return channel; @@ -472,7 +473,8 @@ namespace osu.Game.Online.Chat this.fetchInitialMessages(channel); } - CurrentChannel.Value ??= channel; + if (switchToJoined || CurrentChannel.Value == null) + CurrentChannel.Value = channel; return channel; } From 5315ff794a30295e5f018d23a8e5c4026782d97c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 21:29:32 +0300 Subject: [PATCH 1127/5199] Handle non-existing channels on message highlighting gracefully --- osu.Game/Overlays/ChatOverlay.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index d6a1b23c46..a0b5e55014 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -309,10 +309,21 @@ namespace osu.Game.Overlays /// The message to highlight. public void HighlightMessage(Message message) { - if (channelManager.CurrentChannel.Value.Id != message.ChannelId) - channelManager.CurrentChannel.Value = channelManager.JoinedChannels.Single(c => c.Id == message.ChannelId); + Channel targetChannel; - channelManager.CurrentChannel.Value.HighlightedMessage.Value = message; + if (channelManager.CurrentChannel.Value.Id == message.ChannelId) + targetChannel = channelManager.CurrentChannel.Value; + else + { + targetChannel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id == message.ChannelId); + + if (targetChannel != null) + channelManager.CurrentChannel.Value = targetChannel; + else + targetChannel = channelManager.JoinChannel(channelManager.AvailableChannels.SingleOrDefault(c => c.Id == message.ChannelId), true); + } + + targetChannel.HighlightedMessage.Value = message; } private float startDragChatHeight; From e41937083003a2b9c13ac0abd449c8068e574686 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 21:30:02 +0300 Subject: [PATCH 1128/5199] Add test coverage for highlighting message on left channel --- .../Visual/Online/TestSceneChatOverlay.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 5ee17f80bd..906b3faa98 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -463,6 +463,36 @@ namespace osu.Game.Tests.Visual.Online }); AddStep("Highlight message", () => chatOverlay.HighlightMessage(message)); + AddAssert("Switched to channel 2", () => channelManager.CurrentChannel.Value == channel2); + } + + [Test] + public void TestHighlightOnLeftChannel() + { + Message message = null; + + AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); + AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); + + AddStep("Join channel 2", () => channelManager.JoinChannel(channel2)); + AddStep("Send message in channel 2", () => + { + channel2.AddNewMessages(message = new Message + { + ChannelId = channel2.Id, + Content = "Message to highlight!", + Timestamp = DateTimeOffset.Now, + Sender = new APIUser + { + Id = 2, + Username = "Someone", + } + }); + }); + AddStep("Leave channel 2", () => channelManager.LeaveChannel(channel2)); + + AddStep("Highlight message", () => chatOverlay.HighlightMessage(message)); + AddAssert("Switched to channel 2", () => channelManager.CurrentChannel.Value == channel2); } [Test] From 8086f734514ba70ae9997fd2927af2e5dd432a45 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 22:50:02 +0300 Subject: [PATCH 1129/5199] Revert "Add functionality to switch to successfully joined channel" This reverts commit c72e8a8b5e37d3e959a491e926422ded94dd931a. --- osu.Game/Online/Chat/ChannelManager.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 1404c0f333..47e45e67d1 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -420,11 +420,10 @@ namespace osu.Game.Online.Chat /// Joins a channel if it has not already been joined. Must be called from the update thread. ///
/// The channel to join. - /// Switch to the joined channel on successful request. /// The joined channel. Note that this may not match the parameter channel as it is a backed object. - public Channel JoinChannel(Channel channel, bool switchToJoined = false) => joinChannel(channel, switchToJoined, true); + public Channel JoinChannel(Channel channel) => joinChannel(channel, true); - private Channel joinChannel(Channel channel, bool switchToJoined = false, bool fetchInitialMessages = false) + private Channel joinChannel(Channel channel, bool fetchInitialMessages = false) { if (channel == null) return null; @@ -440,7 +439,7 @@ namespace osu.Game.Online.Chat case ChannelType.Multiplayer: // join is implicit. happens when you join a multiplayer game. // this will probably change in the future. - joinChannel(channel, switchToJoined, fetchInitialMessages); + joinChannel(channel, fetchInitialMessages); return channel; case ChannelType.PM: @@ -461,7 +460,7 @@ namespace osu.Game.Online.Chat default: var req = new JoinChannelRequest(channel); - req.Success += () => joinChannel(channel, switchToJoined, fetchInitialMessages); + req.Success += () => joinChannel(channel, fetchInitialMessages); req.Failure += ex => LeaveChannel(channel); api.Queue(req); return channel; @@ -473,8 +472,7 @@ namespace osu.Game.Online.Chat this.fetchInitialMessages(channel); } - if (switchToJoined || CurrentChannel.Value == null) - CurrentChannel.Value = channel; + CurrentChannel.Value ??= channel; return channel; } From a31611bdec45c0378e9486cfb090733f7d12b2e0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 22:54:47 +0300 Subject: [PATCH 1130/5199] Improve channel switching flow in `HighlightMessage` --- .../Visual/Online/TestSceneChatOverlay.cs | 8 +++---- osu.Game/Online/Chat/MessageNotifier.cs | 22 ++++++++++--------- osu.Game/Overlays/ChatOverlay.cs | 20 ++++++++--------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 906b3faa98..00ff6a9576 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -435,7 +435,7 @@ namespace osu.Game.Tests.Visual.Online }); }); - AddStep("Highlight message", () => chatOverlay.HighlightMessage(message)); + AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, channel1)); } [Test] @@ -462,7 +462,7 @@ namespace osu.Game.Tests.Visual.Online }); }); - AddStep("Highlight message", () => chatOverlay.HighlightMessage(message)); + AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, channel2)); AddAssert("Switched to channel 2", () => channelManager.CurrentChannel.Value == channel2); } @@ -491,7 +491,7 @@ namespace osu.Game.Tests.Visual.Online }); AddStep("Leave channel 2", () => channelManager.LeaveChannel(channel2)); - AddStep("Highlight message", () => chatOverlay.HighlightMessage(message)); + AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, channel2)); AddAssert("Switched to channel 2", () => channelManager.CurrentChannel.Value == channel2); } @@ -522,7 +522,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("Highlight message and show chat", () => { - chatOverlay.HighlightMessage(message); + chatOverlay.HighlightMessage(message, channel1); chatOverlay.Show(); }); } diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 90b6d927c7..bcfec3cc0f 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -99,7 +99,7 @@ namespace osu.Game.Online.Chat if (checkForPMs(channel, message)) continue; - checkForMentions(message); + checkForMentions(channel, message); } } @@ -114,15 +114,15 @@ namespace osu.Game.Online.Chat if (!notifyOnPrivateMessage.Value || channel.Type != ChannelType.PM) return false; - notifications.Post(new PrivateMessageNotification(message)); + notifications.Post(new PrivateMessageNotification(message, channel)); return true; } - private void checkForMentions(Message message) + private void checkForMentions(Channel channel, Message message) { if (!notifyOnUsername.Value || !CheckContainsUsername(message.Content, localUser.Value.Username)) return; - notifications.Post(new MentionNotification(message)); + notifications.Post(new MentionNotification(message, channel)); } /// @@ -138,8 +138,8 @@ namespace osu.Game.Online.Chat public class PrivateMessageNotification : HighlightMessageNotification { - public PrivateMessageNotification(Message message) - : base(message) + public PrivateMessageNotification(Message message, Channel channel) + : base(message, channel) { Icon = FontAwesome.Solid.Envelope; Text = $"You received a private message from '{message.Sender.Username}'. Click to read it!"; @@ -148,8 +148,8 @@ namespace osu.Game.Online.Chat public class MentionNotification : HighlightMessageNotification { - public MentionNotification(Message message) - : base(message) + public MentionNotification(Message message, Channel channel) + : base(message, channel) { Icon = FontAwesome.Solid.At; Text = $"Your name was mentioned in chat by '{message.Sender.Username}'. Click to find out why!"; @@ -158,12 +158,14 @@ namespace osu.Game.Online.Chat public abstract class HighlightMessageNotification : SimpleNotification { - protected HighlightMessageNotification(Message message) + protected HighlightMessageNotification(Message message, Channel channel) { this.message = message; + this.channel = channel; } private readonly Message message; + private readonly Channel channel; public override bool IsImportant => false; @@ -175,7 +177,7 @@ namespace osu.Game.Online.Chat Activated = delegate { notificationOverlay.Hide(); - chatOverlay.HighlightMessage(message); + chatOverlay.HighlightMessage(message, channel); chatOverlay.Show(); return true; diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index a0b5e55014..3764ac42fc 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Specialized; +using System.Diagnostics; using System.Linq; using osuTK; using osuTK.Graphics; @@ -307,23 +308,20 @@ namespace osu.Game.Overlays /// Highlights a certain message in the specified channel. /// /// The message to highlight. - public void HighlightMessage(Message message) + /// The channel containing the message. + public void HighlightMessage(Message message, Channel channel) { - Channel targetChannel; + Debug.Assert(channel.Id == message.ChannelId); - if (channelManager.CurrentChannel.Value.Id == message.ChannelId) - targetChannel = channelManager.CurrentChannel.Value; - else + if (currentChannel.Value.Id != channel.Id) { - targetChannel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id == message.ChannelId); + if (!channel.Joined.Value) + channel = channelManager.JoinChannel(channel); - if (targetChannel != null) - channelManager.CurrentChannel.Value = targetChannel; - else - targetChannel = channelManager.JoinChannel(channelManager.AvailableChannels.SingleOrDefault(c => c.Id == message.ChannelId), true); + channelManager.CurrentChannel.Value = channel; } - targetChannel.HighlightedMessage.Value = message; + channel.HighlightedMessage.Value = message; } private float startDragChatHeight; From d07e3101ea5a040b00c3fc1f66bf1b131aa2dba8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 23:06:58 +0300 Subject: [PATCH 1131/5199] Improve message highlight handling in `DrawableChannel` --- osu.Game/Overlays/Chat/DrawableChannel.cs | 46 ++++++++++++----------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 5086d08e91..1d9f8c7ab7 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -87,29 +87,31 @@ namespace osu.Game.Overlays.Chat base.LoadComplete(); highlightedMessage = Channel.HighlightedMessage.GetBoundCopy(); - highlightedMessage.BindValueChanged(m => - { - if (m.NewValue == null) - return; - - // schedule highlight to ensure it performs after any pending "newMessagesArrived" calls. - // also schedule after children to ensure the scroll flow is updated with any new chat lines. - ScheduleAfterChildren(() => - { - var chatLine = chatLines.SingleOrDefault(c => c.Message.Equals(m.NewValue)); - - if (chatLine != null) - { - float center = scroll.GetChildPosInContent(chatLine, chatLine.DrawSize / 2) - scroll.DisplayableContent / 2; - scroll.ScrollTo(Math.Clamp(center, 0, scroll.ScrollableExtent)); - chatLine.Highlight(); - } - - highlightedMessage.Value = null; - }); - }, true); + highlightedMessage.BindValueChanged(_ => processMessageHighlighting(), true); } + /// + /// Processes any pending message in . + /// + /// + /// is for ensuring the scroll flow has updated with any new chat lines. + /// + private void processMessageHighlighting() => ScheduleAfterChildren(() => + { + if (highlightedMessage.Value == null) + return; + + var chatLine = chatLines.SingleOrDefault(c => c.Message.Equals(highlightedMessage.Value)); + if (chatLine == null) + return; + + float center = scroll.GetChildPosInContent(chatLine, chatLine.DrawSize / 2) - scroll.DisplayableContent / 2; + scroll.ScrollTo(Math.Clamp(center, 0, scroll.ScrollableExtent)); + chatLine.Highlight(); + + highlightedMessage.Value = null; + }); + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -179,6 +181,8 @@ namespace osu.Game.Overlays.Chat // to avoid making the container think the user has scrolled back up and unwantedly disable auto-scrolling. if (newMessages.Any(m => m is LocalMessage)) scroll.ScrollToEnd(); + + processMessageHighlighting(); }); private void pendingMessageResolved(Message existing, Message updated) => Schedule(() => From 38c61b2c1d088e788953e0246b94923dc9b896eb Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 00:14:33 -0800 Subject: [PATCH 1132/5199] Fix crash when loading legacy osu!taiko skins with touch input --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 43c5c07f80..321389676a 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -8,8 +8,6 @@ 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; using osuTK; @@ -112,9 +110,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy public readonly Sprite Rim; public readonly Sprite Centre; - [Resolved] - private DrumSampleTriggerSource sampleTriggerSource { get; set; } - public LegacyHalfDrum(bool flipped) { Masking = true; @@ -149,12 +144,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy if (e.Action == CentreAction) { target = Centre; - sampleTriggerSource.Play(HitType.Centre); } else if (e.Action == RimAction) { target = Rim; - sampleTriggerSource.Play(HitType.Rim); } if (target != null) From 7336c2c0bd06010ea7fabc12a91833ab1bd4e3ab Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 02:48:08 -0800 Subject: [PATCH 1133/5199] Fix osu!taiko alignment issue with legacy skins on Touch Controls --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 321389676a..d3eeb8e16a 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { Child = content = new Container { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Size = new Vector2(180, 200), Children = new Drawable[] { From 0f83308f7b1c18ebca3c89333954e26e34be2b56 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 02:49:10 -0800 Subject: [PATCH 1134/5199] Update osu!taiko TestSceneInputDrum with InputDrum changes for touch controls --- osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs index 24db046748..10fca30865 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200), - Child = new InputDrum(playfield.HitObjectContainer) + Child = new InputDrum() } }); } From 32c7a023f8206ec6cde60e4d1d2dc9641eb06a66 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Mar 2022 19:54:13 +0900 Subject: [PATCH 1135/5199] Make `OsuGame.ScreenChanged` `private` and non-`virtual` Just reducing complexity scope here. --- osu.Game/OsuGame.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index fb81e4fd14..7cd043083a 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1140,7 +1140,7 @@ namespace osu.Game MenuCursorContainer.CanShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false; } - protected virtual void ScreenChanged(IScreen current, IScreen newScreen) + private void screenChanged(IScreen current, IScreen newScreen) { skinEditor.Reset(); @@ -1187,11 +1187,11 @@ namespace osu.Game } } - private void screenPushed(IScreen lastScreen, IScreen newScreen) => ScreenChanged(lastScreen, newScreen); + private void screenPushed(IScreen lastScreen, IScreen newScreen) => screenChanged(lastScreen, newScreen); private void screenExited(IScreen lastScreen, IScreen newScreen) { - ScreenChanged(lastScreen, newScreen); + screenChanged(lastScreen, newScreen); if (newScreen == null) Exit(); From 1ed06f30e7feff438354617b55a32f2022728ef7 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 03:33:03 -0800 Subject: [PATCH 1136/5199] osu!Taiko touch implementation code cleanup --- .../UI/DrumSamplePlayer.cs | 27 +++++++++---------- .../UI/DrumTouchInputArea.cs | 4 +-- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs index 458dbc6ca1..609da0b389 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs @@ -28,21 +28,20 @@ namespace osu.Game.Rulesets.Taiko.UI public bool OnPressed(KeyBindingPressEvent e) { - if (e.Action == TaikoAction.LeftRim) + switch (e.Action) { - leftRimSampleTriggerSource.Play(HitType.Rim); - } - else if (e.Action == TaikoAction.LeftCentre) - { - leftCentreSampleTriggerSource.Play(HitType.Centre); - } - else if (e.Action == TaikoAction.RightCentre) - { - rightCentreSampleTriggerSource.Play(HitType.Centre); - } - else if (e.Action == TaikoAction.RightRim) - { - rightRimSampleTriggerSource.Play(HitType.Rim); + case TaikoAction.LeftRim: + leftRimSampleTriggerSource.Play(HitType.Rim); + break; + case TaikoAction.LeftCentre: + leftCentreSampleTriggerSource.Play(HitType.Centre); + break; + case TaikoAction.RightCentre: + rightCentreSampleTriggerSource.Play(HitType.Centre); + break; + case TaikoAction.RightRim: + rightRimSampleTriggerSource.Play(HitType.Rim); + break; } return false; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index a2c33a85e8..ccf59507d3 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.UI public class DrumTouchInputArea : Container { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float overhangPercent = 0.35f; + private const float offscreenPercent = 0.35f; private InputDrum touchInputDrum; private Circle drumBackground; @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Taiko.UI { Padding = new MarginPadding { Top = TaikoPlayfield.DEFAULT_HEIGHT * 2f, // Visual elements should start right below the playfield - Bottom = -touchInputDrum.DrawHeight * overhangPercent, // The drum should go past the bottom of the screen so that it can be wider + Bottom = -touchInputDrum.DrawHeight * offscreenPercent, // The drum should go past the bottom of the screen so that it can be wider }; } From c33a661a4990d681ec595a7b35afc9016d6b144c Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 03:36:03 -0800 Subject: [PATCH 1137/5199] osu!taiko touch implementation syntax formatting cleanup --- osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs index 609da0b389..d5215c05e9 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs @@ -10,13 +10,15 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Taiko.UI { - internal class DrumSamplePlayer : Container, IKeyBindingHandler { + internal class DrumSamplePlayer : Container, IKeyBindingHandler + { private DrumSampleTriggerSource leftRimSampleTriggerSource; private DrumSampleTriggerSource leftCentreSampleTriggerSource; private DrumSampleTriggerSource rightCentreSampleTriggerSource; private DrumSampleTriggerSource rightRimSampleTriggerSource; - public DrumSamplePlayer(HitObjectContainer hitObjectContainer) { + public DrumSamplePlayer(HitObjectContainer hitObjectContainer) + { Children = new Drawable[] { leftRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), From 35053eaeba38a8e15b609b7ee18421c532a9321a Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 04:43:57 -0800 Subject: [PATCH 1138/5199] Show and hide osu!taiko touch controls overlay based on most recent input type detected --- .../UI/DrumTouchInputArea.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index ccf59507d3..4b2c9c9936 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -35,12 +35,15 @@ namespace osu.Game.Rulesets.Taiko.UI // A map of (Finger Index OnTouchDown -> Which Taiko action was pressed), so that the corresponding action can be released OnTouchUp is released even if the touch position moved private Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); + private Container visibleComponents; + public DrumTouchInputArea() { RelativeSizeAxes = Axes.Both; RelativePositionAxes = Axes.Both; Children = new Drawable[] { - new Container() { + visibleComponents = new Container() { + Alpha = 0.0f, RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, Anchor = Anchor.BottomCentre, @@ -81,6 +84,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnMouseDown(MouseDownEvent e) { + ShowTouchControls(); mouseAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); keyBindingContainer?.TriggerPressed(mouseAction); return true; @@ -94,6 +98,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnTouchDown(TouchDownEvent e) { + ShowTouchControls(); TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); touchActions.Add(e.Touch.Source, taikoAction); keyBindingContainer?.TriggerPressed(touchActions[e.Touch.Source]); @@ -108,6 +113,21 @@ namespace osu.Game.Rulesets.Taiko.UI base.OnTouchUp(e); } + protected override bool OnKeyDown(KeyDownEvent e) + { + HideTouchControls(); + return false; + } + + + public void ShowTouchControls() { + visibleComponents.Animate(components => components.FadeIn(500, Easing.OutQuint)); + } + + public void HideTouchControls() { + visibleComponents.Animate(components => components.FadeOut(2000, Easing.OutQuint)); + } + private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { bool centreHit = inputIsCenterHit(inputPosition); bool leftSide = inputIsOnLeftSide(inputPosition); From 848b005097de38e213d7f33cf474e33840b7af6f Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 04:48:57 -0800 Subject: [PATCH 1139/5199] Remove unneccessary whitespace --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 4b2c9c9936..a944d3770b 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -119,7 +119,6 @@ namespace osu.Game.Rulesets.Taiko.UI return false; } - public void ShowTouchControls() { visibleComponents.Animate(components => components.FadeIn(500, Easing.OutQuint)); } From ac55fea3c953a6c81326308200d4b905cd921c3b Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Fri, 11 Mar 2022 14:04:12 +0100 Subject: [PATCH 1140/5199] Confine the host cursor to area of 'everything' scaling container --- .../Graphics/Containers/ScalingContainer.cs | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index d331b818a1..58d18e1b21 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -6,6 +6,8 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; +using osu.Framework.Layout; +using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Configuration; using osu.Game.Screens; @@ -66,7 +68,7 @@ namespace osu.Game.Graphics.Containers this.targetMode = targetMode; RelativeSizeAxes = Axes.Both; - InternalChild = sizableContainer = new AlwaysInputContainer + InternalChild = sizableContainer = new SizeableAlwaysInputContainer(targetMode == ScalingMode.Everything) { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, @@ -209,13 +211,50 @@ namespace osu.Game.Graphics.Containers } } - private class AlwaysInputContainer : Container + private class SizeableAlwaysInputContainer : Container { + [Resolved] + private GameHost host { get; set; } + + [Resolved] + private ISafeArea safeArea { get; set; } + + private readonly bool confineHostCursor; + private readonly LayoutValue cursorRectCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; - public AlwaysInputContainer() + /// + /// Container used for sizing/positioning purposes in . Always receives mouse input. + /// + /// Whether to confine the host cursor to the draw area of this container. + /// Cursor confinement will abide by the setting. + public SizeableAlwaysInputContainer(bool confineHostCursor) { RelativeSizeAxes = Axes.Both; + this.confineHostCursor = confineHostCursor; + + if (confineHostCursor) + AddLayout(cursorRectCache); + } + + protected override void Update() + { + base.Update(); + + if (confineHostCursor && !cursorRectCache.IsValid) + { + updateHostCursorConfineRect(); + cursorRectCache.Validate(); + } + } + + private void updateHostCursorConfineRect() + { + if (host.Window == null) return; + + bool coversWholeScreen = Size == Vector2.One && safeArea.SafeAreaPadding.Value.Total == Vector2.Zero; + host.Window.CursorConfineRect = coversWholeScreen ? (RectangleF?)null : ToScreenSpace(DrawRectangle).AABBFloat; } } } From 9a1ade4f7907b695aeaa0d5ca19d18e63c4fa554 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Mar 2022 23:08:40 +0900 Subject: [PATCH 1141/5199] Refactor `SkinEditor` to support switching target screens without full reload --- osu.Game/OsuGame.cs | 4 +-- osu.Game/Skinning/Editor/SkinEditor.cs | 29 ++++++++++----- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 35 +++++++++++++++---- 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 7cd043083a..ae117d03d2 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1142,8 +1142,6 @@ namespace osu.Game private void screenChanged(IScreen current, IScreen newScreen) { - skinEditor.Reset(); - switch (newScreen) { case IntroScreen intro: @@ -1185,6 +1183,8 @@ namespace osu.Game else BackButton.Hide(); } + + skinEditor.SetTarget((Screen)newScreen); } private void screenPushed(IScreen lastScreen, IScreen newScreen) => screenChanged(lastScreen, newScreen); diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index ae5cbc95f0..cd21507128 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -28,7 +28,7 @@ namespace osu.Game.Skinning.Editor protected override bool StartHidden => true; - private readonly Drawable targetScreen; + private Drawable targetScreen; private OsuTextFlowContainer headerText; @@ -42,11 +42,13 @@ namespace osu.Game.Skinning.Editor private bool hasBegunMutating; + private Container blueprintContainerContainer; + public SkinEditor(Drawable targetScreen) { - this.targetScreen = targetScreen; - RelativeSizeAxes = Axes.Both; + + UpdateTargetScreen(targetScreen); } [BackgroundDependencyLoader] @@ -113,13 +115,9 @@ namespace osu.Game.Skinning.Editor Origin = Anchor.CentreLeft, RequestPlacement = placeComponent }, - new Container + blueprintContainerContainer = new Container { RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new SkinBlueprintContainer(targetScreen), - } }, } } @@ -147,6 +145,21 @@ namespace osu.Game.Skinning.Editor }, true); } + public void UpdateTargetScreen(Drawable targetScreen) + { + this.targetScreen = targetScreen; + + Scheduler.AddOnce(loadBlueprintContainer); + + void loadBlueprintContainer() + { + blueprintContainerContainer.Children = new Drawable[] + { + new SkinBlueprintContainer(targetScreen), + }; + } + } + private void skinChanged() { headerText.Clear(); diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 61c363b019..08dad2a146 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.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.Diagnostics; using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -8,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Screens; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; @@ -114,15 +116,36 @@ namespace osu.Game.Skinning.Editor } /// - /// Exit any existing skin editor due to the game state changing. + /// Set a new target screen which will be used to find skinnable components. /// - public void Reset() + public void SetTarget(Screen screen) { - skinEditor?.Save(); - skinEditor?.Hide(); - skinEditor?.Expire(); + if (skinEditor == null) return; - skinEditor = null; + skinEditor.Save(); + + // AddOnce with paramter will ensure the newest target is loaded if there is any overlap. + Scheduler.AddOnce(setTarget, screen); + } + + private void setTarget(Screen target) + { + Debug.Assert(skinEditor != null); + + if (!target.IsLoaded) + { + Scheduler.AddOnce(setTarget, target); + return; + } + + if (skinEditor.State.Value == Visibility.Visible) + skinEditor.UpdateTargetScreen(target); + else + { + skinEditor.Hide(); + skinEditor.Expire(); + skinEditor = null; + } } } } From 3db42dd77254df799efa9334a2389efacf31e622 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Mar 2022 20:00:32 +0900 Subject: [PATCH 1142/5199] Allow skin editor to target different target containers for placement purposes --- osu.Game/Skinning/Editor/SkinEditor.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index ae5cbc95f0..36b06f01d2 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -171,7 +171,12 @@ namespace osu.Game.Skinning.Editor private void placeComponent(Type type) { - var targetContainer = getTarget(SkinnableTarget.MainHUDComponents); + var target = availableTargets.FirstOrDefault()?.Target; + + if (target == null) + return; + + var targetContainer = getTarget(target.Value); if (targetContainer == null) return; From 5b70139b333bbe03fa6781423f3bec955a4fa6c1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Mar 2022 18:32:32 +0300 Subject: [PATCH 1143/5199] Avoid running message highlight processing more than once --- osu.Game/Overlays/Chat/DrawableChannel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 1d9f8c7ab7..52878199f0 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -96,7 +96,7 @@ namespace osu.Game.Overlays.Chat /// /// is for ensuring the scroll flow has updated with any new chat lines. /// - private void processMessageHighlighting() => ScheduleAfterChildren(() => + private void processMessageHighlighting() => SchedulerAfterChildren.AddOnce(() => { if (highlightedMessage.Value == null) return; From 53c57661c70a1da9836fd2eb4418ad58a0b45df9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Mar 2022 18:34:22 +0300 Subject: [PATCH 1144/5199] Move implementtaion detail to inline comment --- osu.Game/Overlays/Chat/DrawableChannel.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 52878199f0..632517aa31 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -93,9 +93,7 @@ namespace osu.Game.Overlays.Chat /// /// Processes any pending message in . /// - /// - /// is for ensuring the scroll flow has updated with any new chat lines. - /// + // ScheduleAfterChildren is for ensuring the scroll flow has updated with any new chat lines. private void processMessageHighlighting() => SchedulerAfterChildren.AddOnce(() => { if (highlightedMessage.Value == null) From ac17c047f6264eff3c289810f83f1f5110e495d0 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Sat, 12 Mar 2022 05:01:40 -0800 Subject: [PATCH 1145/5199] Code formatting --- .../UI/DrumTouchInputArea.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index a944d3770b..046816c013 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -37,7 +37,8 @@ namespace osu.Game.Rulesets.Taiko.UI private Container visibleComponents; - public DrumTouchInputArea() { + public DrumTouchInputArea() + { RelativeSizeAxes = Axes.Both; RelativePositionAxes = Axes.Both; Children = new Drawable[] @@ -102,7 +103,6 @@ namespace osu.Game.Rulesets.Taiko.UI TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); touchActions.Add(e.Touch.Source, taikoAction); keyBindingContainer?.TriggerPressed(touchActions[e.Touch.Source]); - return true; } @@ -119,15 +119,18 @@ namespace osu.Game.Rulesets.Taiko.UI return false; } - public void ShowTouchControls() { + public void ShowTouchControls() + { visibleComponents.Animate(components => components.FadeIn(500, Easing.OutQuint)); } - public void HideTouchControls() { + public void HideTouchControls() + { visibleComponents.Animate(components => components.FadeOut(2000, Easing.OutQuint)); } - private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { + private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) + { bool centreHit = inputIsCenterHit(inputPosition); bool leftSide = inputIsOnLeftSide(inputPosition); @@ -136,12 +139,14 @@ namespace osu.Game.Rulesets.Taiko.UI (leftSide ? TaikoAction.LeftRim : TaikoAction.RightRim); } - private bool inputIsOnLeftSide(Vector2 inputPosition) { + private bool inputIsOnLeftSide(Vector2 inputPosition) + { Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; return inputPositionToDrumCentreDelta.X < 0f; } - private bool inputIsCenterHit(Vector2 inputPosition) { + private bool inputIsCenterHit(Vector2 inputPosition) + { Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; float inputDrumRadius = Math.Max(touchInputDrum.Width, touchInputDrum.DrawHeight) / 2f; From 6f99455d9490469af3938fe19fcef361bdab1098 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Sat, 12 Mar 2022 05:17:45 -0800 Subject: [PATCH 1146/5199] Improve centre input size fitting for legacy skins --- .../Skinning/Legacy/LegacyInputDrum.cs | 1 + osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 2 +- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index d3eeb8e16a..8ab9e56aec 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -18,6 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy ///
internal class LegacyInputDrum : Container { + public float centre_size = 0.5f; private LegacyHalfDrum left; private LegacyHalfDrum right; private Container content; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 046816c013..5f4eecd3bb 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Taiko.UI Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; float inputDrumRadius = Math.Max(touchInputDrum.Width, touchInputDrum.DrawHeight) / 2f; - float centreRadius = (inputDrumRadius * InputDrum.centre_size); + float centreRadius = (inputDrumRadius * touchInputDrum.centre_size); return inputPositionToDrumCentreDelta.Length <= centreRadius; } } diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 76594b86c1..a7ae763ab5 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.UI ///
internal class InputDrum : Container { - public const float centre_size = 0.7f; + public float centre_size = 0.7f; private const float middle_split = 0.025f; public InputDrum() @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.UI Scale = new Vector2(0.9f), Children = new Drawable[] { - new TaikoHalfDrum(false) + new TaikoHalfDrum(false, centre_size) { Name = "Left Half", Anchor = Anchor.Centre, @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.UI RimAction = TaikoAction.LeftRim, CentreAction = TaikoAction.LeftCentre }, - new TaikoHalfDrum(true) + new TaikoHalfDrum(true, centre_size) { Name = "Right Half", Anchor = Anchor.Centre, @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Sprite centre; private readonly Sprite centreHit; - public TaikoHalfDrum(bool flipped) + public TaikoHalfDrum(bool flipped, float centre_size) { Masking = true; From 9db80c33350d194fef952c0fb98e0c1252b09097 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Sat, 12 Mar 2022 05:32:02 -0800 Subject: [PATCH 1147/5199] Code cleanup --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 5f4eecd3bb..82b289ef14 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.UI // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) private const float offscreenPercent = 0.35f; private InputDrum touchInputDrum; - private Circle drumBackground; + private Circle drumBackground; private KeyBindingContainer keyBindingContainer; @@ -70,7 +70,8 @@ namespace osu.Game.Rulesets.Taiko.UI protected override void LoadComplete() { - Padding = new MarginPadding { + Padding = new MarginPadding + { Top = TaikoPlayfield.DEFAULT_HEIGHT * 2f, // Visual elements should start right below the playfield Bottom = -touchInputDrum.DrawHeight * offscreenPercent, // The drum should go past the bottom of the screen so that it can be wider }; From 2de6bb033bffdaeeb8a1095264b9ad745b0065c3 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Sat, 12 Mar 2022 07:51:40 -0800 Subject: [PATCH 1148/5199] Adjust default touch drum overlay size to be more comfortable on phones --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 82b289ef14..e501a40d20 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.UI public class DrumTouchInputArea : Container { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float offscreenPercent = 0.35f; + private const float offscreenPercent = 0.4f; private InputDrum touchInputDrum; private Circle drumBackground; @@ -62,6 +62,7 @@ namespace osu.Game.Rulesets.Taiko.UI touchInputDrum = new InputDrum() { Anchor = Anchor.Centre, Origin = Anchor.Centre, + centre_size = 0.8f, }, } }, From b628a65cfa5e790961ec3db6edc27fe41c89334b Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Sat, 12 Mar 2022 08:03:24 -0800 Subject: [PATCH 1149/5199] Revert "Adjust default touch drum overlay size to be more comfortable on phones" This reverts commit 2de6bb033bffdaeeb8a1095264b9ad745b0065c3. --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index e501a40d20..82b289ef14 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.UI public class DrumTouchInputArea : Container { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float offscreenPercent = 0.4f; + private const float offscreenPercent = 0.35f; private InputDrum touchInputDrum; private Circle drumBackground; @@ -62,7 +62,6 @@ namespace osu.Game.Rulesets.Taiko.UI touchInputDrum = new InputDrum() { Anchor = Anchor.Centre, Origin = Anchor.Centre, - centre_size = 0.8f, }, } }, From 0b74bde653c2e8be85b4467b8e3ed3db5fe4f5ed Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 13 Mar 2022 08:21:52 +0300 Subject: [PATCH 1150/5199] Disable taiko playfield aspect ratio lock on "Classic" mod --- osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs | 1 + osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs | 7 ++++++- .../UI/TaikoPlayfieldAdjustmentContainer.cs | 14 ++++++++++---- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs index 6520517039..5a6f57bc36 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs @@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset; + drawableTaikoRuleset.LockPlayfieldAspect.Value = false; } public void Update(Playfield playfield) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 824b95639b..2efc4176f5 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -29,6 +29,8 @@ namespace osu.Game.Rulesets.Taiko.UI { public new BindableDouble TimeRange => base.TimeRange; + public readonly BindableBool LockPlayfieldAspect = new BindableBool(true); + protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping; protected override bool UserScrollSpeedAdjustment => false; @@ -70,7 +72,10 @@ namespace osu.Game.Rulesets.Taiko.UI return ControlPoints[result]; } - public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer(); + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer + { + LockPlayfieldAspect = { BindTarget = LockPlayfieldAspect } + }; protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs index 1041456020..9cf530e903 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs @@ -2,9 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.UI; -using osuTK; namespace osu.Game.Rulesets.Taiko.UI { @@ -13,16 +13,22 @@ namespace osu.Game.Rulesets.Taiko.UI private const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768; private const float default_aspect = 16f / 9f; + public readonly IBindable LockPlayfieldAspect = new BindableBool(true); + protected override void Update() { base.Update(); - float aspectAdjust = Math.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect; - Size = new Vector2(1, default_relative_height * aspectAdjust); + float height = default_relative_height; + + if (LockPlayfieldAspect.Value) + height *= Math.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect; + + Height = height; // Position the taiko playfield exactly one playfield from the top of the screen. RelativePositionAxes = Axes.Y; - Y = Size.Y; + Y = height; } } } From d1a9b88fe7756e6925b26511459840009b3180c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 13 Mar 2022 16:05:45 +0900 Subject: [PATCH 1151/5199] Fix typo in comment Co-authored-by: Salman Ahmed --- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 08dad2a146..abd8272633 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -124,7 +124,7 @@ namespace osu.Game.Skinning.Editor skinEditor.Save(); - // AddOnce with paramter will ensure the newest target is loaded if there is any overlap. + // AddOnce with parameter will ensure the newest target is loaded if there is any overlap. Scheduler.AddOnce(setTarget, screen); } From f95e753adbfb170317c68ba19155f16e1b3a10a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 13 Mar 2022 16:10:06 +0900 Subject: [PATCH 1152/5199] Rename double-container variable name --- osu.Game/Skinning/Editor/SkinEditor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index cd21507128..829faab116 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -42,7 +42,7 @@ namespace osu.Game.Skinning.Editor private bool hasBegunMutating; - private Container blueprintContainerContainer; + private Container content; public SkinEditor(Drawable targetScreen) { @@ -115,7 +115,7 @@ namespace osu.Game.Skinning.Editor Origin = Anchor.CentreLeft, RequestPlacement = placeComponent }, - blueprintContainerContainer = new Container + content = new Container { RelativeSizeAxes = Axes.Both, }, @@ -153,7 +153,7 @@ namespace osu.Game.Skinning.Editor void loadBlueprintContainer() { - blueprintContainerContainer.Children = new Drawable[] + content.Children = new Drawable[] { new SkinBlueprintContainer(targetScreen), }; From c99397f75abc9b431954f7b90176e1d44dd6e51e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Mar 2022 23:27:27 +0900 Subject: [PATCH 1153/5199] Add the ability to add settings to skinnable elements --- osu.Game/Extensions/DrawableExtensions.cs | 13 +++++++++++++ osu.Game/Screens/Play/HUD/SkinnableInfo.cs | 14 ++++++++++++++ osu.Game/Skinning/ISkinnableDrawable.cs | 20 ++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 005804789e..d1aba2bfe3 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -1,8 +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 Humanizer; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Configuration; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; using osuTK; @@ -59,8 +62,18 @@ namespace osu.Game.Extensions component.Origin = info.Origin; if (component is ISkinnableDrawable skinnable) + { skinnable.UsesFixedAnchor = info.UsesFixedAnchor; + foreach (var (_, property) in component.GetSettingsSourceProperties()) + { + if (!info.Settings.TryGetValue(property.Name.Underscore(), out object settingValue)) + continue; + + skinnable.CopyAdjustedSetting((IBindable)property.GetValue(component), settingValue); + } + } + if (component is Container container) { foreach (var child in info.Children) diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index a2b84c79af..5a51e7f455 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -4,11 +4,15 @@ using System; using System.Collections.Generic; using System.Linq; +using Humanizer; using Newtonsoft.Json; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Configuration; using osu.Game.Extensions; using osu.Game.Skinning; +using osu.Game.Utils; using osuTK; namespace osu.Game.Screens.Play.HUD @@ -34,6 +38,8 @@ namespace osu.Game.Screens.Play.HUD /// public bool UsesFixedAnchor { get; set; } + public Dictionary Settings { get; set; } = new Dictionary(); + public List Children { get; } = new List(); [JsonConstructor] @@ -58,6 +64,14 @@ namespace osu.Game.Screens.Play.HUD if (component is ISkinnableDrawable skinnable) UsesFixedAnchor = skinnable.UsesFixedAnchor; + foreach (var (_, property) in component.GetSettingsSourceProperties()) + { + var bindable = (IBindable)property.GetValue(component); + + if (!bindable.IsDefault) + Settings.Add(property.Name.Underscore(), ModUtils.GetSettingUnderlyingValue(bindable)); + } + if (component is Container container) { foreach (var child in container.OfType().OfType()) diff --git a/osu.Game/Skinning/ISkinnableDrawable.cs b/osu.Game/Skinning/ISkinnableDrawable.cs index 60b40982e5..3fc6a2fdd8 100644 --- a/osu.Game/Skinning/ISkinnableDrawable.cs +++ b/osu.Game/Skinning/ISkinnableDrawable.cs @@ -1,6 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using osu.Framework.Bindables; +using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; namespace osu.Game.Skinning @@ -21,5 +24,22 @@ namespace osu.Game.Skinning /// If , a fixed anchor point has been defined. ///
bool UsesFixedAnchor { get; set; } + + void CopyAdjustedSetting(IBindable target, object source) + { + if (source is IBindable sourceBindable) + { + // copy including transfer of default values. + target.BindTo(sourceBindable); + target.UnbindFrom(sourceBindable); + } + else + { + if (!(target is IParseable parseable)) + throw new InvalidOperationException($"Bindable type {target.GetType().ReadableName()} is not {nameof(IParseable)}."); + + parseable.Parse(source); + } + } } } From 8d1ee28e6711eec49735625f8e148993aebbec4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Mar 2022 23:30:46 +0900 Subject: [PATCH 1154/5199] Add settings modification UI to skin editor --- .../Rulesets/Edit/ScrollingToolboxGroup.cs | 4 ++- osu.Game/Skinning/Editor/SkinEditor.cs | 28 ++++++++++++++++++- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 4 ++- .../Skinning/Editor/SkinSettingsToolbox.cs | 23 +++++++++++++++ 4 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Skinning/Editor/SkinSettingsToolbox.cs diff --git a/osu.Game/Rulesets/Edit/ScrollingToolboxGroup.cs b/osu.Game/Rulesets/Edit/ScrollingToolboxGroup.cs index 9998a997b3..98e026c49a 100644 --- a/osu.Game/Rulesets/Edit/ScrollingToolboxGroup.cs +++ b/osu.Game/Rulesets/Edit/ScrollingToolboxGroup.cs @@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Edit { protected readonly OsuScrollContainer Scroll; + protected readonly FillFlowContainer FillFlow; + protected override Container Content { get; } public ScrollingToolboxGroup(string title, float scrollAreaHeight) @@ -20,7 +22,7 @@ namespace osu.Game.Rulesets.Edit { RelativeSizeAxes = Axes.X, Height = scrollAreaHeight, - Child = Content = new FillFlowContainer + Child = Content = FillFlow = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 459fbf3be5..ef26682c03 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -11,10 +12,12 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Testing; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Components.Menus; namespace osu.Game.Skinning.Editor @@ -44,6 +47,8 @@ namespace osu.Game.Skinning.Editor private Container content; + private EditorToolboxGroup settingsToolbox; + public SkinEditor(Drawable targetScreen) { RelativeSizeAxes = Axes.Both; @@ -103,7 +108,8 @@ namespace osu.Game.Skinning.Editor ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize), - new Dimension() + new Dimension(), + new Dimension(GridSizeMode.AutoSize), }, Content = new[] { @@ -119,6 +125,11 @@ namespace osu.Game.Skinning.Editor { RelativeSizeAxes = Axes.Both, }, + settingsToolbox = new SkinSettingsToolbox + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + } } } } @@ -143,12 +154,15 @@ namespace osu.Game.Skinning.Editor hasBegunMutating = false; Scheduler.AddOnce(skinChanged); }, true); + + SelectedComponents.BindCollectionChanged(selectionChanged); } public void UpdateTargetScreen(Drawable targetScreen) { this.targetScreen = targetScreen; + SelectedComponents.Clear(); Scheduler.AddOnce(loadBlueprintContainer); void loadBlueprintContainer() @@ -210,6 +224,18 @@ namespace osu.Game.Skinning.Editor SelectedComponents.Add(component); } + private void selectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + settingsToolbox.Clear(); + + var first = SelectedComponents.OfType().FirstOrDefault(); + + if (first != null) + { + settingsToolbox.Children = first.CreateSettingsControls().ToArray(); + } + } + private IEnumerable availableTargets => targetScreen.ChildrenOfType(); private ISkinnableTarget getTarget(SkinnableTarget target) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index abd8272633..08cdbf0aa9 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -101,9 +101,11 @@ namespace osu.Game.Skinning.Editor private void editorVisibilityChanged(ValueChangedEvent visibility) { + const float toolbar_padding_requirement = 0.18f; + if (visibility.NewValue == Visibility.Visible) { - target.SetCustomRect(new RectangleF(0.18f, 0.1f, VISIBLE_TARGET_SCALE, VISIBLE_TARGET_SCALE), true); + target.SetCustomRect(new RectangleF(toolbar_padding_requirement, 0.1f, 0.8f - toolbar_padding_requirement, 0.7f), true); } else { diff --git a/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs b/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs new file mode 100644 index 0000000000..c0ef8e7316 --- /dev/null +++ b/osu.Game/Skinning/Editor/SkinSettingsToolbox.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. + +using osu.Framework.Graphics; +using osu.Game.Rulesets.Edit; +using osuTK; + +namespace osu.Game.Skinning.Editor +{ + internal class SkinSettingsToolbox : ScrollingToolboxGroup + { + public const float WIDTH = 200; + + public SkinSettingsToolbox() + : base("Settings", 600) + { + RelativeSizeAxes = Axes.None; + Width = WIDTH; + + FillFlow.Spacing = new Vector2(10); + } + } +} From 458136dfe779b7daa83bbaf9c76bb0f37d14cc58 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Mar 2022 19:55:25 +0900 Subject: [PATCH 1155/5199] Add `BigBlackBox` for skinning testing purposes --- osu.Game/Skinning/Components/BigBlackBox.cs | 47 +++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 osu.Game/Skinning/Components/BigBlackBox.cs diff --git a/osu.Game/Skinning/Components/BigBlackBox.cs b/osu.Game/Skinning/Components/BigBlackBox.cs new file mode 100644 index 0000000000..b86a863766 --- /dev/null +++ b/osu.Game/Skinning/Components/BigBlackBox.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using JetBrains.Annotations; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Skinning.Components +{ + /// + /// Intended to be a test bed for skinning. May be removed at some point in the future. + /// + [UsedImplicitly] + public class BigBlackBox : CompositeDrawable, ISkinnableDrawable + { + public bool UsesFixedAnchor { get; set; } + + public BigBlackBox() + { + Size = new Vector2(150); + + Masking = true; + CornerRadius = 20; + CornerExponent = 5; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }, + + new OsuSpriteText + { + Text = "Big Black Box", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }; + } + } +} From e4211104b0fb93ff266cae340b2fc02e54be24be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Mar 2022 20:33:15 +0900 Subject: [PATCH 1156/5199] Add new settings to the big black box --- osu.Game/Skinning/Components/BigBlackBox.cs | 34 +++++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/Components/BigBlackBox.cs b/osu.Game/Skinning/Components/BigBlackBox.cs index b86a863766..fc6c652343 100644 --- a/osu.Game/Skinning/Components/BigBlackBox.cs +++ b/osu.Game/Skinning/Components/BigBlackBox.cs @@ -2,9 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using JetBrains.Annotations; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Configuration; using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -19,6 +21,19 @@ namespace osu.Game.Skinning.Components { public bool UsesFixedAnchor { get; set; } + [SettingSource("Spin", "Should the text spin")] + public Bindable TextSpin { get; } = new BindableBool(); + + [SettingSource("Alpha", "The alpha value of this box")] + public BindableNumber BoxAlpha { get; } = new BindableNumber(1) + { + MinValue = 0, + MaxValue = 1, + }; + + private readonly Box box; + private readonly OsuSpriteText text; + public BigBlackBox() { Size = new Vector2(150); @@ -29,13 +44,12 @@ namespace osu.Game.Skinning.Components InternalChildren = new Drawable[] { - new Box + box = new Box { Colour = Color4.Black, RelativeSizeAxes = Axes.Both, }, - - new OsuSpriteText + text = new OsuSpriteText { Text = "Big Black Box", Anchor = Anchor.Centre, @@ -43,5 +57,19 @@ namespace osu.Game.Skinning.Components } }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + BoxAlpha.BindValueChanged(alpha => box.Alpha = alpha.NewValue, true); + TextSpin.BindValueChanged(spin => + { + if (spin.NewValue) + text.Spin(1000, RotationDirection.Clockwise); + else + text.ClearTransforms(); + }, true); + } } } From 7d2752185dfbf6261a240e35ca539afd1e902485 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 13 Mar 2022 16:47:08 +0900 Subject: [PATCH 1157/5199] Add disclaimer and adjust metrics of `BigBlackBox` --- osu.Game/Skinning/Components/BigBlackBox.cs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Components/BigBlackBox.cs b/osu.Game/Skinning/Components/BigBlackBox.cs index fc6c652343..8e57143a12 100644 --- a/osu.Game/Skinning/Components/BigBlackBox.cs +++ b/osu.Game/Skinning/Components/BigBlackBox.cs @@ -7,6 +7,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -21,7 +23,7 @@ namespace osu.Game.Skinning.Components { public bool UsesFixedAnchor { get; set; } - [SettingSource("Spin", "Should the text spin")] + [SettingSource("Spining text", "Whether the big text should spin")] public Bindable TextSpin { get; } = new BindableBool(); [SettingSource("Alpha", "The alpha value of this box")] @@ -29,14 +31,16 @@ namespace osu.Game.Skinning.Components { MinValue = 0, MaxValue = 1, + Precision = 0.01f, }; private readonly Box box; private readonly OsuSpriteText text; + private readonly OsuTextFlowContainer disclaimer; public BigBlackBox() { - Size = new Vector2(150); + Size = new Vector2(250); Masking = true; CornerRadius = 20; @@ -54,6 +58,17 @@ namespace osu.Game.Skinning.Components Text = "Big Black Box", Anchor = Anchor.Centre, Origin = Anchor.Centre, + Font = OsuFont.Default.With(size: 40) + }, + disclaimer = new OsuTextFlowContainer(st => st.Font = OsuFont.Default.With(size: 10)) + { + Text = "This is intended to be a test component and may disappear in the future!", + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding(10), + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + TextAnchor = Anchor.TopCentre, } }; } @@ -70,6 +85,8 @@ namespace osu.Game.Skinning.Components else text.ClearTransforms(); }, true); + + disclaimer.FadeOutFromOne(5000, Easing.InQuint); } } } From 4a8aa72d1ba96dcb60f545250800ef71437a4cf3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 14 Mar 2022 01:03:08 +0300 Subject: [PATCH 1158/5199] Nudge osu!taiko playfield's height to perfectly match with osu!(stable) --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index d650cab729..504b10e9bc 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Taiko.UI /// /// Default height of a when inside a . /// - public const float DEFAULT_HEIGHT = 212; + public const float DEFAULT_HEIGHT = 200; private Container hitExplosionContainer; private Container kiaiExplosionContainer; From dbc26c3534e37108497ae64e040ba32101988fa6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 14 Mar 2022 03:46:05 +0300 Subject: [PATCH 1159/5199] Add failing test assertion --- .../Gameplay/TestSceneDrawableStoryboardSprite.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs index 52bedc328d..4011a6bc82 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs @@ -1,11 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Skinning; @@ -22,6 +24,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached] private Storyboard storyboard { get; set; } = new Storyboard(); + private IEnumerable sprites => this.ChildrenOfType(); + [Test] public void TestSkinSpriteDisallowedByDefault() { @@ -41,9 +45,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true); - AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.Centre, Vector2.Zero))); + AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); assertSpritesFromSkin(true); + + AddAssert("skinnable sprite has correct size", () => sprites.Any(s => Precision.AlmostEquals(s.ChildrenOfType().Single().Size, new Vector2(128, 128)))); } private DrawableStoryboardSprite createSprite(string lookupName, Anchor origin, Vector2 initialPosition) @@ -57,7 +63,6 @@ namespace osu.Game.Tests.Visual.Gameplay private void assertSpritesFromSkin(bool fromSkin) => AddAssert($"sprites are {(fromSkin ? "from skin" : "from storyboard")}", - () => this.ChildrenOfType() - .All(sprite => sprite.ChildrenOfType().Any() == fromSkin)); + () => sprites.All(sprite => sprite.ChildrenOfType().Any() == fromSkin)); } } From c9d54834be09429edc2a96a99ea579e15ac83870 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 14 Mar 2022 03:35:08 +0300 Subject: [PATCH 1160/5199] Fix `SkinnableSprite`s in storyboards not autosizing to their textures --- osu.Game/Storyboards/Storyboard.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 2faed98ae0..b662b98e4e 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -104,7 +104,13 @@ namespace osu.Game.Storyboards drawable = new Sprite { Texture = textureStore.Get(storyboardPath) }; // if the texture isn't available locally in the beatmap, some storyboards choose to source from the underlying skin lookup hierarchy. else if (UseSkinSprites) - drawable = new SkinnableSprite(path); + { + drawable = new SkinnableSprite(path) + { + RelativeSizeAxes = Axes.None, + AutoSizeAxes = Axes.Both, + }; + } return drawable; } From 720e1cd206154fc40f8889ef813f1e31d01d1e0a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 14 Mar 2022 03:35:23 +0300 Subject: [PATCH 1161/5199] Add failing test cases --- .../TestSceneDrawableStoryboardSprite.cs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs index 4011a6bc82..34e6d1996d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Framework.Utils; @@ -52,6 +53,49 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("skinnable sprite has correct size", () => sprites.Any(s => Precision.AlmostEquals(s.ChildrenOfType().Single().Size, new Vector2(128, 128)))); } + [Test] + public void TestFlippedSprite() + { + const string lookup_name = "hitcircleoverlay"; + + AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true); + AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); + AddStep("flip sprites", () => sprites.ForEach(s => + { + s.FlipH = true; + s.FlipV = true; + })); + AddAssert("origin flipped", () => sprites.All(s => s.Origin == Anchor.BottomRight)); + } + + [Test] + public void TestNegativeScale() + { + const string lookup_name = "hitcircleoverlay"; + + AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true); + AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); + AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(-1))); + AddAssert("origin flipped", () => sprites.All(s => s.Origin == Anchor.BottomRight)); + } + + [Test] + public void TestNegativeScaleWithFlippedSprite() + { + const string lookup_name = "hitcircleoverlay"; + + AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true); + AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); + AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(-1))); + AddAssert("origin flipped", () => sprites.All(s => s.Origin == Anchor.BottomRight)); + AddStep("flip sprites", () => sprites.ForEach(s => + { + s.FlipH = true; + s.FlipV = true; + })); + AddAssert("origin back", () => sprites.All(s => s.Origin == Anchor.TopLeft)); + } + private DrawableStoryboardSprite createSprite(string lookupName, Anchor origin, Vector2 initialPosition) => new DrawableStoryboardSprite( new StoryboardSprite(lookupName, origin, initialPosition) From 0b8c89bfa81c88019f629c8941d542f15d06fc6d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 14 Mar 2022 03:50:12 +0300 Subject: [PATCH 1162/5199] Fix drawable storyboard sprites not flipping origin on negative scale --- osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index eb877f3dff..ad4402dcc4 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -79,7 +79,7 @@ namespace osu.Game.Storyboards.Drawables { var origin = base.Origin; - if (FlipH) + if ((FlipH || VectorScale.X < 0) && !(FlipH && VectorScale.X < 0)) { if (origin.HasFlagFast(Anchor.x0)) origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); @@ -87,7 +87,7 @@ namespace osu.Game.Storyboards.Drawables origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); } - if (FlipV) + if ((FlipV || VectorScale.Y < 0) && !(FlipV && VectorScale.Y < 0)) { if (origin.HasFlagFast(Anchor.y0)) origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); From 9cf05080da2bb0125eaf3483326d1ab4fa0ab66c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 14 Mar 2022 04:40:24 +0300 Subject: [PATCH 1163/5199] Simplify conditionals to one XOR operations with comments --- osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index ad4402dcc4..f3173497e8 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -79,7 +79,8 @@ namespace osu.Game.Storyboards.Drawables { var origin = base.Origin; - if ((FlipH || VectorScale.X < 0) && !(FlipH && VectorScale.X < 0)) + // Either flip horizontally or negative X scale, but not both. + if (FlipH ^ (VectorScale.X < 0)) { if (origin.HasFlagFast(Anchor.x0)) origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); @@ -87,7 +88,8 @@ namespace osu.Game.Storyboards.Drawables origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); } - if ((FlipV || VectorScale.Y < 0) && !(FlipV && VectorScale.Y < 0)) + // Either flip vertically or negative Y scale, but not both. + if (FlipV ^ (VectorScale.Y < 0)) { if (origin.HasFlagFast(Anchor.y0)) origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); From 740a72e16d07b380177c331096780043ded35da1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 14 Mar 2022 05:44:34 +0300 Subject: [PATCH 1164/5199] Share origin adjustment logic between storyboard sprite and animation --- .../Drawables/DrawableStoryboardAnimation.cs | 30 +------------ osu.Game/Storyboards/StoryboardExtensions.cs | 43 +++++++++++++++++++ 2 files changed, 45 insertions(+), 28 deletions(-) create mode 100644 osu.Game/Storyboards/StoryboardExtensions.cs diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 81623a9307..28ac83a25c 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -3,7 +3,6 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Textures; @@ -70,34 +69,9 @@ namespace osu.Game.Storyboards.Drawables public override bool RemoveWhenNotAlive => false; - protected override Vector2 DrawScale - => new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y) * VectorScale; + protected override Vector2 DrawScale => new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y) * VectorScale; - public override Anchor Origin - { - get - { - var origin = base.Origin; - - if (FlipH) - { - if (origin.HasFlagFast(Anchor.x0)) - origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); - else if (origin.HasFlagFast(Anchor.x2)) - origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); - } - - if (FlipV) - { - if (origin.HasFlagFast(Anchor.y0)) - origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); - else if (origin.HasFlagFast(Anchor.y2)) - origin = Anchor.y0 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); - } - - return origin; - } - } + public override Anchor Origin => StoryboardExtensions.AdjustOrigin(base.Origin, VectorScale, FlipH, FlipV); public override bool IsPresent => !float.IsNaN(DrawPosition.X) && !float.IsNaN(DrawPosition.Y) && base.IsPresent; diff --git a/osu.Game/Storyboards/StoryboardExtensions.cs b/osu.Game/Storyboards/StoryboardExtensions.cs new file mode 100644 index 0000000000..4e8251c9e7 --- /dev/null +++ b/osu.Game/Storyboards/StoryboardExtensions.cs @@ -0,0 +1,43 @@ +// 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.Extensions.EnumExtensions; +using osu.Framework.Graphics; +using osuTK; + +namespace osu.Game.Storyboards +{ + public static class StoryboardExtensions + { + /// + /// Given an origin and a set of properties, adjust the origin to display the sprite/animation correctly. + /// + /// The current origin. + /// The vector scale. + /// Whether the element is flipped horizontally. + /// Whether the element is flipped vertically. + /// The adjusted origin. + public static Anchor AdjustOrigin(Anchor origin, Vector2 vectorScale, bool flipH, bool flipV) + { + // Either flip horizontally or negative X scale, but not both. + if (flipH ^ (vectorScale.X < 0)) + { + if (origin.HasFlagFast(Anchor.x0)) + origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); + else if (origin.HasFlagFast(Anchor.x2)) + origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); + } + + // Either flip vertically or negative Y scale, but not both. + if (flipV ^ (vectorScale.Y < 0)) + { + if (origin.HasFlagFast(Anchor.y0)) + origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); + else if (origin.HasFlagFast(Anchor.y2)) + origin = Anchor.y0 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); + } + + return origin; + } + } +} From c1697c7621358e02dccb710d79842696a983c669 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 14 Mar 2022 06:29:49 +0300 Subject: [PATCH 1165/5199] Update `DrawableStoryboardSprite` to use helper method --- .../Drawables/DrawableStoryboardAnimation.cs | 3 +- .../Drawables/DrawableStoryboardSprite.cs | 29 +------------------ 2 files changed, 3 insertions(+), 29 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 28ac83a25c..88cb5f40a1 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -69,7 +69,8 @@ namespace osu.Game.Storyboards.Drawables public override bool RemoveWhenNotAlive => false; - protected override Vector2 DrawScale => new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y) * VectorScale; + protected override Vector2 DrawScale + => new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y) * VectorScale; public override Anchor Origin => StoryboardExtensions.AdjustOrigin(base.Origin, VectorScale, FlipH, FlipV); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index f3173497e8..db10f13896 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -3,7 +3,6 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; @@ -73,33 +72,7 @@ namespace osu.Game.Storyboards.Drawables protected override Vector2 DrawScale => new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y) * VectorScale; - public override Anchor Origin - { - get - { - var origin = base.Origin; - - // Either flip horizontally or negative X scale, but not both. - if (FlipH ^ (VectorScale.X < 0)) - { - if (origin.HasFlagFast(Anchor.x0)) - origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); - else if (origin.HasFlagFast(Anchor.x2)) - origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); - } - - // Either flip vertically or negative Y scale, but not both. - if (FlipV ^ (VectorScale.Y < 0)) - { - if (origin.HasFlagFast(Anchor.y0)) - origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); - else if (origin.HasFlagFast(Anchor.y2)) - origin = Anchor.y0 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); - } - - return origin; - } - } + public override Anchor Origin => StoryboardExtensions.AdjustOrigin(base.Origin, VectorScale, FlipH, FlipV); public override bool IsPresent => !float.IsNaN(DrawPosition.X) && !float.IsNaN(DrawPosition.Y) && base.IsPresent; From 4ae6cba080d707402399b295f4a41b9503545991 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 14 Mar 2022 13:49:38 +0900 Subject: [PATCH 1166/5199] Expose UseDevelopmentServer as virtual --- osu.Game/OsuGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 8b75de9718..602f84b0e1 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -69,7 +69,7 @@ namespace osu.Game ///
private const double global_track_volume_adjust = 0.8; - public bool UseDevelopmentServer { get; } + public virtual bool UseDevelopmentServer { get; } public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); From 21beb8774d5d3d95bbcd19e524e571e181512048 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 14 Mar 2022 13:54:54 +0900 Subject: [PATCH 1167/5199] Change to lambda method --- osu.Game/OsuGameBase.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 602f84b0e1..5468db348e 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -69,7 +69,7 @@ namespace osu.Game ///
private const double global_track_volume_adjust = 0.8; - public virtual bool UseDevelopmentServer { get; } + public virtual bool UseDevelopmentServer => DebugUtils.IsDebugBuild; public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); @@ -177,7 +177,6 @@ namespace osu.Game public OsuGameBase() { - UseDevelopmentServer = DebugUtils.IsDebugBuild; Name = @"osu!"; } From 4a3e3aba65a1cece80e164fefc7622f853637d8c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 14 Mar 2022 14:25:26 +0900 Subject: [PATCH 1168/5199] Restructure PerformanceCalculator to not require ScoreInfo argument --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 3 +- .../Difficulty/CatchPerformanceCalculator.cs | 36 +++-- .../Difficulty/ManiaPerformanceCalculator.cs | 49 +++---- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- .../Difficulty/OsuPerformanceCalculator.cs | 132 +++++++++--------- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- .../Difficulty/TaikoPerformanceCalculator.cs | 44 +++--- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- .../Visual/Ranking/TestSceneResultsScreen.cs | 2 +- .../PerformanceBreakdownCalculator.cs | 2 +- .../Difficulty/PerformanceCalculator.cs | 36 ++--- osu.Game/Rulesets/Ruleset.cs | 18 +-- osu.Game/Scoring/ScorePerformanceCache.cs | 4 +- .../Play/HUD/PerformancePointsCounter.cs | 4 +- 14 files changed, 147 insertions(+), 189 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 70d11c42e5..3a7efb6263 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -19,7 +19,6 @@ using osu.Game.Rulesets.Catch.Difficulty; using osu.Game.Rulesets.Catch.Scoring; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; using System; using osu.Framework.Extensions.EnumExtensions; using osu.Game.Rulesets.Catch.Edit; @@ -182,7 +181,7 @@ namespace osu.Game.Rulesets.Catch public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new CatchLegacySkinTransformer(skin); - public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new CatchPerformanceCalculator(this, attributes, score); + public override PerformanceCalculator CreatePerformanceCalculator() => new CatchPerformanceCalculator(this); public int LegacyID => 2; diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index 8cdbe500f0..6fefb2e5bb 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -13,33 +13,29 @@ namespace osu.Game.Rulesets.Catch.Difficulty { public class CatchPerformanceCalculator : PerformanceCalculator { - protected new CatchDifficultyAttributes Attributes => (CatchDifficultyAttributes)base.Attributes; - - private Mod[] mods; - private int fruitsHit; private int ticksHit; private int tinyTicksHit; private int tinyTicksMissed; private int misses; - public CatchPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score) - : base(ruleset, attributes, score) + public CatchPerformanceCalculator(Ruleset ruleset) + : base(ruleset) { } - public override PerformanceAttributes Calculate() + protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes) { - mods = Score.Mods; + var catchAttributes = (CatchDifficultyAttributes)attributes; - fruitsHit = Score.Statistics.GetValueOrDefault(HitResult.Great); - ticksHit = Score.Statistics.GetValueOrDefault(HitResult.LargeTickHit); - tinyTicksHit = Score.Statistics.GetValueOrDefault(HitResult.SmallTickHit); - tinyTicksMissed = Score.Statistics.GetValueOrDefault(HitResult.SmallTickMiss); - misses = Score.Statistics.GetValueOrDefault(HitResult.Miss); + fruitsHit = score.Statistics.GetValueOrDefault(HitResult.Great); + ticksHit = score.Statistics.GetValueOrDefault(HitResult.LargeTickHit); + tinyTicksHit = score.Statistics.GetValueOrDefault(HitResult.SmallTickHit); + tinyTicksMissed = score.Statistics.GetValueOrDefault(HitResult.SmallTickMiss); + misses = score.Statistics.GetValueOrDefault(HitResult.Miss); // We are heavily relying on aim in catch the beat - double value = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0049) - 4.0, 2.0) / 100000.0; + double value = Math.Pow(5.0 * Math.Max(1.0, catchAttributes.StarRating / 0.0049) - 4.0, 2.0) / 100000.0; // Longer maps are worth more. "Longer" means how many hits there are which can contribute to combo int numTotalHits = totalComboHits(); @@ -52,10 +48,10 @@ namespace osu.Game.Rulesets.Catch.Difficulty value *= Math.Pow(0.97, misses); // Combo scaling - if (Attributes.MaxCombo > 0) - value *= Math.Min(Math.Pow(Score.MaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0); + if (catchAttributes.MaxCombo > 0) + value *= Math.Min(Math.Pow(score.MaxCombo, 0.8) / Math.Pow(catchAttributes.MaxCombo, 0.8), 1.0); - double approachRate = Attributes.ApproachRate; + double approachRate = catchAttributes.ApproachRate; double approachRateFactor = 1.0; if (approachRate > 9.0) approachRateFactor += 0.1 * (approachRate - 9.0); // 10% for each AR above 9 @@ -66,7 +62,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty value *= approachRateFactor; - if (mods.Any(m => m is ModHidden)) + if (score.Mods.Any(m => m is ModHidden)) { // Hiddens gives almost nothing on max approach rate, and more the lower it is if (approachRate <= 10.0) @@ -75,12 +71,12 @@ namespace osu.Game.Rulesets.Catch.Difficulty value *= 1.01 + 0.04 * (11.0 - Math.Min(11.0, approachRate)); // 5% at AR 10, 1% at AR 11 } - if (mods.Any(m => m is ModFlashlight)) + if (score.Mods.Any(m => m is ModFlashlight)) value *= 1.35 * lengthBonus; value *= Math.Pow(accuracy(), 5.5); - if (mods.Any(m => m is ModNoFail)) + if (score.Mods.Any(m => m is ModNoFail)) value *= 0.90; return new CatchPerformanceAttributes diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index 722cb55036..1eaf45e54a 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -13,10 +13,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty { public class ManiaPerformanceCalculator : PerformanceCalculator { - protected new ManiaDifficultyAttributes Attributes => (ManiaDifficultyAttributes)base.Attributes; - - private Mod[] mods; - // Score after being scaled by non-difficulty-increasing mods private double scaledScore; @@ -27,39 +23,40 @@ namespace osu.Game.Rulesets.Mania.Difficulty private int countMeh; private int countMiss; - public ManiaPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score) - : base(ruleset, attributes, score) + public ManiaPerformanceCalculator(Ruleset ruleset) + : base(ruleset) { } - public override PerformanceAttributes Calculate() + protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes) { - mods = Score.Mods; - scaledScore = Score.TotalScore; - countPerfect = Score.Statistics.GetValueOrDefault(HitResult.Perfect); - countGreat = Score.Statistics.GetValueOrDefault(HitResult.Great); - countGood = Score.Statistics.GetValueOrDefault(HitResult.Good); - countOk = Score.Statistics.GetValueOrDefault(HitResult.Ok); - countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh); - countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss); + var maniaAttributes = (ManiaDifficultyAttributes)attributes; - if (Attributes.ScoreMultiplier > 0) + scaledScore = score.TotalScore; + countPerfect = score.Statistics.GetValueOrDefault(HitResult.Perfect); + countGreat = score.Statistics.GetValueOrDefault(HitResult.Great); + countGood = score.Statistics.GetValueOrDefault(HitResult.Good); + countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); + countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); + countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); + + if (maniaAttributes.ScoreMultiplier > 0) { // Scale score up, so it's comparable to other keymods - scaledScore *= 1.0 / Attributes.ScoreMultiplier; + scaledScore *= 1.0 / maniaAttributes.ScoreMultiplier; } // Arbitrary initial value for scaling pp in order to standardize distributions across game modes. // The specific number has no intrinsic meaning and can be adjusted as needed. double multiplier = 0.8; - if (mods.Any(m => m is ModNoFail)) + if (score.Mods.Any(m => m is ModNoFail)) multiplier *= 0.9; - if (mods.Any(m => m is ModEasy)) + if (score.Mods.Any(m => m is ModEasy)) multiplier *= 0.5; - double difficultyValue = computeDifficultyValue(); - double accValue = computeAccuracyValue(difficultyValue); + double difficultyValue = computeDifficultyValue(maniaAttributes); + double accValue = computeAccuracyValue(difficultyValue, maniaAttributes); double totalValue = Math.Pow( Math.Pow(difficultyValue, 1.1) + @@ -75,9 +72,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty }; } - private double computeDifficultyValue() + private double computeDifficultyValue(ManiaDifficultyAttributes attributes) { - double difficultyValue = Math.Pow(5 * Math.Max(1, Attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0; + double difficultyValue = Math.Pow(5 * Math.Max(1, attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0; difficultyValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0); @@ -97,14 +94,14 @@ namespace osu.Game.Rulesets.Mania.Difficulty return difficultyValue; } - private double computeAccuracyValue(double difficultyValue) + private double computeAccuracyValue(double difficultyValue, ManiaDifficultyAttributes attributes) { - if (Attributes.GreatHitWindow <= 0) + if (attributes.GreatHitWindow <= 0) return 0; // Lots of arbitrary values from testing. // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution - double accuracyValue = Math.Max(0.0, 0.2 - (Attributes.GreatHitWindow - 34) * 0.006667) + double accuracyValue = Math.Max(0.0, 0.2 - (attributes.GreatHitWindow - 34) * 0.006667) * difficultyValue * Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index f139a88f50..d04ef69dd2 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Mania public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this); - public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new ManiaPerformanceCalculator(this, attributes, score); + public override PerformanceCalculator CreatePerformanceCalculator() => new ManiaPerformanceCalculator(this); public const string SHORT_NAME = "mania"; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 604ab73454..5644b2009d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -14,10 +13,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty { public class OsuPerformanceCalculator : PerformanceCalculator { - public new OsuDifficultyAttributes Attributes => (OsuDifficultyAttributes)base.Attributes; - - private Mod[] mods; - private double accuracy; private int scoreMaxCombo; private int countGreat; @@ -27,31 +22,32 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double effectiveMissCount; - public OsuPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score) - : base(ruleset, attributes, score) + public OsuPerformanceCalculator(Ruleset ruleset) + : base(ruleset) { } - public override PerformanceAttributes Calculate() + protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes) { - mods = Score.Mods; - accuracy = Score.Accuracy; - scoreMaxCombo = Score.MaxCombo; - countGreat = Score.Statistics.GetValueOrDefault(HitResult.Great); - countOk = Score.Statistics.GetValueOrDefault(HitResult.Ok); - countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh); - countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss); - effectiveMissCount = calculateEffectiveMissCount(); + var osuAttributes = (OsuDifficultyAttributes)attributes; + + accuracy = score.Accuracy; + scoreMaxCombo = score.MaxCombo; + countGreat = score.Statistics.GetValueOrDefault(HitResult.Great); + countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); + countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); + countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); + effectiveMissCount = calculateEffectiveMissCount(osuAttributes); double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things. - if (mods.Any(m => m is OsuModNoFail)) + if (score.Mods.Any(m => m is OsuModNoFail)) multiplier *= Math.Max(0.90, 1.0 - 0.02 * effectiveMissCount); - if (mods.Any(m => m is OsuModSpunOut) && totalHits > 0) - multiplier *= 1.0 - Math.Pow((double)Attributes.SpinnerCount / totalHits, 0.85); + if (score.Mods.Any(m => m is OsuModSpunOut) && totalHits > 0) + multiplier *= 1.0 - Math.Pow((double)osuAttributes.SpinnerCount / totalHits, 0.85); - if (mods.Any(h => h is OsuModRelax)) + if (score.Mods.Any(h => h is OsuModRelax)) { // As we're adding Oks and Mehs to an approximated number of combo breaks the result can be higher than total hits in specific scenarios (which breaks some calculations) so we need to clamp it. effectiveMissCount = Math.Min(effectiveMissCount + countOk + countMeh, totalHits); @@ -59,10 +55,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty multiplier *= 0.6; } - double aimValue = computeAimValue(); - double speedValue = computeSpeedValue(); - double accuracyValue = computeAccuracyValue(); - double flashlightValue = computeFlashlightValue(); + double aimValue = computeAimValue(score, osuAttributes); + double speedValue = computeSpeedValue(score, osuAttributes); + double accuracyValue = computeAccuracyValue(score, osuAttributes); + double flashlightValue = computeFlashlightValue(score, osuAttributes); double totalValue = Math.Pow( Math.Pow(aimValue, 1.1) + @@ -82,11 +78,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty }; } - private double computeAimValue() + private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - double rawAim = Attributes.AimDifficulty; + double rawAim = attributes.AimDifficulty; - if (mods.Any(m => m is OsuModTouchDevice)) + if (score.Mods.Any(m => m is OsuModTouchDevice)) rawAim = Math.Pow(rawAim, 0.8); double aimValue = Math.Pow(5.0 * Math.Max(1.0, rawAim / 0.0675) - 4.0, 3.0) / 100000.0; @@ -99,44 +95,44 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (effectiveMissCount > 0) aimValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), effectiveMissCount); - aimValue *= getComboScalingFactor(); + aimValue *= getComboScalingFactor(attributes); double approachRateFactor = 0.0; - if (Attributes.ApproachRate > 10.33) - approachRateFactor = 0.3 * (Attributes.ApproachRate - 10.33); - else if (Attributes.ApproachRate < 8.0) - approachRateFactor = 0.1 * (8.0 - Attributes.ApproachRate); + if (attributes.ApproachRate > 10.33) + approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33); + else if (attributes.ApproachRate < 8.0) + approachRateFactor = 0.1 * (8.0 - attributes.ApproachRate); aimValue *= 1.0 + approachRateFactor * lengthBonus; // Buff for longer maps with high AR. - if (mods.Any(m => m is OsuModBlinds)) - aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * Attributes.DrainRate * Attributes.DrainRate); - else if (mods.Any(h => h is OsuModHidden)) + if (score.Mods.Any(m => m is OsuModBlinds)) + aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * attributes.DrainRate * attributes.DrainRate); + else if (score.Mods.Any(h => h is OsuModHidden)) { // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. - aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); + aimValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); } // We assume 15% of sliders in a map are difficult since there's no way to tell from the performance calculator. - double estimateDifficultSliders = Attributes.SliderCount * 0.15; + double estimateDifficultSliders = attributes.SliderCount * 0.15; - if (Attributes.SliderCount > 0) + if (attributes.SliderCount > 0) { - double estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, Attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); - double sliderNerfFactor = (1 - Attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + Attributes.SliderFactor; + double estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); + double sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + attributes.SliderFactor; aimValue *= sliderNerfFactor; } aimValue *= accuracy; // It is important to consider accuracy difficulty when scaling with accuracy. - aimValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500; + aimValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500; return aimValue; } - private double computeSpeedValue() + private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0; + double speedValue = Math.Pow(5.0 * Math.Max(1.0, attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0; double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); @@ -146,27 +142,27 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (effectiveMissCount > 0) speedValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); - speedValue *= getComboScalingFactor(); + speedValue *= getComboScalingFactor(attributes); double approachRateFactor = 0.0; - if (Attributes.ApproachRate > 10.33) - approachRateFactor = 0.3 * (Attributes.ApproachRate - 10.33); + if (attributes.ApproachRate > 10.33) + approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33); speedValue *= 1.0 + approachRateFactor * lengthBonus; // Buff for longer maps with high AR. - if (mods.Any(m => m is OsuModBlinds)) + if (score.Mods.Any(m => m is OsuModBlinds)) { // Increasing the speed value by object count for Blinds isn't ideal, so the minimum buff is given. speedValue *= 1.12; } - else if (mods.Any(m => m is OsuModHidden)) + else if (score.Mods.Any(m => m is OsuModHidden)) { // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. - speedValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); + speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); } // Scale the speed value with accuracy and OD. - speedValue *= (0.95 + Math.Pow(Attributes.OverallDifficulty, 2) / 750) * Math.Pow(accuracy, (14.5 - Math.Max(Attributes.OverallDifficulty, 8)) / 2); + speedValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow(accuracy, (14.5 - Math.Max(attributes.OverallDifficulty, 8)) / 2); // Scale the speed value with # of 50s to punish doubletapping. speedValue *= Math.Pow(0.98, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0); @@ -174,14 +170,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty return speedValue; } - private double computeAccuracyValue() + private double computeAccuracyValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - if (mods.Any(h => h is OsuModRelax)) + if (score.Mods.Any(h => h is OsuModRelax)) return 0.0; // This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window. double betterAccuracyPercentage; - int amountHitObjectsWithAccuracy = Attributes.HitCircleCount; + int amountHitObjectsWithAccuracy = attributes.HitCircleCount; if (amountHitObjectsWithAccuracy > 0) betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countOk * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6); @@ -194,43 +190,43 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Lots of arbitrary values from testing. // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution. - double accuracyValue = Math.Pow(1.52163, Attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83; + double accuracyValue = Math.Pow(1.52163, attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83; // Bonus for many hitcircles - it's harder to keep good accuracy up for longer. accuracyValue *= Math.Min(1.15, Math.Pow(amountHitObjectsWithAccuracy / 1000.0, 0.3)); // Increasing the accuracy value by object count for Blinds isn't ideal, so the minimum buff is given. - if (mods.Any(m => m is OsuModBlinds)) + if (score.Mods.Any(m => m is OsuModBlinds)) accuracyValue *= 1.14; - else if (mods.Any(m => m is OsuModHidden)) + else if (score.Mods.Any(m => m is OsuModHidden)) accuracyValue *= 1.08; - if (mods.Any(m => m is OsuModFlashlight)) + if (score.Mods.Any(m => m is OsuModFlashlight)) accuracyValue *= 1.02; return accuracyValue; } - private double computeFlashlightValue() + private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - if (!mods.Any(h => h is OsuModFlashlight)) + if (!score.Mods.Any(h => h is OsuModFlashlight)) return 0.0; - double rawFlashlight = Attributes.FlashlightDifficulty; + double rawFlashlight = attributes.FlashlightDifficulty; - if (mods.Any(m => m is OsuModTouchDevice)) + if (score.Mods.Any(m => m is OsuModTouchDevice)) rawFlashlight = Math.Pow(rawFlashlight, 0.8); double flashlightValue = Math.Pow(rawFlashlight, 2.0) * 25.0; - if (mods.Any(h => h is OsuModHidden)) + if (score.Mods.Any(h => h is OsuModHidden)) flashlightValue *= 1.3; // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); - flashlightValue *= getComboScalingFactor(); + flashlightValue *= getComboScalingFactor(attributes); // Account for shorter maps having a higher ratio of 0 combo/100 combo flashlight radius. flashlightValue *= 0.7 + 0.1 * Math.Min(1.0, totalHits / 200.0) + @@ -239,19 +235,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Scale the flashlight value with accuracy _slightly_. flashlightValue *= 0.5 + accuracy / 2.0; // It is important to also consider accuracy difficulty when doing that. - flashlightValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500; + flashlightValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500; return flashlightValue; } - private double calculateEffectiveMissCount() + private double calculateEffectiveMissCount(OsuDifficultyAttributes attributes) { // Guess the number of misses + slider breaks from combo double comboBasedMissCount = 0.0; - if (Attributes.SliderCount > 0) + if (attributes.SliderCount > 0) { - double fullComboThreshold = Attributes.MaxCombo - 0.1 * Attributes.SliderCount; + double fullComboThreshold = attributes.MaxCombo - 0.1 * attributes.SliderCount; if (scoreMaxCombo < fullComboThreshold) comboBasedMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo); } @@ -262,7 +258,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty return Math.Max(countMiss, comboBasedMissCount); } - private double getComboScalingFactor() => Attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0); + private double getComboScalingFactor(OsuDifficultyAttributes attributes) => attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(attributes.MaxCombo, 0.8), 1.0); private int totalHits => countGreat + countOk + countMeh + countMiss; private int totalSuccessfulHits => countGreat + countOk + countMeh; } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 5b936b1bf1..92b90339f9 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -213,7 +213,7 @@ namespace osu.Game.Rulesets.Osu public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new OsuDifficultyCalculator(RulesetInfo, beatmap); - public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new OsuPerformanceCalculator(this, attributes, score); + public override PerformanceCalculator CreatePerformanceCalculator() => new OsuPerformanceCalculator(this); public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this); diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index bcd55f8fae..9e73390fad 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -14,37 +14,35 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { public class TaikoPerformanceCalculator : PerformanceCalculator { - protected new TaikoDifficultyAttributes Attributes => (TaikoDifficultyAttributes)base.Attributes; - - private Mod[] mods; private int countGreat; private int countOk; private int countMeh; private int countMiss; - public TaikoPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score) - : base(ruleset, attributes, score) + public TaikoPerformanceCalculator(Ruleset ruleset) + : base(ruleset) { } - public override PerformanceAttributes Calculate() + protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes) { - mods = Score.Mods; - countGreat = Score.Statistics.GetValueOrDefault(HitResult.Great); - countOk = Score.Statistics.GetValueOrDefault(HitResult.Ok); - countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh); - countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss); + var taikoAttributes = (TaikoDifficultyAttributes)attributes; + + countGreat = score.Statistics.GetValueOrDefault(HitResult.Great); + countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); + countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); + countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); double multiplier = 1.1; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things - if (mods.Any(m => m is ModNoFail)) + if (score.Mods.Any(m => m is ModNoFail)) multiplier *= 0.90; - if (mods.Any(m => m is ModHidden)) + if (score.Mods.Any(m => m is ModHidden)) multiplier *= 1.10; - double difficultyValue = computeDifficultyValue(); - double accuracyValue = computeAccuracyValue(); + double difficultyValue = computeDifficultyValue(score, taikoAttributes); + double accuracyValue = computeAccuracyValue(score, taikoAttributes); double totalValue = Math.Pow( Math.Pow(difficultyValue, 1.1) + @@ -59,30 +57,30 @@ namespace osu.Game.Rulesets.Taiko.Difficulty }; } - private double computeDifficultyValue() + private double computeDifficultyValue(ScoreInfo score, TaikoDifficultyAttributes attributes) { - double difficultyValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0; + double difficultyValue = Math.Pow(5.0 * Math.Max(1.0, attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0; double lengthBonus = 1 + 0.1 * Math.Min(1.0, totalHits / 1500.0); difficultyValue *= lengthBonus; difficultyValue *= Math.Pow(0.985, countMiss); - if (mods.Any(m => m is ModHidden)) + if (score.Mods.Any(m => m is ModHidden)) difficultyValue *= 1.025; - if (mods.Any(m => m is ModFlashlight)) + if (score.Mods.Any(m => m is ModFlashlight)) difficultyValue *= 1.05 * lengthBonus; - return difficultyValue * Score.Accuracy; + return difficultyValue * score.Accuracy; } - private double computeAccuracyValue() + private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes) { - if (Attributes.GreatHitWindow <= 0) + if (attributes.GreatHitWindow <= 0) return 0; - double accValue = Math.Pow(150.0 / Attributes.GreatHitWindow, 1.1) * Math.Pow(Score.Accuracy, 15) * 22.0; + double accValue = Math.Pow(150.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(score.Accuracy, 15) * 22.0; // Bonus for many objects - it's harder to keep good accuracy up for longer return accValue * Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index dc90845d92..8934b64ca5 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Taiko public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new TaikoDifficultyCalculator(RulesetInfo, beatmap); - public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new TaikoPerformanceCalculator(this, attributes, score); + public override PerformanceCalculator CreatePerformanceCalculator() => new TaikoPerformanceCalculator(this); public int LegacyID => 1; diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index cc380df183..4eed2a25f5 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -390,7 +390,7 @@ namespace osu.Game.Tests.Visual.Ranking private class RulesetWithNoPerformanceCalculator : OsuRuleset { - public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => null; + public override PerformanceCalculator CreatePerformanceCalculator() => null; } } } diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs index fc38ed0298..1e5dda253f 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Difficulty ).ConfigureAwait(false); // ScorePerformanceCache is not used to avoid caching multiple copies of essentially identical perfect performance attributes - return difficulty == null ? null : ruleset.CreatePerformanceCalculator(difficulty.Value.Attributes, perfectPlay)?.Calculate(); + return difficulty == null ? null : ruleset.CreatePerformanceCalculator()?.Calculate(perfectPlay, difficulty.Value.Attributes); }, cancellationToken); } diff --git a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs index 6358ec18b7..4c55249661 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs @@ -1,41 +1,31 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Linq; -using osu.Framework.Audio.Track; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Game.Rulesets.Mods; +using osu.Game.Beatmaps; using osu.Game.Scoring; namespace osu.Game.Rulesets.Difficulty { public abstract class PerformanceCalculator { - protected readonly DifficultyAttributes Attributes; - protected readonly Ruleset Ruleset; - protected readonly ScoreInfo Score; - protected double TimeRate { get; private set; } = 1; - - protected PerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score) + protected PerformanceCalculator(Ruleset ruleset) { Ruleset = ruleset; - Score = score; - - Attributes = attributes ?? throw new ArgumentNullException(nameof(attributes)); - - ApplyMods(score.Mods); } - protected virtual void ApplyMods(Mod[] mods) - { - var track = new TrackVirtual(10000); - mods.OfType().ForEach(m => m.ApplyToTrack(track)); - TimeRate = track.Rate; - } + public PerformanceAttributes Calculate(ScoreInfo score, DifficultyAttributes attributes) + => CreatePerformanceAttributes(score, attributes); - public abstract PerformanceAttributes Calculate(); + public PerformanceAttributes Calculate(ScoreInfo score, IWorkingBeatmap beatmap) + => Calculate(score, Ruleset.CreateDifficultyCalculator(beatmap).Calculate(score.Mods)); + + /// + /// Creates to describe a score's performance. + /// + /// The score to create the attributes for. + /// The difficulty attributes for the beatmap relating to the score. + protected abstract PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes); } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 616540b59c..d5926386e8 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -228,25 +228,9 @@ namespace osu.Game.Rulesets /// /// Optionally creates a to generate performance data from the provided score. /// - /// Difficulty attributes for the beatmap related to the provided score. - /// The score to be processed. /// A performance calculator instance for the provided score. [CanBeNull] - public virtual PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => null; - - /// - /// Optionally creates a to generate performance data from the provided score. - /// - /// The beatmap to use as a source for generating . - /// The score to be processed. - /// A performance calculator instance for the provided score. - [CanBeNull] - public PerformanceCalculator CreatePerformanceCalculator(IWorkingBeatmap beatmap, ScoreInfo score) - { - var difficultyCalculator = CreateDifficultyCalculator(beatmap); - var difficultyAttributes = difficultyCalculator.Calculate(score.Mods); - return CreatePerformanceCalculator(difficultyAttributes, score); - } + public virtual PerformanceCalculator CreatePerformanceCalculator() => null; public virtual HitObjectComposer CreateHitObjectComposer() => null; diff --git a/osu.Game/Scoring/ScorePerformanceCache.cs b/osu.Game/Scoring/ScorePerformanceCache.cs index a428a66aae..e15d59e648 100644 --- a/osu.Game/Scoring/ScorePerformanceCache.cs +++ b/osu.Game/Scoring/ScorePerformanceCache.cs @@ -43,9 +43,7 @@ namespace osu.Game.Scoring token.ThrowIfCancellationRequested(); - var calculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator(attributes.Value.Attributes, score); - - return calculator?.Calculate(); + return score.Ruleset.CreateInstance().CreatePerformanceCalculator()?.Calculate(score, attributes.Value.Attributes); } public readonly struct PerformanceCacheLookup diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 21a7698248..67a8aaabdc 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -130,9 +130,9 @@ namespace osu.Game.Screens.Play.HUD var scoreInfo = gameplayState.Score.ScoreInfo.DeepClone(); scoreInfo.Mods = clonedMods; - var calculator = gameplayState.Ruleset.CreatePerformanceCalculator(attrib, scoreInfo); + var calculator = gameplayState.Ruleset.CreatePerformanceCalculator(); - Current.Value = (int)Math.Round(calculator?.Calculate().Total ?? 0, MidpointRounding.AwayFromZero); + Current.Value = (int)Math.Round(calculator?.Calculate(scoreInfo, attrib).Total ?? 0, MidpointRounding.AwayFromZero); IsValid = true; } From 926827207abc22efc5819b168a6ed94ea91562d8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 14 Mar 2022 14:44:04 +0900 Subject: [PATCH 1169/5199] Reduce calculator allocations in counter --- osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 67a8aaabdc..a9b5544921 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -55,6 +55,7 @@ namespace osu.Game.Screens.Play.HUD private readonly CancellationTokenSource loadCancellationSource = new CancellationTokenSource(); private JudgementResult lastJudgement; + private PerformanceCalculator performanceCalculator; public PerformancePointsCounter() { @@ -70,6 +71,8 @@ namespace osu.Game.Screens.Play.HUD if (gameplayState != null) { + performanceCalculator = gameplayState.Ruleset.CreatePerformanceCalculator(); + clonedMods = gameplayState.Mods.Select(m => m.DeepClone()).ToArray(); var gameplayWorkingBeatmap = new GameplayWorkingBeatmap(gameplayState.Beatmap); @@ -130,9 +133,7 @@ namespace osu.Game.Screens.Play.HUD var scoreInfo = gameplayState.Score.ScoreInfo.DeepClone(); scoreInfo.Mods = clonedMods; - var calculator = gameplayState.Ruleset.CreatePerformanceCalculator(); - - Current.Value = (int)Math.Round(calculator?.Calculate(scoreInfo, attrib).Total ?? 0, MidpointRounding.AwayFromZero); + Current.Value = (int)Math.Round(performanceCalculator?.Calculate(scoreInfo, attrib).Total ?? 0, MidpointRounding.AwayFromZero); IsValid = true; } From 9cc7f70872a00d15c91836114fda6b72781eb652 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 14 Mar 2022 15:38:00 +0900 Subject: [PATCH 1170/5199] Nullable annotate classes --- osu.Game/Rulesets/Scoring/HealthProcessor.cs | 6 ++++-- osu.Game/Rulesets/Scoring/JudgementProcessor.cs | 10 ++++++---- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 6 ++++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HealthProcessor.cs b/osu.Game/Rulesets/Scoring/HealthProcessor.cs index 1535fe4d00..a92c30e593 100644 --- a/osu.Game/Rulesets/Scoring/HealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/HealthProcessor.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using osu.Framework.Bindables; using osu.Framework.Utils; @@ -14,12 +16,12 @@ namespace osu.Game.Rulesets.Scoring /// Invoked when the is in a failed state. /// Return true if the fail was permitted. ///
- public event Func Failed; + public event Func? Failed; /// /// Additional conditions on top of that cause a failing state. /// - public event Func FailConditions; + public event Func? FailConditions; /// /// The current health. diff --git a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs index a643c31daa..3017841c9d 100644 --- a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs +++ b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; @@ -17,12 +19,12 @@ namespace osu.Game.Rulesets.Scoring /// /// Invoked when a new judgement has occurred. This occurs after the judgement has been processed by this . /// - public event Action NewJudgement; + public event Action? NewJudgement; /// /// Invoked when a judgement is reverted, usually due to rewinding gameplay. /// - public event Action JudgementReverted; + public event Action? JudgementReverted; /// /// The maximum number of hits that can be judged. @@ -34,7 +36,7 @@ namespace osu.Game.Rulesets.Scoring /// public int JudgedHits { get; private set; } - private JudgementResult lastAppliedResult; + private JudgementResult? lastAppliedResult; private readonly BindableBool hasCompleted = new BindableBool(); @@ -163,7 +165,7 @@ namespace osu.Game.Rulesets.Scoring protected override void Update() { base.Update(); - hasCompleted.Value = JudgedHits == MaxHits && (JudgedHits == 0 || lastAppliedResult.TimeAbsolute < Clock.CurrentTime); + hasCompleted.Value = JudgedHits == MaxHits && (JudgedHits == 0 || lastAppliedResult?.TimeAbsolute < Clock.CurrentTime); } /// diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index db53e3130c..a7fe8b354b 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using System.Collections.Generic; using System.Diagnostics; @@ -24,7 +26,7 @@ namespace osu.Game.Rulesets.Scoring /// /// Invoked when this was reset from a replay frame. /// - public event Action OnResetFromReplayFrame; + public event Action? OnResetFromReplayFrame; /// /// The current total score. @@ -110,7 +112,7 @@ namespace osu.Game.Rulesets.Scoring private readonly Dictionary scoreResultCounts = new Dictionary(); private readonly List hitEvents = new List(); - private HitObject lastHitObject; + private HitObject? lastHitObject; private double scoreMultiplier = 1; From 3fff7f4b7e9a5921be5928752840e59531090de4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 14 Mar 2022 15:51:10 +0900 Subject: [PATCH 1171/5199] Require ScoreProcessor to receive ruleset --- .../Scoring/PippidonScoreProcessor.cs | 11 ----------- .../TestSceneComboCounter.cs | 2 +- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- .../Scoring/CatchScoreProcessor.cs | 5 +++++ osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- .../Scoring/ManiaScoreProcessor.cs | 5 +++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- .../Scoring/OsuScoreProcessor.cs | 5 +++++ .../Skinning/TestSceneDrawableTaikoMascot.cs | 2 +- .../Scoring/TaikoScoreProcessor.cs | 5 +++++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- .../Gameplay/TestSceneScoreProcessor.cs | 6 +++--- .../Rulesets/Scoring/ScoreProcessorTest.cs | 3 ++- .../Gameplay/TestSceneBeatmapSkinFallbacks.cs | 2 +- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 3 ++- .../Visual/Gameplay/TestSceneHitErrorMeter.cs | 5 +++++ .../TestScenePerformancePointsCounter.cs | 2 +- .../TestSceneSkinEditorMultipleSkins.cs | 2 +- .../TestSceneSkinnableAccuracyCounter.cs | 3 ++- .../Gameplay/TestSceneSkinnableComboCounter.cs | 3 ++- .../Gameplay/TestSceneSkinnableHUDOverlay.cs | 2 +- .../Gameplay/TestSceneSkinnableScoreCounter.cs | 3 ++- .../Gameplay/TestSceneUnstableRateCounter.cs | 6 ++++++ .../TestSceneMultiSpectatorLeaderboard.cs | 2 +- .../TestSceneMultiplayerGameplayLeaderboard.cs | 2 +- ...SceneMultiplayerGameplayLeaderboardTeams.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 11 +++++++---- .../Play/HUD/PerformancePointsCounter.cs | 12 ++++++------ .../Skinning/Editor/SkinComponentToolbox.cs | 18 +++++++++++++++++- 30 files changed, 88 insertions(+), 44 deletions(-) delete mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Scoring/PippidonScoreProcessor.cs diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Scoring/PippidonScoreProcessor.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Scoring/PippidonScoreProcessor.cs deleted file mode 100644 index 1c4fe698c2..0000000000 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Scoring/PippidonScoreProcessor.cs +++ /dev/null @@ -1,11 +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.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Pippidon.Scoring -{ - public class PippidonScoreProcessor : ScoreProcessor - { - } -} diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs index 064a84cb98..b720ab1e97 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Tests [SetUp] public void SetUp() => Schedule(() => { - scoreProcessor = new ScoreProcessor(); + scoreProcessor = new ScoreProcessor(new CatchRuleset()); SetContents(_ => new CatchComboDisplay { diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 3a7efb6263..45198fa0ca 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Catch { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableCatchRuleset(this, beatmap, mods); - public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(); + public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap, this); diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 9cd03dc869..2c183f5d94 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -7,6 +7,11 @@ namespace osu.Game.Rulesets.Catch.Scoring { public class CatchScoreProcessor : ScoreProcessor { + public CatchScoreProcessor(Ruleset ruleset) + : base(ruleset) + { + } + protected override double ClassicScoreMultiplier => 28; } } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index d04ef69dd2..92c77f405b 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableManiaRuleset(this, beatmap, mods); - public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(); + public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new ManiaHealthProcessor(drainStartTime, 0.5); diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index e0b19d87e8..0ffee809fc 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -7,6 +7,11 @@ namespace osu.Game.Rulesets.Mania.Scoring { internal class ManiaScoreProcessor : ScoreProcessor { + public ManiaScoreProcessor(Ruleset ruleset) + : base(ruleset) + { + } + protected override double DefaultAccuracyPortion => 0.99; protected override double DefaultComboPortion => 0.01; diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 92b90339f9..87ffe8983f 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableOsuRuleset(this, beatmap, mods); - public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(); + public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(this); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap, this); diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index df38f0204a..e397fea0ed 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -11,6 +11,11 @@ namespace osu.Game.Rulesets.Osu.Scoring { public class OsuScoreProcessor : ScoreProcessor { + public OsuScoreProcessor(Ruleset ruleset) + : base(ruleset) + { + } + protected override double ClassicScoreMultiplier => 36; protected override HitEvent CreateHitEvent(JudgementResult result) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs index 920a7cd1a1..b7e1c9586a 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning [SetUp] public void SetUp() { - scoreProcessor = new TaikoScoreProcessor(); + scoreProcessor = new TaikoScoreProcessor(new TaikoRuleset()); } [Test] diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index 849b9c14bd..ddf6587022 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -7,6 +7,11 @@ namespace osu.Game.Rulesets.Taiko.Scoring { internal class TaikoScoreProcessor : ScoreProcessor { + public TaikoScoreProcessor(Ruleset ruleset) + : base(ruleset) + { + } + protected override double DefaultAccuracyPortion => 0.75; protected override double DefaultComboPortion => 0.25; diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 8934b64ca5..a3a2858bd5 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableTaikoRuleset(this, beatmap, mods); - public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(); + public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new TaikoHealthProcessor(); diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index 70ba868de6..9c307341bd 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Gameplay { var beatmap = new Beatmap { HitObjects = { new HitObject() } }; - var scoreProcessor = new ScoreProcessor(); + var scoreProcessor = new ScoreProcessor(new OsuRuleset()); scoreProcessor.ApplyBeatmap(beatmap); // Apply a miss judgement @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Gameplay { var beatmap = new Beatmap { HitObjects = { new HitObject() } }; - var scoreProcessor = new ScoreProcessor(); + var scoreProcessor = new ScoreProcessor(new OsuRuleset()); scoreProcessor.ApplyBeatmap(beatmap); // Apply a judgement @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Gameplay { var beatmap = new Beatmap { HitObjects = { new HitCircle() } }; - var scoreProcessor = new ScoreProcessor(); + var scoreProcessor = new ScoreProcessor(new OsuRuleset()); scoreProcessor.ApplyBeatmap(beatmap); scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new TestJudgement(HitResult.Great)) { Type = HitResult.Great }); diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 2a19d51c9d..5c01950bea 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -10,6 +10,7 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; @@ -27,7 +28,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [SetUp] public void SetUp() { - scoreProcessor = new ScoreProcessor(); + scoreProcessor = new ScoreProcessor(new OsuRuleset()); beatmap = new TestBeatmap(new RulesetInfo()) { HitObjects = new List diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index c5f56cae9e..c5ea9e6204 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay private SkinManager skinManager { get; set; } [Cached] - private ScoreProcessor scoreProcessor = new ScoreProcessor(); + private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset()); [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 4b54cd3510..505f73159f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Skinning; @@ -24,7 +25,7 @@ namespace osu.Game.Tests.Visual.Gameplay private HUDOverlay hudOverlay; [Cached] - private ScoreProcessor scoreProcessor = new ScoreProcessor(); + private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset()); [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index c1260f0231..ab9758a2ee 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -273,6 +273,11 @@ namespace osu.Game.Tests.Visual.Gameplay private class TestScoreProcessor : ScoreProcessor { + public TestScoreProcessor() + : base(new OsuRuleset()) + { + } + public void Reset() => base.Reset(false); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs index 84c7f611af..aefe0db36a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Gameplay gameplayState = new GameplayState(beatmap, ruleset); gameplayState.LastJudgementResult.BindTo(lastJudgementResult); - scoreProcessor = new ScoreProcessor(); + scoreProcessor = new ScoreProcessor(ruleset); Child = dependencyContainer = new DependencyProvidingContainer { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs index 856747ad19..8f33f6fac5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs @@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.Gameplay public class TestSceneSkinEditorMultipleSkins : SkinnableTestScene { [Cached] - private readonly ScoreProcessor scoreProcessor = new ScoreProcessor(); + private readonly ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset()); [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs index 80eb887894..9c713b4616 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; @@ -14,7 +15,7 @@ namespace osu.Game.Tests.Visual.Gameplay public class TestSceneSkinnableAccuracyCounter : SkinnableHUDComponentTestScene { [Cached] - private ScoreProcessor scoreProcessor = new ScoreProcessor(); + private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset()); protected override Drawable CreateDefaultImplementation() => new DefaultAccuracyCounter(); protected override Drawable CreateLegacyImplementation() => new LegacyAccuracyCounter(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs index bd1fe050af..f507172931 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; @@ -13,7 +14,7 @@ namespace osu.Game.Tests.Visual.Gameplay public class TestSceneSkinnableComboCounter : SkinnableHUDComponentTestScene { [Cached] - private ScoreProcessor scoreProcessor = new ScoreProcessor(); + private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset()); protected override Drawable CreateDefaultImplementation() => new DefaultComboCounter(); protected override Drawable CreateLegacyImplementation() => new LegacyComboCounter(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index 3074a91dc6..cdf349ff7f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Gameplay private HUDOverlay hudOverlay; [Cached] - private ScoreProcessor scoreProcessor = new ScoreProcessor(); + private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset()); [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs index 1700886263..a871e37ad4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; @@ -13,7 +14,7 @@ namespace osu.Game.Tests.Visual.Gameplay public class TestSceneSkinnableScoreCounter : SkinnableHUDComponentTestScene { [Cached] - private ScoreProcessor scoreProcessor = new ScoreProcessor(); + private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset()); protected override Drawable CreateDefaultImplementation() => new DefaultScoreCounter(); protected override Drawable CreateLegacyImplementation() => new LegacyScoreCounter(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs index be20799479..ca8ecd490d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; @@ -102,6 +103,11 @@ namespace osu.Game.Tests.Visual.Gameplay private class TestScoreProcessor : ScoreProcessor { + public TestScoreProcessor() + : base(new OsuRuleset()) + { + } + public void Reset() => base.Reset(false); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index f57a54d84c..0cc1257a07 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); - var scoreProcessor = new OsuScoreProcessor(); + var scoreProcessor = new OsuScoreProcessor(Ruleset.Value.CreateInstance()); scoreProcessor.ApplyBeatmap(playable); LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(Ruleset.Value, scoreProcessor, clocks.Keys.Select(id => new MultiplayerRoomUser(id)).ToArray()) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index bcd4474876..0edc56f5d3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Children = new Drawable[] { - scoreProcessor = new OsuScoreProcessor(), + scoreProcessor = new OsuScoreProcessor(Ruleset.Value.CreateInstance()), }; scoreProcessor.ApplyBeatmap(playableBeatmap); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs index 7f5aced925..60a5785b31 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Children = new Drawable[] { - scoreProcessor = new OsuScoreProcessor(), + scoreProcessor = new OsuScoreProcessor(Ruleset.Value.CreateInstance()), }; scoreProcessor.ApplyBeatmap(playableBeatmap); diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index d5926386e8..b7c6132bdb 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -201,7 +201,7 @@ namespace osu.Game.Rulesets /// Creates a for this . /// /// The score processor. - public virtual ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(); + public virtual ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this); /// /// Creates a for this . diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index a7fe8b354b..b982c6ece2 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -84,6 +84,7 @@ namespace osu.Game.Rulesets.Scoring /// protected virtual double ClassicScoreMultiplier => 36; + private readonly Ruleset ruleset; private readonly double accuracyPortion; private readonly double comboPortion; @@ -116,8 +117,10 @@ namespace osu.Game.Rulesets.Scoring private double scoreMultiplier = 1; - public ScoreProcessor() + public ScoreProcessor(Ruleset ruleset) { + this.ruleset = ruleset; + accuracyPortion = DefaultAccuracyPortion; comboPortion = DefaultComboPortion; @@ -255,7 +258,7 @@ namespace osu.Game.Rulesets.Scoring /// The total score in the given . public double ComputeFinalScore(ScoringMode mode, ScoreInfo scoreInfo) { - extractFromStatistics(scoreInfo.Ruleset.CreateInstance(), + extractFromStatistics(ruleset, scoreInfo.Statistics, out double extractedBaseScore, out double extractedMaxBaseScore, @@ -282,7 +285,7 @@ namespace osu.Game.Rulesets.Scoring if (!beatmapApplied) throw new InvalidOperationException($"Cannot compute partial score without calling {nameof(ApplyBeatmap)}."); - extractFromStatistics(scoreInfo.Ruleset.CreateInstance(), + extractFromStatistics(ruleset, scoreInfo.Statistics, out double extractedBaseScore, out _, @@ -317,7 +320,7 @@ namespace osu.Game.Rulesets.Scoring if (scoreInfo.IsLegacyScore && scoreInfo.Ruleset.OnlineID == 3) { extractFromStatistics( - scoreInfo.Ruleset.CreateInstance(), + ruleset, scoreInfo.Statistics, out double computedBaseScore, out double computedMaxBaseScore, diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index a9b5544921..af360b42bb 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -26,6 +26,7 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Skinning; using osuTK; @@ -56,6 +57,7 @@ namespace osu.Game.Screens.Play.HUD private JudgementResult lastJudgement; private PerformanceCalculator performanceCalculator; + private ScoreInfo scoreInfo; public PerformancePointsCounter() { @@ -72,9 +74,10 @@ namespace osu.Game.Screens.Play.HUD if (gameplayState != null) { performanceCalculator = gameplayState.Ruleset.CreatePerformanceCalculator(); - clonedMods = gameplayState.Mods.Select(m => m.DeepClone()).ToArray(); + scoreInfo = new ScoreInfo(gameplayState.Score.ScoreInfo.BeatmapInfo, gameplayState.Score.ScoreInfo.Ruleset); + var gameplayWorkingBeatmap = new GameplayWorkingBeatmap(gameplayState.Beatmap); difficultyCache.GetTimedDifficultyAttributesAsync(gameplayWorkingBeatmap, gameplayState.Ruleset, clonedMods, loadCancellationSource.Token) .ContinueWith(task => Schedule(() => @@ -123,16 +126,13 @@ namespace osu.Game.Screens.Play.HUD var attrib = getAttributeAtTime(judgement); - if (gameplayState == null || attrib == null) + if (gameplayState == null || attrib == null || scoreProcessor == null) { IsValid = false; return; } - // awkward but we need to make sure the true mods are not passed to PerformanceCalculator as it makes a mess of track applications. - var scoreInfo = gameplayState.Score.ScoreInfo.DeepClone(); - scoreInfo.Mods = clonedMods; - + scoreProcessor.PopulateScore(scoreInfo); Current.Value = (int)Math.Round(performanceCalculator?.Calculate(scoreInfo, attrib).Total ?? 0, MidpointRounding.AwayFromZero); IsValid = true; } diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index ce9afd650a..a9dcf705a9 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; @@ -11,11 +12,16 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Input.Events; using osu.Framework.Utils; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; using osuTK; using osuTK.Graphics; @@ -30,7 +36,7 @@ namespace osu.Game.Skinning.Editor private const float component_display_scale = 0.8f; [Cached] - private ScoreProcessor scoreProcessor = new ScoreProcessor + private ScoreProcessor scoreProcessor = new ScoreProcessor(new DummyRuleset()) { Combo = { Value = RNG.Next(1, 1000) }, TotalScore = { Value = RNG.Next(1000, 10000000) } @@ -171,5 +177,15 @@ namespace osu.Game.Skinning.Editor return true; } } + + private class DummyRuleset : Ruleset + { + public override IEnumerable GetModsFor(ModType type) => throw new NotImplementedException(); + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new NotImplementedException(); + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); + public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => throw new NotImplementedException(); + public override string Description { get; } + public override string ShortName { get; } + } } } From 028750936c1243f0da0627416cddc067154238e2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 14 Mar 2022 17:10:37 +0900 Subject: [PATCH 1172/5199] Apply review suggestions --- osu.Game/Rulesets/Scoring/JudgementProcessor.cs | 8 +++++++- osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs index 3017841c9d..94ddc32bb7 100644 --- a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs +++ b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Game.Beatmaps; @@ -165,7 +166,12 @@ namespace osu.Game.Rulesets.Scoring protected override void Update() { base.Update(); - hasCompleted.Value = JudgedHits == MaxHits && (JudgedHits == 0 || lastAppliedResult?.TimeAbsolute < Clock.CurrentTime); + + hasCompleted.Value = + JudgedHits == MaxHits + && (JudgedHits == 0 + // Last applied result is guaranteed to be non-null when JudgedHits > 0. + || lastAppliedResult.AsNonNull().TimeAbsolute < Clock.CurrentTime); } /// diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index af360b42bb..c0d0ea0721 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -76,7 +76,7 @@ namespace osu.Game.Screens.Play.HUD performanceCalculator = gameplayState.Ruleset.CreatePerformanceCalculator(); clonedMods = gameplayState.Mods.Select(m => m.DeepClone()).ToArray(); - scoreInfo = new ScoreInfo(gameplayState.Score.ScoreInfo.BeatmapInfo, gameplayState.Score.ScoreInfo.Ruleset); + scoreInfo = new ScoreInfo(gameplayState.Score.ScoreInfo.BeatmapInfo, gameplayState.Score.ScoreInfo.Ruleset) { Mods = clonedMods }; var gameplayWorkingBeatmap = new GameplayWorkingBeatmap(gameplayState.Beatmap); difficultyCache.GetTimedDifficultyAttributesAsync(gameplayWorkingBeatmap, gameplayState.Ruleset, clonedMods, loadCancellationSource.Token) From 8676a2587c12c2a200d6122d53736a611401e1a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 15:45:43 +0900 Subject: [PATCH 1173/5199] Add the ability for `HitObject`s to specify auxiliary samples --- osu.Game/Rulesets/Objects/HitObject.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index c590cc302f..57b897e5b5 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using JetBrains.Annotations; using Newtonsoft.Json; @@ -67,6 +68,12 @@ namespace osu.Game.Rulesets.Objects } } + /// + /// Any samples which may be used by this hit object that are non-standard. + /// This is used only to preload these samples ahead of time. + /// + public virtual IList AuxiliarySamples => ImmutableList.Empty; + public SampleControlPoint SampleControlPoint = SampleControlPoint.DEFAULT; public DifficultyControlPoint DifficultyControlPoint = DifficultyControlPoint.DEFAULT; From 90e34d7686b9a9762ba153ca841c5ad685497f3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 15:45:57 +0900 Subject: [PATCH 1174/5199] Move slider slide samples to auxiliary specification --- .../Objects/Drawables/DrawableSlider.cs | 22 +++++-------------- osu.Game.Rulesets.Osu/Objects/Slider.cs | 17 ++++++++++++++ 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 3acec4498d..1447f131c6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -2,23 +2,22 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; -using osuTK; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Audio; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Scoring; -using osuTK.Graphics; using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -126,18 +125,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } Samples.Samples = HitObject.TailSamples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); - - var slidingSamples = new List(); - - var normalSample = HitObject.Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL); - if (normalSample != null) - slidingSamples.Add(HitObject.SampleControlPoint.ApplyTo(normalSample).With("sliderslide")); - - var whistleSample = HitObject.Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_WHISTLE); - if (whistleSample != null) - slidingSamples.Add(HitObject.SampleControlPoint.ApplyTo(whistleSample).With("sliderwhistle")); - - slidingSample.Samples = slidingSamples.ToArray(); + slidingSample.Samples = HitObject.CreateSlidingSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); } public override void StopAllSamples() diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 5c1c3fd253..601311694d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -29,6 +29,23 @@ namespace osu.Game.Rulesets.Osu.Objects set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } + public override IList AuxiliarySamples => CreateSlidingSamples(); + + public IList CreateSlidingSamples() + { + var slidingSamples = new List(); + + var normalSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL); + if (normalSample != null) + slidingSamples.Add(normalSample.With("sliderslide")); + + var whistleSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_WHISTLE); + if (whistleSample != null) + slidingSamples.Add(whistleSample.With("sliderwhistle")); + + return slidingSamples; + } + private readonly Cached endPositionCache = new Cached(); public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1); From be9920218805cc3280fb1e16d3816774305ca069 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 17:08:04 +0900 Subject: [PATCH 1175/5199] Move spinner spin samples to auxiliary specification --- .../Objects/Drawables/DrawableSpinner.cs | 11 ++--------- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index c6db02ee02..a904658a4c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -121,15 +121,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.LoadSamples(); - var firstSample = HitObject.Samples.FirstOrDefault(); - - if (firstSample != null) - { - var clone = HitObject.SampleControlPoint.ApplyTo(firstSample).With("spinnerspin"); - - spinningSample.Samples = new ISampleInfo[] { clone }; - spinningSample.Frequency.Value = spinning_sample_initial_frequency; - } + spinningSample.Samples = HitObject.CreateSpinningSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); + spinningSample.Frequency.Value = spinning_sample_initial_frequency; } private void updateSpinningSample(ValueChangedEvent tracking) diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 1eddfb7fef..ddee4d3ebd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -1,7 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; @@ -73,5 +77,20 @@ namespace osu.Game.Rulesets.Osu.Objects public override Judgement CreateJudgement() => new OsuJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; + + public override IList AuxiliarySamples => CreateSpinningSamples(); + + public HitSampleInfo[] CreateSpinningSamples() + { + var referenceSample = Samples.FirstOrDefault(); + + if (referenceSample == null) + return Array.Empty(); + + return new[] + { + SampleControlPoint.ApplyTo(referenceSample).With("spinnerspin") + }; + } } } From 6d6f73e0168d6c7d5c447e0b32c6d8566d5b4761 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 17:08:26 +0900 Subject: [PATCH 1176/5199] Add overrides in `DrawableSliderTail` to explain/warn that this class never plays its own samples --- .../Objects/Drawables/DrawableSliderTail.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index ec1387eb54..64964ed396 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -74,6 +74,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue)); } + protected override void LoadSamples() + { + // Tail models don't actually get samples, as the playback is handled by DrawableSlider. + // This override is only here for visibility in explaining this weird flow. + } + + public override void PlaySamples() + { + // Tail models don't actually get samples, as the playback is handled by DrawableSlider. + // This override is only here for visibility in explaining this weird flow. + } + protected override void UpdateInitialTransforms() { base.UpdateInitialTransforms(); From 45233932089ea26d9e329c47f1893137d2d4bcaf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 17:08:57 +0900 Subject: [PATCH 1177/5199] Remove `IsLayered` from `LegacyHitSampleInfo` comparison The equality of samples is generally used to compare the sample equality, not its full properties. For instance, we don't compare `Volume` in the base implementation. Having `IsLayered` here breaks actual usages of equality, ie. for pooling purposes. --- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index b091803406..47fe9032f0 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -500,7 +500,7 @@ namespace osu.Game.Rulesets.Objects.Legacy => new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newCustomSampleBank.GetOr(CustomSampleBank), newIsLayered.GetOr(IsLayered)); public bool Equals(LegacyHitSampleInfo? other) - => base.Equals(other) && CustomSampleBank == other.CustomSampleBank && IsLayered == other.IsLayered; + => base.Equals(other) && CustomSampleBank == other.CustomSampleBank; public override bool Equals(object? obj) => obj is LegacyHitSampleInfo other && Equals(other); From 1b8c632b878b6d5c30a7a5693dc5834d0aa987b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 17:17:14 +0900 Subject: [PATCH 1178/5199] Add `TailSamples` to auxiliary samples list --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 601311694d..776165cfb4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } - public override IList AuxiliarySamples => CreateSlidingSamples(); + public override IList AuxiliarySamples => CreateSlidingSamples().Concat(TailSamples).ToArray(); public IList CreateSlidingSamples() { From 39d95aa8cf412b1a5ef66e1cd1d05541fef4f3bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 17:19:23 +0900 Subject: [PATCH 1179/5199] Add automatic preloading of sample pools at a `Playfield` level --- osu.Game/Rulesets/UI/Playfield.cs | 73 +++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 24 deletions(-) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 30e71dde1c..cd8f99db8b 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -3,22 +3,22 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Game.Audio; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Skinning; using osuTK; -using System.Diagnostics; namespace osu.Game.Rulesets.UI { @@ -264,10 +264,25 @@ namespace osu.Game.Rulesets.UI var entry = CreateLifetimeEntry(hitObject); lifetimeEntryMap[entry.HitObject] = entry; + preloadSamples(hitObject); + HitObjectContainer.Add(entry); OnHitObjectAdded(entry.HitObject); } + private void preloadSamples(HitObject hitObject) + { + // prepare sample pools ahead of time so we're not initialising at runtime. + foreach (var sample in hitObject.Samples) + prepareSamplePool(hitObject.SampleControlPoint.ApplyTo(sample)); + + foreach (var sample in hitObject.AuxiliarySamples) + prepareSamplePool(hitObject.SampleControlPoint.ApplyTo(sample)); + + foreach (var nestedObject in hitObject.NestedHitObjects) + preloadSamples(nestedObject); + } + /// /// Removes a for a pooled from this . /// @@ -330,22 +345,7 @@ namespace osu.Game.Rulesets.UI DrawableHitObject IPooledHitObjectProvider.GetPooledDrawableRepresentation(HitObject hitObject, DrawableHitObject parent) { - var lookupType = hitObject.GetType(); - - IDrawablePool pool; - - // Tests may add derived hitobject instances for which pools don't exist. Try to find any applicable pool and dynamically assign the type if the pool exists. - if (!pools.TryGetValue(lookupType, out pool)) - { - foreach (var (t, p) in pools) - { - if (!t.IsInstanceOfType(hitObject)) - continue; - - pools[lookupType] = pool = p; - break; - } - } + var pool = prepareDrawableHitObjectPool(hitObject); return (DrawableHitObject)pool?.Get(d => { @@ -372,14 +372,39 @@ namespace osu.Game.Rulesets.UI }); } + private IDrawablePool prepareDrawableHitObjectPool(HitObject hitObject) + { + var lookupType = hitObject.GetType(); + + IDrawablePool pool; + + // Tests may add derived hitobject instances for which pools don't exist. Try to find any applicable pool and dynamically assign the type if the pool exists. + if (!pools.TryGetValue(lookupType, out pool)) + { + foreach (var (t, p) in pools) + { + if (!t.IsInstanceOfType(hitObject)) + continue; + + pools[lookupType] = pool = p; + break; + } + } + + return pool; + } + private readonly Dictionary> samplePools = new Dictionary>(); - public PoolableSkinnableSample GetPooledSample(ISampleInfo sampleInfo) - { - if (!samplePools.TryGetValue(sampleInfo, out var existingPool)) - AddInternal(samplePools[sampleInfo] = existingPool = new DrawableSamplePool(sampleInfo, 1)); + public PoolableSkinnableSample GetPooledSample(ISampleInfo sampleInfo) => prepareSamplePool(sampleInfo).Get(); - return existingPool.Get(); + private DrawablePool prepareSamplePool(ISampleInfo sampleInfo) + { + if (samplePools.TryGetValue(sampleInfo, out var pool)) return pool; + + AddInternal(samplePools[sampleInfo] = pool = new DrawableSamplePool(sampleInfo, 1)); + + return pool; } private class DrawableSamplePool : DrawablePool From 3c5fda5f23fce4a384cf286b4cab2ff180cfaa33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 17:24:31 +0900 Subject: [PATCH 1180/5199] Add early exist if the target screen is no longer current --- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index abd8272633..73af2591c8 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -132,6 +132,9 @@ namespace osu.Game.Skinning.Editor { Debug.Assert(skinEditor != null); + if (!target.IsCurrentScreen()) + return; + if (!target.IsLoaded) { Scheduler.AddOnce(setTarget, target); From 16ee6b5fc7700b2a46359fb6b814e9c56eafaa53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 18:12:04 +0900 Subject: [PATCH 1181/5199] Remove `IsLayered` from `GetHasCode` implementation --- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 47fe9032f0..8dc037c7c8 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -505,7 +505,7 @@ namespace osu.Game.Rulesets.Objects.Legacy public override bool Equals(object? obj) => obj is LegacyHitSampleInfo other && Equals(other); - public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), CustomSampleBank, IsLayered); + public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), CustomSampleBank); } private class FileHitSampleInfo : LegacyHitSampleInfo, IEquatable From b31ff679fb1af632f8b40d980a3d3528a08e43a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 19:16:19 +0900 Subject: [PATCH 1182/5199] Provide correct `HitResult` type for random judgements in `TestSceneHitErrorMeter` --- osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index c1260f0231..a93ab6810d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -48,7 +48,11 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create display", () => recreateDisplay(new OsuHitWindows(), 5)); - AddRepeatStep("New random judgement", () => newJudgement(), 40); + AddRepeatStep("New random judgement", () => + { + double offset = RNG.Next(-150, 150); + newJudgement(offset, drawableRuleset.HitWindows.ResultFor(offset)); + }, 400); AddRepeatStep("New max negative", () => newJudgement(-drawableRuleset.HitWindows.WindowFor(HitResult.Meh)), 20); AddRepeatStep("New max positive", () => newJudgement(drawableRuleset.HitWindows.WindowFor(HitResult.Meh)), 20); From 6eed2c35a4cb79877ced63626745d8768fb196a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 19:16:38 +0900 Subject: [PATCH 1183/5199] Adjust visual appearance of `BarHitErrorMeter` for easier reading --- .../HUD/HitErrorMeters/BarHitErrorMeter.cs | 157 ++++++++++-------- 1 file changed, 89 insertions(+), 68 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 7903e54960..2f9206e0fb 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD.HitErrorMeters { @@ -21,14 +20,15 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { private const int arrow_move_duration = 400; - private const int judgement_line_width = 6; + private const int judgement_line_width = 10; + private const int judgement_line_height = 3; + + private const int centre_marker_size = 6; private const int bar_height = 200; private const int bar_width = 2; - private const int spacing = 2; - private const float chevron_size = 8; private SpriteIcon arrow; @@ -50,54 +50,25 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters [BackgroundDependencyLoader] private void load() { - InternalChild = new FillFlowContainer + var hitWindows = HitWindows.GetAllAvailableWindows().ToArray(); + + InternalChild = new Container { AutoSizeAxes = Axes.X, Height = bar_height, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(spacing, 0), Margin = new MarginPadding(2), Children = new Drawable[] { - new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Width = chevron_size, - RelativeSizeAxes = Axes.Y, - Child = arrow = new SpriteIcon - { - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.Y, - Y = 0.5f, - Icon = FontAwesome.Solid.ChevronRight, - Size = new Vector2(chevron_size), - } - }, colourBars = new Container { - Width = bar_width, - RelativeSizeAxes = Axes.Y, + Name = "colour axis", + X = chevron_size, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, + Width = judgement_line_width, + RelativeSizeAxes = Axes.Y, Children = new Drawable[] { - colourBarsEarly = new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Both, - Height = 0.5f, - Scale = new Vector2(1, -1), - }, - colourBarsLate = new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Both, - Height = 0.5f, - }, iconEarly = new SpriteIcon { Y = -10, @@ -113,20 +84,72 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Icon = FontAwesome.Solid.Bicycle, Anchor = Anchor.BottomCentre, Origin = Anchor.Centre, - } + }, + colourBarsEarly = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + Width = bar_width, + RelativeSizeAxes = Axes.Y, + Height = 0.5f, + Scale = new Vector2(1, -1), + }, + colourBarsLate = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + Width = bar_width, + RelativeSizeAxes = Axes.Y, + Height = 0.5f, + }, + judgementsContainer = new Container + { + Name = "judgements", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = judgement_line_width, + }, + new Circle + { + Name = "middle marker behind", + Colour = GetColourForHitResult(hitWindows.Last().result), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(centre_marker_size), + }, + new Circle + { + Name = "middle marker in front", + Colour = GetColourForHitResult(hitWindows.Last().result).Darken(0.5f), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(centre_marker_size), + Scale = new Vector2(0.5f), + }, } }, - judgementsContainer = new Container + new Container { + Name = "average chevron", Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Width = judgement_line_width, + Width = chevron_size, RelativeSizeAxes = Axes.Y, + Child = arrow = new SpriteIcon + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.Y, + Y = 0.5f, + Icon = FontAwesome.Solid.ChevronRight, + Size = new Vector2(chevron_size), + } }, } }; - createColourBars(); + createColourBars(hitWindows); } protected override void LoadComplete() @@ -149,10 +172,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters iconLate.Rotation = -Rotation; } - private void createColourBars() + private void createColourBars((HitResult result, double length)[] windows) { - var windows = HitWindows.GetAllAvailableWindows().ToArray(); - // max to avoid div-by-zero. maxHitWindow = Math.Max(1, windows.First().length); @@ -166,17 +187,11 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters colourBarsLate.Add(createColourBar(result, hitWindow, i == 0)); } - // a little nub to mark the centre point. - var centre = createColourBar(windows.Last().result, 0.01f); - centre.Anchor = centre.Origin = Anchor.CentreLeft; - centre.Width = 2.5f; - colourBars.Add(centre); - - Drawable createColourBar(HitResult result, float height, bool first = false) + Drawable createColourBar(HitResult result, float height, bool requireGradient = false) { var colour = GetColourForHitResult(result); - if (first) + if (requireGradient) { // the first bar needs gradient rendering. const float gradient_start = 0.8f; @@ -243,7 +258,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters judgementsContainer.Add(new JudgementLine { Y = getRelativeJudgementPosition(judgement.TimeOffset), - Origin = Anchor.CentreLeft, + Colour = GetColourForHitResult(judgement.Type), }); arrow.MoveToY( @@ -255,34 +270,40 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters internal class JudgementLine : CompositeDrawable { - private const int judgement_fade_duration = 5000; - public JudgementLine() { RelativeSizeAxes = Axes.X; RelativePositionAxes = Axes.Y; - Height = 3; + Height = judgement_line_height; - InternalChild = new CircularContainer + Blending = BlendingParameters.Additive; + + Origin = Anchor.Centre; + Anchor = Anchor.TopCentre; + + InternalChild = new Circle { - Masking = true, RelativeSizeAxes = Axes.Both, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - } }; } protected override void LoadComplete() { + const int judgement_fade_in_duration = 100; + const int judgement_fade_out_duration = 5000; + base.LoadComplete(); + Alpha = 0; Width = 0; - this.ResizeWidthTo(1, 200, Easing.OutElasticHalf); - this.FadeTo(0.8f, 150).Then().FadeOut(judgement_fade_duration).Expire(); + this + .FadeTo(0.8f, judgement_fade_in_duration, Easing.OutQuint) + .ResizeWidthTo(1, judgement_fade_in_duration, Easing.OutQuint) + .Then() + .FadeOut(judgement_fade_out_duration, Easing.In) + .ResizeWidthTo(0, judgement_fade_out_duration, Easing.In) + .Expire(); } } From e91b3ae5f19fa70fa4062be6aef4e726f12ba10c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 19:18:47 +0900 Subject: [PATCH 1184/5199] Move constants closer to usages --- .../HUD/HitErrorMeters/BarHitErrorMeter.cs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 2f9206e0fb..521d8c01b1 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -18,19 +18,9 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { public class BarHitErrorMeter : HitErrorMeter { - private const int arrow_move_duration = 400; - private const int judgement_line_width = 10; private const int judgement_line_height = 3; - private const int centre_marker_size = 6; - - private const int bar_height = 200; - - private const int bar_width = 2; - - private const float chevron_size = 8; - private SpriteIcon arrow; private SpriteIcon iconEarly; private SpriteIcon iconLate; @@ -50,6 +40,12 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters [BackgroundDependencyLoader] private void load() { + const int centre_marker_size = 6; + const int bar_height = 200; + const int bar_width = 2; + const float chevron_size = 8; + const float icon_size = 14; + var hitWindows = HitWindows.GetAllAvailableWindows().ToArray(); InternalChild = new Container @@ -72,7 +68,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters iconEarly = new SpriteIcon { Y = -10, - Size = new Vector2(10), + Size = new Vector2(icon_size), Icon = FontAwesome.Solid.ShippingFast, Anchor = Anchor.TopCentre, Origin = Anchor.Centre, @@ -80,7 +76,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters iconLate = new SpriteIcon { Y = 10, - Size = new Vector2(10), + Size = new Vector2(icon_size), Icon = FontAwesome.Solid.Bicycle, Anchor = Anchor.BottomCentre, Origin = Anchor.Centre, @@ -235,6 +231,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters protected override void OnNewJudgement(JudgementResult judgement) { + const int arrow_move_duration = 400; + if (!judgement.IsHit || judgement.HitObject.HitWindows?.WindowFor(HitResult.Miss) == 0) return; From 163cd48bf63ec43c3b9677340976544b109eca2f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 19:27:53 +0900 Subject: [PATCH 1185/5199] Further metrics tweaking --- .../HUD/HitErrorMeters/BarHitErrorMeter.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 521d8c01b1..542731cf93 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -18,8 +18,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { public class BarHitErrorMeter : HitErrorMeter { - private const int judgement_line_width = 10; - private const int judgement_line_height = 3; + private const int judgement_line_width = 14; + private const int judgement_line_height = 4; private SpriteIcon arrow; private SpriteIcon iconEarly; @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters [BackgroundDependencyLoader] private void load() { - const int centre_marker_size = 6; + const int centre_marker_size = 8; const int bar_height = 200; const int bar_width = 2; const float chevron_size = 8; @@ -98,6 +98,14 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters RelativeSizeAxes = Axes.Y, Height = 0.5f, }, + new Circle + { + Name = "middle marker behind", + Colour = GetColourForHitResult(hitWindows.Last().result), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(centre_marker_size), + }, judgementsContainer = new Container { Name = "judgements", @@ -107,17 +115,9 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Width = judgement_line_width, }, new Circle - { - Name = "middle marker behind", - Colour = GetColourForHitResult(hitWindows.Last().result), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(centre_marker_size), - }, - new Circle { Name = "middle marker in front", - Colour = GetColourForHitResult(hitWindows.Last().result).Darken(0.5f), + Colour = GetColourForHitResult(hitWindows.Last().result).Darken(0.3f), Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(centre_marker_size), @@ -231,7 +231,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters protected override void OnNewJudgement(JudgementResult judgement) { - const int arrow_move_duration = 400; + const int arrow_move_duration = 800; if (!judgement.IsHit || judgement.HitObject.HitWindows?.WindowFor(HitResult.Miss) == 0) return; @@ -261,7 +261,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters arrow.MoveToY( getRelativeJudgementPosition(floatingAverage = floatingAverage * 0.9 + judgement.TimeOffset * 0.1) - , arrow_move_duration, Easing.Out); + , arrow_move_duration, Easing.OutQuint); } private float getRelativeJudgementPosition(double value) => Math.Clamp((float)((value / maxHitWindow) + 1) / 2, 0, 1); @@ -296,11 +296,11 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Width = 0; this - .FadeTo(0.8f, judgement_fade_in_duration, Easing.OutQuint) + .FadeTo(0.6f, judgement_fade_in_duration, Easing.OutQuint) .ResizeWidthTo(1, judgement_fade_in_duration, Easing.OutQuint) .Then() - .FadeOut(judgement_fade_out_duration, Easing.In) - .ResizeWidthTo(0, judgement_fade_out_duration, Easing.In) + .FadeOut(judgement_fade_out_duration) + .ResizeWidthTo(0, judgement_fade_out_duration, Easing.InQuint) .Expire(); } } From 6657d93b29e2dbeda325333c674fd53c572b0d66 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 14 Mar 2022 20:18:30 +0800 Subject: [PATCH 1186/5199] Separate the two nested classes --- .../OsuHitObjectGenerationUtils_Reposition.cs | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index 37a12b20b4..94f4f154bd 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -60,12 +60,12 @@ namespace osu.Game.Rulesets.Osu.Utils /// The repositioned hit objects. public static List RepositionHitObjects(IEnumerable objectPositionInfos) { - List positionInfos = objectPositionInfos.Select(o => new ObjectPositionInfoInternal(o)).ToList(); - ObjectPositionInfoInternal? previous = null; + List workingObjects = objectPositionInfos.Select(o => new WorkingObject(o)).ToList(); + WorkingObject? previous = null; - for (int i = 0; i < positionInfos.Count; i++) + for (int i = 0; i < workingObjects.Count; i++) { - var current = positionInfos[i]; + var current = workingObjects[i]; var hitObject = current.HitObject; if (hitObject is Spinner) @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.Utils continue; } - computeModifiedPosition(current, previous, i > 1 ? positionInfos[i - 2] : null); + computeModifiedPosition(current, previous, i > 1 ? workingObjects[i - 2] : null); // Move hit objects back into the playfield if they are outside of it Vector2 shift = Vector2.Zero; @@ -97,9 +97,9 @@ namespace osu.Game.Rulesets.Osu.Utils for (int j = i - 1; j >= i - preceding_hitobjects_to_shift && j >= 0; j--) { // only shift hit circles - if (!(positionInfos[j].HitObject is HitCircle)) break; + if (!(workingObjects[j].HitObject is HitCircle)) break; - toBeShifted.Add(positionInfos[j].HitObject); + toBeShifted.Add(workingObjects[j].HitObject); } if (toBeShifted.Count > 0) @@ -109,16 +109,16 @@ namespace osu.Game.Rulesets.Osu.Utils previous = current; } - return positionInfos.Select(p => p.HitObject).ToList(); + return workingObjects.Select(p => p.HitObject).ToList(); } /// /// Compute the modified position of a hit object while attempting to keep it inside the playfield. /// - /// The representing the hit object to have the modified position computed for. - /// The representing the hit object immediately preceding the current one. - /// The representing the hit object immediately preceding the one. - private static void computeModifiedPosition(ObjectPositionInfoInternal current, ObjectPositionInfoInternal? previous, ObjectPositionInfoInternal? beforePrevious) + /// The representing the hit object to have the modified position computed for. + /// The representing the hit object immediately preceding the current one. + /// The representing the hit object immediately preceding the one. + private static void computeModifiedPosition(WorkingObject current, WorkingObject? previous, WorkingObject? beforePrevious) { float previousAbsoluteAngle = 0f; @@ -129,11 +129,11 @@ namespace osu.Game.Rulesets.Osu.Utils previousAbsoluteAngle = (float)Math.Atan2(relativePosition.Y, relativePosition.X); } - float absoluteAngle = previousAbsoluteAngle + current.RelativeAngle; + float absoluteAngle = previousAbsoluteAngle + current.PositionInfo.RelativeAngle; var posRelativeToPrev = new Vector2( - current.DistanceFromPrevious * (float)Math.Cos(absoluteAngle), - current.DistanceFromPrevious * (float)Math.Sin(absoluteAngle) + current.PositionInfo.DistanceFromPrevious * (float)Math.Cos(absoluteAngle), + current.PositionInfo.DistanceFromPrevious * (float)Math.Sin(absoluteAngle) ); Vector2 lastEndPosition = previous?.EndPositionModified ?? playfield_centre; @@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// Move the modified position of a hit circle so that it fits inside the playfield. /// /// The deviation from the original modified position in order to fit within the playfield. - private static Vector2 clampHitCircleToPlayfield(HitCircle circle, ObjectPositionInfoInternal objectPositionInfo) + private static Vector2 clampHitCircleToPlayfield(HitCircle circle, WorkingObject objectPositionInfo) { var previousPosition = objectPositionInfo.PositionModified; objectPositionInfo.EndPositionModified = objectPositionInfo.PositionModified = clampToPlayfieldWithPadding( @@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// Moves the and all necessary nested s into the if they aren't already. /// /// The deviation from the original modified position in order to fit within the playfield. - private static Vector2 clampSliderToPlayfield(Slider slider, ObjectPositionInfoInternal objectPositionInfo) + private static Vector2 clampSliderToPlayfield(Slider slider, WorkingObject objectPositionInfo) { var possibleMovementBounds = calculatePossibleMovementBounds(slider); @@ -319,17 +319,18 @@ namespace osu.Game.Rulesets.Osu.Utils } } - private class ObjectPositionInfoInternal : ObjectPositionInfo + private class WorkingObject { public Vector2 PositionOriginal { get; } public Vector2 PositionModified { get; set; } public Vector2 EndPositionModified { get; set; } - public ObjectPositionInfoInternal(ObjectPositionInfo original) - : base(original.HitObject) + public ObjectPositionInfo PositionInfo { get; } + public OsuHitObject HitObject => PositionInfo.HitObject; + + public WorkingObject(ObjectPositionInfo positionInfo) { - RelativeAngle = original.RelativeAngle; - DistanceFromPrevious = original.DistanceFromPrevious; + PositionInfo = positionInfo; PositionModified = PositionOriginal = HitObject.Position; EndPositionModified = HitObject.EndPosition; } From 76021c76278cf3a91a6f38e6b9b047a93f5dadad Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 14 Mar 2022 20:23:35 +0800 Subject: [PATCH 1187/5199] Remove extra parameters --- .../OsuHitObjectGenerationUtils_Reposition.cs | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index 94f4f154bd..d1bc3b45df 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -81,12 +81,12 @@ namespace osu.Game.Rulesets.Osu.Utils switch (hitObject) { - case HitCircle circle: - shift = clampHitCircleToPlayfield(circle, current); + case HitCircle _: + shift = clampHitCircleToPlayfield(current); break; - case Slider slider: - shift = clampSliderToPlayfield(slider, current); + case Slider _: + shift = clampSliderToPlayfield(current); break; } @@ -144,48 +144,49 @@ namespace osu.Game.Rulesets.Osu.Utils } /// - /// Move the modified position of a hit circle so that it fits inside the playfield. + /// Move the modified position of a so that it fits inside the playfield. /// /// The deviation from the original modified position in order to fit within the playfield. - private static Vector2 clampHitCircleToPlayfield(HitCircle circle, WorkingObject objectPositionInfo) + private static Vector2 clampHitCircleToPlayfield(WorkingObject workingObject) { - var previousPosition = objectPositionInfo.PositionModified; - objectPositionInfo.EndPositionModified = objectPositionInfo.PositionModified = clampToPlayfieldWithPadding( - objectPositionInfo.PositionModified, - (float)circle.Radius + var previousPosition = workingObject.PositionModified; + workingObject.EndPositionModified = workingObject.PositionModified = clampToPlayfieldWithPadding( + workingObject.PositionModified, + (float)workingObject.HitObject.Radius ); - circle.Position = objectPositionInfo.PositionModified; + workingObject.HitObject.Position = workingObject.PositionModified; - return objectPositionInfo.PositionModified - previousPosition; + return workingObject.PositionModified - previousPosition; } /// /// Moves the and all necessary nested s into the if they aren't already. /// /// The deviation from the original modified position in order to fit within the playfield. - private static Vector2 clampSliderToPlayfield(Slider slider, WorkingObject objectPositionInfo) + private static Vector2 clampSliderToPlayfield(WorkingObject workingObject) { + var slider = (Slider)workingObject.HitObject; var possibleMovementBounds = calculatePossibleMovementBounds(slider); - var previousPosition = objectPositionInfo.PositionModified; + var previousPosition = workingObject.PositionModified; // Clamp slider position to the placement area // If the slider is larger than the playfield, force it to stay at the original position float newX = possibleMovementBounds.Width < 0 - ? objectPositionInfo.PositionOriginal.X + ? workingObject.PositionOriginal.X : Math.Clamp(previousPosition.X, possibleMovementBounds.Left, possibleMovementBounds.Right); float newY = possibleMovementBounds.Height < 0 - ? objectPositionInfo.PositionOriginal.Y + ? workingObject.PositionOriginal.Y : Math.Clamp(previousPosition.Y, possibleMovementBounds.Top, possibleMovementBounds.Bottom); - slider.Position = objectPositionInfo.PositionModified = new Vector2(newX, newY); - objectPositionInfo.EndPositionModified = slider.EndPosition; + slider.Position = workingObject.PositionModified = new Vector2(newX, newY); + workingObject.EndPositionModified = slider.EndPosition; - shiftNestedObjects(slider, objectPositionInfo.PositionModified - objectPositionInfo.PositionOriginal); + shiftNestedObjects(slider, workingObject.PositionModified - workingObject.PositionOriginal); - return objectPositionInfo.PositionModified - previousPosition; + return workingObject.PositionModified - previousPosition; } /// From 8fdf2b13ac2d83b206d88940e7582ae80c91a645 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 22:23:50 +0900 Subject: [PATCH 1188/5199] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 839f7882bf..1b5461959a 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c106c373c4..5e194e2aca 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 86cf1b229c..23101c5af6 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 39c30516d0874b025d3e8e0610bd6ab096ebd2a6 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 18:31:13 +0000 Subject: [PATCH 1189/5199] Implement `ChannelControlItem` for new chat design Adds new component `ChannelControlItem` and it's child components to be used as the clickable control in the new chat sidebar for joined channels. Has public properties `HasUnread` and `MentionCount` to control the display of the channel having unread messages or mentions of the user. Channel select/join requests are exposed via `OnRequestSelect` and `OnRequestLeave` events respectively which should be handled by a parent component. Requires a cached `Bindable` instance to be managed by a parent component. Requires a cached `OveralayColourScheme` instance to be provided by a parent component. --- .../Online/TestSceneChannelControlItem.cs | 145 +++++++++++++++ .../Chat/ChannelControl/ControlItem.cs | 167 ++++++++++++++++++ .../Chat/ChannelControl/ControlItemAvatar.cs | 60 +++++++ .../Chat/ChannelControl/ControlItemClose.cs | 55 ++++++ .../Chat/ChannelControl/ControlItemMention.cs | 77 ++++++++ .../Chat/ChannelControl/ControlItemText.cs | 71 ++++++++ 6 files changed, 575 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs create mode 100644 osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs create mode 100644 osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs create mode 100644 osu.Game/Overlays/Chat/ChannelControl/ControlItemClose.cs create mode 100644 osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs create mode 100644 osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs new file mode 100644 index 0000000000..a1431f696b --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -0,0 +1,145 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Chat; +using osu.Game.Overlays; +using osu.Game.Overlays.Chat.ChannelControl; +using osuTK; + +namespace osu.Game.Tests.Visual.Online +{ + [TestFixture] + public class TestSceneChannelControlItem : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); + + [Cached] + private readonly Bindable selected = new Bindable(); + + private static List channels = new List + { + createPublicChannel("#public-channel"), + createPublicChannel("#public-channel-long-name"), + createPrivateChannel("test user", 2), + createPrivateChannel("test user long name", 3), + }; + + private readonly Dictionary channelMap = new Dictionary(); + + private FillFlowContainer flow; + private OsuSpriteText selectedText; + private OsuSpriteText leaveText; + + [SetUp] + public void SetUp() + { + Schedule(() => + { + Children = new Drawable[] + { + selectedText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = -140, + }, + leaveText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = -120, + }, + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(190, 200), + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background6, + }, + flow = new FillFlowContainer + { + Spacing = new Vector2(20), + RelativeSizeAxes = Axes.Both, + }, + }, + }, + }; + + selected.BindValueChanged(change => + { + selectedText.Text = $"Selected Channel: {change.NewValue?.Name ?? "[null]"}"; + }, true); + + foreach (var channel in channels) + { + var item = new ControlItem(channel); + flow.Add(item); + channelMap.Add(channel, item); + item.OnRequestSelect += channel => selected.Value = channel; + item.OnRequestLeave += leaveChannel; + } + }); + } + + [Test] + public void TestVisual() + { + AddStep("Unread Selected", () => + { + if (selected.Value != null) + channelMap[selected.Value].HasUnread = true; + }); + + AddStep("Read Selected", () => + { + if (selected.Value != null) + channelMap[selected.Value].HasUnread = false; + }); + + AddStep("Add Mention Selected", () => + { + if (selected.Value != null) + channelMap[selected.Value].MentionCount++; + }); + + AddStep("Add 98 Mentions Selected", () => + { + if (selected.Value != null) + channelMap[selected.Value].MentionCount += 98; + }); + + AddStep("Clear Mentions Selected", () => + { + if (selected.Value != null) + channelMap[selected.Value].MentionCount = 0; + }); + } + + private void leaveChannel(Channel channel) + { + leaveText.Text = $"OnRequestLeave: {channel.Name}"; + leaveText.FadeIn().Then().FadeOut(1000, Easing.InQuint); + } + + private static Channel createPublicChannel(string name) => + new Channel { Name = name, Type = ChannelType.Public, Id = 1234 }; + + private static Channel createPrivateChannel(string username, int id) + => new Channel(new APIUser { Id = id, Username = username }); + } +} diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs new file mode 100644 index 0000000000..559f75f198 --- /dev/null +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -0,0 +1,167 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; +using osu.Game.Online.Chat; + +namespace osu.Game.Overlays.Chat.ChannelControl +{ + public class ControlItem : OsuClickableContainer + { + public event Action? OnRequestSelect; + public event Action? OnRequestLeave; + + public int MentionCount + { + get => mention?.MentionCount ?? 0; + set + { + if (mention != null) + mention.MentionCount = value; + } + } + + public bool HasUnread + { + get => text?.HasUnread ?? false; + set + { + if (text != null) + text.HasUnread = value; + } + } + + private Box? hoverBox; + private Box? selectBox; + private ControlItemText? text; + private ControlItemMention? mention; + private ControlItemClose? close; + + [Resolved] + private Bindable? selectedChannel { get; set; } + + [Resolved] + private OverlayColourProvider? colourProvider { get; set; } + + private readonly Channel channel; + + public ControlItem(Channel channel) + { + this.channel = channel; + } + + [BackgroundDependencyLoader] + private void load() + { + Height = 30; + RelativeSizeAxes = Axes.X; + + Children = new Drawable[] + { + hoverBox = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider!.Background3, + Alpha = 0f, + }, + selectBox = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider!.Background4, + Alpha = 0f, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = 18, Right = 5 }, + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] + { + createAvatar(), + text = new ControlItemText(channel) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + mention = new ControlItemMention + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Right = 3 }, + }, + close = new ControlItemClose + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Right = 3 }, + } + } + }, + }, + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedChannel?.BindValueChanged(change => + { + if (change.NewValue == channel) + selectBox?.Show(); + else + selectBox?.Hide(); + }, true); + + Action = () => OnRequestSelect?.Invoke(channel); + close!.Action = () => OnRequestLeave?.Invoke(channel); + } + + protected override bool OnHover(HoverEvent e) + { + hoverBox?.Show(); + close?.Show(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hoverBox?.Hide(); + close?.Hide(); + base.OnHoverLost(e); + } + + private Drawable createAvatar() + { + if (channel.Type != ChannelType.PM) + return Drawable.Empty(); + + return new ControlItemAvatar(channel) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }; + } + } +} diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs new file mode 100644 index 0000000000..9192f20cb1 --- /dev/null +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs @@ -0,0 +1,60 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Online.Chat; +using osu.Game.Users.Drawables; +using osuTK; + +namespace osu.Game.Overlays.Chat.ChannelControl +{ + public class ControlItemAvatar : CircularContainer + { + private DrawableAvatar? avatar; + private readonly Channel channel; + + public ControlItemAvatar(Channel channel) + { + this.channel = channel; + } + + [BackgroundDependencyLoader] + private void load() + { + Size = new Vector2(20); + Margin = new MarginPadding { Right = 5 }; + Masking = true; + + Children = new Drawable[] + { + new SpriteIcon + { + Icon = FontAwesome.Solid.At, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = Colour4.Black, + RelativeSizeAxes = Axes.Both, + Alpha = 0.2f, + }, + new DelayedLoadWrapper(avatar = new DrawableAvatar(channel.Users.First()) + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }), + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + avatar!.OnLoadComplete += d => d.FadeInFromZero(300, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemClose.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemClose.cs new file mode 100644 index 0000000000..4b190e18fd --- /dev/null +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemClose.cs @@ -0,0 +1,55 @@ +// 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.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Chat.ChannelControl +{ + public class ControlItemClose : OsuClickableContainer + { + private readonly SpriteIcon icon; + + public ControlItemClose() + { + Alpha = 0f; + Size = new Vector2(20); + Child = icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.75f), + Icon = FontAwesome.Solid.TimesCircle, + RelativeSizeAxes = Axes.Both, + }; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + icon.ScaleTo(0.5f, 1000, Easing.OutQuint); + return base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseUpEvent e) + { + icon.ScaleTo(0.75f, 1000, Easing.OutElastic); + base.OnMouseUp(e); + } + + protected override bool OnHover(HoverEvent e) + { + icon.FadeColour(Color4.Red, 200, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + icon.FadeColour(Color4.White, 200, Easing.OutQuint); + base.OnHoverLost(e); + } + } +} diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs new file mode 100644 index 0000000000..12de154faa --- /dev/null +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -0,0 +1,77 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Overlays.Chat.ChannelControl +{ + public class ControlItemMention : CircularContainer + { + private int mentionCount = 0; + public int MentionCount + { + get => mentionCount; + set + { + if (value == mentionCount) + return; + + mentionCount = value; + updateText(); + } + } + + private OsuSpriteText? countText; + + [Resolved] + private OverlayColourProvider? colourProvider { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + Masking = true; + Size = new Vector2(20, 12); + Alpha = 0f; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider!.Colour1, + }, + countText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Torus.With(size: 11, weight: FontWeight.Bold), + Margin = new MarginPadding { Bottom = 1 }, + Colour = colourProvider.Background5, + }, + }; + + updateText(); + } + + private void updateText() + { + if (mentionCount > 99) + countText!.Text = "99+"; + else + countText!.Text = mentionCount.ToString(); + + if (mentionCount > 0) + this.Show(); + else + this.Hide(); + } + } +} diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs new file mode 100644 index 0000000000..2b8f50e184 --- /dev/null +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs @@ -0,0 +1,71 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; + +namespace osu.Game.Overlays.Chat.ChannelControl +{ + public class ControlItemText : Container + { + public bool HasUnread + { + get => hasUnread; + set + { + if (hasUnread == value) + return; + + hasUnread = value; + updateText(); + } + } + + private bool hasUnread = false; + private OsuSpriteText? text; + + [Resolved] + private OverlayColourProvider? colourProvider { get; set; } + + private readonly Channel channel; + + public ControlItemText(Channel channel) + { + this.channel = channel; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + Child = text = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = channel.Type == ChannelType.Public ? $"# {channel.Name.Substring(1)}" : channel.Name, + Font = OsuFont.Torus.With(size: 17, weight: FontWeight.SemiBold), + Colour = colourProvider!.Light3, + Margin = new MarginPadding { Bottom = 2 }, + RelativeSizeAxes = Axes.X, + Truncate = true, + }; + } + + private void updateText() + { + if (!IsLoaded) + return; + + if (HasUnread) + text!.Colour = Colour4.White; + else + text!.Colour = colourProvider!.Light3; + } + } +} From c0d82dfb41478a97e633a60fe847930bf85bf7ca Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 19:42:55 +0000 Subject: [PATCH 1190/5199] Code quality fixes --- .../Visual/Online/TestSceneChannelControlItem.cs | 4 ++-- osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs | 2 +- .../Chat/ChannelControl/ControlItemMention.cs | 12 +++++------- .../Overlays/Chat/ChannelControl/ControlItemText.cs | 3 ++- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index a1431f696b..64ad924ecb 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly Bindable selected = new Bindable(); - private static List channels = new List + private static readonly List channels = new List { createPublicChannel("#public-channel"), createPublicChannel("#public-channel-long-name"), @@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual.Online var item = new ControlItem(channel); flow.Add(item); channelMap.Add(channel, item); - item.OnRequestSelect += channel => selected.Value = channel; + item.OnRequestSelect += c => selected.Value = c; item.OnRequestLeave += leaveChannel; } }); diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index 559f75f198..e475ac7821 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -95,7 +95,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl }, Content = new[] { - new Drawable[] + new[] { createAvatar(), text = new ControlItemText(channel) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs index 12de154faa..6af2e26af8 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -15,7 +15,8 @@ namespace osu.Game.Overlays.Chat.ChannelControl { public class ControlItemMention : CircularContainer { - private int mentionCount = 0; + private int mentionCount; + public int MentionCount { get => mentionCount; @@ -63,15 +64,12 @@ namespace osu.Game.Overlays.Chat.ChannelControl private void updateText() { - if (mentionCount > 99) - countText!.Text = "99+"; - else - countText!.Text = mentionCount.ToString(); + countText!.Text = MentionCount > 99 ? "99+" : MentionCount.ToString(); if (mentionCount > 0) - this.Show(); + Show(); else - this.Hide(); + Hide(); } } } diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs index 2b8f50e184..bb88d733d4 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs @@ -14,6 +14,8 @@ namespace osu.Game.Overlays.Chat.ChannelControl { public class ControlItemText : Container { + private bool hasUnread; + public bool HasUnread { get => hasUnread; @@ -27,7 +29,6 @@ namespace osu.Game.Overlays.Chat.ChannelControl } } - private bool hasUnread = false; private OsuSpriteText? text; [Resolved] From e9f0ad33efca61ccbbb0a09c14f88615d3e2f819 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 20:06:31 +0000 Subject: [PATCH 1191/5199] Use autosizing for ChannelControlItem test scene --- .../Online/TestSceneChannelControlItem.cs | 63 +++++++++++-------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index 64ad924ecb..7f76dca013 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -45,36 +45,47 @@ namespace osu.Game.Tests.Visual.Online { Schedule(() => { - Children = new Drawable[] + Child = new FillFlowContainer { - selectedText = new OsuSpriteText + Direction = FillDirection.Vertical, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(10), + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Y = -140, - }, - leaveText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Y = -120, - }, - new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(190, 200), - Children = new Drawable[] + selectedText = new OsuSpriteText { - new Box + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + leaveText = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Height = 16, + AlwaysPresent = true, + }, + new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Y, + Width = 190, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background6, - }, - flow = new FillFlowContainer - { - Spacing = new Vector2(20), - RelativeSizeAxes = Axes.Both, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background6, + }, + flow = new FillFlowContainer + { + Direction = FillDirection.Vertical, + Spacing = new Vector2(20), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, }, }, }, From 12472593cc4f2c7b36a8682c7d63d17315e7a9cf Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 20:14:04 +0000 Subject: [PATCH 1192/5199] Mark required dependencies as non-nullable --- .../Overlays/Chat/ChannelControl/ControlItem.cs | 14 +++++++------- .../Chat/ChannelControl/ControlItemAvatar.cs | 3 ++- .../Chat/ChannelControl/ControlItemMention.cs | 4 ++-- .../Chat/ChannelControl/ControlItemText.cs | 8 ++++---- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index e475ac7821..073173f2ff 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -40,6 +40,8 @@ namespace osu.Game.Overlays.Chat.ChannelControl } } + private readonly Channel channel; + private Box? hoverBox; private Box? selectBox; private ControlItemText? text; @@ -47,12 +49,10 @@ namespace osu.Game.Overlays.Chat.ChannelControl private ControlItemClose? close; [Resolved] - private Bindable? selectedChannel { get; set; } + private Bindable selectedChannel { get; set; } = null!; [Resolved] - private OverlayColourProvider? colourProvider { get; set; } - - private readonly Channel channel; + private OverlayColourProvider colourProvider { get; set; } = null!; public ControlItem(Channel channel) { @@ -70,13 +70,13 @@ namespace osu.Game.Overlays.Chat.ChannelControl hoverBox = new Box { RelativeSizeAxes = Axes.Both, - Colour = colourProvider!.Background3, + Colour = colourProvider.Background3, Alpha = 0f, }, selectBox = new Box { RelativeSizeAxes = Axes.Both, - Colour = colourProvider!.Background4, + Colour = colourProvider.Background4, Alpha = 0f, }, new Container @@ -126,7 +126,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl { base.LoadComplete(); - selectedChannel?.BindValueChanged(change => + selectedChannel.BindValueChanged(change => { if (change.NewValue == channel) selectBox?.Show(); diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs index 9192f20cb1..62b893f3bc 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs @@ -16,9 +16,10 @@ namespace osu.Game.Overlays.Chat.ChannelControl { public class ControlItemAvatar : CircularContainer { - private DrawableAvatar? avatar; private readonly Channel channel; + private DrawableAvatar? avatar; + public ControlItemAvatar(Channel channel) { this.channel = channel; diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs index 6af2e26af8..693fb68217 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -33,7 +33,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl private OsuSpriteText? countText; [Resolved] - private OverlayColourProvider? colourProvider { get; set; } + private OverlayColourProvider colourProvider { get; set; } = null!; [BackgroundDependencyLoader] private void load() @@ -47,7 +47,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl new Box { RelativeSizeAxes = Axes.Both, - Colour = colourProvider!.Colour1, + Colour = colourProvider.Colour1, }, countText = new OsuSpriteText { diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs index bb88d733d4..9b0f5011fc 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs @@ -29,12 +29,12 @@ namespace osu.Game.Overlays.Chat.ChannelControl } } + private readonly Channel channel; + private OsuSpriteText? text; [Resolved] - private OverlayColourProvider? colourProvider { get; set; } - - private readonly Channel channel; + private OverlayColourProvider colourProvider { get; set; } = null!; public ControlItemText(Channel channel) { @@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl Origin = Anchor.CentreLeft, Text = channel.Type == ChannelType.Public ? $"# {channel.Name.Substring(1)}" : channel.Name, Font = OsuFont.Torus.With(size: 17, weight: FontWeight.SemiBold), - Colour = colourProvider!.Light3, + Colour = colourProvider.Light3, Margin = new MarginPadding { Bottom = 2 }, RelativeSizeAxes = Axes.X, Truncate = true, From e91af664ef080c250fbfd6997dd5aa1033f502d1 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 20:37:54 +0000 Subject: [PATCH 1193/5199] Adjust ControlItemAvatar placeholder animation and colour --- .../Overlays/Chat/ChannelControl/ControlItemAvatar.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs index 62b893f3bc..bb3109ec1f 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemAvatar.cs @@ -18,6 +18,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl { private readonly Channel channel; + private SpriteIcon? placeholder; private DrawableAvatar? avatar; public ControlItemAvatar(Channel channel) @@ -34,14 +35,14 @@ namespace osu.Game.Overlays.Chat.ChannelControl Children = new Drawable[] { - new SpriteIcon + placeholder = new SpriteIcon { Icon = FontAwesome.Solid.At, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Colour = Colour4.Black, + Colour = Colour4.White, RelativeSizeAxes = Axes.Both, - Alpha = 0.2f, + Alpha = 0.5f, }, new DelayedLoadWrapper(avatar = new DrawableAvatar(channel.Users.First()) { @@ -55,7 +56,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl protected override void LoadComplete() { base.LoadComplete(); - avatar!.OnLoadComplete += d => d.FadeInFromZero(300, Easing.OutQuint); + avatar!.OnLoadComplete += _ => placeholder!.FadeOut(250); } } } From 9621ef9437ca389fde4295e507d8681ee14ee3bb Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 21:10:39 +0000 Subject: [PATCH 1194/5199] Use OsuColour.Red1 for ControlItemClose hover colour --- .../Visual/Online/TestSceneChannelControlItem.cs | 4 ++++ .../Overlays/Chat/ChannelControl/ControlItemClose.cs | 10 +++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index 7f76dca013..9464d244be 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; @@ -23,6 +24,9 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); + [Cached] + private readonly OsuColour osuColour = new OsuColour(); + [Cached] private readonly Bindable selected = new Bindable(); diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemClose.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemClose.cs index 4b190e18fd..4730d7b72d 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemClose.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemClose.cs @@ -1,12 +1,13 @@ // 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.Sprites; using osu.Framework.Input.Events; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; -using osuTK.Graphics; namespace osu.Game.Overlays.Chat.ChannelControl { @@ -14,6 +15,9 @@ namespace osu.Game.Overlays.Chat.ChannelControl { private readonly SpriteIcon icon; + [Resolved] + private OsuColour osuColour { get; set; } = null!; + public ControlItemClose() { Alpha = 0f; @@ -42,13 +46,13 @@ namespace osu.Game.Overlays.Chat.ChannelControl protected override bool OnHover(HoverEvent e) { - icon.FadeColour(Color4.Red, 200, Easing.OutQuint); + icon.FadeColour(osuColour.Red1, 200, Easing.OutQuint); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - icon.FadeColour(Color4.White, 200, Easing.OutQuint); + icon.FadeColour(Colour4.White, 200, Easing.OutQuint); base.OnHoverLost(e); } } From 3aa343b9870dc3487013151889880f282b29cafe Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 21:14:33 +0000 Subject: [PATCH 1195/5199] Use OsuColour.YellowLight for ControlItemMention background --- osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs index 693fb68217..370c435266 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -32,6 +32,9 @@ namespace osu.Game.Overlays.Chat.ChannelControl private OsuSpriteText? countText; + [Resolved] + private OsuColour osuColour { get; set; } = null!; + [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; @@ -47,7 +50,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl new Box { RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Colour1, + Colour = osuColour.YellowLight, }, countText = new OsuSpriteText { From 1f0f6990f096d15a064eda731bd2dbad876ff786 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 21:16:28 +0000 Subject: [PATCH 1196/5199] Use ColourProvider.Content1 for ControlItemText colour --- osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs index 9b0f5011fc..0c91903f6b 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs @@ -64,9 +64,9 @@ namespace osu.Game.Overlays.Chat.ChannelControl return; if (HasUnread) - text!.Colour = Colour4.White; + text!.Colour = colourProvider.Content1; else - text!.Colour = colourProvider!.Light3; + text!.Colour = colourProvider.Light3; } } } From b01a809d551efcb342e59046ae087d46309323fe Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 21:26:33 +0000 Subject: [PATCH 1197/5199] Refactor ControlItemMention to use bindable flow --- .../Online/TestSceneChannelControlItem.cs | 6 +-- .../Chat/ChannelControl/ControlItem.cs | 11 +---- .../Chat/ChannelControl/ControlItemMention.cs | 41 ++++++++----------- 3 files changed, 23 insertions(+), 35 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index 9464d244be..2e5c2cc2cb 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -129,19 +129,19 @@ namespace osu.Game.Tests.Visual.Online AddStep("Add Mention Selected", () => { if (selected.Value != null) - channelMap[selected.Value].MentionCount++; + channelMap[selected.Value].Mentions.Value++; }); AddStep("Add 98 Mentions Selected", () => { if (selected.Value != null) - channelMap[selected.Value].MentionCount += 98; + channelMap[selected.Value].Mentions.Value += 98; }); AddStep("Clear Mentions Selected", () => { if (selected.Value != null) - channelMap[selected.Value].MentionCount = 0; + channelMap[selected.Value].Mentions.Value = 0; }); } diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index 073173f2ff..d88a99c484 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -20,15 +20,8 @@ namespace osu.Game.Overlays.Chat.ChannelControl public event Action? OnRequestSelect; public event Action? OnRequestLeave; - public int MentionCount - { - get => mention?.MentionCount ?? 0; - set - { - if (mention != null) - mention.MentionCount = value; - } - } + [Cached] + public readonly BindableInt Mentions = new BindableInt(); public bool HasUnread { diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs index 370c435266..594a52b8a7 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -4,6 +4,7 @@ #nullable enable using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -15,23 +16,11 @@ namespace osu.Game.Overlays.Chat.ChannelControl { public class ControlItemMention : CircularContainer { - private int mentionCount; - - public int MentionCount - { - get => mentionCount; - set - { - if (value == mentionCount) - return; - - mentionCount = value; - updateText(); - } - } - private OsuSpriteText? countText; + [Resolved] + private BindableInt mentions { get; set; } = null!; + [Resolved] private OsuColour osuColour { get; set; } = null!; @@ -61,18 +50,24 @@ namespace osu.Game.Overlays.Chat.ChannelControl Colour = colourProvider.Background5, }, }; - - updateText(); } - private void updateText() + protected override void LoadComplete() { - countText!.Text = MentionCount > 99 ? "99+" : MentionCount.ToString(); + base.LoadComplete(); - if (mentionCount > 0) - Show(); - else - Hide(); + mentions.BindValueChanged(change => + { + int mentionCount = change.NewValue; + + countText!.Text = mentionCount > 99 ? "99+" : mentionCount.ToString(); + + if (mentionCount > 0) + Show(); + else + Hide(); + }, true); } + } } From 75958bf2702778784caee4c70ed3040170f16a5e Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 21:32:30 +0000 Subject: [PATCH 1198/5199] Refactor ControlItemText to use bindable flow for unread state --- .../Online/TestSceneChannelControlItem.cs | 4 +-- .../Chat/ChannelControl/ControlItem.cs | 11 ++---- .../Chat/ChannelControl/ControlItemText.cs | 35 +++++++------------ 3 files changed, 17 insertions(+), 33 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index 2e5c2cc2cb..9af3b7613b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -117,13 +117,13 @@ namespace osu.Game.Tests.Visual.Online AddStep("Unread Selected", () => { if (selected.Value != null) - channelMap[selected.Value].HasUnread = true; + channelMap[selected.Value].Unread.Value = true; }); AddStep("Read Selected", () => { if (selected.Value != null) - channelMap[selected.Value].HasUnread = false; + channelMap[selected.Value].Unread.Value = false; }); AddStep("Add Mention Selected", () => diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index d88a99c484..4b1bbaec82 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -23,15 +23,8 @@ namespace osu.Game.Overlays.Chat.ChannelControl [Cached] public readonly BindableInt Mentions = new BindableInt(); - public bool HasUnread - { - get => text?.HasUnread ?? false; - set - { - if (text != null) - text.HasUnread = value; - } - } + [Cached] + public readonly BindableBool Unread = new BindableBool(); private readonly Channel channel; diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs index 0c91903f6b..3573c72846 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs @@ -4,6 +4,7 @@ #nullable enable using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; @@ -14,25 +15,13 @@ namespace osu.Game.Overlays.Chat.ChannelControl { public class ControlItemText : Container { - private bool hasUnread; - - public bool HasUnread - { - get => hasUnread; - set - { - if (hasUnread == value) - return; - - hasUnread = value; - updateText(); - } - } - private readonly Channel channel; private OsuSpriteText? text; + [Resolved] + private BindableBool unread { get; set; } = null!; + [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; @@ -58,15 +47,17 @@ namespace osu.Game.Overlays.Chat.ChannelControl }; } - private void updateText() + protected override void LoadComplete() { - if (!IsLoaded) - return; + base.LoadComplete(); - if (HasUnread) - text!.Colour = colourProvider.Content1; - else - text!.Colour = colourProvider.Light3; + unread.BindValueChanged(change => + { + if (change.NewValue) + text!.Colour = colourProvider.Content1; + else + text!.Colour = colourProvider.Light3; + }, true); } } } From ec61b88ec27796836ae380c5e845101e1f06f589 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 21:39:57 +0000 Subject: [PATCH 1199/5199] Adjust ControlItem padding --- osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index 4b1bbaec82..e944bf4c28 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -68,7 +68,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = 18, Right = 5 }, + Padding = new MarginPadding { Left = 18, Right = 10 }, Child = new GridContainer { RelativeSizeAxes = Axes.Both, From 73a0373b4ed0d2de982c84b236dafae6c02ee677 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 21:56:56 +0000 Subject: [PATCH 1200/5199] Code quality fixes --- osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs | 6 ++---- osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs | 1 - osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs | 5 +---- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index e944bf4c28..f2bab64371 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -30,8 +30,6 @@ namespace osu.Game.Overlays.Chat.ChannelControl private Box? hoverBox; private Box? selectBox; - private ControlItemText? text; - private ControlItemMention? mention; private ControlItemClose? close; [Resolved] @@ -84,12 +82,12 @@ namespace osu.Game.Overlays.Chat.ChannelControl new[] { createAvatar(), - text = new ControlItemText(channel) + new ControlItemText(channel) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, - mention = new ControlItemMention + new ControlItemMention { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs index 594a52b8a7..e9172d99ba 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -68,6 +68,5 @@ namespace osu.Game.Overlays.Chat.ChannelControl Hide(); }, true); } - } } diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs index 3573c72846..89845dfef8 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs @@ -53,10 +53,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl unread.BindValueChanged(change => { - if (change.NewValue) - text!.Colour = colourProvider.Content1; - else - text!.Colour = colourProvider.Light3; + text!.Colour = change.NewValue ? colourProvider.Content1 : colourProvider.Light3; }, true); } } From 6628b7c654789619617475f7114c84b35acde86a Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 14 Mar 2022 22:21:18 +0000 Subject: [PATCH 1201/5199] Ensure existing items are expired and cleared in ChannelControlItem test setup --- osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index 9af3b7613b..e496ef60d7 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -49,6 +49,11 @@ namespace osu.Game.Tests.Visual.Online { Schedule(() => { + foreach (var item in channelMap.Values) + item.Expire(); + + channelMap.Clear(); + Child = new FillFlowContainer { Direction = FillDirection.Vertical, From 59d57a44d4c1b7901aab454671ccc9d611379bca Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Mar 2022 09:11:57 +0900 Subject: [PATCH 1202/5199] Prevent incorrect usages by hard-typing ctor type --- osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs | 2 +- osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs | 2 +- osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs | 2 +- osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs | 2 +- .../Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs | 3 ++- .../Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs | 3 ++- .../TestSceneMultiplayerGameplayLeaderboardTeams.cs | 3 ++- 7 files changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 2c183f5d94..3de86c3fb1 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Catch.Scoring { public class CatchScoreProcessor : ScoreProcessor { - public CatchScoreProcessor(Ruleset ruleset) + public CatchScoreProcessor(CatchRuleset ruleset) : base(ruleset) { } diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 0ffee809fc..7251c58eba 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Scoring { internal class ManiaScoreProcessor : ScoreProcessor { - public ManiaScoreProcessor(Ruleset ruleset) + public ManiaScoreProcessor(ManiaRuleset ruleset) : base(ruleset) { } diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index e397fea0ed..8acb1cb59b 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Scoring { public class OsuScoreProcessor : ScoreProcessor { - public OsuScoreProcessor(Ruleset ruleset) + public OsuScoreProcessor(OsuRuleset ruleset) : base(ruleset) { } diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index ddf6587022..fb33ad25d9 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring { internal class TaikoScoreProcessor : ScoreProcessor { - public TaikoScoreProcessor(Ruleset ruleset) + public TaikoScoreProcessor(TaikoRuleset ruleset) : base(ruleset) { } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 0cc1257a07..1c0af829be 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -8,6 +8,7 @@ using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play.HUD; @@ -43,7 +44,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); - var scoreProcessor = new OsuScoreProcessor(Ruleset.Value.CreateInstance()); + var scoreProcessor = new OsuScoreProcessor(new OsuRuleset()); scoreProcessor.ApplyBeatmap(playable); LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(Ruleset.Value, scoreProcessor, clocks.Keys.Select(id => new MultiplayerRoomUser(id)).ToArray()) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 0edc56f5d3..6663180802 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -16,6 +16,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Spectator; using osu.Game.Replays.Legacy; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -64,7 +65,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Children = new Drawable[] { - scoreProcessor = new OsuScoreProcessor(Ruleset.Value.CreateInstance()), + scoreProcessor = new OsuScoreProcessor(new OsuRuleset()), }; scoreProcessor.ApplyBeatmap(playableBeatmap); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs index 60a5785b31..62130ca5be 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs @@ -12,6 +12,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; using osu.Game.Online.Rooms; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.Play.HUD; @@ -75,7 +76,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Children = new Drawable[] { - scoreProcessor = new OsuScoreProcessor(Ruleset.Value.CreateInstance()), + scoreProcessor = new OsuScoreProcessor(new OsuRuleset()), }; scoreProcessor.ApplyBeatmap(playableBeatmap); From 3a6d254d1f87a80a081346353ce8fe71d3a58ee3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Mar 2022 09:15:52 +0900 Subject: [PATCH 1203/5199] Add safeguards around incorrect ruleset sources --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index b982c6ece2..0c585fac98 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -258,6 +258,9 @@ namespace osu.Game.Rulesets.Scoring /// The total score in the given . public double ComputeFinalScore(ScoringMode mode, ScoreInfo scoreInfo) { + if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) + throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\"."); + extractFromStatistics(ruleset, scoreInfo.Statistics, out double extractedBaseScore, @@ -282,6 +285,9 @@ namespace osu.Game.Rulesets.Scoring /// The total score in the given . public double ComputePartialScore(ScoringMode mode, ScoreInfo scoreInfo) { + if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) + throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\"."); + if (!beatmapApplied) throw new InvalidOperationException($"Cannot compute partial score without calling {nameof(ApplyBeatmap)}."); @@ -311,6 +317,9 @@ namespace osu.Game.Rulesets.Scoring /// The total score in the given . public double ComputeFinalLegacyScore(ScoringMode mode, ScoreInfo scoreInfo, int maxAchievableCombo) { + if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) + throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\"."); + double accuracyRatio = scoreInfo.Accuracy; double comboRatio = maxAchievableCombo > 0 ? (double)scoreInfo.MaxCombo / maxAchievableCombo : 1; From ca6256049558e15a9266b0553afd91573ab42235 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Mar 2022 09:16:39 +0900 Subject: [PATCH 1204/5199] Resolve inspections --- osu.Game/Skinning/Editor/SkinComponentToolbox.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index a9dcf705a9..2781f1958f 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -184,8 +184,8 @@ namespace osu.Game.Skinning.Editor public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new NotImplementedException(); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => throw new NotImplementedException(); - public override string Description { get; } - public override string ShortName { get; } + public override string Description => string.Empty; + public override string ShortName => string.Empty; } } } From faf145742fc67bd22be37f9a3604c97a58a226bc Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Mar 2022 09:21:09 +0900 Subject: [PATCH 1205/5199] Fix incorrect provided ruleset in test --- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 5c01950bea..7ecd509193 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -10,7 +10,6 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; @@ -28,7 +27,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [SetUp] public void SetUp() { - scoreProcessor = new ScoreProcessor(new OsuRuleset()); + scoreProcessor = new ScoreProcessor(new TestRuleset()); beatmap = new TestBeatmap(new RulesetInfo()) { HitObjects = new List From 4ff6879b85baed3b42560431b7c4962b7804c0d0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Mar 2022 11:30:57 +0900 Subject: [PATCH 1206/5199] Fix incorrect copied room end dates --- osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index fb8647284f..a2d3b7f4fc 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -334,6 +334,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge // ID must be unset as we use this as a marker for whether this is a client-side (not-yet-created) room or not. r.RoomID.Value = null; + // Null out dates because end date is not supported client-side and the settings overlay will populate a duration. + r.EndDate.Value = null; + r.Duration.Value = null; + Open(r); joiningRoomOperation?.Dispose(); From daac93349864ee975aab450fd6b7d4b48e77bac6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Mar 2022 12:34:58 +0900 Subject: [PATCH 1207/5199] Remove unnecessary ctor arguments --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs | 4 ++-- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs | 4 ++-- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs | 4 ++-- .../Skinning/TestSceneDrawableTaikoMascot.cs | 2 +- osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs | 4 ++-- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- .../Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs | 3 +-- .../Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs | 3 +-- .../TestSceneMultiplayerGameplayLeaderboardTeams.cs | 3 +-- 12 files changed, 16 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 45198fa0ca..3a7efb6263 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Catch { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableCatchRuleset(this, beatmap, mods); - public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this); + public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap, this); diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 3de86c3fb1..51b1ccaaba 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -7,8 +7,8 @@ namespace osu.Game.Rulesets.Catch.Scoring { public class CatchScoreProcessor : ScoreProcessor { - public CatchScoreProcessor(CatchRuleset ruleset) - : base(ruleset) + public CatchScoreProcessor() + : base(new CatchRuleset()) { } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 92c77f405b..d04ef69dd2 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableManiaRuleset(this, beatmap, mods); - public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); + public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(); public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new ManiaHealthProcessor(drainStartTime, 0.5); diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 7251c58eba..02d62a090b 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -7,8 +7,8 @@ namespace osu.Game.Rulesets.Mania.Scoring { internal class ManiaScoreProcessor : ScoreProcessor { - public ManiaScoreProcessor(ManiaRuleset ruleset) - : base(ruleset) + public ManiaScoreProcessor() + : base(new ManiaRuleset()) { } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 87ffe8983f..92b90339f9 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableOsuRuleset(this, beatmap, mods); - public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(this); + public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap, this); diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 8acb1cb59b..ab0c0850dc 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -11,8 +11,8 @@ namespace osu.Game.Rulesets.Osu.Scoring { public class OsuScoreProcessor : ScoreProcessor { - public OsuScoreProcessor(OsuRuleset ruleset) - : base(ruleset) + public OsuScoreProcessor() + : base(new OsuRuleset()) { } diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs index b7e1c9586a..920a7cd1a1 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning [SetUp] public void SetUp() { - scoreProcessor = new TaikoScoreProcessor(new TaikoRuleset()); + scoreProcessor = new TaikoScoreProcessor(); } [Test] diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index fb33ad25d9..bacc22714e 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -7,8 +7,8 @@ namespace osu.Game.Rulesets.Taiko.Scoring { internal class TaikoScoreProcessor : ScoreProcessor { - public TaikoScoreProcessor(TaikoRuleset ruleset) - : base(ruleset) + public TaikoScoreProcessor() + : base(new TaikoRuleset()) { } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index a3a2858bd5..8934b64ca5 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableTaikoRuleset(this, beatmap, mods); - public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); + public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(); public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new TaikoHealthProcessor(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 1c0af829be..f57a54d84c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -8,7 +8,6 @@ using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play.HUD; @@ -44,7 +43,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); - var scoreProcessor = new OsuScoreProcessor(new OsuRuleset()); + var scoreProcessor = new OsuScoreProcessor(); scoreProcessor.ApplyBeatmap(playable); LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(Ruleset.Value, scoreProcessor, clocks.Keys.Select(id => new MultiplayerRoomUser(id)).ToArray()) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 6663180802..bcd4474876 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -16,7 +16,6 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Spectator; using osu.Game.Replays.Legacy; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -65,7 +64,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Children = new Drawable[] { - scoreProcessor = new OsuScoreProcessor(new OsuRuleset()), + scoreProcessor = new OsuScoreProcessor(), }; scoreProcessor.ApplyBeatmap(playableBeatmap); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs index 62130ca5be..7f5aced925 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs @@ -12,7 +12,6 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; using osu.Game.Online.Rooms; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.Play.HUD; @@ -76,7 +75,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Children = new Drawable[] { - scoreProcessor = new OsuScoreProcessor(new OsuRuleset()), + scoreProcessor = new OsuScoreProcessor(), }; scoreProcessor.ApplyBeatmap(playableBeatmap); From 523f668c8cae5211cab71fcc711d34aac70fdd94 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Mar 2022 12:37:39 +0900 Subject: [PATCH 1208/5199] Remove unnecessary ctor argument --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- .../Difficulty/CatchPerformanceCalculator.cs | 4 ++-- .../Difficulty/ManiaPerformanceCalculator.cs | 4 ++-- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 4 ++-- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- .../Difficulty/TaikoPerformanceCalculator.cs | 4 ++-- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 3a7efb6263..80b9436b2c 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Catch public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new CatchLegacySkinTransformer(skin); - public override PerformanceCalculator CreatePerformanceCalculator() => new CatchPerformanceCalculator(this); + public override PerformanceCalculator CreatePerformanceCalculator() => new CatchPerformanceCalculator(); public int LegacyID => 2; diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index 6fefb2e5bb..b30b85be2d 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -19,8 +19,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty private int tinyTicksMissed; private int misses; - public CatchPerformanceCalculator(Ruleset ruleset) - : base(ruleset) + public CatchPerformanceCalculator() + : base(new CatchRuleset()) { } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index 1eaf45e54a..b347cc9ae2 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -23,8 +23,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty private int countMeh; private int countMiss; - public ManiaPerformanceCalculator(Ruleset ruleset) - : base(ruleset) + public ManiaPerformanceCalculator() + : base(new ManiaRuleset()) { } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index d04ef69dd2..bd6a67bf67 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Mania public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this); - public override PerformanceCalculator CreatePerformanceCalculator() => new ManiaPerformanceCalculator(this); + public override PerformanceCalculator CreatePerformanceCalculator() => new ManiaPerformanceCalculator(); public const string SHORT_NAME = "mania"; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 5644b2009d..a93a1641a1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -22,8 +22,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double effectiveMissCount; - public OsuPerformanceCalculator(Ruleset ruleset) - : base(ruleset) + public OsuPerformanceCalculator() + : base(new OsuRuleset()) { } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 92b90339f9..2fdf42fca1 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -213,7 +213,7 @@ namespace osu.Game.Rulesets.Osu public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new OsuDifficultyCalculator(RulesetInfo, beatmap); - public override PerformanceCalculator CreatePerformanceCalculator() => new OsuPerformanceCalculator(this); + public override PerformanceCalculator CreatePerformanceCalculator() => new OsuPerformanceCalculator(); public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this); diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 9e73390fad..a8122551ff 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -19,8 +19,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private int countMeh; private int countMiss; - public TaikoPerformanceCalculator(Ruleset ruleset) - : base(ruleset) + public TaikoPerformanceCalculator() + : base(new TaikoRuleset()) { } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 8934b64ca5..615fbf093f 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Taiko public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new TaikoDifficultyCalculator(RulesetInfo, beatmap); - public override PerformanceCalculator CreatePerformanceCalculator() => new TaikoPerformanceCalculator(this); + public override PerformanceCalculator CreatePerformanceCalculator() => new TaikoPerformanceCalculator(); public int LegacyID => 1; From 6d5692fcecc3e4c88b0bd7395b7b5a6258821885 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 14:25:05 +0900 Subject: [PATCH 1209/5199] Fix typo in setting name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Skinning/Components/BigBlackBox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Components/BigBlackBox.cs b/osu.Game/Skinning/Components/BigBlackBox.cs index 8e57143a12..373e6467e8 100644 --- a/osu.Game/Skinning/Components/BigBlackBox.cs +++ b/osu.Game/Skinning/Components/BigBlackBox.cs @@ -23,7 +23,7 @@ namespace osu.Game.Skinning.Components { public bool UsesFixedAnchor { get; set; } - [SettingSource("Spining text", "Whether the big text should spin")] + [SettingSource("Spinning text", "Whether the big text should spin")] public Bindable TextSpin { get; } = new BindableBool(); [SettingSource("Alpha", "The alpha value of this box")] From 1814a325d8aef692bb174adf9266710f87b445ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 14:53:50 +0900 Subject: [PATCH 1210/5199] Move `GetSettingUnderlyingValue` to a `SettingSource` extension method --- .../Configuration/SettingSourceAttribute.cs | 34 ++++++++++++++++ osu.Game/Online/API/APIMod.cs | 9 ++--- .../API/ModSettingsDictionaryFormatter.cs | 4 +- osu.Game/Rulesets/Mods/Mod.cs | 8 ++-- osu.Game/Screens/Play/HUD/SkinnableInfo.cs | 3 +- osu.Game/Utils/ModUtils.cs | 39 +------------------ 6 files changed, 47 insertions(+), 50 deletions(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 5db502804d..4111a67b24 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using JetBrains.Annotations; @@ -170,6 +171,39 @@ namespace osu.Game.Configuration private static readonly ConcurrentDictionary property_info_cache = new ConcurrentDictionary(); + /// + /// Returns the underlying value of the given mod setting object. + /// Can be used for serialization and equality comparison purposes. + /// + /// A bindable. + public static object GetUnderlyingSettingValue(this object setting) + { + switch (setting) + { + case Bindable d: + return d.Value; + + case Bindable i: + return i.Value; + + case Bindable f: + return f.Value; + + case Bindable b: + return b.Value; + + case IBindable u: + // An unknown (e.g. enum) generic type. + var valueMethod = u.GetType().GetProperty(nameof(IBindable.Value)); + Debug.Assert(valueMethod != null); + return valueMethod.GetValue(u); + + default: + // fall back for non-bindable cases. + return setting; + } + } + public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetSettingsSourceProperties(this object obj) { var type = obj.GetType(); diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index 44b1c460d5..524f7b7108 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -12,7 +12,6 @@ using osu.Framework.Logging; using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Utils; namespace osu.Game.Online.API { @@ -43,7 +42,7 @@ namespace osu.Game.Online.API var bindable = (IBindable)property.GetValue(mod); if (!bindable.IsDefault) - Settings.Add(property.Name.Underscore(), ModUtils.GetSettingUnderlyingValue(bindable)); + Settings.Add(property.Name.Underscore(), bindable.GetUnderlyingSettingValue()); } } @@ -93,13 +92,13 @@ namespace osu.Game.Online.API public bool Equals(KeyValuePair x, KeyValuePair y) { - object xValue = ModUtils.GetSettingUnderlyingValue(x.Value); - object yValue = ModUtils.GetSettingUnderlyingValue(y.Value); + object xValue = x.Value.GetUnderlyingSettingValue(); + object yValue = y.Value.GetUnderlyingSettingValue(); return x.Key == y.Key && EqualityComparer.Default.Equals(xValue, yValue); } - public int GetHashCode(KeyValuePair obj) => HashCode.Combine(obj.Key, ModUtils.GetSettingUnderlyingValue(obj.Value)); + public int GetHashCode(KeyValuePair obj) => HashCode.Combine(obj.Key, obj.Value.GetUnderlyingSettingValue()); } } } diff --git a/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs b/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs index 81ecc74ddb..a7c63c17f9 100644 --- a/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs +++ b/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Text; using MessagePack; using MessagePack.Formatters; -using osu.Game.Utils; +using osu.Game.Configuration; namespace osu.Game.Online.API { @@ -23,7 +23,7 @@ namespace osu.Game.Online.API var stringBytes = new ReadOnlySequence(Encoding.UTF8.GetBytes(kvp.Key)); writer.WriteString(in stringBytes); - primitiveFormatter.Serialize(ref writer, ModUtils.GetSettingUnderlyingValue(kvp.Value), options); + primitiveFormatter.Serialize(ref writer, kvp.Value.GetUnderlyingSettingValue(), options); } } diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 7136795461..b2d4be54ce 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Mods hashCode.Add(GetType()); foreach (var setting in Settings) - hashCode.Add(ModUtils.GetSettingUnderlyingValue(setting)); + hashCode.Add(setting.GetUnderlyingSettingValue()); return hashCode.ToHashCode(); } @@ -208,13 +208,13 @@ namespace osu.Game.Rulesets.Mods public bool Equals(IBindable x, IBindable y) { - object xValue = x == null ? null : ModUtils.GetSettingUnderlyingValue(x); - object yValue = y == null ? null : ModUtils.GetSettingUnderlyingValue(y); + object xValue = x?.GetUnderlyingSettingValue(); + object yValue = y?.GetUnderlyingSettingValue(); return EqualityComparer.Default.Equals(xValue, yValue); } - public int GetHashCode(IBindable obj) => ModUtils.GetSettingUnderlyingValue(obj).GetHashCode(); + public int GetHashCode(IBindable obj) => obj.GetUnderlyingSettingValue().GetHashCode(); } } } diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index 5a51e7f455..95395f8181 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Configuration; using osu.Game.Extensions; using osu.Game.Skinning; -using osu.Game.Utils; using osuTK; namespace osu.Game.Screens.Play.HUD @@ -69,7 +68,7 @@ namespace osu.Game.Screens.Play.HUD var bindable = (IBindable)property.GetValue(component); if (!bindable.IsDefault) - Settings.Add(property.Name.Underscore(), ModUtils.GetSettingUnderlyingValue(bindable)); + Settings.Add(property.Name.Underscore(), bindable.GetUnderlyingSettingValue()); } if (component is Container container) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index cdca277dd8..d5ea74c404 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -1,18 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; -using osu.Framework.Bindables; using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -#nullable enable - namespace osu.Game.Utils { /// @@ -154,39 +152,6 @@ namespace osu.Game.Utils yield return mod; } - /// - /// Returns the underlying value of the given mod setting object. - /// Used in for serialization and equality comparison purposes. - /// - /// The mod setting. - public static object GetSettingUnderlyingValue(object setting) - { - switch (setting) - { - case Bindable d: - return d.Value; - - case Bindable i: - return i.Value; - - case Bindable f: - return f.Value; - - case Bindable b: - return b.Value; - - case IBindable u: - // A mod with unknown (e.g. enum) generic type. - var valueMethod = u.GetType().GetProperty(nameof(IBindable.Value)); - Debug.Assert(valueMethod != null); - return valueMethod.GetValue(u); - - default: - // fall back for non-bindable cases. - return setting; - } - } - /// /// Verifies all proposed mods are valid for a given ruleset and returns instantiated s for further processing. /// From 2b02a6555b2616dceadb941d0b5f01473547ef9b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 15:40:26 +0900 Subject: [PATCH 1211/5199] Remove current screen check from skin editor changes --- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 73af2591c8..abd8272633 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -132,9 +132,6 @@ namespace osu.Game.Skinning.Editor { Debug.Assert(skinEditor != null); - if (!target.IsCurrentScreen()) - return; - if (!target.IsLoaded) { Scheduler.AddOnce(setTarget, target); From 9e476ced63eba9e4ad3e7e2f62d74faf67f079ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 16:35:46 +0900 Subject: [PATCH 1212/5199] Add `EditorSidebar` component --- .../UserInterface/TestSceneEditorSidebar.cs | 97 +++++++++++++++++++ .../Screens/Edit/Components/EditorSidebar.cs | 52 ++++++++++ .../Edit/Components/EditorSidebarSection.cs | 73 ++++++++++++++ 3 files changed, 222 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneEditorSidebar.cs create mode 100644 osu.Game/Screens/Edit/Components/EditorSidebar.cs create mode 100644 osu.Game/Screens/Edit/Components/EditorSidebarSection.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneEditorSidebar.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneEditorSidebar.cs new file mode 100644 index 0000000000..7e2b5e0bad --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneEditorSidebar.cs @@ -0,0 +1,97 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Overlays; +using osu.Game.Screens.Edit.Components; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneEditorSidebar : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + + [Test] + public void TestSidebars() + { + AddStep("Add sidebars", () => + { + Children = new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] + { + new EditorSidebar + { + Children = new[] + { + new EditorSidebarSection("Section 1") + { + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + Spacing = new Vector2(3), + ChildrenEnumerable = Enumerable.Range(0, 10).Select(_ => new Box + { + Colour = Color4.White, + Size = new Vector2(32), + }) + }, + }, + new EditorSidebarSection("Section 2") + { + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + Spacing = new Vector2(3), + ChildrenEnumerable = Enumerable.Range(0, 400).Select(_ => new Box + { + Colour = Color4.Gray, + Size = new Vector2(32), + }) + }, + }, + }, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + }, + new EditorSidebar + { + Children = new[] + { + new EditorSidebarSection("Section 1"), + new EditorSidebarSection("Section 2"), + }, + }, + } + } + } + }; + }); + } + } +} diff --git a/osu.Game/Screens/Edit/Components/EditorSidebar.cs b/osu.Game/Screens/Edit/Components/EditorSidebar.cs new file mode 100644 index 0000000000..cd7ef98401 --- /dev/null +++ b/osu.Game/Screens/Edit/Components/EditorSidebar.cs @@ -0,0 +1,52 @@ +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; +using osu.Game.Overlays; + +namespace osu.Game.Screens.Edit.Components +{ + /// + /// A sidebar area that can be attached to the left or right edge of the screen. + /// Houses scrolling sectionised content. + /// + internal class EditorSidebar : Container + { + private readonly Box background; + + protected override Container Content { get; } + + public EditorSidebar() + { + Width = 250; + RelativeSizeAxes = Axes.Y; + + InternalChildren = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new OsuScrollContainer + { + Padding = new MarginPadding { Left = 20 }, + ScrollbarOverlapsContent = false, + RelativeSizeAxes = Axes.Both, + Child = Content = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }, + } + }; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + background.Colour = colourProvider.Background5; + } + } +} diff --git a/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs b/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs new file mode 100644 index 0000000000..5c000471a6 --- /dev/null +++ b/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs @@ -0,0 +1,73 @@ +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Screens.Edit.Components +{ + public class EditorSidebarSection : Container + { + protected override Container Content { get; } + + public EditorSidebarSection(LocalisableString sectionName) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new SectionHeader(sectionName), + Content = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }, + } + }; + } + + public class SectionHeader : CompositeDrawable + { + private readonly LocalisableString text; + + public SectionHeader(LocalisableString text) + { + this.text = text; + + Margin = new MarginPadding { Vertical = 10, Horizontal = 5 }; + + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + InternalChildren = new Drawable[] + { + new OsuSpriteText + { + Text = text, + Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold), + }, + new Circle + { + Y = 18, + Colour = colourProvider.Highlight1, + Size = new Vector2(28, 2), + } + }; + } + } + } +} \ No newline at end of file From 4ab5d6e3f0b76ad04067fdfb274b188b2c29c0f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 16:46:57 +0900 Subject: [PATCH 1213/5199] Remove unnecessary `FillFlowContainer` from section --- osu.Game/Screens/Edit/Components/EditorSidebarSection.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs b/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs index 5c000471a6..319ad82b79 100644 --- a/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs +++ b/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs @@ -27,11 +27,10 @@ namespace osu.Game.Screens.Edit.Components Children = new Drawable[] { new SectionHeader(sectionName), - Content = new FillFlowContainer + Content = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, }, } }; @@ -70,4 +69,4 @@ namespace osu.Game.Screens.Edit.Components } } } -} \ No newline at end of file +} From a0a033520f17b765f91a9a09d346a0cbc115c2b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 16:48:14 +0900 Subject: [PATCH 1214/5199] Rider no add licence headers --- osu.Game/Screens/Edit/Components/EditorSidebar.cs | 3 +++ osu.Game/Screens/Edit/Components/EditorSidebarSection.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game/Screens/Edit/Components/EditorSidebar.cs b/osu.Game/Screens/Edit/Components/EditorSidebar.cs index cd7ef98401..4edcef41b1 100644 --- a/osu.Game/Screens/Edit/Components/EditorSidebar.cs +++ b/osu.Game/Screens/Edit/Components/EditorSidebar.cs @@ -1,3 +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 osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs b/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs index 319ad82b79..d403694bde 100644 --- a/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs +++ b/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs @@ -1,3 +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 osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; From c56df80106c50e4281aa8a28291c852b99cafecc Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 15 Mar 2022 20:38:04 +0000 Subject: [PATCH 1215/5199] Fix `PlaylistResultsScreen` test failures As seen: https://github.com/ppy/osu/pull/17252/checks?check_run_id=5545717506 The `PlaylistsResultsScreen` takes a lease on the `Beatmap` bindable when entered. During `SetUp`, the `Beatmap` bindable is reassigned but fails in cases where an existing test instance of the screen hasn't been exited yet. This fix moves the assignment into the `SetUpSteps` function after `base.SetUpSteps` is called which will exit the existing screens first. --- .../TestScenePlaylistsResultsScreen.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 67894bab38..82cca1f61e 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -53,12 +53,23 @@ namespace osu.Game.Tests.Visual.Playlists userScore.Statistics = new Dictionary(); bindHandler(); - - // beatmap is required to be an actual beatmap so the scores can get their scores correctly calculated for standardised scoring. - // else the tests that rely on ordering will fall over. - Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); }); + [SetUpSteps] + public override void SetUpSteps() + { + base.SetUpSteps(); + + // Existing test instances of the results screen hold a leased bindable of the beatmap, + // so wait for those screens to be cleaned up by the base SetUpSteps before re-assigning + AddStep("Create Working Beatmap", () => + { + // Beatmap is required to be an actual beatmap so the scores can get their scores correctly calculated for standardised scoring. + // else the tests that rely on ordering will fall over. + Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); + }); + } + [Test] public void TestShowWithUserScore() { From 1c79083f96dfb6bd9eb848caf02f180f8ff761da Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 15 Mar 2022 22:15:35 +0000 Subject: [PATCH 1216/5199] Move all `PlaylistResultScreen` test state initialisation into `SetUpSteps` --- .../TestScenePlaylistsResultsScreen.cs | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 82cca1f61e..ee9a0e263b 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -40,32 +40,30 @@ namespace osu.Game.Tests.Visual.Playlists private int totalCount; private ScoreInfo userScore; - [SetUp] - public void Setup() => Schedule(() => - { - lowestScoreId = 1; - highestScoreId = 1; - requestComplete = false; - totalCount = 0; - - userScore = TestResources.CreateTestScoreInfo(); - userScore.TotalScore = 0; - userScore.Statistics = new Dictionary(); - - bindHandler(); - }); - [SetUpSteps] public override void SetUpSteps() { base.SetUpSteps(); - // Existing test instances of the results screen hold a leased bindable of the beatmap, - // so wait for those screens to be cleaned up by the base SetUpSteps before re-assigning - AddStep("Create Working Beatmap", () => + // Previous test instances of the results screen may still exist at this point so wait for + // those screens to be cleaned up by the base SetUpSteps before re-initialising test state. + // The the screen also holds a leased Beatmap bindable so reassigning it must happen after + // the screen as been exited. + AddStep("initialise user scores and beatmap", () => { - // Beatmap is required to be an actual beatmap so the scores can get their scores correctly calculated for standardised scoring. - // else the tests that rely on ordering will fall over. + lowestScoreId = 1; + highestScoreId = 1; + requestComplete = false; + totalCount = 0; + + userScore = TestResources.CreateTestScoreInfo(); + userScore.TotalScore = 0; + userScore.Statistics = new Dictionary(); + + bindHandler(); + + // Beatmap is required to be an actual beatmap so the scores can get their scores correctly + // calculated for standardised scoring, else the tests that rely on ordering will fall over. Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); }); } From 7621e779fa8667110e1b477c5fba3481627ea7f1 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 15 Mar 2022 22:19:58 +0000 Subject: [PATCH 1217/5199] Move `ControlItem` Action assignments into BDL --- osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index f2bab64371..d886e8b45a 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -98,12 +98,15 @@ namespace osu.Game.Overlays.Chat.ChannelControl Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Margin = new MarginPadding { Right = 3 }, + Action = () => OnRequestLeave?.Invoke(channel), } } }, }, }, }; + + Action = () => OnRequestSelect?.Invoke(channel); } protected override void LoadComplete() @@ -117,9 +120,6 @@ namespace osu.Game.Overlays.Chat.ChannelControl else selectBox?.Hide(); }, true); - - Action = () => OnRequestSelect?.Invoke(channel); - close!.Action = () => OnRequestLeave?.Invoke(channel); } protected override bool OnHover(HoverEvent e) From 481b8fe80bc043b6a70ad1b799fefd94ba58af06 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 15 Mar 2022 22:22:32 +0000 Subject: [PATCH 1218/5199] Use `Orange1` for `ControlItemMention` background colour --- osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs index e9172d99ba..5118386977 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl new Box { RelativeSizeAxes = Axes.Both, - Colour = osuColour.YellowLight, + Colour = osuColour.Orange1, }, countText = new OsuSpriteText { From 49b74d78670d85ef7a3b159b43b9504c5ff7156a Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 15 Mar 2022 22:33:36 +0000 Subject: [PATCH 1219/5199] Use `BindTarget` instead of caching for `ControlItem` mentions bindable flow --- osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs | 2 +- .../Overlays/Chat/ChannelControl/ControlItemMention.cs | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index d886e8b45a..d73ebed817 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -20,7 +20,6 @@ namespace osu.Game.Overlays.Chat.ChannelControl public event Action? OnRequestSelect; public event Action? OnRequestLeave; - [Cached] public readonly BindableInt Mentions = new BindableInt(); [Cached] @@ -92,6 +91,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Margin = new MarginPadding { Right = 3 }, + Mentions = { BindTarget = Mentions }, }, close = new ControlItemClose { diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs index 5118386977..beb2713c92 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -16,10 +16,9 @@ namespace osu.Game.Overlays.Chat.ChannelControl { public class ControlItemMention : CircularContainer { - private OsuSpriteText? countText; + public readonly BindableInt Mentions = new BindableInt(); - [Resolved] - private BindableInt mentions { get; set; } = null!; + private OsuSpriteText? countText; [Resolved] private OsuColour osuColour { get; set; } = null!; @@ -56,7 +55,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl { base.LoadComplete(); - mentions.BindValueChanged(change => + Mentions.BindValueChanged(change => { int mentionCount = change.NewValue; From e38d9eafa053a4ddc0e374cbead1063a3f194d8c Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 15 Mar 2022 22:37:15 +0000 Subject: [PATCH 1220/5199] Use `BindTarget` instead of caching for `ControlItem` unread flow --- osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs | 2 +- osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index d73ebed817..cc00550965 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -22,7 +22,6 @@ namespace osu.Game.Overlays.Chat.ChannelControl public readonly BindableInt Mentions = new BindableInt(); - [Cached] public readonly BindableBool Unread = new BindableBool(); private readonly Channel channel; @@ -85,6 +84,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, + Unread = { BindTarget = Unread }, }, new ControlItemMention { diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs index 89845dfef8..490edb5d28 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemText.cs @@ -15,13 +15,12 @@ namespace osu.Game.Overlays.Chat.ChannelControl { public class ControlItemText : Container { + public readonly BindableBool Unread = new BindableBool(); + private readonly Channel channel; private OsuSpriteText? text; - [Resolved] - private BindableBool unread { get; set; } = null!; - [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; @@ -51,7 +50,7 @@ namespace osu.Game.Overlays.Chat.ChannelControl { base.LoadComplete(); - unread.BindValueChanged(change => + Unread.BindValueChanged(change => { text!.Colour = change.NewValue ? colourProvider.Content1 : colourProvider.Light3; }, true); From ba1642a680d34ffc5c1f26953735965031542f22 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Mar 2022 17:19:08 +0900 Subject: [PATCH 1221/5199] Allow section headers to wrap --- .../UserInterface/TestSceneEditorSidebar.cs | 2 +- .../Edit/Components/EditorSidebarSection.cs | 32 ++++++++++++------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneEditorSidebar.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneEditorSidebar.cs index 7e2b5e0bad..f2f475e063 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneEditorSidebar.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneEditorSidebar.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.UserInterface }) }, }, - new EditorSidebarSection("Section 2") + new EditorSidebarSection("Section with a really long section header") { Child = new FillFlowContainer { diff --git a/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs b/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs index d403694bde..3871720562 100644 --- a/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs +++ b/osu.Game/Screens/Edit/Components/EditorSidebarSection.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.Containers; using osu.Game.Overlays; using osuTK; @@ -49,24 +49,32 @@ namespace osu.Game.Screens.Edit.Components Margin = new MarginPadding { Vertical = 10, Horizontal = 5 }; - AutoSizeAxes = Axes.Both; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; } [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - InternalChildren = new Drawable[] + InternalChild = new FillFlowContainer { - new OsuSpriteText + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(2), + Children = new Drawable[] { - Text = text, - Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold), - }, - new Circle - { - Y = 18, - Colour = colourProvider.Highlight1, - Size = new Vector2(28, 2), + new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold)) + { + Text = text, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + new Circle + { + Colour = colourProvider.Highlight1, + Size = new Vector2(28, 2), + } } }; } From 1025e1939ba90b96595c3ab0d9fc5a86a3f0527a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 16 Mar 2022 11:54:18 +0300 Subject: [PATCH 1222/5199] Disable "Adaptive Speed" mod in multiplayer --- .../OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index e30ec36e9c..5ccf89e703 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -117,6 +117,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); + protected override bool IsValidMod(Mod mod) => base.IsValidMod(mod) && !(mod is ModAdaptiveSpeed); + protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && !(mod is ModTimeRamp) && !(mod is ModRateAdjust); } } From 603527d72d09eaf0e727cf075e98fb9cdc78d51c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Mar 2022 18:38:01 +0900 Subject: [PATCH 1223/5199] Fix potential crash when highlighting chat messages Test failed locally in `TestPublicChannelMention`. This test seems to specify that the same message may arrive twice with the same ID, so rather than overthinking this one I propose we just use `FirstOrDefault`. ```csharp TearDown : System.AggregateException : One or more errors occurred. (Sequence contains more than one matching element) ----> System.InvalidOperationException : Sequence contains more than one matching element --TearDown at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) at osu.Framework.Extensions.TaskExtensions.WaitSafely(Task task) at osu.Framework.Testing.TestScene.checkForErrors() --InvalidOperationException at System.Linq.ThrowHelper.ThrowMoreThanOneMatchException() at System.Linq.Enumerable.TryGetSingle[TSource](IEnumerable`1 source, Func`2 predicate, Boolean& found) at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source, Func`2 predicate) at osu.Game.Overlays.Chat.DrawableChannel.b__14_0() in /Users/dean/Projects/osu/osu.Game/Overlays/Chat/DrawableChannel.cs:line 102 at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal() ``` --- osu.Game/Overlays/Chat/DrawableChannel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 632517aa31..161fe1d5be 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -99,7 +99,7 @@ namespace osu.Game.Overlays.Chat if (highlightedMessage.Value == null) return; - var chatLine = chatLines.SingleOrDefault(c => c.Message.Equals(highlightedMessage.Value)); + var chatLine = chatLines.FirstOrDefault(c => c.Message.Equals(highlightedMessage.Value)); if (chatLine == null) return; From 2452d84e98fab4a975caef4af19fac619143047d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Mar 2022 18:44:30 +0900 Subject: [PATCH 1224/5199] Add missing `Schedule` call to allow individual tests from `TestSceneMessageNotifier` to pass --- osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs index 175d2ea36b..2c253650d5 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Online private int messageIdCounter; [SetUp] - public void Setup() + public void Setup() => Schedule(() => { if (API is DummyAPIAccess daa) { @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Online testContainer.ChatOverlay.Show(); }); - } + }); private bool dummyAPIHandleRequest(APIRequest request) { From 99e3161cf0a40e6e6bfc70d201a967df29f2bcf2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 18:00:32 +0900 Subject: [PATCH 1225/5199] Fix `SkinEditor`'s initial target not being a `Screen` --- osu.Game/Skinning/Editor/SkinEditor.cs | 14 +++++++++----- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 18 ++++++++++++------ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index ef26682c03..19c39d23d5 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -49,16 +48,20 @@ namespace osu.Game.Skinning.Editor private EditorToolboxGroup settingsToolbox; + public SkinEditor() + { + } + public SkinEditor(Drawable targetScreen) { - RelativeSizeAxes = Axes.Both; - UpdateTargetScreen(targetScreen); } [BackgroundDependencyLoader] private void load() { + RelativeSizeAxes = Axes.Both; + InternalChild = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, @@ -155,7 +158,7 @@ namespace osu.Game.Skinning.Editor Scheduler.AddOnce(skinChanged); }, true); - SelectedComponents.BindCollectionChanged(selectionChanged); + SelectedComponents.BindCollectionChanged((_, __) => Scheduler.AddOnce(populateSettings), true); } public void UpdateTargetScreen(Drawable targetScreen) @@ -163,6 +166,7 @@ namespace osu.Game.Skinning.Editor this.targetScreen = targetScreen; SelectedComponents.Clear(); + Scheduler.AddOnce(loadBlueprintContainer); void loadBlueprintContainer() @@ -224,7 +228,7 @@ namespace osu.Game.Skinning.Editor SelectedComponents.Add(component); } - private void selectionChanged(object sender, NotifyCollectionChangedEventArgs e) + private void populateSettings() { settingsToolbox.Clear(); diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 08cdbf0aa9..1bab499979 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -21,16 +21,18 @@ namespace osu.Game.Skinning.Editor /// public class SkinEditorOverlay : CompositeDrawable, IKeyBindingHandler { - private readonly ScalingContainer target; + private readonly ScalingContainer scalingContainer; [CanBeNull] private SkinEditor skinEditor; public const float VISIBLE_TARGET_SCALE = 0.8f; - public SkinEditorOverlay(ScalingContainer target) + private Screen lastTargetScreen; + + public SkinEditorOverlay(ScalingContainer scalingContainer) { - this.target = target; + this.scalingContainer = scalingContainer; RelativeSizeAxes = Axes.Both; } @@ -77,7 +79,7 @@ namespace osu.Game.Skinning.Editor return; } - var editor = new SkinEditor(target); + var editor = new SkinEditor(); editor.State.BindValueChanged(editorVisibilityChanged); skinEditor = editor; @@ -95,6 +97,8 @@ namespace osu.Game.Skinning.Editor return; AddInternal(editor); + + SetTarget(lastTargetScreen); }); }); } @@ -105,11 +109,11 @@ namespace osu.Game.Skinning.Editor if (visibility.NewValue == Visibility.Visible) { - target.SetCustomRect(new RectangleF(toolbar_padding_requirement, 0.1f, 0.8f - toolbar_padding_requirement, 0.7f), true); + scalingContainer.SetCustomRect(new RectangleF(toolbar_padding_requirement, 0.1f, 0.8f - toolbar_padding_requirement, 0.7f), true); } else { - target.SetCustomRect(null); + scalingContainer.SetCustomRect(null); } } @@ -122,6 +126,8 @@ namespace osu.Game.Skinning.Editor /// public void SetTarget(Screen screen) { + lastTargetScreen = screen; + if (skinEditor == null) return; skinEditor.Save(); From 86960c791f783c614ada963bc105f065f42e3cfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 18:10:30 +0900 Subject: [PATCH 1226/5199] Close overlays and toolbar on entering the skin editor --- osu.Game/OsuGame.cs | 2 +- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 20 +++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ae117d03d2..25bd3d71de 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1184,7 +1184,7 @@ namespace osu.Game BackButton.Hide(); } - skinEditor.SetTarget((Screen)newScreen); + skinEditor.SetTarget((OsuScreen)newScreen); } private void screenPushed(IScreen lastScreen, IScreen newScreen) => screenChanged(lastScreen, newScreen); diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 1bab499979..2fdb16abfc 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -3,15 +3,16 @@ using System.Diagnostics; using JetBrains.Annotations; +using osu.Framework.Allocation; using osu.Framework.Bindables; 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.Framework.Screens; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; +using osu.Game.Screens; namespace osu.Game.Skinning.Editor { @@ -28,7 +29,10 @@ namespace osu.Game.Skinning.Editor public const float VISIBLE_TARGET_SCALE = 0.8f; - private Screen lastTargetScreen; + [Resolved(canBeNull: true)] + private OsuGame game { get; set; } + + private OsuScreen lastTargetScreen; public SkinEditorOverlay(ScalingContainer scalingContainer) { @@ -105,15 +109,23 @@ namespace osu.Game.Skinning.Editor private void editorVisibilityChanged(ValueChangedEvent visibility) { + Debug.Assert(skinEditor != null); + const float toolbar_padding_requirement = 0.18f; if (visibility.NewValue == Visibility.Visible) { scalingContainer.SetCustomRect(new RectangleF(toolbar_padding_requirement, 0.1f, 0.8f - toolbar_padding_requirement, 0.7f), true); + + game?.Toolbar.Hide(); + game?.CloseAllOverlays(); } else { scalingContainer.SetCustomRect(null); + + if (lastTargetScreen?.HideOverlaysOnEnter != true) + game?.Toolbar.Show(); } } @@ -124,7 +136,7 @@ namespace osu.Game.Skinning.Editor /// /// Set a new target screen which will be used to find skinnable components. /// - public void SetTarget(Screen screen) + public void SetTarget(OsuScreen screen) { lastTargetScreen = screen; @@ -136,7 +148,7 @@ namespace osu.Game.Skinning.Editor Scheduler.AddOnce(setTarget, screen); } - private void setTarget(Screen target) + private void setTarget(OsuScreen target) { Debug.Assert(skinEditor != null); From c807ad7e4ee131563ed45079bfde1e00d4226ae3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Mar 2022 19:11:17 +0900 Subject: [PATCH 1227/5199] Ensure toolbar is hidden even when the active screen is changed while the editor is open --- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 2fdb16abfc..780e3d5b67 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -4,7 +4,6 @@ using System.Diagnostics; using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; @@ -84,7 +83,7 @@ namespace osu.Game.Skinning.Editor } var editor = new SkinEditor(); - editor.State.BindValueChanged(editorVisibilityChanged); + editor.State.BindValueChanged(visibility => updateComponentVisibility()); skinEditor = editor; @@ -107,13 +106,13 @@ namespace osu.Game.Skinning.Editor }); } - private void editorVisibilityChanged(ValueChangedEvent visibility) + private void updateComponentVisibility() { Debug.Assert(skinEditor != null); const float toolbar_padding_requirement = 0.18f; - if (visibility.NewValue == Visibility.Visible) + if (skinEditor.State.Value == Visibility.Visible) { scalingContainer.SetCustomRect(new RectangleF(toolbar_padding_requirement, 0.1f, 0.8f - toolbar_padding_requirement, 0.7f), true); @@ -144,6 +143,9 @@ namespace osu.Game.Skinning.Editor skinEditor.Save(); + // ensure the toolbar is re-hidden even if a new screen decides to try and show it. + updateComponentVisibility(); + // AddOnce with parameter will ensure the newest target is loaded if there is any overlap. Scheduler.AddOnce(setTarget, screen); } From d062810ff2b4c04019f2523faa58e2d3cecca758 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Mar 2022 23:30:46 +0900 Subject: [PATCH 1228/5199] Add basic scene selector --- .../Visual/Gameplay/TestSceneSkinEditor.cs | 2 +- osu.Game/Skinning/Editor/SkinEditor.cs | 53 +++++++++++++++++++ osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 2 +- 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index a0602e21b9..9012492028 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("reload skin editor", () => { skinEditor?.Expire(); - Player.ScaleTo(SkinEditorOverlay.VISIBLE_TARGET_SCALE); + Player.ScaleTo(0.8f); LoadComponentAsync(skinEditor = new SkinEditor(Player), Add); }); } diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 19c39d23d5..37900d9920 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -10,14 +10,20 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Components.Menus; +using osu.Game.Screens.OnlinePlay.Match.Components; +using osu.Game.Screens.Play; +using osu.Game.Screens.Select; +using osuTK; namespace osu.Game.Skinning.Editor { @@ -42,6 +48,12 @@ namespace osu.Game.Skinning.Editor [Resolved] private OsuColour colours { get; set; } + [Resolved] + private IBindable ruleset { get; set; } + + [Resolved(canBeNull: true)] + private OsuGame game { get; set; } + private bool hasBegunMutating; private Container content; @@ -105,6 +117,47 @@ namespace osu.Game.Skinning.Editor }, }, }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + Y = 45, + Height = 30, + Name = "Scene library", + Spacing = new Vector2(10), + Padding = new MarginPadding(10), + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new PurpleTriangleButton + { + Text = "Song Select", + Width = 100, + Height = 30, + Action = () => game?.PerformFromScreen(screen => + { + if (screen is SongSelect) + return; + + screen.Push(new PlaySongSelect()); + }, new[] { typeof(SongSelect) }) + }, + new PurpleTriangleButton + { + Text = "Gameplay", + Width = 100, + Height = 30, + Action = () => game?.PerformFromScreen(screen => + { + if (screen is Player) + return; + + var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod(); + if (replayGeneratingMod != null) + screen.Push(new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateReplayScore(beatmap, mods))); + }, new[] { typeof(Player) }) + }, + } + }, new GridContainer { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 780e3d5b67..9fc233d3e3 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -114,7 +114,7 @@ namespace osu.Game.Skinning.Editor if (skinEditor.State.Value == Visibility.Visible) { - scalingContainer.SetCustomRect(new RectangleF(toolbar_padding_requirement, 0.1f, 0.8f - toolbar_padding_requirement, 0.7f), true); + scalingContainer.SetCustomRect(new RectangleF(toolbar_padding_requirement, 0.2f, 0.8f - toolbar_padding_requirement, 0.7f), true); game?.Toolbar.Hide(); game?.CloseAllOverlays(); From 8d85723a62f1e0ad3f0d6b9e2e2480ce877b0b72 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Mar 2022 19:23:57 +0900 Subject: [PATCH 1229/5199] Split out `SceneLibrary` into its own component --- osu.Game/Skinning/Editor/SkinEditor.cs | 126 ++++++++++++++++--------- 1 file changed, 82 insertions(+), 44 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 37900d9920..9d5d496837 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -6,8 +6,10 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Screens; @@ -24,6 +26,7 @@ using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.Play; using osu.Game.Screens.Select; using osuTK; +using osuTK.Graphics; namespace osu.Game.Skinning.Editor { @@ -48,12 +51,6 @@ namespace osu.Game.Skinning.Editor [Resolved] private OsuColour colours { get; set; } - [Resolved] - private IBindable ruleset { get; set; } - - [Resolved(canBeNull: true)] - private OsuGame game { get; set; } - private bool hasBegunMutating; private Container content; @@ -117,47 +114,12 @@ namespace osu.Game.Skinning.Editor }, }, }, - new FillFlowContainer + new SceneLibrary { RelativeSizeAxes = Axes.X, Y = 45, - Height = 30, - Name = "Scene library", - Spacing = new Vector2(10), - Padding = new MarginPadding(10), - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new PurpleTriangleButton - { - Text = "Song Select", - Width = 100, - Height = 30, - Action = () => game?.PerformFromScreen(screen => - { - if (screen is SongSelect) - return; - - screen.Push(new PlaySongSelect()); - }, new[] { typeof(SongSelect) }) - }, - new PurpleTriangleButton - { - Text = "Gameplay", - Width = 100, - Height = 30, - Action = () => game?.PerformFromScreen(screen => - { - if (screen is Player) - return; - - var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod(); - if (replayGeneratingMod != null) - screen.Push(new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateReplayScore(beatmap, mods))); - }, new[] { typeof(Player) }) - }, - } }, + new GridContainer { RelativeSizeAxes = Axes.Both, @@ -226,7 +188,10 @@ namespace osu.Game.Skinning.Editor { content.Children = new Drawable[] { - new SkinBlueprintContainer(targetScreen), + new SkinBlueprintContainer(targetScreen) + { + Margin = new MarginPadding { Top = 100 }, + } }; } } @@ -345,5 +310,78 @@ namespace osu.Game.Skinning.Editor foreach (var item in items) availableTargets.FirstOrDefault(t => t.Components.Contains(item))?.Remove(item); } + + private class SceneLibrary : CompositeDrawable + { + public const float HEIGHT = 30; + private const float padding = 10; + + [Resolved(canBeNull: true)] + private OsuGame game { get; set; } + + [Resolved] + private IBindable ruleset { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + new OsuScrollContainer(Direction.Horizontal) + { + RelativeSizeAxes = Axes.X, + Height = HEIGHT + padding * 2, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.5f) + }, + new FillFlowContainer + { + Name = "Scene library", + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Spacing = new Vector2(padding), + Padding = new MarginPadding(padding), + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new PurpleTriangleButton + { + Text = "Song Select", + Width = 100, + Height = HEIGHT, + Action = () => game?.PerformFromScreen(screen => + { + if (screen is SongSelect) + return; + + screen.Push(new PlaySongSelect()); + }, new[] { typeof(SongSelect) }) + }, + new PurpleTriangleButton + { + Text = "Gameplay", + Width = 100, + Height = HEIGHT, + Action = () => game?.PerformFromScreen(screen => + { + if (screen is Player) + return; + + var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod(); + if (replayGeneratingMod != null) + screen.Push(new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateReplayScore(beatmap, mods))); + }, new[] { typeof(Player), typeof(SongSelect) }) + }, + } + }, + } + } + }; + } + } } } From c6aa32a003925fcde04c258409e7685016e9b95e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Mar 2022 23:26:27 +0900 Subject: [PATCH 1230/5199] Add basic song select setup for skinnability --- osu.Game/Screens/Select/SongSelect.cs | 5 +++++ osu.Game/Skinning/DefaultSkin.cs | 8 ++++++++ osu.Game/Skinning/SkinnableTarget.cs | 3 ++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index f5b11448f8..2d1a2bce4e 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -37,6 +37,7 @@ using osu.Game.Graphics.UserInterface; using System.Diagnostics; using osu.Game.Screens.Play; using osu.Game.Database; +using osu.Game.Skinning; namespace osu.Game.Screens.Select { @@ -235,6 +236,10 @@ namespace osu.Game.Screens.Select } } }, + new SkinnableTargetContainer(SkinnableTarget.SongSelect) + { + RelativeSizeAxes = Axes.Both, + }, }); if (ShowFooter) diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 951e3f9cc5..7c6d138f4c 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -70,6 +70,14 @@ namespace osu.Game.Skinning case SkinnableTargetComponent target: switch (target.Target) { + case SkinnableTarget.SongSelect: + var songSelectComponents = new SkinnableTargetComponentsContainer(container => + { + // do stuff when we need to. + }); + + return songSelectComponents; + case SkinnableTarget.MainHUDComponents: var skinnableTargetWrapper = new SkinnableTargetComponentsContainer(container => { diff --git a/osu.Game/Skinning/SkinnableTarget.cs b/osu.Game/Skinning/SkinnableTarget.cs index 7b1eae126c..09de8a5d71 100644 --- a/osu.Game/Skinning/SkinnableTarget.cs +++ b/osu.Game/Skinning/SkinnableTarget.cs @@ -5,6 +5,7 @@ namespace osu.Game.Skinning { public enum SkinnableTarget { - MainHUDComponents + MainHUDComponents, + SongSelect } } From aff6a5a428d6ff232835d844066161b45b0f7f45 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 15:11:47 +0900 Subject: [PATCH 1231/5199] Better align scene selector with menu bar --- osu.Game/Skinning/Editor/SkinEditor.cs | 27 +++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 9d5d496837..921849c0aa 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -26,7 +26,6 @@ using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.Play; using osu.Game.Screens.Select; using osuTK; -using osuTK.Graphics; namespace osu.Game.Skinning.Editor { @@ -71,6 +70,8 @@ namespace osu.Game.Skinning.Editor { RelativeSizeAxes = Axes.Both; + const float menu_height = 40; + InternalChild = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, @@ -78,10 +79,10 @@ namespace osu.Game.Skinning.Editor { new Container { - Name = "Top bar", + Name = "Menu container", RelativeSizeAxes = Axes.X, Depth = float.MinValue, - Height = 40, + Height = menu_height, Children = new Drawable[] { new EditorMenuBar @@ -117,7 +118,7 @@ namespace osu.Game.Skinning.Editor new SceneLibrary { RelativeSizeAxes = Axes.X, - Y = 45, + Y = menu_height, }, new GridContainer @@ -322,22 +323,26 @@ namespace osu.Game.Skinning.Editor [Resolved] private IBindable ruleset { get; set; } + public SceneLibrary() + { + Height = HEIGHT + padding * 2; + } + [BackgroundDependencyLoader] private void load() { InternalChildren = new Drawable[] { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("222") + }, new OsuScrollContainer(Direction.Horizontal) { - RelativeSizeAxes = Axes.X, - Height = HEIGHT + padding * 2, + RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.5f) - }, new FillFlowContainer { Name = "Scene library", From ee3715f5cfe7e10f1f44d4a564ea0977aebc7bbf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 15:33:01 +0900 Subject: [PATCH 1232/5199] Use `OverlayColourProvider` and adjust metrics to roughly match new designs --- osu.Game/Skinning/Editor/SkinEditor.cs | 52 ++++++++++++++++++++------ 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 921849c0aa..b891d63da3 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -4,9 +4,9 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -18,11 +18,12 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Components.Menus; -using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.Play; using osu.Game.Screens.Select; using osuTK; @@ -50,6 +51,9 @@ namespace osu.Game.Skinning.Editor [Resolved] private OsuColour colours { get; set; } + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + private bool hasBegunMutating; private Container content; @@ -314,7 +318,8 @@ namespace osu.Game.Skinning.Editor private class SceneLibrary : CompositeDrawable { - public const float HEIGHT = 30; + public const float BUTTON_HEIGHT = 40; + private const float padding = 10; [Resolved(canBeNull: true)] @@ -325,18 +330,18 @@ namespace osu.Game.Skinning.Editor public SceneLibrary() { - Height = HEIGHT + padding * 2; + Height = BUTTON_HEIGHT + padding * 2; } [BackgroundDependencyLoader] - private void load() + private void load(OverlayColourProvider overlayColourProvider) { InternalChildren = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("222") + Colour = overlayColourProvider.Background5, }, new OsuScrollContainer(Direction.Horizontal) { @@ -353,11 +358,18 @@ namespace osu.Game.Skinning.Editor Direction = FillDirection.Horizontal, Children = new Drawable[] { - new PurpleTriangleButton + new OsuSpriteText + { + Text = "Scene library", + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding(10), + }, + new SceneButton { Text = "Song Select", - Width = 100, - Height = HEIGHT, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Action = () => game?.PerformFromScreen(screen => { if (screen is SongSelect) @@ -366,11 +378,11 @@ namespace osu.Game.Skinning.Editor screen.Push(new PlaySongSelect()); }, new[] { typeof(SongSelect) }) }, - new PurpleTriangleButton + new SceneButton { Text = "Gameplay", - Width = 100, - Height = HEIGHT, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Action = () => game?.PerformFromScreen(screen => { if (screen is Player) @@ -387,6 +399,22 @@ namespace osu.Game.Skinning.Editor } }; } + + private class SceneButton : OsuButton + { + public SceneButton() + { + Width = 100; + Height = BUTTON_HEIGHT; + } + + [BackgroundDependencyLoader(true)] + private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours) + { + BackgroundColour = overlayColourProvider?.Background3 ?? colours.Blue3; + Content.CornerRadius = 5; + } + } } } } From b08d4bb8eb12c6d8f7337aa1445b821203cd1c95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 15:34:04 +0900 Subject: [PATCH 1233/5199] Move `SceneLibrary` implementation to its own file --- osu.Game/Skinning/Editor/SkinEditor.cs | 111 +--------------- .../Skinning/Editor/SkinEditorSceneLibrary.cs | 123 ++++++++++++++++++ 2 files changed, 124 insertions(+), 110 deletions(-) create mode 100644 osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index b891d63da3..acae09ee71 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -4,29 +4,21 @@ using System; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; -using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; -using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Components.Menus; -using osu.Game.Screens.Play; -using osu.Game.Screens.Select; -using osuTK; namespace osu.Game.Skinning.Editor { @@ -119,7 +111,7 @@ namespace osu.Game.Skinning.Editor }, }, }, - new SceneLibrary + new SkinEditorSceneLibrary { RelativeSizeAxes = Axes.X, Y = menu_height, @@ -315,106 +307,5 @@ namespace osu.Game.Skinning.Editor foreach (var item in items) availableTargets.FirstOrDefault(t => t.Components.Contains(item))?.Remove(item); } - - private class SceneLibrary : CompositeDrawable - { - public const float BUTTON_HEIGHT = 40; - - private const float padding = 10; - - [Resolved(canBeNull: true)] - private OsuGame game { get; set; } - - [Resolved] - private IBindable ruleset { get; set; } - - public SceneLibrary() - { - Height = BUTTON_HEIGHT + padding * 2; - } - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider overlayColourProvider) - { - InternalChildren = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = overlayColourProvider.Background5, - }, - new OsuScrollContainer(Direction.Horizontal) - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new FillFlowContainer - { - Name = "Scene library", - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Spacing = new Vector2(padding), - Padding = new MarginPadding(padding), - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = "Scene library", - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Margin = new MarginPadding(10), - }, - new SceneButton - { - Text = "Song Select", - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Action = () => game?.PerformFromScreen(screen => - { - if (screen is SongSelect) - return; - - screen.Push(new PlaySongSelect()); - }, new[] { typeof(SongSelect) }) - }, - new SceneButton - { - Text = "Gameplay", - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Action = () => game?.PerformFromScreen(screen => - { - if (screen is Player) - return; - - var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod(); - if (replayGeneratingMod != null) - screen.Push(new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateReplayScore(beatmap, mods))); - }, new[] { typeof(Player), typeof(SongSelect) }) - }, - } - }, - } - } - }; - } - - private class SceneButton : OsuButton - { - public SceneButton() - { - Width = 100; - Height = BUTTON_HEIGHT; - } - - [BackgroundDependencyLoader(true)] - private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours) - { - BackgroundColour = overlayColourProvider?.Background3 ?? colours.Blue3; - Content.CornerRadius = 5; - } - } - } } } diff --git a/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs b/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs new file mode 100644 index 0000000000..5da6147e4c --- /dev/null +++ b/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs @@ -0,0 +1,123 @@ +// 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.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Screens; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osu.Game.Rulesets; +using osu.Game.Screens.Play; +using osu.Game.Screens.Select; +using osuTK; + +namespace osu.Game.Skinning.Editor +{ + public class SkinEditorSceneLibrary : CompositeDrawable + { + public const float BUTTON_HEIGHT = 40; + + private const float padding = 10; + + [Resolved(canBeNull: true)] + private OsuGame game { get; set; } + + [Resolved] + private IBindable ruleset { get; set; } + + public SkinEditorSceneLibrary() + { + Height = BUTTON_HEIGHT + padding * 2; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider overlayColourProvider) + { + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = overlayColourProvider.Background6, + }, + new OsuScrollContainer(Direction.Horizontal) + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new FillFlowContainer + { + Name = "Scene library", + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Spacing = new Vector2(padding), + Padding = new MarginPadding(padding), + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = "Scene library", + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding(10), + }, + new SceneButton + { + Text = "Song Select", + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Action = () => game?.PerformFromScreen(screen => + { + if (screen is SongSelect) + return; + + screen.Push(new PlaySongSelect()); + }, new[] { typeof(SongSelect) }) + }, + new SceneButton + { + Text = "Gameplay", + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Action = () => game?.PerformFromScreen(screen => + { + if (screen is Player) + return; + + var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod(); + if (replayGeneratingMod != null) + screen.Push(new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateReplayScore(beatmap, mods))); + }, new[] { typeof(Player), typeof(SongSelect) }) + }, + } + }, + } + } + }; + } + + private class SceneButton : OsuButton + { + public SceneButton() + { + Width = 100; + Height = BUTTON_HEIGHT; + } + + [BackgroundDependencyLoader(true)] + private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours) + { + BackgroundColour = overlayColourProvider?.Background3 ?? colours.Blue3; + Content.CornerRadius = 5; + } + } + } +} From fdb411c0f3dcd9bf7ea7e5ec4a13028dac551160 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 17:19:20 +0900 Subject: [PATCH 1234/5199] Update skin editor toolbox design to suck less --- .../Skinning/Editor/SkinComponentToolbox.cs | 62 +++++++++---------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 2781f1958f..e5fe13b64d 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -6,16 +6,15 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Edit; @@ -23,7 +22,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osuTK; -using osuTK.Graphics; namespace osu.Game.Skinning.Editor { @@ -33,8 +31,6 @@ namespace osu.Game.Skinning.Editor public Action RequestPlacement; - private const float component_display_scale = 0.8f; - [Cached] private ScoreProcessor scoreProcessor = new ScoreProcessor(new DummyRuleset()) { @@ -62,7 +58,7 @@ namespace osu.Game.Skinning.Editor RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(20) + Spacing = new Vector2(2) }; var skinnableTypes = typeof(OsuGame).Assembly.GetTypes() @@ -120,39 +116,36 @@ namespace osu.Game.Skinning.Editor Enabled.Value = true; RelativeSizeAxes = Axes.X; - Height = 70; + Height = 60; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OverlayColourProvider colourProvider, OsuColour colours) { - BackgroundColour = colours.Gray3; - Content.EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Radius = 2, - Offset = new Vector2(0, 1), - Colour = Color4.Black.Opacity(0.5f) - }; + BackgroundColour = colourProvider.Background3; AddRange(new Drawable[] { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10) { Bottom = 20 }, + Masking = true, + Child = innerContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = component + }, + }, new OsuSpriteText { Text = component.GetType().Name, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Margin = new MarginPadding(5), }, - innerContainer = new Container - { - Y = 10, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Scale = new Vector2(component_display_scale), - Masking = true, - Child = component - } }); // adjust provided component to fit / display in a known state. @@ -160,14 +153,17 @@ namespace osu.Game.Skinning.Editor component.Origin = Anchor.Centre; } - protected override void LoadComplete() + protected override void Update() { - base.LoadComplete(); + base.Update(); - if (component.RelativeSizeAxes != Axes.None) + if (component.DrawSize != Vector2.Zero) { - innerContainer.AutoSizeAxes = Axes.None; - innerContainer.Height = 100; + float bestScale = Math.Min( + innerContainer.DrawWidth / component.DrawWidth, + innerContainer.DrawHeight / component.DrawHeight); + + innerContainer.Scale = new Vector2(bestScale); } } From e4a6b7ae9139ec2b8600accc717fcad1e525a3cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 17:33:06 +0900 Subject: [PATCH 1235/5199] Expand toolbox component items on hover --- .../Skinning/Editor/SkinComponentToolbox.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index e5fe13b64d..3980163a9d 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -109,6 +109,9 @@ namespace osu.Game.Skinning.Editor private Container innerContainer; + private const float contracted_size = 60; + private const float expanded_size = 120; + public ToolboxComponentButton(Drawable component) { this.component = component; @@ -116,7 +119,19 @@ namespace osu.Game.Skinning.Editor Enabled.Value = true; RelativeSizeAxes = Axes.X; - Height = 60; + Height = contracted_size; + } + + protected override bool OnHover(HoverEvent e) + { + this.Delay(300).ResizeHeightTo(expanded_size, 500, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + this.ResizeHeightTo(contracted_size, 500, Easing.OutQuint); } [BackgroundDependencyLoader] From 59cb1ac126bed54c93ccc600cc134696d7d82482 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 17:35:25 +0900 Subject: [PATCH 1236/5199] Order components by name for now --- osu.Game/Skinning/Editor/SkinComponentToolbox.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 3980163a9d..98fdc7c93f 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -64,6 +64,7 @@ namespace osu.Game.Skinning.Editor var skinnableTypes = typeof(OsuGame).Assembly.GetTypes() .Where(t => !t.IsInterface) .Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t)) + .OrderBy(t => t.Name) .ToArray(); foreach (var type in skinnableTypes) From 4525ed645c4c844c21774eeb6b2ced2e0028c3e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 16:36:04 +0900 Subject: [PATCH 1237/5199] Update skin editor to use `EditorSidebar` --- .../TestSceneSkinEditorComponentsList.cs | 12 ++++++--- .../Skinning/Editor/SkinComponentToolbox.cs | 12 +++------ osu.Game/Skinning/Editor/SkinEditor.cs | 27 +++++++++++++------ .../Skinning/Editor/SkinSettingsToolbox.cs | 20 ++++++++------ 4 files changed, 44 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs index 58c89411c0..5385a9983b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Skinning.Editor; @@ -11,13 +13,17 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneSkinEditorComponentsList : SkinnableTestScene { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + [Test] public void TestToggleEditor() { - AddStep("show available components", () => SetContents(_ => new SkinComponentToolbox(300) + AddStep("show available components", () => SetContents(_ => new SkinComponentToolbox { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Width = 0.6f, })); } diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 2781f1958f..05da229c3e 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -18,19 +18,17 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit.Components; using osuTK; using osuTK.Graphics; namespace osu.Game.Skinning.Editor { - public class SkinComponentToolbox : ScrollingToolboxGroup + public class SkinComponentToolbox : EditorSidebarSection { - public const float WIDTH = 200; - public Action RequestPlacement; private const float component_display_scale = 0.8f; @@ -45,11 +43,9 @@ namespace osu.Game.Skinning.Editor [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); - public SkinComponentToolbox(float height) - : base("Components", height) + public SkinComponentToolbox() + : base("Components") { - RelativeSizeAxes = Axes.None; - Width = WIDTH; } [BackgroundDependencyLoader] diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index acae09ee71..381f7a1cc3 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -18,6 +18,7 @@ using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; +using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; namespace osu.Game.Skinning.Editor @@ -130,21 +131,31 @@ namespace osu.Game.Skinning.Editor { new Drawable[] { - new SkinComponentToolbox(600) + new EditorSidebar { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RequestPlacement = placeComponent + Children = new[] + { + new SkinComponentToolbox + { + RequestPlacement = placeComponent + }, + } }, content = new Container { RelativeSizeAxes = Axes.Both, }, - settingsToolbox = new SkinSettingsToolbox + new EditorSidebar { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - } + Children = new[] + { + settingsToolbox = new SkinSettingsToolbox + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + } + } + }, } } } diff --git a/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs b/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs index c0ef8e7316..fc06d3647a 100644 --- a/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs @@ -2,22 +2,26 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Rulesets.Edit; +using osu.Framework.Graphics.Containers; +using osu.Game.Screens.Edit.Components; using osuTK; namespace osu.Game.Skinning.Editor { - internal class SkinSettingsToolbox : ScrollingToolboxGroup + internal class SkinSettingsToolbox : EditorSidebarSection { - public const float WIDTH = 200; + protected override Container Content { get; } public SkinSettingsToolbox() - : base("Settings", 600) + : base("Settings") { - RelativeSizeAxes = Axes.None; - Width = WIDTH; - - FillFlow.Spacing = new Vector2(10); + base.Content.Add(Content = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + }); } } } From 54e351efe9e6dfe53fe13034108a454ff480ab03 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 16:57:39 +0900 Subject: [PATCH 1238/5199] Convert top level skin editor layout to use grid container Fix `SkinEditor`'s initial target not being a `Screen` --- osu.Game/Skinning/Editor/SkinEditor.cs | 180 +++++++++++++------------ 1 file changed, 95 insertions(+), 85 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 381f7a1cc3..f7e5aeecdf 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -17,7 +17,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; -using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; @@ -51,7 +50,7 @@ namespace osu.Game.Skinning.Editor private Container content; - private EditorToolboxGroup settingsToolbox; + private EditorSidebarSection settingsToolbox; public SkinEditor() { @@ -72,92 +71,111 @@ namespace osu.Game.Skinning.Editor InternalChild = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + Child = new GridContainer { - new Container + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { - Name = "Menu container", - RelativeSizeAxes = Axes.X, - Depth = float.MinValue, - Height = menu_height, - Children = new Drawable[] - { - new EditorMenuBar - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - Items = new[] - { - new MenuItem("File") - { - Items = new[] - { - new EditorMenuItem("Save", MenuItemType.Standard, Save), - new EditorMenuItem("Revert to default", MenuItemType.Destructive, revert), - new EditorMenuItemSpacer(), - new EditorMenuItem("Exit", MenuItemType.Standard, Hide), - }, - }, - } - }, - headerText = new OsuTextFlowContainer - { - TextAnchor = Anchor.TopRight, - Padding = new MarginPadding(5), - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - }, - }, - }, - new SkinEditorSceneLibrary - { - RelativeSizeAxes = Axes.X, - Y = menu_height, + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), }, - new GridContainer + Content = new[] { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + new Drawable[] { - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] - { - new Drawable[] + new Container { - new EditorSidebar + Name = "Menu container", + RelativeSizeAxes = Axes.X, + Depth = float.MinValue, + Height = menu_height, + Children = new Drawable[] { - Children = new[] + new EditorMenuBar { - new SkinComponentToolbox + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Items = new[] { - RequestPlacement = placeComponent + new MenuItem("File") + { + Items = new[] + { + new EditorMenuItem("Save", MenuItemType.Standard, Save), + new EditorMenuItem("Revert to default", MenuItemType.Destructive, revert), + new EditorMenuItemSpacer(), + new EditorMenuItem("Exit", MenuItemType.Standard, Hide), + }, + }, + } + }, + headerText = new OsuTextFlowContainer + { + TextAnchor = Anchor.TopRight, + Padding = new MarginPadding(5), + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + }, + }, + }, + }, + new Drawable[] + { + new SkinEditorSceneLibrary + { + RelativeSizeAxes = Axes.X, + }, + }, + new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] + { + new EditorSidebar + { + Children = new[] + { + new SkinComponentToolbox + { + RequestPlacement = placeComponent + }, + } + }, + content = new Container + { + Depth = float.MaxValue, + RelativeSizeAxes = Axes.Both, + }, + new EditorSidebar + { + Children = new[] + { + settingsToolbox = new SkinSettingsToolbox + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + } + } }, } - }, - content = new Container - { - RelativeSizeAxes = Axes.Both, - }, - new EditorSidebar - { - Children = new[] - { - settingsToolbox = new SkinSettingsToolbox - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - } - } - }, + } } - } + }, } } }; @@ -191,17 +209,9 @@ namespace osu.Game.Skinning.Editor SelectedComponents.Clear(); Scheduler.AddOnce(loadBlueprintContainer); + Scheduler.AddOnce(populateSettings); - void loadBlueprintContainer() - { - content.Children = new Drawable[] - { - new SkinBlueprintContainer(targetScreen) - { - Margin = new MarginPadding { Top = 100 }, - } - }; - } + void loadBlueprintContainer() => content.Child = new SkinBlueprintContainer(targetScreen); } private void skinChanged() From 27122c17c91e1b2e0e5cc14a5f13c37d6123ef64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 17:41:20 +0900 Subject: [PATCH 1239/5199] Show settings for multiple components in a selection --- osu.Game/Skinning/Editor/SkinEditor.cs | 25 ++++--------------- .../Skinning/Editor/SkinSettingsToolbox.cs | 7 ++++-- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index f7e5aeecdf..6fe3df6e8a 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Testing; -using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; @@ -50,7 +49,7 @@ namespace osu.Game.Skinning.Editor private Container content; - private EditorSidebarSection settingsToolbox; + private EditorSidebar settingsSidebar; public SkinEditor() { @@ -161,17 +160,7 @@ namespace osu.Game.Skinning.Editor Depth = float.MaxValue, RelativeSizeAxes = Axes.Both, }, - new EditorSidebar - { - Children = new[] - { - settingsToolbox = new SkinSettingsToolbox - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - } - } - }, + settingsSidebar = new EditorSidebar(), } } } @@ -266,14 +255,10 @@ namespace osu.Game.Skinning.Editor private void populateSettings() { - settingsToolbox.Clear(); + settingsSidebar.Clear(); - var first = SelectedComponents.OfType().FirstOrDefault(); - - if (first != null) - { - settingsToolbox.Children = first.CreateSettingsControls().ToArray(); - } + foreach (var component in SelectedComponents.OfType()) + settingsSidebar.Add(new SkinSettingsToolbox(component)); } private IEnumerable availableTargets => targetScreen.ChildrenOfType(); diff --git a/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs b/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs index fc06d3647a..d2823ed0e4 100644 --- a/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinSettingsToolbox.cs @@ -1,8 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Configuration; using osu.Game.Screens.Edit.Components; using osuTK; @@ -12,8 +14,8 @@ namespace osu.Game.Skinning.Editor { protected override Container Content { get; } - public SkinSettingsToolbox() - : base("Settings") + public SkinSettingsToolbox(Drawable component) + : base($"Settings ({component.GetType().Name})") { base.Content.Add(Content = new FillFlowContainer { @@ -21,6 +23,7 @@ namespace osu.Game.Skinning.Editor AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Spacing = new Vector2(10), + Children = component.CreateSettingsControls().ToArray() }); } } From cc356bcfe4afacb6392e22bdfa665cf4f3249cc9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 18:57:53 +0900 Subject: [PATCH 1240/5199] Show components available for current screen only (using actual live dependencies) --- .../Skinning/Editor/SkinComponentToolbox.cs | 76 ++++++++----------- osu.Game/Skinning/Editor/SkinEditor.cs | 31 ++++---- 2 files changed, 47 insertions(+), 60 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 483e365e78..9c4a369c1d 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -2,24 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using osu.Framework.Utils; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Components; using osuTK; @@ -29,26 +21,19 @@ namespace osu.Game.Skinning.Editor { public Action RequestPlacement; - [Cached] - private ScoreProcessor scoreProcessor = new ScoreProcessor(new DummyRuleset()) - { - Combo = { Value = RNG.Next(1, 1000) }, - TotalScore = { Value = RNG.Next(1000, 10000000) } - }; + private readonly CompositeDrawable target; - [Cached(typeof(HealthProcessor))] - private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); - - public SkinComponentToolbox() + public SkinComponentToolbox(CompositeDrawable target = null) : base("Components") { + this.target = target; } + private FillFlowContainer fill; + [BackgroundDependencyLoader] private void load() { - FillFlowContainer fill; - Child = fill = new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -57,6 +42,13 @@ namespace osu.Game.Skinning.Editor Spacing = new Vector2(2) }; + reloadComponents(); + } + + private void reloadComponents() + { + fill.Clear(); + var skinnableTypes = typeof(OsuGame).Assembly.GetTypes() .Where(t => !t.IsInterface) .Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t)) @@ -64,18 +56,10 @@ namespace osu.Game.Skinning.Editor .ToArray(); foreach (var type in skinnableTypes) - { - var component = attemptAddComponent(type); - - if (component != null) - { - component.RequestPlacement = t => RequestPlacement?.Invoke(t); - fill.Add(component); - } - } + attemptAddComponent(type); } - private static ToolboxComponentButton attemptAddComponent(Type type) + private void attemptAddComponent(Type type) { try { @@ -83,14 +67,15 @@ namespace osu.Game.Skinning.Editor Debug.Assert(instance != null); - if (!((ISkinnableDrawable)instance).IsEditable) - return null; + if (!((ISkinnableDrawable)instance).IsEditable) return; - return new ToolboxComponentButton(instance); + fill.Add(new ToolboxComponentButton(instance, target) + { + RequestPlacement = t => RequestPlacement?.Invoke(t) + }); } catch { - return null; } } @@ -101,6 +86,7 @@ namespace osu.Game.Skinning.Editor public override bool PropagateNonPositionalInputSubTree => false; private readonly Drawable component; + private readonly CompositeDrawable dependencySource; public Action RequestPlacement; @@ -109,9 +95,10 @@ namespace osu.Game.Skinning.Editor private const float contracted_size = 60; private const float expanded_size = 120; - public ToolboxComponentButton(Drawable component) + public ToolboxComponentButton(Drawable component, CompositeDrawable dependencySource) { this.component = component; + this.dependencySource = dependencySource; Enabled.Value = true; @@ -143,7 +130,7 @@ namespace osu.Game.Skinning.Editor RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(10) { Bottom = 20 }, Masking = true, - Child = innerContainer = new Container + Child = innerContainer = new DependencyBorrowingContainer(dependencySource) { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, @@ -186,14 +173,17 @@ namespace osu.Game.Skinning.Editor } } - private class DummyRuleset : Ruleset + public class DependencyBorrowingContainer : Container { - public override IEnumerable GetModsFor(ModType type) => throw new NotImplementedException(); - public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new NotImplementedException(); - public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); - public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => throw new NotImplementedException(); - public override string Description => string.Empty; - public override string ShortName => string.Empty; + private readonly CompositeDrawable donor; + + public DependencyBorrowingContainer(CompositeDrawable donor) + { + this.donor = donor; + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => + new DependencyContainer(donor?.Dependencies ?? base.CreateChildDependencies(parent)); } } } diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index f7e5aeecdf..2a7e3439c0 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -51,6 +51,7 @@ namespace osu.Game.Skinning.Editor private Container content; private EditorSidebarSection settingsToolbox; + private EditorSidebar componentsSidebar; public SkinEditor() { @@ -146,16 +147,7 @@ namespace osu.Game.Skinning.Editor { new Drawable[] { - new EditorSidebar - { - Children = new[] - { - new SkinComponentToolbox - { - RequestPlacement = placeComponent - }, - } - }, + componentsSidebar = new EditorSidebar(), content = new Container { Depth = float.MaxValue, @@ -211,7 +203,15 @@ namespace osu.Game.Skinning.Editor Scheduler.AddOnce(loadBlueprintContainer); Scheduler.AddOnce(populateSettings); - void loadBlueprintContainer() => content.Child = new SkinBlueprintContainer(targetScreen); + void loadBlueprintContainer() + { + content.Child = new SkinBlueprintContainer(targetScreen); + + componentsSidebar.Child = new SkinComponentToolbox(getFirstTarget() as CompositeDrawable) + { + RequestPlacement = placeComponent + }; + } } private void skinChanged() @@ -238,12 +238,7 @@ namespace osu.Game.Skinning.Editor private void placeComponent(Type type) { - var target = availableTargets.FirstOrDefault()?.Target; - - if (target == null) - return; - - var targetContainer = getTarget(target.Value); + var targetContainer = getFirstTarget(); if (targetContainer == null) return; @@ -278,6 +273,8 @@ namespace osu.Game.Skinning.Editor private IEnumerable availableTargets => targetScreen.ChildrenOfType(); + private ISkinnableTarget getFirstTarget() => availableTargets.FirstOrDefault(); + private ISkinnableTarget getTarget(SkinnableTarget target) { return availableTargets.FirstOrDefault(c => c.Target == target); From 7e5262364525dc4cd0685b3ef8bc4287e8092b74 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Mar 2022 20:02:52 +0900 Subject: [PATCH 1241/5199] Add "Reset position" menu item in skin editor --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index bd6d097eb2..46755728a7 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.EnumExtensions; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; @@ -199,6 +200,12 @@ namespace osu.Game.Skinning.Editor Items = createAnchorItems((d, o) => ((Drawable)d).Origin == o, applyOrigins).ToArray() }; + yield return new OsuMenuItem("Reset position", MenuItemType.Standard, () => + { + foreach (var blueprint in SelectedBlueprints) + ((Drawable)blueprint.Item).Position = Vector2.Zero; + }); + foreach (var item in base.GetContextMenuItemsForSelection(selection)) yield return item; From c2e4779c2ed429a5e54dd405938e80347f1d2bab Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 16 Mar 2022 11:57:11 +0000 Subject: [PATCH 1242/5199] Remove redundant spacing in `ControlItem` test scene --- osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index e496ef60d7..8289be033a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -91,7 +91,6 @@ namespace osu.Game.Tests.Visual.Online flow = new FillFlowContainer { Direction = FillDirection.Vertical, - Spacing = new Vector2(20), RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, }, From 3f61b0d9688e99221c51522563be6aee7939c5aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Mar 2022 22:23:19 +0900 Subject: [PATCH 1243/5199] Add missing `OverlayColourProvider` to `SkinEditor` test --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 9012492028..e74345aae9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Skinning.Editor; @@ -16,6 +18,9 @@ namespace osu.Game.Tests.Visual.Gameplay protected override bool Autoplay => true; + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + [SetUpSteps] public override void SetUpSteps() { From 03d3969b38ebd37e03e8b09c4f6eed5218f4766d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Mar 2022 21:46:02 +0100 Subject: [PATCH 1244/5199] Remove unnecessary `OsuColour` cache One is already cached at `OsuGameBase` level. --- osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index 8289be033a..3465e7dfec 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -8,7 +8,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; @@ -24,9 +23,6 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); - [Cached] - private readonly OsuColour osuColour = new OsuColour(); - [Cached] private readonly Bindable selected = new Bindable(); From a8cefca685f9ec5faed81112a70f894c838f7f54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Mar 2022 21:49:04 +0100 Subject: [PATCH 1245/5199] Simplify fade-out transform --- osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs index 3465e7dfec..e66092bf27 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelControlItem.cs @@ -148,7 +148,7 @@ namespace osu.Game.Tests.Visual.Online private void leaveChannel(Channel channel) { leaveText.Text = $"OnRequestLeave: {channel.Name}"; - leaveText.FadeIn().Then().FadeOut(1000, Easing.InQuint); + leaveText.FadeOutFromOne(1000, Easing.InQuint); } private static Channel createPublicChannel(string name) => From b21fa78cbfa57b749138fdcd36a256ea727bc610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Mar 2022 21:55:36 +0100 Subject: [PATCH 1246/5199] Move dependencies out of fields to BDL args where possible --- osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs | 5 +---- .../Overlays/Chat/ChannelControl/ControlItemMention.cs | 8 +------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs index cc00550965..c27d5fdf5d 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItem.cs @@ -33,16 +33,13 @@ namespace osu.Game.Overlays.Chat.ChannelControl [Resolved] private Bindable selectedChannel { get; set; } = null!; - [Resolved] - private OverlayColourProvider colourProvider { get; set; } = null!; - public ControlItem(Channel channel) { this.channel = channel; } [BackgroundDependencyLoader] - private void load() + private void load(OverlayColourProvider colourProvider) { Height = 30; RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs index beb2713c92..34e1e33c87 100644 --- a/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs +++ b/osu.Game/Overlays/Chat/ChannelControl/ControlItemMention.cs @@ -20,14 +20,8 @@ namespace osu.Game.Overlays.Chat.ChannelControl private OsuSpriteText? countText; - [Resolved] - private OsuColour osuColour { get; set; } = null!; - - [Resolved] - private OverlayColourProvider colourProvider { get; set; } = null!; - [BackgroundDependencyLoader] - private void load() + private void load(OsuColour osuColour, OverlayColourProvider colourProvider) { Masking = true; Size = new Vector2(20, 12); From 624f9fc774732ae8c7ee795968414d3f7f97926b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Mar 2022 22:41:28 +0100 Subject: [PATCH 1247/5199] Implement mod settings area component --- .../UserInterface/TestSceneModSettingsArea.cs | 40 ++++ osu.Game/Overlays/Mods/ModSettingsArea.cs | 176 ++++++++++++++++++ 2 files changed, 216 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModSettingsArea.cs create mode 100644 osu.Game/Overlays/Mods/ModSettingsArea.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettingsArea.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettingsArea.cs new file mode 100644 index 0000000000..ddc1c8c128 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettingsArea.cs @@ -0,0 +1,40 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; +using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestSceneModSettingsArea : OsuTestScene + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + + [Test] + public void TestModToggleArea() + { + ModSettingsArea modSettingsArea = null; + + AddStep("create content", () => Child = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = modSettingsArea = new ModSettingsArea() + }); + AddStep("set DT", () => modSettingsArea.SelectedMods.Value = new[] { new OsuModDoubleTime() }); + AddStep("set DA", () => modSettingsArea.SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() }); + AddStep("set FL+WU+DA+AD", () => modSettingsArea.SelectedMods.Value = new Mod[] { new OsuModFlashlight(), new ModWindUp(), new OsuModDifficultyAdjust(), new OsuModApproachDifferent() }); + AddStep("set empty", () => modSettingsArea.SelectedMods.Value = Array.Empty()); + } + } +} diff --git a/osu.Game/Overlays/Mods/ModSettingsArea.cs b/osu.Game/Overlays/Mods/ModSettingsArea.cs new file mode 100644 index 0000000000..e0a30f60c2 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModSettingsArea.cs @@ -0,0 +1,176 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Overlays.Mods +{ + public class ModSettingsArea : CompositeDrawable + { + public Bindable> SelectedMods { get; } = new Bindable>(); + + private readonly Box background; + private readonly FillFlowContainer modSettingsFlow; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + public ModSettingsArea() + { + RelativeSizeAxes = Axes.X; + Height = 250; + + Anchor = Anchor.BottomRight; + Origin = Anchor.BottomRight; + + InternalChild = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = 2, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new OsuScrollContainer(Direction.Horizontal) + { + RelativeSizeAxes = Axes.Both, + ScrollbarOverlapsContent = false, + Child = modSettingsFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Padding = new MarginPadding { Vertical = 7, Horizontal = 70 }, + Spacing = new Vector2(7), + Direction = FillDirection.Horizontal + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load() + { + background.Colour = colourProvider.Dark3; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + SelectedMods.BindValueChanged(_ => updateMods()); + } + + private void updateMods() + { + modSettingsFlow.Clear(); + + foreach (var mod in SelectedMods.Value.OrderBy(mod => mod.Type).ThenBy(mod => mod.Acronym)) + { + var settings = mod.CreateSettingsControls().ToList(); + + if (settings.Count > 0) + { + if (modSettingsFlow.Any()) + { + modSettingsFlow.Add(new Box + { + RelativeSizeAxes = Axes.Y, + Width = 2, + Colour = colourProvider.Dark4, + }); + } + + modSettingsFlow.Add(new ModSettingsColumn(mod, settings)); + } + } + } + + protected override bool OnMouseDown(MouseDownEvent e) => true; + protected override bool OnHover(HoverEvent e) => true; + + private class ModSettingsColumn : CompositeDrawable + { + public ModSettingsColumn(Mod mod, IEnumerable settingsControls) + { + Width = 250; + RelativeSizeAxes = Axes.Y; + Padding = new MarginPadding { Bottom = 7 }; + + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension() + }, + Content = new[] + { + new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(7), + Children = new Drawable[] + { + new ModSwitchTiny(mod) + { + Active = { Value = true }, + Scale = new Vector2(0.6f), + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft + }, + new OsuSpriteText + { + Text = mod.Name, + Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold), + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Margin = new MarginPadding { Bottom = 2 } + } + } + } + }, + new[] { Empty() }, + new Drawable[] + { + new OsuScrollContainer(Direction.Vertical) + { + RelativeSizeAxes = Axes.Both, + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Right = 7 }, + ChildrenEnumerable = settingsControls, + Spacing = new Vector2(0, 7) + } + } + } + } + }; + } + } + } +} From d90a33485318f926e51c6f9da270876756bfab30 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Mar 2022 03:40:15 +0300 Subject: [PATCH 1248/5199] Introduce multiplayer playability and free mod validity in `Mod` --- osu.Game/Rulesets/Mods/IMod.cs | 12 ++++++++++++ osu.Game/Rulesets/Mods/Mod.cs | 6 ++++++ 2 files changed, 18 insertions(+) diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index d5d1de91de..cda59bae55 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -39,6 +39,18 @@ namespace osu.Game.Rulesets.Mods /// bool UserPlayable { get; } + /// + /// Whether this mod is playable in a multiplayer match. + /// Should be false for mods that affect the gameplay progress based on user input (e.g. ). + /// + bool PlayableInMultiplayer { get; } + + /// + /// Whether this mod is valid to be a "free mod" in a multiplayer match. + /// Should be false for mods that affect the gameplay progress (e.g. and ). + /// + bool ValidFreeModInMultiplayer { get; } + /// /// Create a fresh instance based on this mod. /// diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index b2d4be54ce..a6562b4f4c 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -94,6 +94,12 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual bool UserPlayable => true; + [JsonIgnore] + public virtual bool PlayableInMultiplayer => UserPlayable; + + [JsonIgnore] + public virtual bool ValidFreeModInMultiplayer => PlayableInMultiplayer; + [Obsolete("Going forward, the concept of \"ranked\" doesn't exist. The only exceptions are automation mods, which should now override and set UserPlayable to false.")] // Can be removed 20211009 public virtual bool Ranked => false; From 187059a37f69ed4ceff8ea4e2e2a0b8dda3b0085 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Mar 2022 03:40:40 +0300 Subject: [PATCH 1249/5199] Replace hardcoded overrides with the newly introduced `Mod` properties --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 2 ++ osu.Game/Rulesets/Mods/ModRateAdjust.cs | 2 ++ osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 ++ .../OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs | 4 ++-- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 1115b95e6f..54a4c054c9 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -31,6 +31,8 @@ namespace osu.Game.Rulesets.Mods public override double ScoreMultiplier => 1; + public override bool PlayableInMultiplayer => false; + public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModTimeRamp) }; [SettingSource("Initial rate", "The starting speed of the track")] diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index ebe18f2188..3a11c3034b 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Mods { public abstract class ModRateAdjust : Mod, IApplicableToRate { + public override bool ValidFreeModInMultiplayer => false; + public abstract BindableNumber SpeedChange { get; } public virtual void ApplyToTrack(ITrack track) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index b6b2decede..59aac62686 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] public abstract BindableBool AdjustPitch { get; } + public override bool ValidFreeModInMultiplayer => false; + public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModAdaptiveSpeed) }; public override string SettingDescription => $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x"; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 5ccf89e703..e4f1d9587d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -117,8 +117,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); - protected override bool IsValidMod(Mod mod) => base.IsValidMod(mod) && !(mod is ModAdaptiveSpeed); + protected override bool IsValidMod(Mod mod) => base.IsValidMod(mod) && mod.PlayableInMultiplayer; - protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && !(mod is ModTimeRamp) && !(mod is ModRateAdjust); + protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && mod.ValidFreeModInMultiplayer; } } From 59741ccee60d9b05466d9b7a1fea6157106ba5d8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Mar 2022 05:15:05 +0300 Subject: [PATCH 1250/5199] Add multiplayer mod validity check methods for server consumption --- osu.Game/OsuGame.cs | 15 ++++++++++----- osu.Game/Utils/ModUtils.cs | 26 +++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ae117d03d2..bc2d2083fe 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -575,11 +575,16 @@ namespace osu.Game if (SelectedMods.Disabled) return; - if (!ModUtils.CheckValidForGameplay(mods.NewValue, out var invalid)) - { - // ensure we always have a valid set of mods. - SelectedMods.Value = mods.NewValue.Except(invalid).ToArray(); - } + var validMods = mods.NewValue; + + if (!ModUtils.CheckCompatibleSet(validMods, out var incompatible)) + validMods = validMods.Except(incompatible).ToArray(); + + if (!ModUtils.CheckValidForGameplay(validMods, out var invalid)) + validMods = validMods.Except(invalid).ToArray(); + + // ensure we always have a valid set of mods. + SelectedMods.Value = validMods; } #endregion diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index d5ea74c404..d169ace80a 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -112,14 +112,34 @@ namespace osu.Game.Utils /// Invalid mods, if any were found. Can be null if all mods were valid. /// Whether the input mods were all valid. If false, will contain all invalid entries. public static bool CheckValidForGameplay(IEnumerable mods, [NotNullWhen(false)] out List? invalidMods) + => checkValid(mods, m => m.Type != ModType.System && m.HasImplementation && !(m is MultiMod), out invalidMods); + + /// + /// Check the provided combination of mods are valid for a multiplayer match session. + /// + /// The mods to check. + /// Invalid mods, if any were found. Can be null if all mods were valid. + /// Whether the input mods were all valid. If false, will contain all invalid entries. + public static bool CheckValidForMultiplayer(IEnumerable mods, [NotNullWhen(false)] out List? invalidMods) + => checkValid(mods, m => m.PlayableInMultiplayer, out invalidMods); + + /// + /// Check the provided combination of mods are valid as "free mods" in a multiplayer match session. + /// + /// The mods to check. + /// Invalid mods, if any were found. Can be null if all mods were valid. + /// Whether the input mods were all valid. If false, will contain all invalid entries. + public static bool CheckValidFreeModsForMultiplayer(IEnumerable mods, [NotNullWhen(false)] out List? invalidMods) + => checkValid(mods, m => m.ValidFreeModInMultiplayer, out invalidMods); + + private static bool checkValid(IEnumerable mods, Predicate valid, [NotNullWhen(false)] out List? invalidMods) { mods = mods.ToArray(); - - CheckCompatibleSet(mods, out invalidMods); + invalidMods = null; foreach (var mod in mods) { - if (mod.Type == ModType.System || !mod.HasImplementation || mod is MultiMod) + if (!valid(mod)) { invalidMods ??= new List(); invalidMods.Add(mod); From 07e9f3780a8335ca07d3e076193c0b7cbdc80982 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Mar 2022 05:15:48 +0300 Subject: [PATCH 1251/5199] Consider `UnknownMod` to be "playable in multiplayer" --- osu.Game/Rulesets/Mods/UnknownMod.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Mods/UnknownMod.cs b/osu.Game/Rulesets/Mods/UnknownMod.cs index b426386d7a..790306c0ca 100644 --- a/osu.Game/Rulesets/Mods/UnknownMod.cs +++ b/osu.Game/Rulesets/Mods/UnknownMod.cs @@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Mods public override double ScoreMultiplier => 0; public override bool UserPlayable => false; + public override bool PlayableInMultiplayer => true; public override ModType Type => ModType.System; From 1a04260807bfaf54675e753b4a0b7561d7349fad Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Mar 2022 05:51:12 +0300 Subject: [PATCH 1252/5199] Update mod instantiaton utility method to no longer check for validity --- osu.Game/Utils/ModUtils.cs | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index d5ea74c404..bccb706842 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -153,31 +153,17 @@ namespace osu.Game.Utils } /// - /// Verifies all proposed mods are valid for a given ruleset and returns instantiated s for further processing. + /// Returns an instantiated list of all proposed mods on a given ruleset. /// - /// The ruleset to verify mods against. + /// The ruleset to instantiate mods. /// The proposed mods. - /// Mods instantiated from which were valid for the given . - /// Whether all were valid for the given . - public static bool InstantiateValidModsForRuleset(Ruleset ruleset, IEnumerable proposedMods, out List valid) + /// Mods instantiated from on the given . + public static void InstantiateModsForRuleset(Ruleset ruleset, IEnumerable proposedMods, out List mods) { - valid = new List(); - bool proposedWereValid = true; + mods = new List(); foreach (var apiMod in proposedMods) - { - try - { - // will throw if invalid - valid.Add(apiMod.ToMod(ruleset)); - } - catch - { - proposedWereValid = false; - } - } - - return proposedWereValid; + mods.Add(apiMod.ToMod(ruleset)); } } } From 83189d1f07dc21af413f74f48e38c52082d6d005 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Mar 2022 06:24:55 +0300 Subject: [PATCH 1253/5199] Revert "Update mod instantiaton utility method to no longer check for validity" This reverts commit 1a04260807bfaf54675e753b4a0b7561d7349fad. --- osu.Game/Utils/ModUtils.cs | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index bccb706842..d5ea74c404 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -153,17 +153,31 @@ namespace osu.Game.Utils } /// - /// Returns an instantiated list of all proposed mods on a given ruleset. + /// Verifies all proposed mods are valid for a given ruleset and returns instantiated s for further processing. /// - /// The ruleset to instantiate mods. + /// The ruleset to verify mods against. /// The proposed mods. - /// Mods instantiated from on the given . - public static void InstantiateModsForRuleset(Ruleset ruleset, IEnumerable proposedMods, out List mods) + /// Mods instantiated from which were valid for the given . + /// Whether all were valid for the given . + public static bool InstantiateValidModsForRuleset(Ruleset ruleset, IEnumerable proposedMods, out List valid) { - mods = new List(); + valid = new List(); + bool proposedWereValid = true; foreach (var apiMod in proposedMods) - mods.Add(apiMod.ToMod(ruleset)); + { + try + { + // will throw if invalid + valid.Add(apiMod.ToMod(ruleset)); + } + catch + { + proposedWereValid = false; + } + } + + return proposedWereValid; } } } From e0a06bf5d931ceffb4ff0a42d4269483896d99df Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Mar 2022 06:28:10 +0300 Subject: [PATCH 1254/5199] Update mod instantiation utility method inline with `APIMod.ToMod` changes --- osu.Game/Utils/ModUtils.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index d5ea74c404..ff8e04cc58 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -166,15 +166,15 @@ namespace osu.Game.Utils foreach (var apiMod in proposedMods) { - try - { - // will throw if invalid - valid.Add(apiMod.ToMod(ruleset)); - } - catch + var mod = apiMod.ToMod(ruleset); + + if (mod is UnknownMod) { proposedWereValid = false; + continue; } + + valid.Add(mod); } return proposedWereValid; From b3ac544d655e6846a94b6bf2d31332b471d90de4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Mar 2022 06:31:51 +0300 Subject: [PATCH 1255/5199] Revert "Consider `UnknownMod` to be "playable in multiplayer"" This reverts commit 07e9f3780a8335ca07d3e076193c0b7cbdc80982. --- osu.Game/Rulesets/Mods/UnknownMod.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/UnknownMod.cs b/osu.Game/Rulesets/Mods/UnknownMod.cs index 790306c0ca..b426386d7a 100644 --- a/osu.Game/Rulesets/Mods/UnknownMod.cs +++ b/osu.Game/Rulesets/Mods/UnknownMod.cs @@ -16,7 +16,6 @@ namespace osu.Game.Rulesets.Mods public override double ScoreMultiplier => 0; public override bool UserPlayable => false; - public override bool PlayableInMultiplayer => true; public override ModType Type => ModType.System; From 1eac0f41bf4e624e32d125de2635561a8ff01e14 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Mar 2022 13:44:54 +0900 Subject: [PATCH 1256/5199] Remove unused using --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 46755728a7..d7fb5c0498 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.EnumExtensions; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; From 46e66e66e477507c3c7564930b2c615ed81f47da Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Mar 2022 08:19:38 +0300 Subject: [PATCH 1257/5199] Make opening chat overlay opt-in to add coverage for unloaded chat overlays Causes `TestHighlightWhileChatNeverOpen` to fail as expected. --- .../Visual/Online/TestSceneChatOverlay.cs | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 00ff6a9576..80a6698761 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -122,6 +122,8 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestHideOverlay() { + AddStep("Open chat overlay", () => chatOverlay.Show()); + AddAssert("Chat overlay is visible", () => chatOverlay.State.Value == Visibility.Visible); AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible); @@ -134,6 +136,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestChannelSelection() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible); AddStep("Setup get message response", () => onGetMessages = channel => { @@ -169,6 +172,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestSearchInSelector() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Search for 'no. 2'", () => chatOverlay.ChildrenOfType().First().Text = "no. 2"); AddUntilStep("Only channel 2 visible", () => { @@ -180,6 +184,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestChannelShortcutKeys() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Join channels", () => channels.ForEach(channel => channelManager.JoinChannel(channel))); AddStep("Close channel selector", () => InputManager.Key(Key.Escape)); AddUntilStep("Wait for close", () => chatOverlay.SelectionOverlayState == Visibility.Hidden); @@ -199,6 +204,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestCloseChannelBehaviour() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddUntilStep("Join until dropdown has channels", () => { if (visibleChannels.Count() < joinedChannels.Count()) @@ -269,6 +275,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestChannelCloseButton() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Join 2 channels", () => { channelManager.JoinChannel(channel1); @@ -289,6 +296,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestCloseTabShortcut() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Join 2 channels", () => { channelManager.JoinChannel(channel1); @@ -314,6 +322,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestNewTabShortcut() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Join 2 channels", () => { channelManager.JoinChannel(channel1); @@ -330,6 +339,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestRestoreTabShortcut() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Join 3 channels", () => { channelManager.JoinChannel(channel1); @@ -375,6 +385,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestChatCommand() { + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); @@ -398,6 +409,8 @@ namespace osu.Game.Tests.Visual.Online { Channel multiplayerChannel = null; + AddStep("open chat overlay", () => chatOverlay.Show()); + AddStep("join multiplayer channel", () => channelManager.JoinChannel(multiplayerChannel = new Channel(new APIUser()) { Name = "#mp_1", @@ -417,6 +430,7 @@ namespace osu.Game.Tests.Visual.Online { Message message = null; + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); @@ -443,6 +457,7 @@ namespace osu.Game.Tests.Visual.Online { Message message = null; + AddStep("Open chat overlay", () => chatOverlay.Show()); AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); @@ -471,6 +486,8 @@ namespace osu.Game.Tests.Visual.Online { Message message = null; + AddStep("Open chat overlay", () => chatOverlay.Show()); + AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); @@ -496,14 +513,11 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestHighlightWhileChatHidden() + public void TestHighlightWhileChatNeverOpen() { Message message = null; - AddStep("hide chat", () => chatOverlay.Hide()); - AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); - AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); AddStep("Send message in channel 1", () => { @@ -520,7 +534,7 @@ namespace osu.Game.Tests.Visual.Online }); }); - AddStep("Highlight message and show chat", () => + AddStep("Highlight message and open chat", () => { chatOverlay.HighlightMessage(message, channel1); chatOverlay.Show(); @@ -571,8 +585,6 @@ namespace osu.Game.Tests.Visual.Online ChannelManager, ChatOverlay = new TestChatOverlay { RelativeSizeAxes = Axes.Both, }, }; - - ChatOverlay.Show(); } } From 7aae9bbd1b33943fab4301934db6d3d20322660e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 17 Mar 2022 08:31:38 +0300 Subject: [PATCH 1258/5199] Improve channel bindable logic in `ChatOverlay` to avoid potential nullrefs --- osu.Game/Overlays/ChatOverlay.cs | 38 ++++++++++++-------------------- 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 3764ac42fc..3d39c7ce3a 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -73,6 +73,10 @@ namespace osu.Game.Overlays private Container channelSelectionContainer; protected ChannelSelectionOverlay ChannelSelectionOverlay; + private readonly IBindableList availableChannels = new BindableList(); + private readonly IBindableList joinedChannels = new BindableList(); + private readonly Bindable currentChannel = new Bindable(); + public override bool Contains(Vector2 screenSpacePos) => chatContainer.ReceivePositionalInputAt(screenSpacePos) || (ChannelSelectionOverlay.State.Value == Visibility.Visible && ChannelSelectionOverlay.ReceivePositionalInputAt(screenSpacePos)); @@ -198,9 +202,13 @@ namespace osu.Game.Overlays }, }; + availableChannels.BindTo(channelManager.AvailableChannels); + joinedChannels.BindTo(channelManager.JoinedChannels); + currentChannel.BindTo(channelManager.CurrentChannel); + textBox.OnCommit += postMessage; - ChannelTabControl.Current.ValueChanged += current => channelManager.CurrentChannel.Value = current.NewValue; + ChannelTabControl.Current.ValueChanged += current => currentChannel.Value = current.NewValue; ChannelTabControl.ChannelSelectorActive.ValueChanged += active => ChannelSelectionOverlay.State.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden; ChannelSelectionOverlay.State.ValueChanged += state => { @@ -238,18 +246,12 @@ namespace osu.Game.Overlays Schedule(() => { // TODO: consider scheduling bindable callbacks to not perform when overlay is not present. - channelManager.JoinedChannels.BindCollectionChanged(joinedChannelsChanged, true); - - channelManager.AvailableChannels.CollectionChanged += availableChannelsChanged; - availableChannelsChanged(null, null); - - currentChannel = channelManager.CurrentChannel.GetBoundCopy(); + joinedChannels.BindCollectionChanged(joinedChannelsChanged, true); + availableChannels.BindCollectionChanged(availableChannelsChanged, true); currentChannel.BindValueChanged(currentChannelChanged, true); }); } - private Bindable currentChannel; - private void currentChannelChanged(ValueChangedEvent e) { if (e.NewValue == null) @@ -318,7 +320,7 @@ namespace osu.Game.Overlays if (!channel.Joined.Value) channel = channelManager.JoinChannel(channel); - channelManager.CurrentChannel.Value = channel; + currentChannel.Value = channel; } channel.HighlightedMessage.Value = message; @@ -407,7 +409,7 @@ namespace osu.Game.Overlays return true; case PlatformAction.DocumentClose: - channelManager.LeaveChannel(channelManager.CurrentChannel.Value); + channelManager.LeaveChannel(currentChannel.Value); return true; } @@ -487,19 +489,7 @@ namespace osu.Game.Overlays private void availableChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) { - ChannelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (channelManager != null) - { - channelManager.CurrentChannel.ValueChanged -= currentChannelChanged; - channelManager.JoinedChannels.CollectionChanged -= joinedChannelsChanged; - channelManager.AvailableChannels.CollectionChanged -= availableChannelsChanged; - } + ChannelSelectionOverlay.UpdateAvailableChannels(availableChannels); } private void postMessage(TextBox textBox, bool newText) From ac739c9dae7a6ab6ca09eac1e9a9b93eeeac5ce4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Mar 2022 22:55:47 +0900 Subject: [PATCH 1259/5199] Change `PerformancePointsCounter` resolution requirements to be required All other similar UI components have required dependencies, so this is mainly to bring things in line with expectations. I am using this fact in the skin editor to only show components which can be used in the current editor context (by `try-catch`ing their `Activator.CreateInstance`). --- .../Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs | 5 +++++ osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs | 4 ++++ .../Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs | 4 ++++ .../Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs | 4 ++++ osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 6 ++---- 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index c5ea9e6204..c70313ee8a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -18,9 +18,11 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Skinning.Legacy; using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; using osu.Game.Storyboards; +using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual.Gameplay { @@ -37,6 +39,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); + [Cached] + private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()); + protected override bool HasCustomSteps => true; [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 505f73159f..d6a4d9bf79 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -14,6 +14,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Skinning; +using osu.Game.Tests.Beatmaps; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -30,6 +31,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); + [Cached] + private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()); + // best way to check without exposing. private Drawable hideTarget => hudOverlay.KeyCounter; private FillFlowContainer keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().First(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs index 8f33f6fac5..5dc4bdb041 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs @@ -10,6 +10,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Skinning.Editor; +using osu.Game.Tests.Beatmaps; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -22,6 +23,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); + [Cached] + private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()); + [SetUpSteps] public void SetUpSteps() { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index cdf349ff7f..da443a6c92 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -29,6 +30,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); + [Cached] + private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()); + private IEnumerable hudOverlays => CreatedDrawables.OfType(); // best way to check without exposing. diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index c0d0ea0721..7a1f724cfb 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -42,12 +42,10 @@ namespace osu.Game.Screens.Play.HUD private const float alpha_when_invalid = 0.3f; - [CanBeNull] - [Resolved(CanBeNull = true)] + [Resolved] private ScoreProcessor scoreProcessor { get; set; } - [Resolved(CanBeNull = true)] - [CanBeNull] + [Resolved] private GameplayState gameplayState { get; set; } [CanBeNull] From fd71aa4a4d321078f0da1194db47f18590052c8f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Mar 2022 15:05:51 +0900 Subject: [PATCH 1260/5199] Change `SongProgress` resolution requirements to be required --- osu.Game/Screens/Play/SongProgress.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index b27a9c5f5d..694b5ec628 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -73,9 +73,12 @@ namespace osu.Game.Screens.Play [Resolved(canBeNull: true)] private Player player { get; set; } - [Resolved(canBeNull: true)] + [Resolved] private GameplayClock gameplayClock { get; set; } + [Resolved] + private DrawableRuleset drawableRuleset { get; set; } + private IClock referenceClock; public bool UsesFixedAnchor { get; set; } @@ -113,7 +116,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, OsuConfigManager config, DrawableRuleset drawableRuleset) + private void load(OsuColour colours, OsuConfigManager config) { base.LoadComplete(); From 93bc0ac86966bed11fd59ce02df600e4cf717844 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 17 Mar 2022 17:49:35 +0900 Subject: [PATCH 1261/5199] Update download links in README.md for macOS (to add Apple Silicon) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f64240f67a..dba0b2670d 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ If you are looking to install or test osu! without setting up a development envi **Latest build:** -| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.15+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) +| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | macOS 10.15+ ([Intel](https://github.com/ppy/osu/releases/latest/download/osu.app.Intel.zip), [Apple Silicon](https://github.com/ppy/osu/releases/latest/download/osu.app.Apple.Silicon.zip)) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) | ------------- | ------------- | ------------- | ------------- | ------------- | - The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets. From 7b8fb341a54e9a5dab83bf363aec810cc466782e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Mar 2022 17:59:26 +0900 Subject: [PATCH 1262/5199] Fix not handling IconButtons --- osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs index b7aa8af4aa..2deb8686cc 100644 --- a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs +++ b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs @@ -127,9 +127,9 @@ namespace osu.Game.Tests.Visual where T : Drawable { if (typeof(T) == typeof(Button)) - AddUntilStep($"wait for {typeof(T).Name} enabled", () => (this.ChildrenOfType().Single() as Button)?.Enabled.Value == true); + AddUntilStep($"wait for {typeof(T).Name} enabled", () => (this.ChildrenOfType().Single() as ClickableContainer)?.Enabled.Value == true); else - AddUntilStep($"wait for {typeof(T).Name} enabled", () => this.ChildrenOfType().Single().ChildrenOfType