From 83cb70db17e10ee9c42ba5c20e5547efff6a9118 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Mon, 19 Aug 2019 22:54:07 +0200 Subject: [PATCH 0001/4398] 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/4398] 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/4398] 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/4398] 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/4398] 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/4398] 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 508b26ac6973a07a7edb6870ec13e065b055a21c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 7 Sep 2019 07:31:07 +0300 Subject: [PATCH 0007/4398] Implement basic layout --- .../UserInterface/TestScenePageSelector.cs | 29 +++++ .../Graphics/UserInterface/PageSelector.cs | 100 ++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs create mode 100644 osu.Game/Graphics/UserInterface/PageSelector.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs new file mode 100644 index 0000000000..9911a23bf1 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.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 System; +using System.Collections.Generic; +using osu.Game.Graphics.UserInterface; +using osu.Framework.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestScenePageSelector : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(PageSelector) + }; + + public TestScenePageSelector() + { + PageSelector pageSelector; + + Child = pageSelector = new PageSelector(10) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + } + } +} diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs new file mode 100644 index 0000000000..53f112ed68 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -0,0 +1,100 @@ +// 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.Containers; +using osu.Framework.Graphics; +using osu.Game.Graphics.Containers; +using osu.Framework.Bindables; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Graphics.UserInterface +{ + public class PageSelector : CompositeDrawable + { + private BindableInt currentPage = new BindableInt(1); + + private readonly int maxPages; + private readonly FillFlowContainer pillsFlow; + + public PageSelector(int maxPages) + { + this.maxPages = maxPages; + + AutoSizeAxes = Axes.Both; + InternalChild = pillsFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + currentPage.BindValueChanged(page => redraw(page.NewValue), true); + } + + private void redraw(int newPage) + { + pillsFlow.Clear(); + + for (int i = 0; i < maxPages; i++) + { + addPagePill(i); + } + } + + private void addPagePill(int page) + { + var pill = new Pill(page); + + if (page != currentPage.Value) + pill.Action = () => currentPage.Value = page; + + pillsFlow.Add(pill); + } + + private class Pill : OsuClickableContainer + { + private const int height = 20; + + private readonly Box background; + + public Pill(int page) + { + AutoSizeAxes = Axes.X; + Height = height; + Child = new CircularContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Masking = true, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = page.ToString(), + Margin = new MarginPadding { Horizontal = 8 } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Seafoam; + } + } + } +} From f77cd6582d94811874d74315265d4e91b5f81321 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 7 Sep 2019 08:20:09 +0300 Subject: [PATCH 0008/4398] Implement CurrentPage class --- .../Graphics/UserInterface/PageSelector.cs | 150 ++++++++++++++---- 1 file changed, 119 insertions(+), 31 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 53f112ed68..fef35ab229 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -8,6 +8,9 @@ using osu.Framework.Bindables; using osu.Framework.Allocation; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Framework.Extensions.Color4Extensions; +using System; namespace osu.Game.Graphics.UserInterface { @@ -42,59 +45,144 @@ namespace osu.Game.Graphics.UserInterface { pillsFlow.Clear(); - for (int i = 0; i < maxPages; i++) + for (int i = 1; i <= maxPages; i++) { - addPagePill(i); + if (i == currentPage.Value) + addCurrentPagePill(); + else + addPagePill(i); } } private void addPagePill(int page) { - var pill = new Pill(page); - - if (page != currentPage.Value) - pill.Action = () => currentPage.Value = page; - - pillsFlow.Add(pill); + pillsFlow.Add(new Page(page.ToString(), () => currentPage.Value = page)); } - private class Pill : OsuClickableContainer + private void addCurrentPagePill() + { + pillsFlow.Add(new CurrentPage(currentPage.Value.ToString())); + } + + private abstract class DrawablePage : CompositeDrawable { private const int height = 20; + private const int margin = 8; - private readonly Box background; + protected readonly string Text; - public Pill(int page) + protected DrawablePage(string text) { + Text = text; + AutoSizeAxes = Axes.X; Height = height; - Child = new CircularContainer - { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Masking = true, - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - }, - new SpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = page.ToString(), - Margin = new MarginPadding { Horizontal = 8 } - } - } - }; + + var background = CreateBackground(); + + if (background != null) + AddInternal(background); + + var content = CreateContent(); + content.Margin = new MarginPadding { Horizontal = margin }; + + AddInternal(content); + } + + protected abstract Drawable CreateContent(); + + protected virtual Drawable CreateBackground() => null; + } + + private abstract class ActivatedDrawablePage : DrawablePage + { + private readonly Action action; + + public ActivatedDrawablePage(string text, Action action) + : base(text) + { + this.action = action; + } + + protected override bool OnClick(ClickEvent e) + { + action?.Invoke(); + return base.OnClick(e); + } + } + + private class Page : ActivatedDrawablePage + { + private SpriteText text; + + private OsuColour colours; + + public Page(string text, Action action) + : base(text, action) + { } [BackgroundDependencyLoader] private void load(OsuColour colours) { + this.colours = colours; + text.Colour = colours.Seafoam; + } + + protected override bool OnHover(HoverEvent e) + { + text.Colour = colours.Seafoam.Lighten(30f); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + text.Colour = colours.Seafoam; + base.OnHoverLost(e); + } + + protected override Drawable CreateContent() => text = new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = Text + }; + } + + private class CurrentPage : DrawablePage + { + private SpriteText text; + + private Box background; + + public CurrentPage(string text) + : base(text) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + text.Colour = colours.GreySeafoam; background.Colour = colours.Seafoam; } + + protected override Drawable CreateContent() => text = new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = Text + }; + + protected override Drawable CreateBackground() => new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = background = new Box + { + RelativeSizeAxes = Axes.Both, + } + }; } } } From cea26baaefb58171ca76b1b3b39a70772d8eb7a5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 7 Sep 2019 08:46:16 +0300 Subject: [PATCH 0009/4398] Implement placeholder and correct redraw algorithm --- .../UserInterface/TestScenePageSelector.cs | 4 +- .../Graphics/UserInterface/PageSelector.cs | 63 +++++++++++++++---- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 9911a23bf1..e5efa65c91 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -17,9 +17,7 @@ namespace osu.Game.Tests.Visual.UserInterface public TestScenePageSelector() { - PageSelector pageSelector; - - Child = pageSelector = new PageSelector(10) + Child = new PageSelector(200) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index fef35ab229..244e87f023 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -3,7 +3,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; -using osu.Game.Graphics.Containers; using osu.Framework.Bindables; using osu.Framework.Allocation; using osu.Framework.Graphics.Shapes; @@ -16,7 +15,7 @@ namespace osu.Game.Graphics.UserInterface { public class PageSelector : CompositeDrawable { - private BindableInt currentPage = new BindableInt(1); + public readonly BindableInt CurrentPage = new BindableInt(); private readonly int maxPages; private readonly FillFlowContainer pillsFlow; @@ -38,30 +37,47 @@ namespace osu.Game.Graphics.UserInterface { base.LoadComplete(); - currentPage.BindValueChanged(page => redraw(page.NewValue), true); + CurrentPage.BindValueChanged(_ => redraw(), true); } - private void redraw(int newPage) + private void redraw() { pillsFlow.Clear(); - for (int i = 1; i <= maxPages; i++) + if (CurrentPage.Value > 3) + addDrawablePage(1); + + if (CurrentPage.Value > 4) + addPlaceholder(); + + for (int i = Math.Max(CurrentPage.Value - 2, 1); i <= Math.Min(CurrentPage.Value + 2, maxPages); i++) { - if (i == currentPage.Value) + if (i == CurrentPage.Value) addCurrentPagePill(); else - addPagePill(i); + addDrawablePage(i); } + + if (CurrentPage.Value + 2 < maxPages - 1) + addPlaceholder(); + + if (CurrentPage.Value + 2 < maxPages) + addDrawablePage(maxPages); } - private void addPagePill(int page) + private void addDrawablePage(int page) { - pillsFlow.Add(new Page(page.ToString(), () => currentPage.Value = page)); + pillsFlow.Add(new Page(page.ToString(), () => CurrentPage.Value = page)); + } + + private void addPlaceholder() + { + pillsFlow.Add(new Placeholder()); } private void addCurrentPagePill() { - pillsFlow.Add(new CurrentPage(currentPage.Value.ToString())); + pillsFlow.Add(new SelectedPage(CurrentPage.Value.ToString())); } private abstract class DrawablePage : CompositeDrawable @@ -149,13 +165,13 @@ namespace osu.Game.Graphics.UserInterface }; } - private class CurrentPage : DrawablePage + private class SelectedPage : DrawablePage { private SpriteText text; private Box background; - public CurrentPage(string text) + public SelectedPage(string text) : base(text) { } @@ -184,5 +200,28 @@ namespace osu.Game.Graphics.UserInterface } }; } + + private class Placeholder : DrawablePage + { + private SpriteText text; + + public Placeholder() + : base("...") + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + text.Colour = colours.Seafoam; + } + + protected override Drawable CreateContent() => text = new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = Text + }; + } } } From 9bd4220e9f6817e3200926c7abce593b6f8e52aa Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 7 Sep 2019 09:20:11 +0300 Subject: [PATCH 0010/4398] Add prev/next buttons --- .../Graphics/UserInterface/PageSelector.cs | 206 ++++++++++++++++-- 1 file changed, 184 insertions(+), 22 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 244e87f023..d93dcdace9 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -10,12 +10,13 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Extensions.Color4Extensions; using System; +using osuTK; namespace osu.Game.Graphics.UserInterface { public class PageSelector : CompositeDrawable { - public readonly BindableInt CurrentPage = new BindableInt(); + public readonly BindableInt CurrentPage = new BindableInt(1); private readonly int maxPages; private readonly FillFlowContainer pillsFlow; @@ -44,6 +45,11 @@ namespace osu.Game.Graphics.UserInterface { pillsFlow.Clear(); + if (CurrentPage.Value == 1) + addPreviousPageButton(); + else + addPreviousPageButton(() => CurrentPage.Value -= 1); + if (CurrentPage.Value > 3) addDrawablePage(1); @@ -63,6 +69,11 @@ namespace osu.Game.Graphics.UserInterface if (CurrentPage.Value + 2 < maxPages) addDrawablePage(maxPages); + + if (CurrentPage.Value == maxPages) + addNextPageButton(); + else + addNextPageButton(() => CurrentPage.Value += 1); } private void addDrawablePage(int page) @@ -80,12 +91,23 @@ namespace osu.Game.Graphics.UserInterface pillsFlow.Add(new SelectedPage(CurrentPage.Value.ToString())); } + private void addPreviousPageButton(Action action = null) + { + pillsFlow.Add(new PreviousPageButton(action)); + } + + private void addNextPageButton(Action action = null) + { + pillsFlow.Add(new NextPageButton(action)); + } + private abstract class DrawablePage : CompositeDrawable { private const int height = 20; private const int margin = 8; protected readonly string Text; + protected readonly Drawable Content; protected DrawablePage(string text) { @@ -99,10 +121,10 @@ namespace osu.Game.Graphics.UserInterface if (background != null) AddInternal(background); - var content = CreateContent(); - content.Margin = new MarginPadding { Horizontal = margin }; + Content = CreateContent(); + Content.Margin = new MarginPadding { Horizontal = margin }; - AddInternal(content); + AddInternal(Content); } protected abstract Drawable CreateContent(); @@ -112,25 +134,23 @@ namespace osu.Game.Graphics.UserInterface private abstract class ActivatedDrawablePage : DrawablePage { - private readonly Action action; + protected readonly Action Action; - public ActivatedDrawablePage(string text, Action action) + public ActivatedDrawablePage(string text, Action action = null) : base(text) { - this.action = action; + Action = action; } protected override bool OnClick(ClickEvent e) { - action?.Invoke(); + Action?.Invoke(); return base.OnClick(e); } } private class Page : ActivatedDrawablePage { - private SpriteText text; - private OsuColour colours; public Page(string text, Action action) @@ -142,22 +162,22 @@ namespace osu.Game.Graphics.UserInterface private void load(OsuColour colours) { this.colours = colours; - text.Colour = colours.Seafoam; + Content.Colour = colours.Seafoam; } protected override bool OnHover(HoverEvent e) { - text.Colour = colours.Seafoam.Lighten(30f); + Content.Colour = colours.Seafoam.Lighten(30f); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - text.Colour = colours.Seafoam; + Content.Colour = colours.Seafoam; base.OnHoverLost(e); } - protected override Drawable CreateContent() => text = new SpriteText + protected override Drawable CreateContent() => new SpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -167,8 +187,6 @@ namespace osu.Game.Graphics.UserInterface private class SelectedPage : DrawablePage { - private SpriteText text; - private Box background; public SelectedPage(string text) @@ -179,11 +197,11 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(OsuColour colours) { - text.Colour = colours.GreySeafoam; + Content.Colour = colours.GreySeafoam; background.Colour = colours.Seafoam; } - protected override Drawable CreateContent() => text = new SpriteText + protected override Drawable CreateContent() => new SpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -203,8 +221,6 @@ namespace osu.Game.Graphics.UserInterface private class Placeholder : DrawablePage { - private SpriteText text; - public Placeholder() : base("...") { @@ -213,15 +229,161 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(OsuColour colours) { - text.Colour = colours.Seafoam; + Content.Colour = colours.Seafoam; } - protected override Drawable CreateContent() => text = new SpriteText + protected override Drawable CreateContent() => new SpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = Text }; } + + private class PreviousPageButton : ActivatedDrawablePage + { + private OsuColour colours; + private Box background; + + public PreviousPageButton(Action action) + : base("prev", action) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + this.colours = colours; + Content.Colour = colours.Seafoam; + background.Colour = colours.GreySeafoam; + + if (Action == null) + { + Content.FadeColour(colours.GrayA); + background.FadeColour(colours.GrayA); + } + } + + protected override bool OnHover(HoverEvent e) + { + Content.Colour = colours.Seafoam.Lighten(30f); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + Content.Colour = colours.Seafoam; + base.OnHoverLost(e); + } + + protected override Drawable CreateContent() => new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(3), + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.CaretLeft, + Size = new Vector2(10), + }, + new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = Text.ToUpper(), + } + } + }; + + protected override Drawable CreateBackground() => new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = background = new Box + { + RelativeSizeAxes = Axes.Both, + } + }; + } + + private class NextPageButton : ActivatedDrawablePage + { + private OsuColour colours; + private Box background; + + public NextPageButton(Action action) + : base("next", action) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + this.colours = colours; + Content.Colour = colours.Seafoam; + background.Colour = colours.GreySeafoam; + + if (Action == null) + { + Content.FadeColour(colours.GrayA); + background.FadeColour(colours.GrayA); + } + } + + protected override bool OnHover(HoverEvent e) + { + Content.Colour = colours.Seafoam.Lighten(30f); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + Content.Colour = colours.Seafoam; + base.OnHoverLost(e); + } + + protected override Drawable CreateContent() => new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(3), + Children = new Drawable[] + { + new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = Text.ToUpper(), + }, + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.CaretRight, + Size = new Vector2(10), + }, + } + }; + + protected override Drawable CreateBackground() => new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = background = new Box + { + RelativeSizeAxes = Axes.Both, + } + }; + } } } From ba18f77b628496569cb5d5718081ea271b51e4f3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 7 Sep 2019 22:31:08 +0300 Subject: [PATCH 0011/4398] Use OsuHoverContainer for prev/next buttons --- .../Graphics/UserInterface/PageSelector.cs | 231 ++++++------------ 1 file changed, 78 insertions(+), 153 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index d93dcdace9..3ca133d64b 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -11,6 +11,8 @@ using osu.Framework.Input.Events; using osu.Framework.Extensions.Color4Extensions; using System; using osuTK; +using osu.Game.Graphics.Containers; +using System.Collections.Generic; namespace osu.Game.Graphics.UserInterface { @@ -21,16 +23,34 @@ namespace osu.Game.Graphics.UserInterface private readonly int maxPages; private readonly FillFlowContainer pillsFlow; + private readonly Button previousPageButton; + private readonly Button nextPageButton; + public PageSelector(int maxPages) { this.maxPages = maxPages; AutoSizeAxes = Axes.Both; - InternalChild = pillsFlow = new FillFlowContainer + InternalChild = new FillFlowContainer { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + previousPageButton = new Button(false, "prev") + { + Action = () => CurrentPage.Value -= 1, + }, + pillsFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + }, + nextPageButton = new Button(true, "next") + { + Action = () => CurrentPage.Value += 1 + } + } }; } @@ -43,12 +63,10 @@ namespace osu.Game.Graphics.UserInterface private void redraw() { - pillsFlow.Clear(); + previousPageButton.Enabled.Value = CurrentPage.Value != 1; + nextPageButton.Enabled.Value = CurrentPage.Value != maxPages; - if (CurrentPage.Value == 1) - addPreviousPageButton(); - else - addPreviousPageButton(() => CurrentPage.Value -= 1); + pillsFlow.Clear(); if (CurrentPage.Value > 3) addDrawablePage(1); @@ -69,11 +87,6 @@ namespace osu.Game.Graphics.UserInterface if (CurrentPage.Value + 2 < maxPages) addDrawablePage(maxPages); - - if (CurrentPage.Value == maxPages) - addNextPageButton(); - else - addNextPageButton(() => CurrentPage.Value += 1); } private void addDrawablePage(int page) @@ -91,16 +104,6 @@ namespace osu.Game.Graphics.UserInterface pillsFlow.Add(new SelectedPage(CurrentPage.Value.ToString())); } - private void addPreviousPageButton(Action action = null) - { - pillsFlow.Add(new PreviousPageButton(action)); - } - - private void addNextPageButton(Action action = null) - { - pillsFlow.Add(new NextPageButton(action)); - } - private abstract class DrawablePage : CompositeDrawable { private const int height = 20; @@ -240,150 +243,72 @@ namespace osu.Game.Graphics.UserInterface }; } - private class PreviousPageButton : ActivatedDrawablePage + private class Button : OsuHoverContainer { - private OsuColour colours; - private Box background; + private const int height = 20; + private const int margin = 8; - public PreviousPageButton(Action action) - : base("prev", action) + private readonly Anchor alignment; + private readonly Box background; + + protected override IEnumerable EffectTargets => new[] { background }; + + public Button(bool rightAligned, string text) { - } + alignment = rightAligned ? Anchor.x0 : Anchor.x2; - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - this.colours = colours; - Content.Colour = colours.Seafoam; - background.Colour = colours.GreySeafoam; + AutoSizeAxes = Axes.X; + Height = height; - if (Action == null) + Child = new CircularContainer { - Content.FadeColour(colours.GrayA); - background.FadeColour(colours.GrayA); - } - } - - protected override bool OnHover(HoverEvent e) - { - Content.Colour = colours.Seafoam.Lighten(30f); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - Content.Colour = colours.Seafoam; - base.OnHoverLost(e); - } - - protected override Drawable CreateContent() => new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(3), - Children = new Drawable[] - { - new SpriteIcon + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Masking = true, + Children = new Drawable[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Icon = FontAwesome.Solid.CaretLeft, - Size = new Vector2(10), - }, - new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = Text.ToUpper(), + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new Container + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Horizontal = margin }, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new SpriteText + { + Anchor = Anchor.y1 | alignment, + Origin = Anchor.y1 | alignment, + Text = text.ToUpper(), + }, + new SpriteIcon + { + Anchor = Anchor.y1 | alignment, + Origin = Anchor.y1 | alignment, + Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight, + Size = new Vector2(10), + }, + } + } + } } - } - }; - - protected override Drawable CreateBackground() => new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Child = background = new Box - { - RelativeSizeAxes = Axes.Both, - } - }; - } - - private class NextPageButton : ActivatedDrawablePage - { - private OsuColour colours; - private Box background; - - public NextPageButton(Action action) - : base("next", action) - { + }; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - this.colours = colours; - Content.Colour = colours.Seafoam; - background.Colour = colours.GreySeafoam; - - if (Action == null) - { - Content.FadeColour(colours.GrayA); - background.FadeColour(colours.GrayA); - } + IdleColour = colours.GreySeafoamDark; + HoverColour = colours.GrayA; } - - protected override bool OnHover(HoverEvent e) - { - Content.Colour = colours.Seafoam.Lighten(30f); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - Content.Colour = colours.Seafoam; - base.OnHoverLost(e); - } - - protected override Drawable CreateContent() => new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(3), - Children = new Drawable[] - { - new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = Text.ToUpper(), - }, - new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Icon = FontAwesome.Solid.CaretRight, - Size = new Vector2(10), - }, - } - }; - - protected override Drawable CreateBackground() => new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Child = background = new Box - { - RelativeSizeAxes = Axes.Both, - } - }; } } } From ec8298ac53108d65a7901228bf524ebcae794356 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 7 Sep 2019 22:33:26 +0300 Subject: [PATCH 0012/4398] Simplify redraw function --- .../Graphics/UserInterface/PageSelector.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 3ca133d64b..0b0c2eec83 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -58,34 +58,34 @@ namespace osu.Game.Graphics.UserInterface { base.LoadComplete(); - CurrentPage.BindValueChanged(_ => redraw(), true); + CurrentPage.BindValueChanged(page => redraw(page.NewValue), true); } - private void redraw() + private void redraw(int newPage) { - previousPageButton.Enabled.Value = CurrentPage.Value != 1; - nextPageButton.Enabled.Value = CurrentPage.Value != maxPages; + previousPageButton.Enabled.Value = newPage != 1; + nextPageButton.Enabled.Value = newPage != maxPages; pillsFlow.Clear(); - if (CurrentPage.Value > 3) + if (newPage > 3) addDrawablePage(1); - if (CurrentPage.Value > 4) + if (newPage > 4) addPlaceholder(); - for (int i = Math.Max(CurrentPage.Value - 2, 1); i <= Math.Min(CurrentPage.Value + 2, maxPages); i++) + for (int i = Math.Max(newPage - 2, 1); i <= Math.Min(newPage + 2, maxPages); i++) { - if (i == CurrentPage.Value) + if (i == newPage) addCurrentPagePill(); else addDrawablePage(i); } - if (CurrentPage.Value + 2 < maxPages - 1) + if (newPage + 2 < maxPages - 1) addPlaceholder(); - if (CurrentPage.Value + 2 < maxPages) + if (newPage + 2 < maxPages) addDrawablePage(maxPages); } From df29465ba44c974290bdaa775d78b736a2340f8d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 7 Sep 2019 23:42:30 +0300 Subject: [PATCH 0013/4398] Implement a single PageItem component --- .../Graphics/UserInterface/PageSelector.cs | 237 +++++++----------- 1 file changed, 86 insertions(+), 151 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 0b0c2eec83..9dea9232ac 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -7,12 +7,12 @@ using osu.Framework.Bindables; using osu.Framework.Allocation; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; using osu.Framework.Extensions.Color4Extensions; using System; using osuTK; using osu.Game.Graphics.Containers; using System.Collections.Generic; +using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Graphics.UserInterface { @@ -21,7 +21,7 @@ namespace osu.Game.Graphics.UserInterface public readonly BindableInt CurrentPage = new BindableInt(1); private readonly int maxPages; - private readonly FillFlowContainer pillsFlow; + private readonly FillFlowContainer itemsFlow; private readonly Button previousPageButton; private readonly Button nextPageButton; @@ -41,7 +41,7 @@ namespace osu.Game.Graphics.UserInterface { Action = () => CurrentPage.Value -= 1, }, - pillsFlow = new FillFlowContainer + itemsFlow = new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, @@ -66,7 +66,7 @@ namespace osu.Game.Graphics.UserInterface previousPageButton.Enabled.Value = newPage != 1; nextPageButton.Enabled.Value = newPage != maxPages; - pillsFlow.Clear(); + itemsFlow.Clear(); if (newPage > 3) addDrawablePage(1); @@ -77,7 +77,7 @@ namespace osu.Game.Graphics.UserInterface for (int i = Math.Max(newPage - 2, 1); i <= Math.Min(newPage + 2, maxPages); i++) { if (i == newPage) - addCurrentPagePill(); + addDrawableCurrentPage(); else addDrawablePage(i); } @@ -89,137 +89,101 @@ namespace osu.Game.Graphics.UserInterface addDrawablePage(maxPages); } - private void addDrawablePage(int page) + private void addDrawablePage(int page) => itemsFlow.Add(new DrawablePage(page.ToString()) { - pillsFlow.Add(new Page(page.ToString(), () => CurrentPage.Value = page)); - } + Action = () => CurrentPage.Value = page, + }); - private void addPlaceholder() - { - pillsFlow.Add(new Placeholder()); - } + private void addPlaceholder() => itemsFlow.Add(new Placeholder()); - private void addCurrentPagePill() - { - pillsFlow.Add(new SelectedPage(CurrentPage.Value.ToString())); - } + private void addDrawableCurrentPage() => itemsFlow.Add(new SelectedPage(CurrentPage.Value.ToString())); - private abstract class DrawablePage : CompositeDrawable + private abstract class PageItem : OsuHoverContainer { - private const int height = 20; private const int margin = 8; + private const int height = 20; - protected readonly string Text; - protected readonly Drawable Content; - - protected DrawablePage(string text) + protected PageItem(string text) { - Text = text; - AutoSizeAxes = Axes.X; Height = height; + var contentContainer = new CircularContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Masking = true, + }; + var background = CreateBackground(); - if (background != null) - AddInternal(background); + contentContainer.Add(background); - Content = CreateContent(); - Content.Margin = new MarginPadding { Horizontal = margin }; + var drawableText = CreateText(text); + if (drawableText != null) + { + drawableText.Margin = new MarginPadding { Horizontal = margin }; + contentContainer.Add(drawableText); + } - AddInternal(Content); + Add(contentContainer); } - protected abstract Drawable CreateContent(); + protected abstract Drawable CreateText(string text); - protected virtual Drawable CreateBackground() => null; + protected abstract Drawable CreateBackground(); } - private abstract class ActivatedDrawablePage : DrawablePage + private class DrawablePage : PageItem { - protected readonly Action Action; + protected SpriteText SpriteText; - public ActivatedDrawablePage(string text, Action action = null) + protected override IEnumerable EffectTargets => new[] { SpriteText }; + + public DrawablePage(string text) : base(text) { - Action = action; } - protected override bool OnClick(ClickEvent e) + protected override Drawable CreateBackground() => null; + + protected override Drawable CreateText(string text) => SpriteText = new SpriteText { - Action?.Invoke(); - return base.OnClick(e); - } - } - - private class Page : ActivatedDrawablePage - { - private OsuColour colours; - - public Page(string text, Action action) - : base(text, action) - { - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = text, + }; [BackgroundDependencyLoader] private void load(OsuColour colours) { - this.colours = colours; - Content.Colour = colours.Seafoam; + IdleColour = colours.Seafoam; + HoverColour = colours.Seafoam.Lighten(30f); } - - protected override bool OnHover(HoverEvent e) - { - Content.Colour = colours.Seafoam.Lighten(30f); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - Content.Colour = colours.Seafoam; - base.OnHoverLost(e); - } - - protected override Drawable CreateContent() => new SpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = Text - }; } private class SelectedPage : DrawablePage { private Box background; + protected override IEnumerable EffectTargets => null; + public SelectedPage(string text) : base(text) { } + protected override Drawable CreateBackground() => background = new Box + { + RelativeSizeAxes = Axes.Both, + }; + [BackgroundDependencyLoader] private void load(OsuColour colours) { - Content.Colour = colours.GreySeafoam; background.Colour = colours.Seafoam; + SpriteText.Colour = colours.GreySeafoamDark; } - - protected override Drawable CreateContent() => new SpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = Text - }; - - protected override Drawable CreateBackground() => new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Child = background = new Box - { - RelativeSizeAxes = Axes.Both, - } - }; } private class Placeholder : DrawablePage @@ -228,79 +192,28 @@ namespace osu.Game.Graphics.UserInterface : base("...") { } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Content.Colour = colours.Seafoam; - } - - protected override Drawable CreateContent() => new SpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = Text - }; } - private class Button : OsuHoverContainer + private class Button : PageItem { - private const int height = 20; - private const int margin = 8; - - private readonly Anchor alignment; - private readonly Box background; + private Box background; + private FillFlowContainer textContainer; + private SpriteIcon icon; protected override IEnumerable EffectTargets => new[] { background }; public Button(bool rightAligned, string text) + : base(text) { - alignment = rightAligned ? Anchor.x0 : Anchor.x2; + var alignment = rightAligned ? Anchor.x0 : Anchor.x2; - AutoSizeAxes = Axes.X; - Height = height; - - Child = new CircularContainer + textContainer.ForEach(drawable => { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Masking = true, - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - }, - new Container - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Child = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Horizontal = margin }, - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new SpriteText - { - Anchor = Anchor.y1 | alignment, - Origin = Anchor.y1 | alignment, - Text = text.ToUpper(), - }, - new SpriteIcon - { - Anchor = Anchor.y1 | alignment, - Origin = Anchor.y1 | alignment, - Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight, - Size = new Vector2(10), - }, - } - } - } - } - }; + drawable.Anchor = Anchor.y1 | alignment; + drawable.Origin = Anchor.y1 | alignment; + }); + + icon.Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight; } [BackgroundDependencyLoader] @@ -309,6 +222,28 @@ namespace osu.Game.Graphics.UserInterface IdleColour = colours.GreySeafoamDark; HoverColour = colours.GrayA; } + + protected override Drawable CreateBackground() => background = new Box + { + RelativeSizeAxes = Axes.Both, + }; + + protected override Drawable CreateText(string text) => textContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new SpriteText + { + Text = text.ToUpper(), + }, + icon = new SpriteIcon + { + Size = new Vector2(10), + }, + } + }; } } } From b0884d16fbcc4a540af86f40aa359a577d39d988 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 8 Sep 2019 00:13:23 +0300 Subject: [PATCH 0014/4398] Visual adjustments --- .../Graphics/UserInterface/PageSelector.cs | 67 +++++++++++++------ 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 9dea9232ac..9a5ffad0d4 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -13,6 +13,7 @@ using osuTK; using osu.Game.Graphics.Containers; using System.Collections.Generic; using osu.Framework.Extensions.IEnumerableExtensions; +using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { @@ -100,38 +101,47 @@ namespace osu.Game.Graphics.UserInterface private abstract class PageItem : OsuHoverContainer { - private const int margin = 8; + private const int margin = 10; private const int height = 20; + protected override Container Content => contentContainer; + + private readonly CircularContainer contentContainer; + protected PageItem(string text) { AutoSizeAxes = Axes.X; Height = height; - var contentContainer = new CircularContainer + base.Content.Add(contentContainer = new CircularContainer { AutoSizeAxes = Axes.X, RelativeSizeAxes = Axes.Y, Masking = true, - }; + }); var background = CreateBackground(); if (background != null) - contentContainer.Add(background); + Add(background); var drawableText = CreateText(text); if (drawableText != null) { drawableText.Margin = new MarginPadding { Horizontal = margin }; - contentContainer.Add(drawableText); + Add(drawableText); } + } - Add(contentContainer); + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + IdleColour = colours.Seafoam; + HoverColour = colours.Seafoam.Lighten(30f); } protected abstract Drawable CreateText(string text); - protected abstract Drawable CreateBackground(); + protected virtual Drawable CreateBackground() => null; } private class DrawablePage : PageItem @@ -145,28 +155,20 @@ namespace osu.Game.Graphics.UserInterface { } - protected override Drawable CreateBackground() => null; - protected override Drawable CreateText(string text) => SpriteText = new SpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = text, + Font = OsuFont.GetFont(size: 12), }; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - IdleColour = colours.Seafoam; - HoverColour = colours.Seafoam.Lighten(30f); - } } private class SelectedPage : DrawablePage { private Box background; - protected override IEnumerable EffectTargets => null; + protected override IEnumerable EffectTargets => new[] { background }; public SelectedPage(string text) : base(text) @@ -181,7 +183,6 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(OsuColour colours) { - background.Colour = colours.Seafoam; SpriteText.Colour = colours.GreySeafoamDark; } } @@ -196,11 +197,14 @@ namespace osu.Game.Graphics.UserInterface private class Button : PageItem { + private const int duration = 100; + private Box background; private FillFlowContainer textContainer; private SpriteIcon icon; + private readonly Box fadeBox; - protected override IEnumerable EffectTargets => new[] { background }; + protected override IEnumerable EffectTargets => new[] { textContainer }; public Button(bool rightAligned, string text) : base(text) @@ -214,13 +218,29 @@ namespace osu.Game.Graphics.UserInterface }); icon.Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight; + + Add(fadeBox = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(100) + }); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - IdleColour = colours.GreySeafoamDark; - HoverColour = colours.GrayA; + background.Colour = colours.GreySeafoamDark; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Enabled.BindValueChanged(onEnabledChanged, true); + } + + private void onEnabledChanged(ValueChangedEvent enabled) + { + fadeBox.FadeTo(enabled.NewValue ? 0 : 1, duration); } protected override Drawable CreateBackground() => background = new Box @@ -231,16 +251,19 @@ namespace osu.Game.Graphics.UserInterface protected override Drawable CreateText(string text) => textContainer = new FillFlowContainer { AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Direction = FillDirection.Horizontal, Children = new Drawable[] { new SpriteText { Text = text.ToUpper(), + Font = OsuFont.GetFont(size: 12), }, icon = new SpriteIcon { - Size = new Vector2(10), + Size = new Vector2(8), }, } }; From b97f4a81db6a35ba5fac734bbdb0f8b42a948419 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 8 Sep 2019 00:27:40 +0300 Subject: [PATCH 0015/4398] Add more testing --- .../UserInterface/TestScenePageSelector.cs | 22 +++++++++++++++++++ .../Graphics/UserInterface/PageSelector.cs | 18 +++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index e5efa65c91..cb83fbd028 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -22,6 +22,28 @@ namespace osu.Game.Tests.Visual.UserInterface Anchor = Anchor.Centre, Origin = Anchor.Centre, }; + + AddStep("1 max pages", () => redraw(1)); + AddStep("10 max pages", () => redraw(10)); + AddStep("200 max pages, current 199", () => redraw(200, 199)); + AddStep("200 max pages, current 201", () => redraw(200, 201)); + AddStep("200 max pages, current -10", () => redraw(200, -10)); + } + + private void redraw(int maxPages, int currentPage = 0) + { + Clear(); + + var selector = new PageSelector(maxPages) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + + if (currentPage != 0) + selector.CurrentPage.Value = currentPage; + + Add(selector); } } } diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 9a5ffad0d4..79a1680e4b 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -59,11 +59,25 @@ namespace osu.Game.Graphics.UserInterface { base.LoadComplete(); - CurrentPage.BindValueChanged(page => redraw(page.NewValue), true); + CurrentPage.BindValueChanged(_ => redraw(), true); } - private void redraw(int newPage) + private void redraw() { + if (CurrentPage.Value > maxPages) + { + CurrentPage.Value = maxPages; + return; + } + + if (CurrentPage.Value < 1) + { + CurrentPage.Value = 1; + return; + } + + int newPage = CurrentPage.Value; + previousPageButton.Enabled.Value = newPage != 1; nextPageButton.Enabled.Value = newPage != maxPages; From 0451b45fab4ed1a377f99e7f508e7b9c0b451ef6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 8 Sep 2019 00:37:11 +0300 Subject: [PATCH 0016/4398] Add missing blank line --- osu.Game/Graphics/UserInterface/PageSelector.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 79a1680e4b..9db6366ad6 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -139,6 +139,7 @@ namespace osu.Game.Graphics.UserInterface Add(background); var drawableText = CreateText(text); + if (drawableText != null) { drawableText.Margin = new MarginPadding { Horizontal = margin }; From bee7760d29e884dff532828e3bbb1f1d71fd3d6d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 10 Sep 2019 01:36:25 +0300 Subject: [PATCH 0017/4398] Make MaxPages value a bindable --- .../UserInterface/TestScenePageSelector.cs | 47 ++++++++++--------- .../Graphics/UserInterface/PageSelector.cs | 40 ++++++++++++---- 2 files changed, 57 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index cb83fbd028..14cb27c97e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -15,35 +15,40 @@ namespace osu.Game.Tests.Visual.UserInterface typeof(PageSelector) }; + private readonly PageSelector pageSelector; + public TestScenePageSelector() { - Child = new PageSelector(200) + Child = pageSelector = new PageSelector { Anchor = Anchor.Centre, Origin = Anchor.Centre, }; - AddStep("1 max pages", () => redraw(1)); - AddStep("10 max pages", () => redraw(10)); - AddStep("200 max pages, current 199", () => redraw(200, 199)); - AddStep("200 max pages, current 201", () => redraw(200, 201)); - AddStep("200 max pages, current -10", () => redraw(200, -10)); - } - - private void redraw(int maxPages, int currentPage = 0) - { - Clear(); - - var selector = new PageSelector(maxPages) + AddStep("10 max pages", () => setMaxPages(10)); + AddStep("200 max pages, current 199", () => { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; - - if (currentPage != 0) - selector.CurrentPage.Value = currentPage; - - Add(selector); + setMaxPages(200); + setCurrentPage(199); + }); + AddStep("200 max pages, current 201", () => + { + setMaxPages(200); + setCurrentPage(201); + }); + AddStep("200 max pages, current -10", () => + { + setMaxPages(200); + setCurrentPage(-10); + }); + AddStep("-10 max pages", () => + { + setMaxPages(-10); + }); } + + private void setMaxPages(int maxPages) => pageSelector.MaxPages.Value = maxPages; + + private void setCurrentPage(int currentPage) => pageSelector.CurrentPage.Value = currentPage; } } diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 9db6366ad6..66f51a6dad 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -20,17 +20,15 @@ namespace osu.Game.Graphics.UserInterface public class PageSelector : CompositeDrawable { public readonly BindableInt CurrentPage = new BindableInt(1); + public readonly BindableInt MaxPages = new BindableInt(1); - private readonly int maxPages; private readonly FillFlowContainer itemsFlow; private readonly Button previousPageButton; private readonly Button nextPageButton; - public PageSelector(int maxPages) + public PageSelector() { - this.maxPages = maxPages; - AutoSizeAxes = Axes.Both; InternalChild = new FillFlowContainer { @@ -59,24 +57,48 @@ namespace osu.Game.Graphics.UserInterface { base.LoadComplete(); - CurrentPage.BindValueChanged(_ => redraw(), true); + MaxPages.BindValueChanged(pagesAmount => onMaxPagesChanged(pagesAmount.NewValue), true); + CurrentPage.BindValueChanged(page => onCurrentPageChanged(page.NewValue), true); } - private void redraw() + private void onMaxPagesChanged(int pagesAmount) { - if (CurrentPage.Value > maxPages) + if (pagesAmount < 1) { - CurrentPage.Value = maxPages; + MaxPages.Value = 1; return; } - if (CurrentPage.Value < 1) + if (CurrentPage.Value > pagesAmount) + { + CurrentPage.Value = pagesAmount; + return; + } + + redraw(); + } + + private void onCurrentPageChanged(int newPage) + { + if (newPage > MaxPages.Value) + { + CurrentPage.Value = MaxPages.Value; + return; + } + + if (newPage < 1) { CurrentPage.Value = 1; return; } + redraw(); + } + + private void redraw() + { int newPage = CurrentPage.Value; + int maxPages = MaxPages.Value; previousPageButton.Enabled.Value = newPage != 1; nextPageButton.Enabled.Value = newPage != maxPages; From eb683b079b5c8dd20888a8dab9ce5ac8daf6077a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 10 Sep 2019 05:12:50 +0300 Subject: [PATCH 0018/4398] Adjust colours --- osu.Game/Graphics/UserInterface/PageSelector.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 66f51a6dad..25e6ed654c 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -172,8 +172,8 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(OsuColour colours) { - IdleColour = colours.Seafoam; - HoverColour = colours.Seafoam.Lighten(30f); + IdleColour = colours.Lime; + HoverColour = colours.Lime.Lighten(20f); } protected abstract Drawable CreateText(string text); From 41be9b3f8f08dd691a38ab1ff50872d126fe9f2e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 19 Sep 2019 16:45:38 +0300 Subject: [PATCH 0019/4398] Add asserts to the test scene --- osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 14cb27c97e..3516e23f98 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -36,15 +36,18 @@ namespace osu.Game.Tests.Visual.UserInterface setMaxPages(200); setCurrentPage(201); }); + AddAssert("Current equals max", () => pageSelector.CurrentPage.Value == pageSelector.MaxPages.Value); AddStep("200 max pages, current -10", () => { setMaxPages(200); setCurrentPage(-10); }); + AddAssert("Current is 1", () => pageSelector.CurrentPage.Value == 1); AddStep("-10 max pages", () => { setMaxPages(-10); }); + AddAssert("Current is 1, max is 1", () => pageSelector.CurrentPage.Value == 1 && pageSelector.MaxPages.Value == 1); } private void setMaxPages(int maxPages) => pageSelector.MaxPages.Value = maxPages; From 2c0694257cd82e1dd539e0fb88424db7a6e1daba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Dec 2019 14:44:34 +0900 Subject: [PATCH 0020/4398] Fix incorrect spritetext usage --- osu.Game/Graphics/UserInterface/PageSelector.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector.cs index 25e6ed654c..c9f0f3c74d 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector.cs @@ -13,6 +13,7 @@ using osuTK; using osu.Game.Graphics.Containers; using System.Collections.Generic; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Graphics.Sprites; using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface @@ -192,7 +193,7 @@ namespace osu.Game.Graphics.UserInterface { } - protected override Drawable CreateText(string text) => SpriteText = new SpriteText + protected override Drawable CreateText(string text) => SpriteText = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -293,7 +294,7 @@ namespace osu.Game.Graphics.UserInterface Direction = FillDirection.Horizontal, Children = new Drawable[] { - new SpriteText + new OsuSpriteText { Text = text.ToUpper(), Font = OsuFont.GetFont(size: 12), From 6fbbee3093a124021ee80bb053807dba8ec71832 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 4 Jan 2020 19:03:39 +0300 Subject: [PATCH 0021/4398] Move PageSelector to another namespace and organize TestScene --- .../UserInterface/TestScenePageSelector.cs | 18 +++++++++++++----- .../{ => PageSelector}/PageSelector.cs | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) rename osu.Game/Graphics/UserInterface/{ => PageSelector}/PageSelector.cs (99%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 3516e23f98..59491b7f90 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -3,8 +3,9 @@ using System; using System.Collections.Generic; -using osu.Game.Graphics.UserInterface; +using NUnit.Framework; using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface.PageSelector; namespace osu.Game.Tests.Visual.UserInterface { @@ -19,12 +20,19 @@ namespace osu.Game.Tests.Visual.UserInterface public TestScenePageSelector() { - Child = pageSelector = new PageSelector + AddRange(new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; + pageSelector = new PageSelector + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }); + } + [Test] + public void TestPageSelectorValues() + { AddStep("10 max pages", () => setMaxPages(10)); AddStep("200 max pages, current 199", () => { diff --git a/osu.Game/Graphics/UserInterface/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs similarity index 99% rename from osu.Game/Graphics/UserInterface/PageSelector.cs rename to osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index c9f0f3c74d..6767cb3a75 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -16,7 +16,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Graphics.Sprites; using osuTK.Graphics; -namespace osu.Game.Graphics.UserInterface +namespace osu.Game.Graphics.UserInterface.PageSelector { public class PageSelector : CompositeDrawable { From 70387c19f3f4be9413541b2588d1cf0d9de49d60 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 4 Jan 2020 19:36:05 +0300 Subject: [PATCH 0022/4398] Implement proper DrawablePage component --- .../UserInterface/TestScenePageSelector.cs | 17 ++- .../PageSelector/DrawablePage.cs | 108 ++++++++++++++++++ .../PageSelector/PageSelector.cs | 2 + 3 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 59491b7f90..aad640ab58 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -13,10 +13,12 @@ namespace osu.Game.Tests.Visual.UserInterface { public override IReadOnlyList RequiredTypes => new[] { - typeof(PageSelector) + typeof(PageSelector), + typeof(DrawablePage) }; private readonly PageSelector pageSelector; + private readonly DrawablePage drawablePage; public TestScenePageSelector() { @@ -26,6 +28,12 @@ namespace osu.Game.Tests.Visual.UserInterface { Anchor = Anchor.Centre, Origin = Anchor.Centre, + }, + drawablePage = new DrawablePage(1234) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding { Top = 50 }, } }); } @@ -58,6 +66,13 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("Current is 1, max is 1", () => pageSelector.CurrentPage.Value == 1 && pageSelector.MaxPages.Value == 1); } + [Test] + public void TestDrawablePage() + { + AddStep("Select", () => drawablePage.Selected = true); + AddStep("Deselect", () => drawablePage.Selected = false); + } + private void setMaxPages(int maxPages) => pageSelector.MaxPages.Value = maxPages; private void setCurrentPage(int currentPage) => pageSelector.CurrentPage.Value = currentPage; diff --git a/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs b/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs new file mode 100644 index 0000000000..fe91874159 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs @@ -0,0 +1,108 @@ +// 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.Containers; +using osu.Framework.Graphics; +using osu.Framework.Bindables; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Framework.Input.Events; + +namespace osu.Game.Graphics.UserInterface.PageSelector +{ + public class DrawablePage : OsuClickableContainer + { + private const int duration = 200; + + private readonly BindableBool selected = new BindableBool(); + + public bool Selected + { + get => selected.Value; + set => selected.Value = value; + } + + [Resolved] + private OsuColour colours { get; set; } + + private readonly Box background; + private readonly OsuSpriteText text; + + public DrawablePage(int page) + { + AutoSizeAxes = Axes.X; + Height = PageSelector.HEIGHT; + Child = new CircularContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = page.ToString(), + Font = OsuFont.GetFont(size: 12), + Margin = new MarginPadding { Horizontal = 10 } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load() + { + background.Colour = colours.Lime; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + selected.BindValueChanged(onSelectedChanged, true); + } + + private void onSelectedChanged(ValueChangedEvent selected) + { + background.FadeTo(selected.NewValue ? 1 : 0, duration, Easing.OutQuint); + text.FadeColour(selected.NewValue ? colours.GreySeafoamDarker : colours.Lime, duration, Easing.OutQuint); + } + + protected override bool OnClick(ClickEvent e) + { + if (!selected.Value) + selected.Value = true; + + return base.OnClick(e); + } + + protected override bool OnHover(HoverEvent e) + { + updateHoverState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + updateHoverState(); + } + + private void updateHoverState() + { + if (selected.Value) + return; + + text.FadeColour(IsHovered ? colours.Lime.Lighten(20f) : colours.Lime, duration, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index 6767cb3a75..54e3a035ec 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -20,6 +20,8 @@ namespace osu.Game.Graphics.UserInterface.PageSelector { public class PageSelector : CompositeDrawable { + public const int HEIGHT = 20; + public readonly BindableInt CurrentPage = new BindableInt(1); public readonly BindableInt MaxPages = new BindableInt(1); From 9af9da039da0c696a3277f4f19d5251a7561883f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 4 Jan 2020 21:14:56 +0300 Subject: [PATCH 0023/4398] Implement proper PageSelectorItem --- .../UserInterface/TestScenePageSelector.cs | 5 +- .../PageSelector/DrawablePage.cs | 79 ++--- .../PageSelector/PageSelector.cs | 284 +++--------------- .../PageSelector/PageSelectorButton.cs | 77 +++++ .../PageSelector/PageSelectorItem.cs | 75 +++++ 5 files changed, 222 insertions(+), 298 deletions(-) create mode 100644 osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs create mode 100644 osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index aad640ab58..33deff58dc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -14,7 +14,9 @@ namespace osu.Game.Tests.Visual.UserInterface public override IReadOnlyList RequiredTypes => new[] { typeof(PageSelector), - typeof(DrawablePage) + typeof(DrawablePage), + typeof(PageSelectorButton), + typeof(PageSelectorItem) }; private readonly PageSelector pageSelector; @@ -42,6 +44,7 @@ namespace osu.Game.Tests.Visual.UserInterface public void TestPageSelectorValues() { AddStep("10 max pages", () => setMaxPages(10)); + AddStep("11 max pages", () => setMaxPages(11)); AddStep("200 max pages, current 199", () => { setMaxPages(200); diff --git a/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs b/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs index fe91874159..20f418085d 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/DrawablePage.cs @@ -1,22 +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.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Bindables; using osu.Framework.Allocation; -using osu.Framework.Graphics.Shapes; using osu.Framework.Extensions.Color4Extensions; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Framework.Input.Events; namespace osu.Game.Graphics.UserInterface.PageSelector { - public class DrawablePage : OsuClickableContainer + public class DrawablePage : PageSelectorItem { - private const int duration = 200; - private readonly BindableBool selected = new BindableBool(); public bool Selected @@ -25,44 +19,33 @@ namespace osu.Game.Graphics.UserInterface.PageSelector set => selected.Value = value; } - [Resolved] - private OsuColour colours { get; set; } + public int Page { get; private set; } - private readonly Box background; - private readonly OsuSpriteText text; + private OsuSpriteText text; public DrawablePage(int page) { - AutoSizeAxes = Axes.X; - Height = PageSelector.HEIGHT; - Child = new CircularContainer + Page = page; + text.Text = page.ToString(); + + Background.Alpha = 0; + + Action = () => { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Masking = true, - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - }, - text = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = page.ToString(), - Font = OsuFont.GetFont(size: 12), - Margin = new MarginPadding { Horizontal = 10 } - } - } + if (!selected.Value) + selected.Value = true; }; } + protected override Drawable CreateContent() => text = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + }; + [BackgroundDependencyLoader] private void load() { - background.Colour = colours.Lime; + Background.Colour = Colours.Lime; } protected override void LoadComplete() @@ -73,36 +56,16 @@ namespace osu.Game.Graphics.UserInterface.PageSelector private void onSelectedChanged(ValueChangedEvent selected) { - background.FadeTo(selected.NewValue ? 1 : 0, duration, Easing.OutQuint); - text.FadeColour(selected.NewValue ? colours.GreySeafoamDarker : colours.Lime, duration, Easing.OutQuint); + Background.FadeTo(selected.NewValue ? 1 : 0, DURATION, Easing.OutQuint); + text.FadeColour(selected.NewValue ? Colours.GreySeafoamDarker : Colours.Lime, DURATION, Easing.OutQuint); } - protected override bool OnClick(ClickEvent e) - { - if (!selected.Value) - selected.Value = true; - - return base.OnClick(e); - } - - protected override bool OnHover(HoverEvent e) - { - updateHoverState(); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - base.OnHoverLost(e); - updateHoverState(); - } - - private void updateHoverState() + protected override void UpdateHoverState() { if (selected.Value) return; - text.FadeColour(IsHovered ? colours.Lime.Lighten(20f) : colours.Lime, duration, Easing.OutQuint); + text.FadeColour(IsHovered ? Colours.Lime.Lighten(20f) : Colours.Lime, DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index 54e3a035ec..ae6fc2b500 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -4,17 +4,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Bindables; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Extensions.Color4Extensions; -using System; -using osuTK; -using osu.Game.Graphics.Containers; -using System.Collections.Generic; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Game.Graphics.Sprites; -using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface.PageSelector { @@ -25,10 +15,10 @@ namespace osu.Game.Graphics.UserInterface.PageSelector public readonly BindableInt CurrentPage = new BindableInt(1); public readonly BindableInt MaxPages = new BindableInt(1); - private readonly FillFlowContainer itemsFlow; + private readonly FillFlowContainer itemsFlow; - private readonly Button previousPageButton; - private readonly Button nextPageButton; + private readonly PageSelectorButton previousPageButton; + private readonly PageSelectorButton nextPageButton; public PageSelector() { @@ -39,16 +29,16 @@ namespace osu.Game.Graphics.UserInterface.PageSelector Direction = FillDirection.Horizontal, Children = new Drawable[] { - previousPageButton = new Button(false, "prev") + previousPageButton = new PageSelectorButton(false, "prev") { Action = () => CurrentPage.Value -= 1, }, - itemsFlow = new FillFlowContainer + itemsFlow = new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, }, - nextPageButton = new Button(true, "next") + nextPageButton = new PageSelectorButton(true, "next") { Action = () => CurrentPage.Value += 1 } @@ -60,253 +50,69 @@ namespace osu.Game.Graphics.UserInterface.PageSelector { base.LoadComplete(); - MaxPages.BindValueChanged(pagesAmount => onMaxPagesChanged(pagesAmount.NewValue), true); - CurrentPage.BindValueChanged(page => onCurrentPageChanged(page.NewValue), true); - } - - private void onMaxPagesChanged(int pagesAmount) - { - if (pagesAmount < 1) - { - MaxPages.Value = 1; - return; - } - - if (CurrentPage.Value > pagesAmount) - { - CurrentPage.Value = pagesAmount; - return; - } - + MaxPages.BindValueChanged(_ => redraw()); + CurrentPage.BindValueChanged(page => onCurrentPageChanged(page.NewValue)); redraw(); } private void onCurrentPageChanged(int newPage) { - if (newPage > MaxPages.Value) - { - CurrentPage.Value = MaxPages.Value; - return; - } - if (newPage < 1) { CurrentPage.Value = 1; return; } - redraw(); + if (newPage > MaxPages.Value) + { + CurrentPage.Value = MaxPages.Value; + return; + } + + itemsFlow.ForEach(page => page.Selected = page.Page == newPage ? true : false); + updateButtonsState(); } private void redraw() + { + itemsFlow.Clear(); + + if (MaxPages.Value < 1) + { + MaxPages.Value = 1; + return; + } + + for (int i = 1; i <= MaxPages.Value; i++) + addDrawablePage(i); + + if (CurrentPage.Value > MaxPages.Value) + { + CurrentPage.Value = MaxPages.Value; + return; + } + + if (CurrentPage.Value < 1) + { + CurrentPage.Value = 1; + return; + } + + CurrentPage.TriggerChange(); + } + + private void updateButtonsState() { int newPage = CurrentPage.Value; int maxPages = MaxPages.Value; previousPageButton.Enabled.Value = newPage != 1; nextPageButton.Enabled.Value = newPage != maxPages; - - itemsFlow.Clear(); - - if (newPage > 3) - addDrawablePage(1); - - if (newPage > 4) - addPlaceholder(); - - for (int i = Math.Max(newPage - 2, 1); i <= Math.Min(newPage + 2, maxPages); i++) - { - if (i == newPage) - addDrawableCurrentPage(); - else - addDrawablePage(i); - } - - if (newPage + 2 < maxPages - 1) - addPlaceholder(); - - if (newPage + 2 < maxPages) - addDrawablePage(maxPages); } - private void addDrawablePage(int page) => itemsFlow.Add(new DrawablePage(page.ToString()) + private void addDrawablePage(int page) => itemsFlow.Add(new DrawablePage(page) { Action = () => CurrentPage.Value = page, }); - - private void addPlaceholder() => itemsFlow.Add(new Placeholder()); - - private void addDrawableCurrentPage() => itemsFlow.Add(new SelectedPage(CurrentPage.Value.ToString())); - - private abstract class PageItem : OsuHoverContainer - { - private const int margin = 10; - private const int height = 20; - - protected override Container Content => contentContainer; - - private readonly CircularContainer contentContainer; - - protected PageItem(string text) - { - AutoSizeAxes = Axes.X; - Height = height; - - base.Content.Add(contentContainer = new CircularContainer - { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Masking = true, - }); - - var background = CreateBackground(); - if (background != null) - Add(background); - - var drawableText = CreateText(text); - - if (drawableText != null) - { - drawableText.Margin = new MarginPadding { Horizontal = margin }; - Add(drawableText); - } - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - IdleColour = colours.Lime; - HoverColour = colours.Lime.Lighten(20f); - } - - protected abstract Drawable CreateText(string text); - - protected virtual Drawable CreateBackground() => null; - } - - private class DrawablePage : PageItem - { - protected SpriteText SpriteText; - - protected override IEnumerable EffectTargets => new[] { SpriteText }; - - public DrawablePage(string text) - : base(text) - { - } - - protected override Drawable CreateText(string text) => SpriteText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = text, - Font = OsuFont.GetFont(size: 12), - }; - } - - private class SelectedPage : DrawablePage - { - private Box background; - - protected override IEnumerable EffectTargets => new[] { background }; - - public SelectedPage(string text) - : base(text) - { - } - - protected override Drawable CreateBackground() => background = new Box - { - RelativeSizeAxes = Axes.Both, - }; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - SpriteText.Colour = colours.GreySeafoamDark; - } - } - - private class Placeholder : DrawablePage - { - public Placeholder() - : base("...") - { - } - } - - private class Button : PageItem - { - private const int duration = 100; - - private Box background; - private FillFlowContainer textContainer; - private SpriteIcon icon; - private readonly Box fadeBox; - - protected override IEnumerable EffectTargets => new[] { textContainer }; - - public Button(bool rightAligned, string text) - : base(text) - { - var alignment = rightAligned ? Anchor.x0 : Anchor.x2; - - textContainer.ForEach(drawable => - { - drawable.Anchor = Anchor.y1 | alignment; - drawable.Origin = Anchor.y1 | alignment; - }); - - icon.Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight; - - Add(fadeBox = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(100) - }); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.GreySeafoamDark; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - Enabled.BindValueChanged(onEnabledChanged, true); - } - - private void onEnabledChanged(ValueChangedEvent enabled) - { - fadeBox.FadeTo(enabled.NewValue ? 0 : 1, duration); - } - - protected override Drawable CreateBackground() => background = new Box - { - RelativeSizeAxes = Axes.Both, - }; - - protected override Drawable CreateText(string text) => textContainer = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = text.ToUpper(), - Font = OsuFont.GetFont(size: 12), - }, - icon = new SpriteIcon - { - Size = new Vector2(8), - }, - } - }; - } } } diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs new file mode 100644 index 0000000000..df007b32e0 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.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 osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics.Sprites; +using osu.Framework.Graphics.Sprites; +using osuTK.Graphics; +using osuTK; +using osu.Framework.Extensions.IEnumerableExtensions; + +namespace osu.Game.Graphics.UserInterface.PageSelector +{ + public class PageSelectorButton : PageSelectorItem + { + private readonly Box fadeBox; + private SpriteIcon icon; + private OsuSpriteText name; + private FillFlowContainer buttonContent; + + public PageSelectorButton(bool rightAligned, string text) + { + Add(fadeBox = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(100) + }); + + var alignment = rightAligned ? Anchor.x0 : Anchor.x2; + + buttonContent.ForEach(drawable => + { + drawable.Anchor = Anchor.y1 | alignment; + drawable.Origin = Anchor.y1 | alignment; + }); + + icon.Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight; + + name.Text = text.ToUpper(); + } + + protected override Drawable CreateContent() => buttonContent = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + name = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + }, + icon = new SpriteIcon + { + Size = new Vector2(8), + }, + } + }; + + [BackgroundDependencyLoader] + private void load() + { + Background.Colour = Colours.GreySeafoamDark; + name.Colour = icon.Colour = Colours.Lime; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Enabled.BindValueChanged(enabled => fadeBox.FadeTo(enabled.NewValue ? 0 : 1, DURATION), true); + } + + protected override void UpdateHoverState() => Background.FadeColour(IsHovered ? Colours.GreySeafoam : Colours.GreySeafoamDark, DURATION, Easing.OutQuint); + } +} diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs new file mode 100644 index 0000000000..d457b7ea0e --- /dev/null +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.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 osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Framework.Bindables; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Framework.Graphics.Sprites; +using osuTK.Graphics; +using osuTK; +using osu.Framework.Extensions.IEnumerableExtensions; +using JetBrains.Annotations; + +namespace osu.Game.Graphics.UserInterface.PageSelector +{ + public abstract class PageSelectorItem : OsuClickableContainer + { + protected const int DURATION = 200; + + [Resolved] + protected OsuColour Colours { get; private set; } + + protected override Container Content => content; + + protected readonly Box Background; + private readonly CircularContainer content; + + protected PageSelectorItem() + { + AutoSizeAxes = Axes.X; + Height = PageSelector.HEIGHT; + base.Content.Add(content = new CircularContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + Children = new Drawable[] + { + Background = new Box + { + RelativeSizeAxes = Axes.Both + }, + CreateContent().With(content => + { + content.Anchor = Anchor.Centre; + content.Origin = Anchor.Centre; + content.Margin = new MarginPadding { Horizontal = 10 }; + }) + } + }); + } + + [NotNull] + protected abstract Drawable CreateContent(); + + protected override bool OnHover(HoverEvent e) + { + UpdateHoverState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + UpdateHoverState(); + } + + protected abstract void UpdateHoverState(); + } +} From b1c5e437ccf2ca260d5b8f177d78144651b1d24d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 4 Jan 2020 21:22:45 +0300 Subject: [PATCH 0024/4398] Remove usings --- .../UserInterface/PageSelector/PageSelectorItem.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs index d457b7ea0e..63eb87a638 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs @@ -3,17 +3,10 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; -using osu.Framework.Bindables; using osu.Framework.Allocation; using osu.Framework.Graphics.Shapes; -using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; using osu.Framework.Input.Events; -using osu.Framework.Graphics.Sprites; -using osuTK.Graphics; -using osuTK; -using osu.Framework.Extensions.IEnumerableExtensions; using JetBrains.Annotations; namespace osu.Game.Graphics.UserInterface.PageSelector From 37482b2ad474379ed941a5ab3a8c40f813427eaf Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 4 Jan 2020 22:05:34 +0300 Subject: [PATCH 0025/4398] CI fixes --- osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs | 2 +- .../Graphics/UserInterface/PageSelector/PageSelectorItem.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index ae6fc2b500..eaa102bdd2 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -69,7 +69,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector return; } - itemsFlow.ForEach(page => page.Selected = page.Page == newPage ? true : false); + itemsFlow.ForEach(page => page.Selected = page.Page == newPage); updateButtonsState(); } diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs index 63eb87a638..5f0bfcdfdb 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs @@ -32,7 +32,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Masking = true, - Children = new Drawable[] + Children = new[] { Background = new Box { From d3c2dc43bd28812bafffb6d81a596268ed5ddca1 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 4 Jan 2020 22:25:08 +0300 Subject: [PATCH 0026/4398] TestScene improvements --- .../UserInterface/TestScenePageSelector.cs | 48 +++++++++---------- .../PageSelector/PageSelector.cs | 15 ++---- .../PageSelector/PageSelectorButton.cs | 1 + 3 files changed, 27 insertions(+), 37 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 33deff58dc..5e1105c834 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -41,32 +41,30 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void TestPageSelectorValues() + public void TestCurrentPageReset() { - AddStep("10 max pages", () => setMaxPages(10)); - AddStep("11 max pages", () => setMaxPages(11)); - AddStep("200 max pages, current 199", () => - { - setMaxPages(200); - setCurrentPage(199); - }); - AddStep("200 max pages, current 201", () => - { - setMaxPages(200); - setCurrentPage(201); - }); - AddAssert("Current equals max", () => pageSelector.CurrentPage.Value == pageSelector.MaxPages.Value); - AddStep("200 max pages, current -10", () => - { - setMaxPages(200); - setCurrentPage(-10); - }); - AddAssert("Current is 1", () => pageSelector.CurrentPage.Value == 1); - AddStep("-10 max pages", () => - { - setMaxPages(-10); - }); - AddAssert("Current is 1, max is 1", () => pageSelector.CurrentPage.Value == 1 && pageSelector.MaxPages.Value == 1); + AddStep("Set 10 pages", () => setMaxPages(10)); + AddStep("Select 5 page", () => setCurrentPage(5)); + AddStep("Set 11 pages", () => setMaxPages(11)); + AddAssert("Check 1 page is current", () => pageSelector.CurrentPage.Value == 1); + } + + [Test] + public void TestUnexistingPageSelection() + { + AddStep("Set 10 pages", () => setMaxPages(10)); + AddStep("Select 11 page", () => setCurrentPage(11)); + AddAssert("Check current equals max", () => pageSelector.CurrentPage.Value == pageSelector.MaxPages.Value); + + AddStep("Select -1 page", () => setCurrentPage(-1)); + AddAssert("Check current is 1", () => pageSelector.CurrentPage.Value == 1); + } + + [Test] + public void TestNegativeMaxPages() + { + AddStep("Set -10 pages", () => setMaxPages(-10)); + AddAssert("Check current and max is 1", () => pageSelector.CurrentPage.Value == 1 && pageSelector.MaxPages.Value == 1); } [Test] diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index eaa102bdd2..c2482d6330 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -86,19 +86,10 @@ namespace osu.Game.Graphics.UserInterface.PageSelector for (int i = 1; i <= MaxPages.Value; i++) addDrawablePage(i); - if (CurrentPage.Value > MaxPages.Value) - { - CurrentPage.Value = MaxPages.Value; - return; - } - - if (CurrentPage.Value < 1) - { + if (CurrentPage.Value == 1) + CurrentPage.TriggerChange(); + else CurrentPage.Value = 1; - return; - } - - CurrentPage.TriggerChange(); } private void updateButtonsState() diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs index df007b32e0..e81ce20d27 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorButton.cs @@ -46,6 +46,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, + Spacing = new Vector2(3, 0), Children = new Drawable[] { name = new OsuSpriteText From 753db9599a225784c19e3459b6d830d5f62683c6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 4 Jan 2020 22:29:11 +0300 Subject: [PATCH 0027/4398] Move items height out of PageSelector --- osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs | 2 -- .../Graphics/UserInterface/PageSelector/PageSelectorItem.cs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs index c2482d6330..8e055faea3 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelector.cs @@ -10,8 +10,6 @@ namespace osu.Game.Graphics.UserInterface.PageSelector { public class PageSelector : CompositeDrawable { - public const int HEIGHT = 20; - public readonly BindableInt CurrentPage = new BindableInt(1); public readonly BindableInt MaxPages = new BindableInt(1); diff --git a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs index 5f0bfcdfdb..cd61961dbe 100644 --- a/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs +++ b/osu.Game/Graphics/UserInterface/PageSelector/PageSelectorItem.cs @@ -26,7 +26,7 @@ namespace osu.Game.Graphics.UserInterface.PageSelector protected PageSelectorItem() { AutoSizeAxes = Axes.X; - Height = PageSelector.HEIGHT; + Height = 20; base.Content.Add(content = new CircularContainer { RelativeSizeAxes = Axes.Y, From 20ab415838fab4cca9fcf4675d908670032d1b56 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Jan 2020 18:04:27 +0900 Subject: [PATCH 0028/4398] Reword tests --- .../UserInterface/TestScenePageSelector.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs index 5e1105c834..6494486d4e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePageSelector.cs @@ -41,30 +41,31 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void TestCurrentPageReset() + public void TestResetCurrentPage() { AddStep("Set 10 pages", () => setMaxPages(10)); - AddStep("Select 5 page", () => setCurrentPage(5)); + AddStep("Select page 5", () => setCurrentPage(5)); AddStep("Set 11 pages", () => setMaxPages(11)); - AddAssert("Check 1 page is current", () => pageSelector.CurrentPage.Value == 1); + AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 1); } [Test] - public void TestUnexistingPageSelection() + public void TestOutOfBoundsSelection() { AddStep("Set 10 pages", () => setMaxPages(10)); - AddStep("Select 11 page", () => setCurrentPage(11)); - AddAssert("Check current equals max", () => pageSelector.CurrentPage.Value == pageSelector.MaxPages.Value); + AddStep("Select page 11", () => setCurrentPage(11)); + AddAssert("Page 10 is current", () => pageSelector.CurrentPage.Value == pageSelector.MaxPages.Value); - AddStep("Select -1 page", () => setCurrentPage(-1)); - AddAssert("Check current is 1", () => pageSelector.CurrentPage.Value == 1); + AddStep("Select page -1", () => setCurrentPage(-1)); + AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 1); } [Test] public void TestNegativeMaxPages() { AddStep("Set -10 pages", () => setMaxPages(-10)); - AddAssert("Check current and max is 1", () => pageSelector.CurrentPage.Value == 1 && pageSelector.MaxPages.Value == 1); + AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 1); + AddAssert("Max is 1", () => pageSelector.MaxPages.Value == 1); } [Test] From 2784ba1423fa69bdaaa84989c459595c0ae1c7f4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 14 Oct 2021 16:34:23 +0900 Subject: [PATCH 0029/4398] Add queueing modes --- osu.Game/Online/Multiplayer/QueueingModes.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 osu.Game/Online/Multiplayer/QueueingModes.cs diff --git a/osu.Game/Online/Multiplayer/QueueingModes.cs b/osu.Game/Online/Multiplayer/QueueingModes.cs new file mode 100644 index 0000000000..684768fa57 --- /dev/null +++ b/osu.Game/Online/Multiplayer/QueueingModes.cs @@ -0,0 +1,11 @@ +// 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.Multiplayer +{ + public enum QueueingModes + { + Host, + Karaoke, + } +} From 6f89e30f5677586057953520ca184d82a8f1552f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 14 Oct 2021 23:24:01 +0900 Subject: [PATCH 0030/4398] Add QueueingMode to MultiplayerRoomSettings --- osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs index 001cf2aa93..596f50d54b 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs @@ -43,6 +43,9 @@ namespace osu.Game.Online.Multiplayer [Key(8)] public MatchType MatchType { get; set; } = MatchType.HeadToHead; + [Key(9)] + public QueueingModes QueueingMode { get; set; } = QueueingModes.Host; + public bool Equals(MultiplayerRoomSettings other) => BeatmapID == other.BeatmapID && BeatmapChecksum == other.BeatmapChecksum @@ -52,7 +55,8 @@ namespace osu.Game.Online.Multiplayer && Password.Equals(other.Password, StringComparison.Ordinal) && Name.Equals(other.Name, StringComparison.Ordinal) && PlaylistItemId == other.PlaylistItemId - && MatchType == other.MatchType; + && MatchType == other.MatchType + && QueueingMode == other.QueueingMode; public override string ToString() => $"Name:{Name}" + $" Beatmap:{BeatmapID} ({BeatmapChecksum})" @@ -61,6 +65,7 @@ namespace osu.Game.Online.Multiplayer + $" Password:{(string.IsNullOrEmpty(Password) ? "no" : "yes")}" + $" Ruleset:{RulesetID}" + $" Type:{MatchType}" - + $" Item:{PlaylistItemId}"; + + $" Item:{PlaylistItemId}" + + $" Queue:{QueueingMode}"; } } From 6cc81840418c0f463194c0474c555e2a0d09e3cb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 Oct 2021 20:17:43 +0900 Subject: [PATCH 0031/4398] Make test scene create room immediately --- .../TestSceneMultiplayerMatchSubScreen.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 21364fe154..9cf9cb7e06 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -62,19 +62,20 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestCreatedRoom() { - AddStep("set playlist", () => + AddStep("create room", () => { SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, }); - }); - AddStep("click create button", () => - { - InputManager.MoveMouseTo(this.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); + // Needs to run after components update with the playlist item. + Schedule(() => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); }); AddUntilStep("wait for join", () => Client.Room != null); From a70d7e4febeb0fb1fbab3e25544d580871d60bb4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 Oct 2021 20:18:25 +0900 Subject: [PATCH 0032/4398] Remove unnecessary extra GridContainer --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 174 +++++++++--------- 1 file changed, 82 insertions(+), 92 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 544eac4127..6302a6532e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -80,116 +80,106 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer }, 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(GridSizeMode.Relative, size: 0.5f, maxSize: 400), + new Dimension(), + new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 600), + }, + Content = new[] + { + new Drawable[] { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = 5, Vertical = 10 }, - Child = new GridContainer + // Main left column + new GridContainer { RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + RowDimensions = new[] { - new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 400), - new Dimension(), - new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 600), + new Dimension(GridSizeMode.AutoSize) }, Content = new[] { + new Drawable[] { new ParticipantsListHeader() }, new Drawable[] { - // Main left column - new GridContainer + new ParticipantsList { - RelativeSizeAxes = Axes.Both, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] { new ParticipantsListHeader() }, - new Drawable[] - { - new ParticipantsList - { - RelativeSizeAxes = Axes.Both - }, - } - } - }, - // Spacer - null, - // Main right column - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] { new OverlinedHeader("Beatmap") }, - new Drawable[] { new BeatmapSelectionControl { RelativeSizeAxes = Axes.X } }, - new[] - { - UserModsSection = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Top = 10 }, - Alpha = 0, - 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 OverlinedHeader("Chat") { Margin = new MarginPadding { Vertical = 5 }, }, }, - new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } } - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - } + RelativeSizeAxes = Axes.Both }, } } - } + }, + // Spacer + null, + // Main right column + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { new OverlinedHeader("Beatmap") }, + new Drawable[] { new BeatmapSelectionControl { RelativeSizeAxes = Axes.X } }, + new[] + { + UserModsSection = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 10 }, + Alpha = 0, + 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 OverlinedHeader("Chat") { Margin = new MarginPadding { Vertical = 5 }, }, }, + new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } } + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + } + }, } - }, - }, + } + } }; protected override Drawable CreateFooter() => new MultiplayerMatchFooter From 2fd101ad50eee95a5331caf0be6fdcbe1057efd4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 Oct 2021 20:25:32 +0900 Subject: [PATCH 0033/4398] Reorder elements to follow designs --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 6302a6532e..33df9fe2fa 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -89,15 +89,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer RelativeSizeAxes = Axes.Both, ColumnDimensions = new[] { - new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 400), new Dimension(), - new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 600), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(), }, Content = new[] { new Drawable[] { - // Main left column + // Participants column new GridContainer { RelativeSizeAxes = Axes.Both, @@ -119,7 +121,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer }, // Spacer null, - // Main right column + // Beatmap column new GridContainer { RelativeSizeAxes = Axes.Both, @@ -165,14 +167,27 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } }, }, - new Drawable[] { new OverlinedHeader("Chat") { Margin = new MarginPadding { Vertical = 5 }, }, }, - new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } } }, RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), new Dimension(GridSizeMode.AutoSize), new Dimension(GridSizeMode.AutoSize), + } + }, + // 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(), } From ea9c070e597e39d5c3c6bed5c849782e1e8cbd95 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 Oct 2021 14:51:59 +0900 Subject: [PATCH 0034/4398] Add setting for queue mode --- .../Online/Multiplayer/MultiplayerRoomSettings.cs | 6 +++--- .../Multiplayer/{QueueingModes.cs => QueueModes.cs} | 6 ++++-- osu.Game/Online/Rooms/Room.cs | 13 +++++++++++++ .../Match/MultiplayerMatchSettingsOverlay.cs | 8 ++++++++ 4 files changed, 28 insertions(+), 5 deletions(-) rename osu.Game/Online/Multiplayer/{QueueingModes.cs => QueueModes.cs} (64%) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs index 596f50d54b..d244e69a6b 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs @@ -44,7 +44,7 @@ namespace osu.Game.Online.Multiplayer public MatchType MatchType { get; set; } = MatchType.HeadToHead; [Key(9)] - public QueueingModes QueueingMode { get; set; } = QueueingModes.Host; + public QueueModes QueueMode { get; set; } = QueueModes.HostPick; public bool Equals(MultiplayerRoomSettings other) => BeatmapID == other.BeatmapID @@ -56,7 +56,7 @@ namespace osu.Game.Online.Multiplayer && Name.Equals(other.Name, StringComparison.Ordinal) && PlaylistItemId == other.PlaylistItemId && MatchType == other.MatchType - && QueueingMode == other.QueueingMode; + && QueueMode == other.QueueMode; public override string ToString() => $"Name:{Name}" + $" Beatmap:{BeatmapID} ({BeatmapChecksum})" @@ -66,6 +66,6 @@ namespace osu.Game.Online.Multiplayer + $" Ruleset:{RulesetID}" + $" Type:{MatchType}" + $" Item:{PlaylistItemId}" - + $" Queue:{QueueingMode}"; + + $" Queue:{QueueMode}"; } } diff --git a/osu.Game/Online/Multiplayer/QueueingModes.cs b/osu.Game/Online/Multiplayer/QueueModes.cs similarity index 64% rename from osu.Game/Online/Multiplayer/QueueingModes.cs rename to osu.Game/Online/Multiplayer/QueueModes.cs index 684768fa57..501c87c1cf 100644 --- a/osu.Game/Online/Multiplayer/QueueingModes.cs +++ b/osu.Game/Online/Multiplayer/QueueModes.cs @@ -3,9 +3,11 @@ namespace osu.Game.Online.Multiplayer { - public enum QueueingModes + public enum QueueModes { - Host, + // used for osu-web deserialization so names shouldn't be changed. + + HostPick, Karaoke, } } diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 39fc7f1da8..fe410208cc 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -7,6 +7,7 @@ using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.IO.Serialization.Converters; +using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Users; using osu.Game.Utils; @@ -73,6 +74,18 @@ namespace osu.Game.Online.Rooms set => Type.Value = value; } + [Cached] + [JsonIgnore] + public readonly Bindable QueueMode = new Bindable(); + + [JsonConverter(typeof(SnakeCaseStringEnumConverter))] + [JsonProperty("queue_mode")] + private QueueModes queueMode + { + get => QueueMode.Value; + set => QueueMode.Value = value; + } + [Cached] [JsonIgnore] public readonly Bindable MaxParticipants = new Bindable(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 0edf5dde6d..9049d315fb 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -59,6 +59,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match public OsuTextBox NameField, MaxParticipantsField; public RoomAvailabilityPicker AvailabilityPicker; public MatchTypePicker TypePicker; + public OsuEnumDropdown QueueModeDropdown; public OsuTextBox PasswordTextBox; public TriangleButton ApplyButton; @@ -190,6 +191,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match }, }, }, + new Section("Beatmap queueing mode") + { + Child = QueueModeDropdown = new OsuEnumDropdown + { + RelativeSizeAxes = Axes.X + } + } }, }, new SectionContainer From 0b8edb081278561854bb16808b7c88e175f9094d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 Oct 2021 17:50:02 +0900 Subject: [PATCH 0035/4398] Add english descriptions --- osu.Game/Online/Multiplayer/QueueModes.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Online/Multiplayer/QueueModes.cs b/osu.Game/Online/Multiplayer/QueueModes.cs index 501c87c1cf..2a650c61dc 100644 --- a/osu.Game/Online/Multiplayer/QueueModes.cs +++ b/osu.Game/Online/Multiplayer/QueueModes.cs @@ -1,13 +1,18 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.ComponentModel; + namespace osu.Game.Online.Multiplayer { public enum QueueModes { // used for osu-web deserialization so names shouldn't be changed. + [Description("Host pick")] HostPick, + + [Description("Karaoke")] Karaoke, } } From bc57190fb41b0db23483f76aba0c5d58fceabcf9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 Oct 2021 17:52:05 +0900 Subject: [PATCH 0036/4398] Make dropdown not move the selection control --- .../Match/MultiplayerMatchSettingsOverlay.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 9049d315fb..a36034a026 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -193,9 +193,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match }, new Section("Beatmap queueing mode") { - Child = QueueModeDropdown = new OsuEnumDropdown + Child = new Container { - RelativeSizeAxes = Axes.X + RelativeSizeAxes = Axes.X, + Height = 40, + Child = QueueModeDropdown = new OsuEnumDropdown + { + RelativeSizeAxes = Axes.X + } } } }, @@ -235,7 +240,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X, - Width = 0.5f + Width = 0.5f, + Depth = float.MaxValue } } } From 0e446105fbb6c9bc9cd0c906d8ffd0a2161c524a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 Oct 2021 17:52:51 +0900 Subject: [PATCH 0037/4398] Rename to "picking mode" as simpler english --- .../Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index a36034a026..fa9e2834c1 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -191,7 +191,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match }, }, }, - new Section("Beatmap queueing mode") + new Section("Picking mode") { Child = new Container { From 599867a3b172206e0bd321513d634149d119511c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 Oct 2021 20:25:06 +0900 Subject: [PATCH 0038/4398] Add implementation to settings overlay --- .../Online/Multiplayer/MultiplayerClient.cs | 5 ++++- .../Match/MultiplayerMatchSettingsOverlay.cs | 20 ++++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 28505f6b0e..7701e217c6 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -212,7 +212,9 @@ namespace osu.Game.Online.Multiplayer /// The new password, if any. /// The type of the match, if any. /// The new room playlist item, if any. - public Task ChangeSettings(Optional name = default, Optional password = default, Optional matchType = default, Optional item = default) + /// The new queue mode, if any. + public Task ChangeSettings(Optional name = default, Optional password = default, Optional matchType = default, Optional item = default, + Optional queueMode = default) { if (Room == null) throw new InvalidOperationException("Must be joined to a match to change settings."); @@ -239,6 +241,7 @@ namespace osu.Game.Online.Multiplayer BeatmapChecksum = item.GetOr(existingPlaylistItem).Beatmap.Value.MD5Hash, RulesetID = item.GetOr(existingPlaylistItem).RulesetID, MatchType = matchType.GetOr(Room.Settings.MatchType), + QueueMode = queueMode.GetOr(Room.Settings.QueueMode), RequiredMods = item.HasValue ? item.Value.AsNonNull().RequiredMods.Select(m => new APIMod(m)).ToList() : Room.Settings.RequiredMods, AllowedMods = item.HasValue ? item.Value.AsNonNull().AllowedMods.Select(m => new APIMod(m)).ToList() : Room.Settings.AllowedMods, }); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index fa9e2834c1..33c033486e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -339,13 +339,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match // Otherwise, update the room directly in preparation for it to be submitted to the API on match creation. if (client.Room != null) { - client.ChangeSettings(name: NameField.Text, password: PasswordTextBox.Text, matchType: TypePicker.Current.Value).ContinueWith(t => Schedule(() => - { - if (t.IsCompletedSuccessfully) - onSuccess(room); - else - onError(t.Exception?.AsSingular().Message ?? "Error changing settings."); - })); + client.ChangeSettings( + name: NameField.Text, + password: PasswordTextBox.Text, + matchType: TypePicker.Current.Value, + queueMode: QueueModeDropdown.Current.Value) + .ContinueWith(t => Schedule(() => + { + if (t.IsCompletedSuccessfully) + onSuccess(room); + else + onError(t.Exception?.AsSingular().Message ?? "Error changing settings."); + })); } else { @@ -353,6 +358,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match room.Availability.Value = AvailabilityPicker.Current.Value; room.Type.Value = TypePicker.Current.Value; room.Password.Value = PasswordTextBox.Current.Value; + room.QueueMode.Value = QueueModeDropdown.Current.Value; if (int.TryParse(MaxParticipantsField.Text, out int max)) room.MaxParticipants.Value = max; From 67090fc59845b17a41bba2f2555e11db0c0498bf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 Oct 2021 20:52:16 +0900 Subject: [PATCH 0039/4398] Re-namespace enum --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 1 + osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs | 1 + osu.Game/Online/Multiplayer/{ => Queueing}/QueueModes.cs | 2 +- osu.Game/Online/Rooms/Room.cs | 2 +- .../Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs | 1 + 5 files changed, 5 insertions(+), 2 deletions(-) rename osu.Game/Online/Multiplayer/{ => Queueing}/QueueModes.cs (89%) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 7701e217c6..31fa7b1e0d 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -17,6 +17,7 @@ using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Online.API; +using osu.Game.Online.Multiplayer.Queueing; using osu.Game.Online.Rooms; using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Rulesets; diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs index d244e69a6b..af8ee96693 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Linq; using MessagePack; using osu.Game.Online.API; +using osu.Game.Online.Multiplayer.Queueing; using osu.Game.Online.Rooms; namespace osu.Game.Online.Multiplayer diff --git a/osu.Game/Online/Multiplayer/QueueModes.cs b/osu.Game/Online/Multiplayer/Queueing/QueueModes.cs similarity index 89% rename from osu.Game/Online/Multiplayer/QueueModes.cs rename to osu.Game/Online/Multiplayer/Queueing/QueueModes.cs index 2a650c61dc..cb1e765891 100644 --- a/osu.Game/Online/Multiplayer/QueueModes.cs +++ b/osu.Game/Online/Multiplayer/Queueing/QueueModes.cs @@ -3,7 +3,7 @@ using System.ComponentModel; -namespace osu.Game.Online.Multiplayer +namespace osu.Game.Online.Multiplayer.Queueing { public enum QueueModes { diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index fe410208cc..875cdeeb8c 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -7,7 +7,7 @@ using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.IO.Serialization.Converters; -using osu.Game.Online.Multiplayer; +using osu.Game.Online.Multiplayer.Queueing; using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Users; using osu.Game.Utils; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 33c033486e..4231aeae87 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -17,6 +17,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Multiplayer.Queueing; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Rulesets; From 691e414acb514133fc23eeb727f67ffe74fa2b21 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 Oct 2021 21:08:58 +0900 Subject: [PATCH 0040/4398] Add request to add a new playlist item --- .../Online/Multiplayer/MatchUserRequest.cs | 2 ++ .../Online/Multiplayer/MultiplayerClient.cs | 24 +-------------- .../Queueing/AddPlaylistItemRequest.cs | 30 +++++++++++++++++++ .../Online/SignalRUnionWorkaroundResolver.cs | 2 ++ .../Multiplayer/MultiplayerMatchSongSelect.cs | 12 +++++++- 5 files changed, 46 insertions(+), 24 deletions(-) create mode 100644 osu.Game/Online/Multiplayer/Queueing/AddPlaylistItemRequest.cs diff --git a/osu.Game/Online/Multiplayer/MatchUserRequest.cs b/osu.Game/Online/Multiplayer/MatchUserRequest.cs index 8c6809e7f3..96e2bb3c15 100644 --- a/osu.Game/Online/Multiplayer/MatchUserRequest.cs +++ b/osu.Game/Online/Multiplayer/MatchUserRequest.cs @@ -4,6 +4,7 @@ using System; using MessagePack; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; +using osu.Game.Online.Multiplayer.Queueing; namespace osu.Game.Online.Multiplayer { @@ -13,6 +14,7 @@ namespace osu.Game.Online.Multiplayer [Serializable] [MessagePackObject] [Union(0, typeof(ChangeTeamRequest))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types. + [Union(1, typeof(AddPlaylistItemRequest))] public abstract class MatchUserRequest { } diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 31fa7b1e0d..eb581ad3fd 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -11,7 +11,6 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Beatmaps; @@ -212,39 +211,18 @@ namespace osu.Game.Online.Multiplayer /// The new room name, if any. /// The new password, if any. /// The type of the match, if any. - /// The new room playlist item, if any. /// The new queue mode, if any. - public Task ChangeSettings(Optional name = default, Optional password = default, Optional matchType = default, Optional item = default, - Optional queueMode = default) + public Task ChangeSettings(Optional name = default, Optional password = default, Optional matchType = default, Optional queueMode = default) { if (Room == null) throw new InvalidOperationException("Must be joined to a match to change settings."); - // A dummy playlist item filled with the current room settings (except mods). - var existingPlaylistItem = new PlaylistItem - { - Beatmap = - { - Value = new BeatmapInfo - { - OnlineBeatmapID = Room.Settings.BeatmapID, - MD5Hash = Room.Settings.BeatmapChecksum - } - }, - RulesetID = Room.Settings.RulesetID - }; - return ChangeSettings(new MultiplayerRoomSettings { Name = name.GetOr(Room.Settings.Name), Password = password.GetOr(Room.Settings.Password), - BeatmapID = item.GetOr(existingPlaylistItem).BeatmapID, - BeatmapChecksum = item.GetOr(existingPlaylistItem).Beatmap.Value.MD5Hash, - RulesetID = item.GetOr(existingPlaylistItem).RulesetID, MatchType = matchType.GetOr(Room.Settings.MatchType), QueueMode = queueMode.GetOr(Room.Settings.QueueMode), - RequiredMods = item.HasValue ? item.Value.AsNonNull().RequiredMods.Select(m => new APIMod(m)).ToList() : Room.Settings.RequiredMods, - AllowedMods = item.HasValue ? item.Value.AsNonNull().AllowedMods.Select(m => new APIMod(m)).ToList() : Room.Settings.AllowedMods, }); } diff --git a/osu.Game/Online/Multiplayer/Queueing/AddPlaylistItemRequest.cs b/osu.Game/Online/Multiplayer/Queueing/AddPlaylistItemRequest.cs new file mode 100644 index 0000000000..427461e733 --- /dev/null +++ b/osu.Game/Online/Multiplayer/Queueing/AddPlaylistItemRequest.cs @@ -0,0 +1,30 @@ +// 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.Collections.Generic; +using System.Linq; +using MessagePack; +using osu.Game.Online.API; + +namespace osu.Game.Online.Multiplayer.Queueing +{ + public class AddPlaylistItemRequest : MatchUserRequest + { + [Key(0)] + public int BeatmapID { get; set; } + + [Key(1)] + public int RulesetID { get; set; } + + [Key(2)] + public string BeatmapChecksum { get; set; } = string.Empty; + + [Key(3)] + public IEnumerable RequiredMods { get; set; } = Enumerable.Empty(); + + [Key(4)] + public IEnumerable AllowedMods { get; set; } = Enumerable.Empty(); + } +} diff --git a/osu.Game/Online/SignalRUnionWorkaroundResolver.cs b/osu.Game/Online/SignalRUnionWorkaroundResolver.cs index e44da044cc..ff4505c450 100644 --- a/osu.Game/Online/SignalRUnionWorkaroundResolver.cs +++ b/osu.Game/Online/SignalRUnionWorkaroundResolver.cs @@ -8,6 +8,7 @@ using MessagePack.Formatters; using MessagePack.Resolvers; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; +using osu.Game.Online.Multiplayer.Queueing; namespace osu.Game.Online { @@ -25,6 +26,7 @@ namespace osu.Game.Online { typeof(TeamVersusUserState), new TypeRedirectingFormatter() }, { typeof(TeamVersusRoomState), new TypeRedirectingFormatter() }, { typeof(ChangeTeamRequest), new TypeRedirectingFormatter() }, + { typeof(AddPlaylistItemRequest), new TypeRedirectingFormatter() }, // These 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. diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index ad4bb90551..31ee09bdcd 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -1,12 +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.Linq; using osu.Framework.Allocation; 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.Multiplayer.Queueing; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -54,7 +57,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { loadingLayer.Show(); - client.ChangeSettings(item: item).ContinueWith(t => + client.SendMatchRequest(new AddPlaylistItemRequest + { + BeatmapID = item.BeatmapID, + RulesetID = item.RulesetID, + BeatmapChecksum = item.Beatmap.Value.MD5Hash, + RequiredMods = item.RequiredMods.Select(m => new APIMod(m)).ToArray(), + AllowedMods = item.AllowedMods.Select(m => new APIMod(m)).ToArray() + }).ContinueWith(t => { Schedule(() => { From f41cf822b062cf7936d48d701ff65e4f99c10633 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 Oct 2021 17:02:11 +0900 Subject: [PATCH 0041/4398] Rename request --- osu.Game/Online/Multiplayer/MatchUserRequest.cs | 2 +- ...{AddPlaylistItemRequest.cs => EnqueuePlaylistItemRequest.cs} | 2 +- osu.Game/Online/SignalRUnionWorkaroundResolver.cs | 2 +- .../OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Online/Multiplayer/Queueing/{AddPlaylistItemRequest.cs => EnqueuePlaylistItemRequest.cs} (92%) diff --git a/osu.Game/Online/Multiplayer/MatchUserRequest.cs b/osu.Game/Online/Multiplayer/MatchUserRequest.cs index 96e2bb3c15..1656823b05 100644 --- a/osu.Game/Online/Multiplayer/MatchUserRequest.cs +++ b/osu.Game/Online/Multiplayer/MatchUserRequest.cs @@ -14,7 +14,7 @@ namespace osu.Game.Online.Multiplayer [Serializable] [MessagePackObject] [Union(0, typeof(ChangeTeamRequest))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types. - [Union(1, typeof(AddPlaylistItemRequest))] + [Union(1, typeof(EnqueuePlaylistItemRequest))] public abstract class MatchUserRequest { } diff --git a/osu.Game/Online/Multiplayer/Queueing/AddPlaylistItemRequest.cs b/osu.Game/Online/Multiplayer/Queueing/EnqueuePlaylistItemRequest.cs similarity index 92% rename from osu.Game/Online/Multiplayer/Queueing/AddPlaylistItemRequest.cs rename to osu.Game/Online/Multiplayer/Queueing/EnqueuePlaylistItemRequest.cs index 427461e733..4c66311f68 100644 --- a/osu.Game/Online/Multiplayer/Queueing/AddPlaylistItemRequest.cs +++ b/osu.Game/Online/Multiplayer/Queueing/EnqueuePlaylistItemRequest.cs @@ -10,7 +10,7 @@ using osu.Game.Online.API; namespace osu.Game.Online.Multiplayer.Queueing { - public class AddPlaylistItemRequest : MatchUserRequest + public class EnqueuePlaylistItemRequest : MatchUserRequest { [Key(0)] public int BeatmapID { get; set; } diff --git a/osu.Game/Online/SignalRUnionWorkaroundResolver.cs b/osu.Game/Online/SignalRUnionWorkaroundResolver.cs index ff4505c450..ab393eadfe 100644 --- a/osu.Game/Online/SignalRUnionWorkaroundResolver.cs +++ b/osu.Game/Online/SignalRUnionWorkaroundResolver.cs @@ -26,7 +26,7 @@ namespace osu.Game.Online { typeof(TeamVersusUserState), new TypeRedirectingFormatter() }, { typeof(TeamVersusRoomState), new TypeRedirectingFormatter() }, { typeof(ChangeTeamRequest), new TypeRedirectingFormatter() }, - { typeof(AddPlaylistItemRequest), new TypeRedirectingFormatter() }, + { typeof(EnqueuePlaylistItemRequest), new TypeRedirectingFormatter() }, // These 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. diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 31ee09bdcd..eb5252b3e0 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { loadingLayer.Show(); - client.SendMatchRequest(new AddPlaylistItemRequest + client.SendMatchRequest(new EnqueuePlaylistItemRequest { BeatmapID = item.BeatmapID, RulesetID = item.RulesetID, From a1c9b56083f1b7360343de2e3b74a357c7592417 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 22 Oct 2021 12:39:48 +0900 Subject: [PATCH 0042/4398] Rename modes --- .../Online/Multiplayer/MultiplayerRoomSettings.cs | 2 +- osu.Game/Online/Multiplayer/Queueing/QueueModes.cs | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs index af8ee96693..91042d3a08 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs @@ -45,7 +45,7 @@ namespace osu.Game.Online.Multiplayer public MatchType MatchType { get; set; } = MatchType.HeadToHead; [Key(9)] - public QueueModes QueueMode { get; set; } = QueueModes.HostPick; + public QueueModes QueueMode { get; set; } = QueueModes.HostOnly; public bool Equals(MultiplayerRoomSettings other) => BeatmapID == other.BeatmapID diff --git a/osu.Game/Online/Multiplayer/Queueing/QueueModes.cs b/osu.Game/Online/Multiplayer/Queueing/QueueModes.cs index cb1e765891..0ead8d2f06 100644 --- a/osu.Game/Online/Multiplayer/Queueing/QueueModes.cs +++ b/osu.Game/Online/Multiplayer/Queueing/QueueModes.cs @@ -9,10 +9,13 @@ namespace osu.Game.Online.Multiplayer.Queueing { // used for osu-web deserialization so names shouldn't be changed. - [Description("Host pick")] - HostPick, + [Description("Host only")] + HostOnly, - [Description("Karaoke")] - Karaoke, + [Description("Free-for-all")] + FreeForAll, + + [Description("Fair rotate")] + FairRotate } } From db87e42d4784cf63389f86289827769ee3ea570a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 22 Oct 2021 16:48:28 +0900 Subject: [PATCH 0043/4398] Remove beatmap/ruleset/etc from MultiplayerRoomSettings --- .../Online/Multiplayer/IMultiplayerClient.cs | 12 ++ .../Multiplayer/IMultiplayerRoomServer.cs | 12 ++ .../Online/Multiplayer/MatchUserRequest.cs | 2 - .../Online/Multiplayer/MultiplayerClient.cs | 116 +++++++++--------- .../Multiplayer/MultiplayerRoomSettings.cs | 37 +----- .../Multiplayer/OnlineMultiplayerClient.cs | 18 +++ .../APIPlaylistItem.cs} | 27 +++- .../Online/SignalRUnionWorkaroundResolver.cs | 2 - .../Multiplayer/MultiplayerMatchSongSelect.cs | 4 +- .../Participants/ParticipantPanel.cs | 3 +- .../Multiplayer/TestMultiplayerClient.cs | 55 ++++++++- 11 files changed, 180 insertions(+), 108 deletions(-) rename osu.Game/Online/{Multiplayer/Queueing/EnqueuePlaylistItemRequest.cs => Rooms/APIPlaylistItem.cs} (54%) diff --git a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs index 8f16d22c4c..3eafb31510 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs @@ -106,5 +106,17 @@ namespace osu.Game.Online.Multiplayer /// Signals that the match has ended, all players have finished and results are ready to be displayed. /// Task ResultsReady(); + + /// + /// Signals that an item has been added to the playlist. + /// + /// The added item. + Task PlaylistItemAdded(APIPlaylistItem item); + + /// + /// Signals that an item has been removed from the playlist. + /// + /// The removed item. + Task PlaylistItemRemoved(APIPlaylistItem item); } } diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index da637c229f..1f2e45b482 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs @@ -76,5 +76,17 @@ namespace osu.Game.Online.Multiplayer /// If the user is not in a room. /// If an attempt to start the game occurs when the game's (or users') state disallows it. Task StartMatch(); + + /// + /// Adds an item to the playlist. + /// + /// The item to add. + Task AddPlaylistItem(APIPlaylistItem item); + + /// + /// Removes an item from the playlist. + /// + /// The item to remove. + Task RemovePlaylistItem(APIPlaylistItem item); } } diff --git a/osu.Game/Online/Multiplayer/MatchUserRequest.cs b/osu.Game/Online/Multiplayer/MatchUserRequest.cs index 1656823b05..8c6809e7f3 100644 --- a/osu.Game/Online/Multiplayer/MatchUserRequest.cs +++ b/osu.Game/Online/Multiplayer/MatchUserRequest.cs @@ -4,7 +4,6 @@ using System; using MessagePack; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; -using osu.Game.Online.Multiplayer.Queueing; namespace osu.Game.Online.Multiplayer { @@ -14,7 +13,6 @@ namespace osu.Game.Online.Multiplayer [Serializable] [MessagePackObject] [Union(0, typeof(ChangeTeamRequest))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types. - [Union(1, typeof(EnqueuePlaylistItemRequest))] public abstract class MatchUserRequest { } diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index eb581ad3fd..72ac13c2f2 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -301,6 +301,10 @@ namespace osu.Game.Online.Multiplayer public abstract Task StartMatch(); + public abstract Task AddPlaylistItem(APIPlaylistItem item); + + public abstract Task RemovePlaylistItem(APIPlaylistItem item); + Task IMultiplayerClient.RoomStateChanged(MultiplayerRoomState state) { if (Room == null) @@ -416,11 +420,7 @@ namespace osu.Game.Online.Multiplayer return Task.CompletedTask; } - Task IMultiplayerClient.SettingsChanged(MultiplayerRoomSettings newSettings) - { - updateLocalRoomSettings(newSettings); - return Task.CompletedTask; - } + Task IMultiplayerClient.SettingsChanged(MultiplayerRoomSettings newSettings) => updateLocalRoomSettings(newSettings); Task IMultiplayerClient.UserStateChanged(int userId, MultiplayerUserState state) { @@ -572,6 +572,59 @@ namespace osu.Game.Online.Multiplayer return Task.CompletedTask; } + public async Task PlaylistItemAdded(APIPlaylistItem item) + { + if (Room == null) + return; + + var set = await GetOnlineBeatmapSet(item.BeatmapID).ConfigureAwait(false); + + var beatmap = set.Beatmaps.Single(b => b.OnlineBeatmapID == item.BeatmapID); + beatmap.MD5Hash = item.BeatmapChecksum; + + var ruleset = Rulesets.GetRuleset(item.RulesetID); + + await scheduleAsync(() => + { + if (Room == null) + return; + + Debug.Assert(APIRoom != null); + + var playlistItem = new PlaylistItem + { + ID = item.ID, + Beatmap = { Value = beatmap }, + Ruleset = { Value = ruleset }, + }; + + var rulesetInstance = ruleset.CreateInstance(); + + playlistItem.RequiredMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance))); + playlistItem.AllowedMods.AddRange(item.AllowedMods.Select(m => m.ToMod(rulesetInstance))); + + APIRoom.Playlist.Add(playlistItem); + RoomUpdated?.Invoke(); + }).ConfigureAwait(false); + } + + public Task PlaylistItemRemoved(APIPlaylistItem item) + { + if (Room == null) + return Task.CompletedTask; + + return scheduleAsync(() => + { + if (Room == null) + return; + + Debug.Assert(APIRoom != null); + + APIRoom.Playlist.RemoveAll(i => i.ID == item.ID); + RoomUpdated?.Invoke(); + }); + } + /// /// Populates the for a given . /// @@ -597,62 +650,11 @@ namespace osu.Game.Online.Multiplayer Room.Settings = settings; APIRoom.Name.Value = Room.Settings.Name; APIRoom.Password.Value = Room.Settings.Password; - - // The current item update is delayed until an online beatmap lookup (below) succeeds. - // In-order for the client to not display an outdated beatmap, the current item is forcefully cleared here. - CurrentMatchPlayingItem.Value = null; - RoomUpdated?.Invoke(); - GetOnlineBeatmapSet(settings.BeatmapID, cancellationToken).ContinueWith(set => Schedule(() => - { - if (cancellationToken.IsCancellationRequested) - return; - - updatePlaylist(settings, set.Result); - }), TaskContinuationOptions.OnlyOnRanToCompletion); + CurrentMatchPlayingItem.Value = APIRoom.Playlist.SingleOrDefault(p => p.ID == settings.PlaylistItemId); }, cancellationToken); - private void updatePlaylist(MultiplayerRoomSettings settings, BeatmapSetInfo beatmapSet) - { - if (Room == null || !Room.Settings.Equals(settings)) - return; - - Debug.Assert(APIRoom != null); - - var beatmap = beatmapSet.Beatmaps.Single(b => b.OnlineBeatmapID == settings.BeatmapID); - beatmap.MD5Hash = settings.BeatmapChecksum; - - var ruleset = Rulesets.GetRuleset(settings.RulesetID).CreateInstance(); - var mods = settings.RequiredMods.Select(m => m.ToMod(ruleset)); - var allowedMods = settings.AllowedMods.Select(m => m.ToMod(ruleset)); - - // Try to retrieve the existing playlist item from the API room. - var playlistItem = APIRoom.Playlist.FirstOrDefault(i => i.ID == settings.PlaylistItemId); - - if (playlistItem != null) - updateItem(playlistItem); - else - { - // An existing playlist item does not exist, so append a new one. - updateItem(playlistItem = new PlaylistItem()); - APIRoom.Playlist.Add(playlistItem); - } - - CurrentMatchPlayingItem.Value = playlistItem; - - void updateItem(PlaylistItem item) - { - item.ID = settings.PlaylistItemId; - item.Beatmap.Value = beatmap; - item.Ruleset.Value = ruleset.RulesetInfo; - item.RequiredMods.Clear(); - item.RequiredMods.AddRange(mods); - item.AllowedMods.Clear(); - item.AllowedMods.AddRange(allowedMods); - } - } - /// /// Retrieves a from an online source. /// diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs index 91042d3a08..70e884dcce 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs @@ -4,10 +4,7 @@ #nullable enable using System; -using System.Collections.Generic; -using System.Linq; using MessagePack; -using osu.Game.Online.API; using osu.Game.Online.Multiplayer.Queueing; using osu.Game.Online.Rooms; @@ -18,53 +15,29 @@ namespace osu.Game.Online.Multiplayer public class MultiplayerRoomSettings : IEquatable { [Key(0)] - public int BeatmapID { get; set; } - - [Key(1)] - public int RulesetID { get; set; } - - [Key(2)] - public string BeatmapChecksum { get; set; } = string.Empty; - - [Key(3)] public string Name { get; set; } = "Unnamed room"; - [Key(4)] - public IEnumerable RequiredMods { get; set; } = Enumerable.Empty(); - - [Key(5)] - public IEnumerable AllowedMods { get; set; } = Enumerable.Empty(); - - [Key(6)] + [Key(1)] public long PlaylistItemId { get; set; } - [Key(7)] + [Key(2)] public string Password { get; set; } = string.Empty; - [Key(8)] + [Key(3)] public MatchType MatchType { get; set; } = MatchType.HeadToHead; - [Key(9)] + [Key(4)] public QueueModes QueueMode { get; set; } = QueueModes.HostOnly; public bool Equals(MultiplayerRoomSettings other) - => BeatmapID == other.BeatmapID - && BeatmapChecksum == other.BeatmapChecksum - && RequiredMods.SequenceEqual(other.RequiredMods) - && AllowedMods.SequenceEqual(other.AllowedMods) - && RulesetID == other.RulesetID - && Password.Equals(other.Password, StringComparison.Ordinal) + => Password.Equals(other.Password, StringComparison.Ordinal) && Name.Equals(other.Name, StringComparison.Ordinal) && PlaylistItemId == other.PlaylistItemId && MatchType == other.MatchType && QueueMode == other.QueueMode; public override string ToString() => $"Name:{Name}" - + $" Beatmap:{BeatmapID} ({BeatmapChecksum})" - + $" RequiredMods:{string.Join(',', RequiredMods)}" - + $" AllowedMods:{string.Join(',', AllowedMods)}" + $" Password:{(string.IsNullOrEmpty(Password) ? "no" : "yes")}" - + $" Ruleset:{RulesetID}" + $" Type:{MatchType}" + $" Item:{PlaylistItemId}" + $" Queue:{QueueMode}"; diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 965674c2f2..fd57b42000 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -62,6 +62,8 @@ namespace osu.Game.Online.Multiplayer connection.On(nameof(IMultiplayerClient.MatchRoomStateChanged), ((IMultiplayerClient)this).MatchRoomStateChanged); connection.On(nameof(IMultiplayerClient.MatchUserStateChanged), ((IMultiplayerClient)this).MatchUserStateChanged); connection.On(nameof(IMultiplayerClient.MatchEvent), ((IMultiplayerClient)this).MatchEvent); + connection.On(nameof(IMultiplayerClient.PlaylistItemAdded), ((IMultiplayerClient)this).PlaylistItemAdded); + connection.On(nameof(IMultiplayerClient.PlaylistItemRemoved), ((IMultiplayerClient)this).PlaylistItemRemoved); }; IsConnected.BindTo(connector.IsConnected); @@ -148,6 +150,22 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.StartMatch)); } + public override Task AddPlaylistItem(APIPlaylistItem item) + { + if (!IsConnected.Value) + return Task.CompletedTask; + + return connection.InvokeAsync(nameof(IMultiplayerServer.AddPlaylistItem), item); + } + + public override Task RemovePlaylistItem(APIPlaylistItem item) + { + if (!IsConnected.Value) + return Task.CompletedTask; + + return connection.InvokeAsync(nameof(IMultiplayerServer.RemovePlaylistItem), item); + } + protected override Task GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default) { var tcs = new TaskCompletionSource(); diff --git a/osu.Game/Online/Multiplayer/Queueing/EnqueuePlaylistItemRequest.cs b/osu.Game/Online/Rooms/APIPlaylistItem.cs similarity index 54% rename from osu.Game/Online/Multiplayer/Queueing/EnqueuePlaylistItemRequest.cs rename to osu.Game/Online/Rooms/APIPlaylistItem.cs index 4c66311f68..121d661c65 100644 --- a/osu.Game/Online/Multiplayer/Queueing/EnqueuePlaylistItemRequest.cs +++ b/osu.Game/Online/Rooms/APIPlaylistItem.cs @@ -8,23 +8,40 @@ using System.Linq; using MessagePack; using osu.Game.Online.API; -namespace osu.Game.Online.Multiplayer.Queueing +namespace osu.Game.Online.Rooms { - public class EnqueuePlaylistItemRequest : MatchUserRequest + public class APIPlaylistItem { [Key(0)] - public int BeatmapID { get; set; } + public long ID { get; set; } [Key(1)] - public int RulesetID { get; set; } + public int BeatmapID { get; set; } [Key(2)] public string BeatmapChecksum { get; set; } = string.Empty; [Key(3)] - public IEnumerable RequiredMods { get; set; } = Enumerable.Empty(); + public int RulesetID { get; set; } [Key(4)] + public IEnumerable RequiredMods { get; set; } = Enumerable.Empty(); + + [Key(5)] public IEnumerable AllowedMods { get; set; } = Enumerable.Empty(); + + public APIPlaylistItem() + { + } + + public APIPlaylistItem(PlaylistItem item) + { + ID = item.ID; + 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(); + } } } diff --git a/osu.Game/Online/SignalRUnionWorkaroundResolver.cs b/osu.Game/Online/SignalRUnionWorkaroundResolver.cs index ab393eadfe..e44da044cc 100644 --- a/osu.Game/Online/SignalRUnionWorkaroundResolver.cs +++ b/osu.Game/Online/SignalRUnionWorkaroundResolver.cs @@ -8,7 +8,6 @@ using MessagePack.Formatters; using MessagePack.Resolvers; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; -using osu.Game.Online.Multiplayer.Queueing; namespace osu.Game.Online { @@ -26,7 +25,6 @@ namespace osu.Game.Online { typeof(TeamVersusUserState), new TypeRedirectingFormatter() }, { typeof(TeamVersusRoomState), new TypeRedirectingFormatter() }, { typeof(ChangeTeamRequest), new TypeRedirectingFormatter() }, - { typeof(EnqueuePlaylistItemRequest), new TypeRedirectingFormatter() }, // These 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. diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index eb5252b3e0..df1e0c1fc2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -9,7 +9,6 @@ using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; -using osu.Game.Online.Multiplayer.Queueing; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -57,11 +56,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { loadingLayer.Show(); - client.SendMatchRequest(new EnqueuePlaylistItemRequest + client.AddPlaylistItem(new APIPlaylistItem { BeatmapID = item.BeatmapID, RulesetID = item.RulesetID, - BeatmapChecksum = item.Beatmap.Value.MD5Hash, RequiredMods = item.RequiredMods.Select(m => new APIMod(m)).ToArray(), AllowedMods = item.AllowedMods.Select(m => new APIMod(m)).ToArray() }).ContinueWith(t => diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 79e305b765..96213f1444 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -184,7 +184,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants const double fade_time = 50; - var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID).CreateInstance(); + // Todo: FIX THIS + var ruleset = rulesets.GetRuleset(0).CreateInstance(); var currentModeRank = User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank; userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty; diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 5e4e5942d9..7606f6c456 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -15,6 +15,7 @@ using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; +using osu.Game.Online.Multiplayer.Queueing; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Mods; using osu.Game.Users; @@ -41,6 +42,8 @@ namespace osu.Game.Tests.Visual.Multiplayer private readonly TestRequestHandlingMultiplayerRoomManager roomManager; + private long lastPlaylistItemId; + public TestMultiplayerClient(TestRequestHandlingMultiplayerRoomManager roomManager) { this.roomManager = roomManager; @@ -141,6 +144,8 @@ namespace osu.Game.Tests.Visual.Multiplayer if (password != apiRoom.Password.Value) throw new InvalidOperationException("Invalid password."); + lastPlaylistItemId = apiRoom.Playlist.Last().ID; + var localUser = new MultiplayerRoomUser(api.LocalUser.Value.Id) { User = api.LocalUser.Value @@ -152,12 +157,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = apiRoom.Name.Value, MatchType = apiRoom.Type.Value, - BeatmapID = apiRoom.Playlist.Last().BeatmapID, - RulesetID = apiRoom.Playlist.Last().RulesetID, - BeatmapChecksum = apiRoom.Playlist.Last().Beatmap.Value.MD5Hash, - RequiredMods = apiRoom.Playlist.Last().RequiredMods.Select(m => new APIMod(m)).ToArray(), - AllowedMods = apiRoom.Playlist.Last().AllowedMods.Select(m => new APIMod(m)).ToArray(), - PlaylistItemId = apiRoom.Playlist.Last().ID, + PlaylistItemId = lastPlaylistItemId, // ReSharper disable once ConstantNullCoalescingCondition Incorrect inspection due to lack of nullable in Room.cs. Password = password ?? string.Empty, }, @@ -265,6 +265,49 @@ namespace osu.Game.Tests.Visual.Multiplayer return ((IMultiplayerClient)this).LoadRequested(); } + public override async Task AddPlaylistItem(APIPlaylistItem item) + { + Debug.Assert(Room != null); + Debug.Assert(APIRoom != null); + + item.ID = lastPlaylistItemId + 1; + + switch (Room.Settings.QueueMode) + { + case QueueModes.HostOnly: + if (Room.Host?.UserID != LocalUser?.UserID) + throw new InvalidOperationException("Local user is not the room host."); + + await RemovePlaylistItem(new APIPlaylistItem(APIRoom.Playlist.Single(i => i.ID == lastPlaylistItemId))).ConfigureAwait(false); + await ((IMultiplayerClient)this).PlaylistItemAdded(item).ConfigureAwait(false); + + Room.Settings.PlaylistItemId = item.ID; + await ChangeSettings(Room.Settings).ConfigureAwait(false); + break; + + case QueueModes.FreeForAll: + await ((IMultiplayerClient)this).PlaylistItemAdded(item).ConfigureAwait(false); + // Todo: Advance if the added item is the last non-expired one. + break; + + case QueueModes.FairRotate: + // Todo: Not implemented. + throw new NotImplementedException(); + } + + lastPlaylistItemId = item.ID; + } + + public override Task RemovePlaylistItem(APIPlaylistItem item) + { + Debug.Assert(Room != null); + + if (Room.Host?.UserID != LocalUser?.UserID) + throw new InvalidOperationException("Local user is not the room host."); + + return ((IMultiplayerClient)this).PlaylistItemRemoved(item); + } + protected override Task GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default) { Debug.Assert(Room != null); From 95ab82fb5808f93ec5841556e9c516fb728d2572 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 22 Oct 2021 19:41:36 +0900 Subject: [PATCH 0044/4398] Remove BeatmapSelectionControl and inline at usage sites --- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 4 +- .../Match/BeatmapSelectionControl.cs | 87 ------------------- .../Match/MultiplayerMatchSettingsOverlay.cs | 40 +++++++-- .../Multiplayer/MultiplayerMatchSubScreen.cs | 54 +++++++++++- 4 files changed, 89 insertions(+), 96 deletions(-) delete mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 585b024623..9fee739048 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -34,6 +34,8 @@ namespace osu.Game.Screens.OnlinePlay { public class DrawableRoomPlaylistItem : OsuRearrangeableListItem { + public const float HEIGHT = 50; + public Action RequestDeletion; public readonly Bindable SelectedItem = new Bindable(); @@ -135,7 +137,7 @@ namespace osu.Game.Screens.OnlinePlay return maskingContainer = new Container { RelativeSizeAxes = Axes.X, - Height = 50, + Height = HEIGHT, Masking = true, CornerRadius = 10, Children = new Drawable[] diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs deleted file mode 100644 index 35f30edf65..0000000000 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Screens; -using osu.Game.Online.API; -using osu.Game.Screens.OnlinePlay.Match.Components; -using osuTK; - -namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match -{ - public class BeatmapSelectionControl : OnlinePlayComposite - { - [Resolved] - private MultiplayerMatchSubScreen matchSubScreen { get; set; } - - [Resolved] - private IAPIProvider api { get; set; } - - private Container beatmapPanelContainer; - private Button selectButton; - - public BeatmapSelectionControl() - { - AutoSizeAxes = Axes.Y; - } - - [BackgroundDependencyLoader] - private void load() - { - InternalChild = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(5), - Children = new Drawable[] - { - beatmapPanelContainer = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y - }, - selectButton = new PurpleTriangleButton - { - RelativeSizeAxes = Axes.X, - Height = 40, - Text = "Select beatmap", - Action = () => - { - if (matchSubScreen.IsCurrentScreen()) - matchSubScreen.Push(new MultiplayerMatchSongSelect(matchSubScreen.Room)); - }, - Alpha = 0 - } - } - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - SelectedItem.BindValueChanged(_ => updateBeatmap(), true); - Host.BindValueChanged(host => - { - if (RoomID.Value == null || host.NewValue?.Equals(api.LocalUser.Value) == true) - selectButton.Show(); - else - selectButton.Hide(); - }, true); - } - - public void BeginSelection() => selectButton.TriggerClick(); - - private void updateBeatmap() - { - if (SelectedItem.Value == null) - beatmapPanelContainer.Clear(); - else - beatmapPanelContainer.Child = new DrawableRoomPlaylistItem(SelectedItem.Value, false, false); - } - } -} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 4231aeae87..b5079fd2f2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -11,6 +11,7 @@ using osu.Framework.Extensions.ExceptionExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -68,9 +69,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private OsuSpriteText typeLabel; private LoadingLayer loadingLayer; - private BeatmapSelectionControl initialBeatmapControl; - public void SelectBeatmap() => initialBeatmapControl.BeginSelection(); + public void SelectBeatmap() + { + if (matchSubScreen.IsCurrentScreen()) + matchSubScreen.Push(new MultiplayerMatchSongSelect(matchSubScreen.Room)); + } + + [Resolved] + private MultiplayerMatchSubScreen matchSubScreen { get; set; } [Resolved] private IRoomManager manager { get; set; } @@ -94,6 +101,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private readonly Room room; + private Drawable playlistContainer; + public MatchSettings(Room room) { this.room = room; @@ -137,7 +146,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Spacing = new Vector2(0, 10), - Children = new Drawable[] + Children = new[] { new Container { @@ -236,13 +245,32 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match } }, }, - initialBeatmapControl = new BeatmapSelectionControl + playlistContainer = new FillFlowContainer { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Width = 0.5f, - Depth = float.MaxValue + Depth = float.MaxValue, + Spacing = new Vector2(5), + Children = new Drawable[] + { + new DrawableRoomPlaylist(false, false) + { + RelativeSizeAxes = Axes.X, + Height = DrawableRoomPlaylistItem.HEIGHT, + Items = { BindTarget = Playlist }, + SelectedItem = { BindTarget = SelectedItem } + }, + new PurpleTriangleButton + { + RelativeSizeAxes = Axes.X, + Height = 40, + Text = "Select beatmap", + Action = SelectBeatmap + } + } } } } @@ -306,7 +334,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match Availability.BindValueChanged(availability => AvailabilityPicker.Current.Value = availability.NewValue, true); Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true); MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); - RoomID.BindValueChanged(roomId => initialBeatmapControl.Alpha = roomId.NewValue == null ? 1 : 0, true); + RoomID.BindValueChanged(roomId => playlistContainer.Alpha = roomId.NewValue == null ? 1 : 0, true); Password.BindValueChanged(password => PasswordTextBox.Text = password.NewValue ?? string.Empty, true); operationInProgress.BindTo(ongoingOperationTracker.InProgress); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 33df9fe2fa..ba6d3f354a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -14,8 +14,10 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Multiplayer.Queueing; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; @@ -53,6 +55,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [CanBeNull] private IDisposable readyClickOperation; + private OsuButton addOrEditPlaylistButton; + public MultiplayerMatchSubScreen(Room room) : base(room) { @@ -128,7 +132,35 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Content = new[] { new Drawable[] { new OverlinedHeader("Beatmap") }, - new Drawable[] { new BeatmapSelectionControl { RelativeSizeAxes = Axes.X } }, + new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(5), + Children = new Drawable[] + { + addOrEditPlaylistButton = new PurpleTriangleButton + { + RelativeSizeAxes = Axes.X, + Height = 40, + Action = () => + { + if (this.IsCurrentScreen()) + this.Push(new MultiplayerMatchSongSelect(Room)); + }, + Alpha = 0 + }, + new DrawableRoomPlaylist(false, false) + { + RelativeSizeAxes = Axes.Both, + Items = { BindTarget = Room.Playlist }, + SelectedItem = { BindTarget = SelectedItem } + }, + } + } + }, new[] { UserModsSection = new FillFlowContainer @@ -171,7 +203,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), + new Dimension(), new Dimension(GridSizeMode.AutoSize), } }, @@ -354,6 +386,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return; } + switch (client.Room.Settings.QueueMode) + { + case QueueModes.HostOnly: + addOrEditPlaylistButton.Text = "Edit beatmap"; + addOrEditPlaylistButton.Alpha = client.Room.Host?.User?.Equals(client.LocalUser?.User) == true ? 1 : 0; + break; + + case QueueModes.FreeForAll: + case QueueModes.FairRotate: + addOrEditPlaylistButton.Text = "Add beatmap"; + addOrEditPlaylistButton.Alpha = 1; + break; + + default: + addOrEditPlaylistButton.Alpha = 0; + break; + } + Scheduler.AddOnce(UpdateMods); } From 4e6a02bde9ad6537a3a63060af98839cff726e29 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 22 Oct 2021 20:14:04 +0900 Subject: [PATCH 0045/4398] Fix missing QueueMode copies --- osu.Game/Online/Rooms/Room.cs | 1 + osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 875cdeeb8c..c5f7e240ec 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -182,6 +182,7 @@ namespace osu.Game.Online.Rooms ParticipantCount.Value = other.ParticipantCount.Value; EndDate.Value = other.EndDate.Value; UserScore.Value = other.UserScore.Value; + QueueMode.Value = other.QueueMode.Value; if (EndDate.Value != null && DateTimeOffset.Now >= EndDate.Value) Status.Value = new RoomStatusEnded(); diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 7606f6c456..45c3f28eab 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -158,8 +158,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = apiRoom.Name.Value, MatchType = apiRoom.Type.Value, PlaylistItemId = lastPlaylistItemId, - // ReSharper disable once ConstantNullCoalescingCondition Incorrect inspection due to lack of nullable in Room.cs. - Password = password ?? string.Empty, + Password = password, + QueueMode = apiRoom.QueueMode.Value }, Users = { localUser }, Host = localUser From ec02e16c8196f08f5b7c63ca1cedd94a40de629c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 22 Oct 2021 20:53:45 +0900 Subject: [PATCH 0046/4398] Add playlist item change event --- .../Online/Multiplayer/IMultiplayerClient.cs | 6 ++ .../Online/Multiplayer/MultiplayerClient.cs | 62 +++++++++++++------ 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs index 3eafb31510..81979c146c 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs @@ -118,5 +118,11 @@ namespace osu.Game.Online.Multiplayer /// /// The removed item. Task PlaylistItemRemoved(APIPlaylistItem item); + + /// + /// Signals that an item has been changed in the playlist. + /// + /// The changed item. + Task PlaylistItemChanged(APIPlaylistItem item); } } diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 72ac13c2f2..1b321421e3 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -577,12 +577,7 @@ namespace osu.Game.Online.Multiplayer if (Room == null) return; - var set = await GetOnlineBeatmapSet(item.BeatmapID).ConfigureAwait(false); - - var beatmap = set.Beatmaps.Single(b => b.OnlineBeatmapID == item.BeatmapID); - beatmap.MD5Hash = item.BeatmapChecksum; - - var ruleset = Rulesets.GetRuleset(item.RulesetID); + var playlistItem = await createPlaylistItem(item).ConfigureAwait(false); await scheduleAsync(() => { @@ -591,18 +586,6 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(APIRoom != null); - var playlistItem = new PlaylistItem - { - ID = item.ID, - Beatmap = { Value = beatmap }, - Ruleset = { Value = ruleset }, - }; - - var rulesetInstance = ruleset.CreateInstance(); - - playlistItem.RequiredMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance))); - playlistItem.AllowedMods.AddRange(item.AllowedMods.Select(m => m.ToMod(rulesetInstance))); - APIRoom.Playlist.Add(playlistItem); RoomUpdated?.Invoke(); }).ConfigureAwait(false); @@ -625,6 +608,26 @@ namespace osu.Game.Online.Multiplayer }); } + public async Task PlaylistItemChanged(APIPlaylistItem item) + { + if (Room == null) + return; + + var playlistItem = await createPlaylistItem(item).ConfigureAwait(false); + + await scheduleAsync(() => + { + if (Room == null) + return; + + Debug.Assert(APIRoom != null); + + int index = APIRoom.Playlist.Where(p => p.ID == item.ID).Select((_, i) => i).Single(); + APIRoom.Playlist.RemoveAt(index); + APIRoom.Playlist.Insert(index, playlistItem); + }).ConfigureAwait(false); + } + /// /// Populates the for a given . /// @@ -663,6 +666,29 @@ namespace osu.Game.Online.Multiplayer /// The retrieval task. protected abstract Task GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default); + private async Task createPlaylistItem(APIPlaylistItem item) + { + var set = await GetOnlineBeatmapSet(item.BeatmapID).ConfigureAwait(false); + + var beatmap = set.Beatmaps.Single(b => b.OnlineBeatmapID == item.BeatmapID); + beatmap.MD5Hash = item.BeatmapChecksum; + + var ruleset = Rulesets.GetRuleset(item.RulesetID); + var rulesetInstance = ruleset.CreateInstance(); + + var playlistItem = new PlaylistItem + { + ID = item.ID, + Beatmap = { Value = beatmap }, + Ruleset = { Value = ruleset }, + }; + + playlistItem.RequiredMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance))); + playlistItem.AllowedMods.AddRange(item.AllowedMods.Select(m => m.ToMod(rulesetInstance))); + + return playlistItem; + } + /// /// For the provided user ID, update whether the user is included in . /// From d7a98097a127294e740f57455a079ba92caf4364 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 22 Oct 2021 21:04:52 +0900 Subject: [PATCH 0047/4398] Make DrawableRoomPlaylist handle expired items --- .../TestSceneDrawableRoomPlaylist.cs | 47 ++++++++++++++++++- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 3 ++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 93bdbb79f4..470c5bed59 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -241,6 +241,50 @@ namespace osu.Game.Tests.Visual.Multiplayer createPlaylist(beatmap); } + [Test] + public void TestExpiredItems() + { + AddStep("create playlist", () => + { + Child = playlist = new TestPlaylist(false, false) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500, 300), + Items = + { + new PlaylistItem + { + ID = 0, + Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + Expired = true, + RequiredMods = + { + new OsuModHardRock(), + new OsuModDoubleTime(), + new OsuModAutoplay() + } + }, + new PlaylistItem + { + ID = 1, + Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RequiredMods = + { + new OsuModHardRock(), + new OsuModDoubleTime(), + new OsuModAutoplay() + } + } + } + }; + }); + + AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded)); + } + private void moveToItem(int index, Vector2? offset = null) => AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType().ElementAt(index), offset)); @@ -261,7 +305,8 @@ namespace osu.Game.Tests.Visual.Multiplayer () => (playlist.ChildrenOfType.PlaylistItemHandle>().ElementAt(index).Alpha > 0) == visible); private void assertDeleteButtonVisibility(int index, bool visible) - => AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible", () => (playlist.ChildrenOfType().ElementAt(2 + index * 2).Alpha > 0) == visible); + => AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible", + () => (playlist.ChildrenOfType().ElementAt(2 + index * 2).Alpha > 0) == visible); private void createPlaylist(bool allowEdit, bool allowSelection) { diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 9fee739048..2ee096eb8e 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -72,6 +72,9 @@ namespace osu.Game.Screens.OnlinePlay requiredMods.BindTo(item.RequiredMods); ShowDragHandle.Value = allowEdit; + + if (item.Expired) + Colour = OsuColour.Gray(0.5f); } [BackgroundDependencyLoader] From 1b4be61ed16e0a4224072c33c0b23d7686189288 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 22 Oct 2021 21:16:10 +0900 Subject: [PATCH 0048/4398] Fix finished-play state not handled correctly --- .../Visual/Multiplayer/TestMultiplayerClient.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 45c3f28eab..3450cc0fc9 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -96,31 +96,31 @@ namespace osu.Game.Tests.Visual.Multiplayer public void ChangeUserState(int userId, MultiplayerUserState newState) { Debug.Assert(Room != null); - ((IMultiplayerClient)this).UserStateChanged(userId, newState); Schedule(() => { - switch (newState) + switch (Room.State) { - case MultiplayerUserState.Loaded: + case MultiplayerRoomState.WaitingForLoad: if (Room.Users.All(u => u.State != MultiplayerUserState.WaitingForLoad)) { - ChangeRoomState(MultiplayerRoomState.Playing); foreach (var u in Room.Users.Where(u => u.State == MultiplayerUserState.Loaded)) ChangeUserState(u.UserID, MultiplayerUserState.Playing); ((IMultiplayerClient)this).MatchStarted(); + + ChangeRoomState(MultiplayerRoomState.Playing); } break; - case MultiplayerUserState.FinishedPlay: + case MultiplayerRoomState.Playing: if (Room.Users.All(u => u.State != MultiplayerUserState.Playing)) { - ChangeRoomState(MultiplayerRoomState.Open); foreach (var u in Room.Users.Where(u => u.State == MultiplayerUserState.FinishedPlay)) ChangeUserState(u.UserID, MultiplayerUserState.Results); + ChangeRoomState(MultiplayerRoomState.Open); ((IMultiplayerClient)this).ResultsReady(); } From 6775151c30a38dfa5a304d471bb92a0504a589eb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 22 Oct 2021 21:25:49 +0900 Subject: [PATCH 0049/4398] Add Expired bool to APIPlaylistItem --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 1 + osu.Game/Online/Rooms/APIPlaylistItem.cs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 1b321421e3..10dd0e9b0e 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -681,6 +681,7 @@ namespace osu.Game.Online.Multiplayer ID = item.ID, Beatmap = { Value = beatmap }, Ruleset = { Value = ruleset }, + Expired = item.Expired }; playlistItem.RequiredMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance))); diff --git a/osu.Game/Online/Rooms/APIPlaylistItem.cs b/osu.Game/Online/Rooms/APIPlaylistItem.cs index 121d661c65..a9156f35ce 100644 --- a/osu.Game/Online/Rooms/APIPlaylistItem.cs +++ b/osu.Game/Online/Rooms/APIPlaylistItem.cs @@ -30,6 +30,9 @@ namespace osu.Game.Online.Rooms [Key(5)] public IEnumerable AllowedMods { get; set; } = Enumerable.Empty(); + [Key(6)] + public bool Expired { get; set; } + public APIPlaylistItem() { } @@ -42,6 +45,7 @@ namespace osu.Game.Online.Rooms RulesetID = item.RulesetID; RequiredMods = item.RequiredMods.Select(m => new APIMod(m)).ToArray(); AllowedMods = item.AllowedMods.Select(m => new APIMod(m)).ToArray(); + Expired = item.Expired; } } } From 76eff7f6b1fcbf742b69505027892c0862821e02 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 22 Oct 2021 21:57:29 +0900 Subject: [PATCH 0050/4398] Fix incorrect indexing --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 10dd0e9b0e..6b1042df2d 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -622,7 +622,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(APIRoom != null); - int index = APIRoom.Playlist.Where(p => p.ID == item.ID).Select((_, i) => i).Single(); + int index = APIRoom.Playlist.Select((i, index) => (i, index)).Single(kvp => kvp.i.ID == item.ID).index; APIRoom.Playlist.RemoveAt(index); APIRoom.Playlist.Insert(index, playlistItem); }).ConfigureAwait(false); From a4397ee68ca3be618f663efafa195154da3cd647 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 22 Oct 2021 22:07:41 +0900 Subject: [PATCH 0051/4398] Add playlist continuation flow --- .../Multiplayer/TestMultiplayerClient.cs | 116 +++++++++++++----- 1 file changed, 88 insertions(+), 28 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 3450cc0fc9..717f55d377 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -42,8 +42,6 @@ namespace osu.Game.Tests.Visual.Multiplayer private readonly TestRequestHandlingMultiplayerRoomManager roomManager; - private long lastPlaylistItemId; - public TestMultiplayerClient(TestRequestHandlingMultiplayerRoomManager roomManager) { this.roomManager = roomManager; @@ -123,6 +121,8 @@ namespace osu.Game.Tests.Visual.Multiplayer ChangeRoomState(MultiplayerRoomState.Open); ((IMultiplayerClient)this).ResultsReady(); + + finishPlaylistItem().ContinueWith(_ => advanceToNextPlaylistItem()); } break; @@ -144,8 +144,6 @@ namespace osu.Game.Tests.Visual.Multiplayer if (password != apiRoom.Password.Value) throw new InvalidOperationException("Invalid password."); - lastPlaylistItemId = apiRoom.Playlist.Last().ID; - var localUser = new MultiplayerRoomUser(api.LocalUser.Value.Id) { User = api.LocalUser.Value @@ -157,7 +155,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = apiRoom.Name.Value, MatchType = apiRoom.Type.Value, - PlaylistItemId = lastPlaylistItemId, + PlaylistItemId = apiRoom.Playlist.Single().ID, Password = password, QueueMode = apiRoom.QueueMode.Value }, @@ -193,6 +191,24 @@ namespace osu.Game.Tests.Visual.Multiplayer public override async Task ChangeSettings(MultiplayerRoomSettings settings) { Debug.Assert(Room != null); + Debug.Assert(APIRoom != null); + + switch (Room.Settings.QueueMode, settings.QueueMode) + { + case (QueueModes.HostOnly, QueueModes.HostOnly): + break; + + // Host-only is incompatible with other queueing modes, so expire all non-expired items. + case (QueueModes.HostOnly, _): + case (_, QueueModes.HostOnly): + foreach (var playlistItem in APIRoom.Playlist.Where(i => !i.Expired).ToArray()) + { + playlistItem.Expired = true; + await ((IMultiplayerClient)this).PlaylistItemChanged(new APIPlaylistItem(playlistItem)).ConfigureAwait(false); + } + + break; + } await ((IMultiplayerClient)this).SettingsChanged(settings).ConfigureAwait(false); @@ -270,32 +286,16 @@ namespace osu.Game.Tests.Visual.Multiplayer Debug.Assert(Room != null); Debug.Assert(APIRoom != null); - item.ID = lastPlaylistItemId + 1; - - switch (Room.Settings.QueueMode) + if (Room.Settings.QueueMode == QueueModes.HostOnly && APIRoom.Playlist.Count > 0) { - case QueueModes.HostOnly: - if (Room.Host?.UserID != LocalUser?.UserID) - throw new InvalidOperationException("Local user is not the room host."); + if (Room.Host?.UserID != LocalUser?.UserID) + throw new InvalidOperationException("Local user is not the room host."); - await RemovePlaylistItem(new APIPlaylistItem(APIRoom.Playlist.Single(i => i.ID == lastPlaylistItemId))).ConfigureAwait(false); - await ((IMultiplayerClient)this).PlaylistItemAdded(item).ConfigureAwait(false); - - Room.Settings.PlaylistItemId = item.ID; - await ChangeSettings(Room.Settings).ConfigureAwait(false); - break; - - case QueueModes.FreeForAll: - await ((IMultiplayerClient)this).PlaylistItemAdded(item).ConfigureAwait(false); - // Todo: Advance if the added item is the last non-expired one. - break; - - case QueueModes.FairRotate: - // Todo: Not implemented. - throw new NotImplementedException(); + item.ID = APIRoom.Playlist.Last().ID; + await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); } - - lastPlaylistItemId = item.ID; + else + await addPlaylistItem(item).ConfigureAwait(false); } public override Task RemovePlaylistItem(APIPlaylistItem item) @@ -343,5 +343,65 @@ namespace osu.Game.Tests.Visual.Multiplayer break; } } + + private async Task addPlaylistItem(APIPlaylistItem newItem) + { + Debug.Assert(Room != null); + Debug.Assert(APIRoom != null); + + newItem.ID = (APIRoom.Playlist.LastOrDefault()?.ID ?? 0) + 1; + await ((IMultiplayerClient)this).PlaylistItemAdded(newItem).ConfigureAwait(false); + + // A more valid selection can occur as a result of adding a new playlist item (e.g. if all previous items were expired). + await advanceToNextPlaylistItem().ConfigureAwait(false); + } + + private async Task finishPlaylistItem() + { + Debug.Assert(Room != null); + Debug.Assert(APIRoom != null); + + var currentItem = APIRoom.Playlist.Single(i => i.ID == Room.Settings.PlaylistItemId); + + // Expire the current playlist item. + await ((IMultiplayerClient)this).PlaylistItemChanged(new APIPlaylistItem(currentItem) { Expired = true }).ConfigureAwait(false); + + // In host-only mode, a duplicate playlist item will be used for the next round. + if (Room.Settings.QueueMode == QueueModes.HostOnly) + await addPlaylistItem(new APIPlaylistItem(currentItem)).ConfigureAwait(false); + } + + private async Task advanceToNextPlaylistItem() + { + Debug.Assert(Room != null); + Debug.Assert(APIRoom != null); + + long nextId = 0; + + switch (Room.Settings.QueueMode) + { + case QueueModes.HostOnly: + // Pick the single non-expired playlist item. + nextId = APIRoom.Playlist.SingleOrDefault(i => !i.Expired)?.ID ?? 0; + break; + + case QueueModes.FairRotate: + // Group playlist items by (user_id -> count_expired), and select the first available playlist item from a user that has available beatmaps where count_expired is the lowest. + throw new NotImplementedException(); + + case QueueModes.FreeForAll: + // Pick the first available non-expired playlist item, or default to the last. + nextId = APIRoom.Playlist.FirstOrDefault(i => !i.Expired)?.ID + ?? APIRoom.Playlist.LastOrDefault()?.ID + ?? 0; + break; + } + + if (nextId != Room.Settings.PlaylistItemId) + { + Room.Settings.PlaylistItemId = nextId; + await ChangeSettings(Room.Settings).ConfigureAwait(false); + } + } } } From 04b0529a7fb112432b5b69b86322bc6bde100f7e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 22 Oct 2021 22:17:26 +0900 Subject: [PATCH 0052/4398] Fix list overflowing subscreen background --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index ba6d3f354a..6ac8ee1ee0 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -134,32 +134,27 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new Drawable[] { new OverlinedHeader("Beatmap") }, new Drawable[] { - new FillFlowContainer + addOrEditPlaylistButton = new PurpleTriangleButton + { + RelativeSizeAxes = Axes.X, + Height = 40, + Action = () => + { + if (this.IsCurrentScreen()) + this.Push(new MultiplayerMatchSongSelect(Room)); + }, + Alpha = 0 + }, + }, + null, + new Drawable[] + { + new DrawableRoomPlaylist(false, false) { RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(5), - Children = new Drawable[] - { - addOrEditPlaylistButton = new PurpleTriangleButton - { - RelativeSizeAxes = Axes.X, - Height = 40, - Action = () => - { - if (this.IsCurrentScreen()) - this.Push(new MultiplayerMatchSongSelect(Room)); - }, - Alpha = 0 - }, - new DrawableRoomPlaylist(false, false) - { - RelativeSizeAxes = Axes.Both, - Items = { BindTarget = Room.Playlist }, - SelectedItem = { BindTarget = SelectedItem } - }, - } - } + Items = { BindTarget = Room.Playlist }, + SelectedItem = { BindTarget = SelectedItem } + }, }, new[] { @@ -203,6 +198,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Absolute, 5), new Dimension(), new Dimension(GridSizeMode.AutoSize), } From 7d910ebb572a6a421ed32a41759085d3f2c2ba3f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 22 Oct 2021 22:37:01 +0900 Subject: [PATCH 0053/4398] Add SignalR binding for PlaylistItemChanged --- osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index fd57b42000..f69464e37e 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -64,6 +64,7 @@ namespace osu.Game.Online.Multiplayer connection.On(nameof(IMultiplayerClient.MatchEvent), ((IMultiplayerClient)this).MatchEvent); connection.On(nameof(IMultiplayerClient.PlaylistItemAdded), ((IMultiplayerClient)this).PlaylistItemAdded); connection.On(nameof(IMultiplayerClient.PlaylistItemRemoved), ((IMultiplayerClient)this).PlaylistItemRemoved); + connection.On(nameof(IMultiplayerClient.PlaylistItemChanged), ((IMultiplayerClient)this).PlaylistItemChanged); }; IsConnected.BindTo(connector.IsConnected); From 746f4a49c1e00e40b877619b8da805e554fc1f19 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 29 Oct 2021 13:56:07 +0900 Subject: [PATCH 0054/4398] Add base test scene --- .../QueueingModes/QueueModeTestScene.cs | 103 ++++++++++++++++++ .../TestSceneHostOnlyQueueingMode.cs | 12 ++ 2 files changed, 115 insertions(+) create mode 100644 osu.Game.Tests/Visual/Multiplayer/QueueingModes/QueueModeTestScene.cs create mode 100644 osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueingMode.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/QueueModeTestScene.cs new file mode 100644 index 0000000000..0480e3bb78 --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/QueueModeTestScene.cs @@ -0,0 +1,103 @@ +// 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.Audio; +using osu.Framework.Platform; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Online.Multiplayer.Queueing; +using osu.Game.Online.Rooms; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.OnlinePlay.Lounge; +using osu.Game.Screens.OnlinePlay.Multiplayer; +using osu.Game.Screens.OnlinePlay.Multiplayer.Match; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Multiplayer.QueueingModes +{ + public abstract class QueueModeTestScene : ScreenTestScene + { + protected abstract QueueModes Mode { get; } + + private BeatmapManager beatmaps; + private RulesetStore rulesets; + private ILive importedBeatmap; + + private TestMultiplayerScreenStack multiplayerScreenStack; + + private TestMultiplayerClient client => multiplayerScreenStack.Client; + private TestMultiplayerRoomManager roomManager => multiplayerScreenStack.RoomManager; + + [Cached(typeof(UserLookupCache))] + private UserLookupCache lookupCache = new TestUserLookupCache(); + + [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)); + } + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("import beatmap", () => + { + var beatmap1 = CreateBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; + beatmap1.Version = "1"; + + var beatmap2 = CreateBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; + beatmap2.Version = "2"; + + // Move beatmap2 to beatmap1's set. + var beatmapSet = beatmap1.BeatmapSet; + beatmapSet.Beatmaps.Add(beatmap2); + beatmap2.BeatmapSet = beatmapSet; + + importedBeatmap = beatmaps.Import(beatmapSet).Result; + }); + + AddStep("load multiplayer", () => LoadScreen(multiplayerScreenStack = new TestMultiplayerScreenStack())); + AddUntilStep("wait for multiplayer to load", () => multiplayerScreenStack.IsLoaded); + AddUntilStep("wait for lounge to load", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + + AddUntilStep("wait for lounge", () => multiplayerScreenStack.ChildrenOfType().SingleOrDefault()?.IsLoaded == true); + AddStep("open room", () => multiplayerScreenStack.ChildrenOfType().Single().Open(new Room + { + Name = { Value = "Test Room" }, + QueueMode = { Value = Mode }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = importedBeatmap.Value.Beatmaps.First() }, + Ruleset = { Value = new OsuRuleset().RulesetInfo } + } + } + })); + + AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + AddWaitStep("wait for transition", 2); + + AddStep("create room", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("wait for join", () => client.Room != null); + } + + [Test] + public void TestCreatedWithCorrectMode() + { + AddAssert("room created with correct mode", () => client.APIRoom?.QueueMode.Value == Mode); + } + } +} diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueingMode.cs b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueingMode.cs new file mode 100644 index 0000000000..f643377221 --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueingMode.cs @@ -0,0 +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.Game.Online.Multiplayer.Queueing; + +namespace osu.Game.Tests.Visual.Multiplayer.QueueingModes +{ + public class TestSceneHostOnlyQueueingMode : QueueModeTestScene + { + protected override QueueModes Mode => QueueModes.HostOnly; + } +} From 42dfb341daa7a94bb66934dfb24ab670e7ee6f46 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 29 Oct 2021 15:44:42 +0900 Subject: [PATCH 0055/4398] Fix PlaylistItemChanged() not updating current item --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 6b1042df2d..cdf6684112 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -625,6 +625,10 @@ namespace osu.Game.Online.Multiplayer int index = APIRoom.Playlist.Select((i, index) => (i, index)).Single(kvp => kvp.i.ID == item.ID).index; APIRoom.Playlist.RemoveAt(index); APIRoom.Playlist.Insert(index, playlistItem); + + // If the current item changed, update the selected playlist item. + if (item.ID == Room.Settings.PlaylistItemId) + CurrentMatchPlayingItem.Value = APIRoom.Playlist[index]; }).ConfigureAwait(false); } From e667ef4eea9a21e2cbe1b0f9fbfe0ad57662d2cf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 29 Oct 2021 15:44:48 +0900 Subject: [PATCH 0056/4398] Add basic tests --- .../QueueingModes/QueueModeTestScene.cs | 60 +++++++++++++------ .../TestSceneHostOnlyQueueingMode.cs | 53 ++++++++++++++++ .../Multiplayer/MultiplayerMatchSubScreen.cs | 14 ++--- .../Screens/OnlinePlay/OnlinePlayScreen.cs | 2 +- 4 files changed, 102 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/QueueModeTestScene.cs index 0480e3bb78..cea90ff12d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/QueueModeTestScene.cs @@ -6,9 +6,11 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; 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.Multiplayer.Queueing; using osu.Game.Online.Rooms; using osu.Game.Rulesets; @@ -16,6 +18,8 @@ using osu.Game.Rulesets.Osu; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; +using osu.Game.Screens.Play; +using osu.Game.Tests.Resources; using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer.QueueingModes @@ -24,14 +28,20 @@ namespace osu.Game.Tests.Visual.Multiplayer.QueueingModes { protected abstract QueueModes Mode { get; } + protected BeatmapInfo InitialBeatmap { get; private set; } + protected BeatmapInfo OtherBeatmap { get; private set; } + + protected IScreen CurrentScreen => multiplayerScreenStack.CurrentScreen; + protected IScreen CurrentSubScreen => multiplayerScreenStack.MultiplayerScreen.CurrentSubScreen; + private BeatmapManager beatmaps; private RulesetStore rulesets; - private ILive importedBeatmap; + private BeatmapSetInfo importedSet; private TestMultiplayerScreenStack multiplayerScreenStack; - private TestMultiplayerClient client => multiplayerScreenStack.Client; - private TestMultiplayerRoomManager roomManager => multiplayerScreenStack.RoomManager; + protected TestMultiplayerClient Client => multiplayerScreenStack.Client; + protected TestMultiplayerRoomManager RoomManager => multiplayerScreenStack.RoomManager; [Cached(typeof(UserLookupCache))] private UserLookupCache lookupCache = new TestUserLookupCache(); @@ -49,18 +59,10 @@ namespace osu.Game.Tests.Visual.Multiplayer.QueueingModes AddStep("import beatmap", () => { - var beatmap1 = CreateBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; - beatmap1.Version = "1"; - - var beatmap2 = CreateBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; - beatmap2.Version = "2"; - - // Move beatmap2 to beatmap1's set. - var beatmapSet = beatmap1.BeatmapSet; - beatmapSet.Beatmaps.Add(beatmap2); - beatmap2.BeatmapSet = beatmapSet; - - importedBeatmap = beatmaps.Import(beatmapSet).Result; + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); + InitialBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); + OtherBeatmap = importedSet.Beatmaps.Last(b => b.RulesetID == 0); }); AddStep("load multiplayer", () => LoadScreen(multiplayerScreenStack = new TestMultiplayerScreenStack())); @@ -76,8 +78,8 @@ namespace osu.Game.Tests.Visual.Multiplayer.QueueingModes { new PlaylistItem { - Beatmap = { Value = importedBeatmap.Value.Beatmaps.First() }, - Ruleset = { Value = new OsuRuleset().RulesetInfo } + Beatmap = { Value = InitialBeatmap }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } })); @@ -91,13 +93,33 @@ namespace osu.Game.Tests.Visual.Multiplayer.QueueingModes InputManager.Click(MouseButton.Left); }); - AddUntilStep("wait for join", () => client.Room != null); + AddUntilStep("wait for join", () => Client.Room != null); } [Test] public void TestCreatedWithCorrectMode() { - AddAssert("room created with correct mode", () => client.APIRoom?.QueueMode.Value == Mode); + AddAssert("room created with correct mode", () => Client.APIRoom?.QueueMode.Value == Mode); + } + + protected void RunGameplay() + { + AddStep("click ready button", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("wait for ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready); + + AddStep("click ready button", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("wait for player", () => multiplayerScreenStack.CurrentScreen is Player player && player.IsLoaded); + AddStep("exit player", () => multiplayerScreenStack.MultiplayerScreen.MakeCurrent()); } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueingMode.cs b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueingMode.cs index f643377221..ada04abc5a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueingMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueingMode.cs @@ -1,12 +1,65 @@ // 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.Testing; +using osu.Game.Beatmaps; using osu.Game.Online.Multiplayer.Queueing; +using osu.Game.Screens.OnlinePlay.Multiplayer; +using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer.QueueingModes { public class TestSceneHostOnlyQueueingMode : QueueModeTestScene { protected override QueueModes Mode => QueueModes.HostOnly; + + [Test] + public void TestItemStillSelectedAfterChange() + { + selectNewItem(() => OtherBeatmap); + } + + [Test] + public void TestNewItemCreatedAfterGameplayFinished() + { + 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.CurrentMatchPlayingItem.Value == Client.APIRoom?.Playlist[1]); + } + + [Test] + public void TestOnlyLastItemChangedAfterGameplayFinished() + { + RunGameplay(); + + BeatmapInfo firstBeatmap = null; + AddStep("get first playlist item beatmap", () => firstBeatmap = Client.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); + } + + private void selectNewItem(Func beatmap) + { + AddStep("click edit button", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single().AddOrEditPlaylistButton); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.IsLoaded); + + AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(beatmap())); + AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); + AddUntilStep("selected item is new beatmap", () => Client.CurrentMatchPlayingItem.Value?.Beatmap.Value?.OnlineID == OtherBeatmap.OnlineID); + } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 6ac8ee1ee0..4fcf60c0e8 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [CanBeNull] private IDisposable readyClickOperation; - private OsuButton addOrEditPlaylistButton; + public OsuButton AddOrEditPlaylistButton { get; private set; } public MultiplayerMatchSubScreen(Room room) : base(room) @@ -134,7 +134,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new Drawable[] { new OverlinedHeader("Beatmap") }, new Drawable[] { - addOrEditPlaylistButton = new PurpleTriangleButton + AddOrEditPlaylistButton = new PurpleTriangleButton { RelativeSizeAxes = Axes.X, Height = 40, @@ -386,18 +386,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer switch (client.Room.Settings.QueueMode) { case QueueModes.HostOnly: - addOrEditPlaylistButton.Text = "Edit beatmap"; - addOrEditPlaylistButton.Alpha = client.Room.Host?.User?.Equals(client.LocalUser?.User) == true ? 1 : 0; + AddOrEditPlaylistButton.Text = "Edit beatmap"; + AddOrEditPlaylistButton.Alpha = client.Room.Host?.User?.Equals(client.LocalUser?.User) == true ? 1 : 0; break; case QueueModes.FreeForAll: case QueueModes.FairRotate: - addOrEditPlaylistButton.Text = "Add beatmap"; - addOrEditPlaylistButton.Alpha = 1; + AddOrEditPlaylistButton.Text = "Add beatmap"; + AddOrEditPlaylistButton.Alpha = 1; break; default: - addOrEditPlaylistButton.Alpha = 0; + AddOrEditPlaylistButton.Alpha = 0; break; } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index fc20b21b60..a18e4b45cf 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -211,7 +211,7 @@ namespace osu.Game.Screens.OnlinePlay ((IBindable)Activity).BindTo(newOsuScreen.Activity); } - protected IScreen CurrentSubScreen => screenStack.CurrentScreen; + public IScreen CurrentSubScreen => screenStack.CurrentScreen; protected abstract string ScreenTitle { get; } From f732c44265bf768acb2ef902e922ea59325ff7a7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 29 Oct 2021 16:23:10 +0900 Subject: [PATCH 0057/4398] Fix broken equality comparer --- osu.Game/Online/Rooms/PlaylistItem.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs index 7fcce1514d..e2a47ed744 100644 --- a/osu.Game/Online/Rooms/PlaylistItem.cs +++ b/osu.Game/Online/Rooms/PlaylistItem.cs @@ -96,6 +96,12 @@ namespace osu.Game.Online.Rooms public bool ShouldSerializeID() => false; public bool ShouldSerializeapiBeatmap() => false; - public bool Equals(PlaylistItem other) => ID == other?.ID && BeatmapID == other.BeatmapID && RulesetID == other.RulesetID; + public bool Equals(PlaylistItem other) + => ID == other?.ID + && BeatmapID == other.BeatmapID + && RulesetID == other.RulesetID + && Expired == other.Expired + && allowedMods.SequenceEqual(other.allowedMods) + && requiredMods.SequenceEqual(other.requiredMods); } } From 2408011c81882b71f49d5dda9d1ae98976d2058d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 29 Oct 2021 16:44:39 +0900 Subject: [PATCH 0058/4398] Don't replace identical playlist items --- .../TestSceneHostOnlyQueueingMode.cs | 18 +++++++++++++++--- .../Online/Multiplayer/MultiplayerClient.cs | 9 +++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueingMode.cs b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueingMode.cs index ada04abc5a..59baccf181 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueingMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueingMode.cs @@ -17,9 +17,19 @@ namespace osu.Game.Tests.Visual.Multiplayer.QueueingModes protected override QueueModes Mode => QueueModes.HostOnly; [Test] - public void TestItemStillSelectedAfterChange() + public void TestItemStillSelectedAfterChangeToSameBeatmap() + { + selectNewItem(() => InitialBeatmap); + + AddAssert("playlist item still selected", () => Client.CurrentMatchPlayingItem.Value == Client.APIRoom?.Playlist[0]); + } + + [Test] + public void TestItemStillSelectedAfterChangeToOtherBeatmap() { selectNewItem(() => OtherBeatmap); + + AddAssert("playlist item still selected", () => Client.CurrentMatchPlayingItem.Value == Client.APIRoom?.Playlist[0]); } [Test] @@ -57,9 +67,11 @@ namespace osu.Game.Tests.Visual.Multiplayer.QueueingModes AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.IsLoaded); - AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(beatmap())); + BeatmapInfo otherBeatmap = null; + 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", () => Client.CurrentMatchPlayingItem.Value?.Beatmap.Value?.OnlineID == OtherBeatmap.OnlineID); + AddUntilStep("selected item is new beatmap", () => Client.CurrentMatchPlayingItem.Value?.Beatmap.Value?.OnlineID == otherBeatmap.OnlineID); } } } diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index cdf6684112..d3e1a7c91a 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -623,11 +623,16 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(APIRoom != null); int index = APIRoom.Playlist.Select((i, index) => (i, index)).Single(kvp => kvp.i.ID == item.ID).index; + var oldItem = APIRoom.Playlist[index]; + if (oldItem.Equals(playlistItem)) + return; + + // Replace the item. APIRoom.Playlist.RemoveAt(index); APIRoom.Playlist.Insert(index, playlistItem); - // If the current item changed, update the selected playlist item. - if (item.ID == Room.Settings.PlaylistItemId) + // If the currently-selected item was the one that got replaced, update the selected item to the new one. + if (CurrentMatchPlayingItem.Value == oldItem) CurrentMatchPlayingItem.Value = APIRoom.Playlist[index]; }).ConfigureAwait(false); } From 7c6b3cb0802917d15dc508351ccb9812eb42991d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 29 Oct 2021 16:45:30 +0900 Subject: [PATCH 0059/4398] Rename test scene --- ...eneHostOnlyQueueingMode.cs => TestSceneHostOnlyQueueMode.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Tests/Visual/Multiplayer/QueueingModes/{TestSceneHostOnlyQueueingMode.cs => TestSceneHostOnlyQueueMode.cs} (97%) diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueingMode.cs b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueMode.cs similarity index 97% rename from osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueingMode.cs rename to osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueMode.cs index 59baccf181..34a490e073 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueingMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueMode.cs @@ -12,7 +12,7 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer.QueueingModes { - public class TestSceneHostOnlyQueueingMode : QueueModeTestScene + public class TestSceneHostOnlyQueueMode : QueueModeTestScene { protected override QueueModes Mode => QueueModes.HostOnly; From 3db199292fa63d6f105329d2057bc659a492ebac Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Nov 2021 18:52:57 +0900 Subject: [PATCH 0060/4398] Fix queue mode not passed from room to settings overlay --- .../QueueingModes/TestSceneFreeForAllQueueMode.cs | 12 ++++++++++++ .../Match/MultiplayerMatchSettingsOverlay.cs | 1 + osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs | 4 ++++ 3 files changed, 17 insertions(+) create mode 100644 osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneFreeForAllQueueMode.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneFreeForAllQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneFreeForAllQueueMode.cs new file mode 100644 index 0000000000..7b03517355 --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneFreeForAllQueueMode.cs @@ -0,0 +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.Game.Online.Multiplayer.Queueing; + +namespace osu.Game.Tests.Visual.Multiplayer.QueueingModes +{ + public class TestSceneFreeForAllQueueMode : QueueModeTestScene + { + protected override QueueModes Mode => QueueModes.FreeForAll; + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index ab3cbffe2c..3d95960bef 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -336,6 +336,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); RoomID.BindValueChanged(roomId => playlistContainer.Alpha = roomId.NewValue == null ? 1 : 0, true); Password.BindValueChanged(password => PasswordTextBox.Text = password.NewValue ?? string.Empty, true); + QueueMode.BindValueChanged(mode => QueueModeDropdown.Current.Value = mode.NewValue, true); operationInProgress.BindTo(ongoingOperationTracker.InProgress); operationInProgress.BindValueChanged(v => diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index aa971864ef..40410538d6 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; +using osu.Game.Online.Multiplayer.Queueing; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Users; @@ -65,6 +66,9 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable Duration { get; private set; } + [Resolved(typeof(Room), nameof(Room.QueueMode))] + protected Bindable QueueMode { get; private set; } + [Resolved(CanBeNull = true)] private IBindable subScreenSelectedItem { get; set; } From 1564048d8d77a91e16f81fef6ebb18e06634c069 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Nov 2021 18:55:42 +0900 Subject: [PATCH 0061/4398] Add one more test --- .../Multiplayer/QueueingModes/TestSceneHostOnlyQueueMode.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueMode.cs index 34a490e073..c71a36f1a4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueMode.cs @@ -16,6 +16,12 @@ namespace osu.Game.Tests.Visual.Multiplayer.QueueingModes { protected override QueueModes Mode => QueueModes.HostOnly; + [Test] + public void TestFirstItemSelectedByDefault() + { + AddAssert("first item selected", () => Client.CurrentMatchPlayingItem.Value == Client.APIRoom?.Playlist[0]); + } + [Test] public void TestItemStillSelectedAfterChangeToSameBeatmap() { From 887fa54988717d3550039f5c1a51a692fd3a2197 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Nov 2021 19:25:47 +0900 Subject: [PATCH 0062/4398] Add FFA queueing mode tests --- .../QueueingModes/QueueModeTestScene.cs | 2 + .../TestSceneFreeForAllQueueMode.cs | 82 +++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/QueueModeTestScene.cs index cea90ff12d..82ad9f3afe 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/QueueModeTestScene.cs @@ -104,6 +104,8 @@ namespace osu.Game.Tests.Visual.Multiplayer.QueueingModes protected void RunGameplay() { + AddUntilStep("wait for idle", () => Client.LocalUser?.State == MultiplayerUserState.Idle); + AddStep("click ready button", () => { InputManager.MoveMouseTo(this.ChildrenOfType().Single()); diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneFreeForAllQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneFreeForAllQueueMode.cs index 7b03517355..906e528ac5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneFreeForAllQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneFreeForAllQueueMode.cs @@ -1,12 +1,94 @@ // 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.Testing; +using osu.Game.Beatmaps; using osu.Game.Online.Multiplayer.Queueing; +using osu.Game.Screens.OnlinePlay.Multiplayer; +using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer.QueueingModes { public class TestSceneFreeForAllQueueMode : QueueModeTestScene { protected override QueueModes Mode => QueueModes.FreeForAll; + + [Test] + public void TestFirstItemSelectedByDefault() + { + AddAssert("first item selected", () => Client.CurrentMatchPlayingItem.Value == Client.APIRoom?.Playlist[0]); + } + + [Test] + public void TestItemAddedToTheEndOfQueue() + { + addItem(() => OtherBeatmap); + AddAssert("playlist has 2 items", () => Client.APIRoom?.Playlist.Count == 2); + AddAssert("last playlist item is different", () => Client.APIRoom?.Playlist[1].Beatmap.Value.OnlineID == OtherBeatmap.OnlineID); + + addItem(() => InitialBeatmap); + AddAssert("playlist has 3 items", () => Client.APIRoom?.Playlist.Count == 3); + AddAssert("last playlist item is different", () => Client.APIRoom?.Playlist[2].Beatmap.Value.OnlineID == InitialBeatmap.OnlineID); + + AddAssert("first item still selected", () => Client.CurrentMatchPlayingItem.Value == Client.APIRoom?.Playlist[0]); + } + + [Test] + public void TestSingleItemExpiredAfterGameplay() + { + 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.CurrentMatchPlayingItem.Value == Client.APIRoom?.Playlist[0]); + } + + [Test] + public void TestNextItemSelectedAfterGameplayFinish() + { + addItem(() => OtherBeatmap); + addItem(() => InitialBeatmap); + + RunGameplay(); + + AddAssert("first item expired", () => Client.APIRoom?.Playlist[0].Expired == true); + AddAssert("next item selected", () => Client.CurrentMatchPlayingItem.Value == Client.APIRoom?.Playlist[1]); + + RunGameplay(); + + AddAssert("second item expired", () => Client.APIRoom?.Playlist[1].Expired == true); + AddAssert("next item selected", () => Client.CurrentMatchPlayingItem.Value == Client.APIRoom?.Playlist[2]); + } + + [Test] + public void TestItemsClearedWhenSwitchToHostOnlyMode() + { + addItem(() => OtherBeatmap); + addItem(() => InitialBeatmap); + + // Move to the "other" beatmap. + RunGameplay(); + + AddStep("change queue mode", () => Client.ChangeSettings(queueMode: QueueModes.HostOnly)); + AddAssert("playlist has 1 item", () => Client.APIRoom?.Playlist.Count == 1); + AddAssert("playlist item is the same as last selected", () => Client.APIRoom?.Playlist[0].Beatmap.Value.OnlineID == OtherBeatmap.OnlineID); + AddAssert("playlist item is not expired", () => Client.APIRoom?.Playlist[0].Expired == false); + } + + private void addItem(Func beatmap) + { + AddStep("click edit button", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single().AddOrEditPlaylistButton); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.IsLoaded); + AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(beatmap())); + AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); + } } } From 7bbb2a7a674518eb77d19de3b42b5454f5072a31 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Nov 2021 15:57:32 +0900 Subject: [PATCH 0063/4398] Fix post-merge issues --- .../Multiplayer/QueueingModes/TestSceneHostOnlyQueueMode.cs | 2 +- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueMode.cs index c71a36f1a4..fb11416140 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueMode.cs @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.Multiplayer.QueueingModes { RunGameplay(); - BeatmapInfo firstBeatmap = null; + IBeatmapInfo firstBeatmap = null; AddStep("get first playlist item beatmap", () => firstBeatmap = Client.APIRoom?.Playlist[0].Beatmap.Value); selectNewItem(() => OtherBeatmap); diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index a058420f78..0e0f978a30 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -13,7 +13,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Logging; -using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.Multiplayer.Queueing; From c65e7a4436f0068f86ae2801564ef11bbc2bf705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Oct 2021 15:01:53 +0200 Subject: [PATCH 0064/4398] Add test coverage for download button --- .../TestSceneBeatmapCardDownloadButton.cs | 166 ++++++++++++++++++ .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 4 +- .../Drawables/Cards/Buttons/DownloadButton.cs | 59 ++++++- 3 files changed, 225 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs new file mode 100644 index 0000000000..88de1f8b98 --- /dev/null +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.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 System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables.Cards.Buttons; +using osu.Game.Online; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Rulesets.Osu; +using osu.Game.Tests.Resources; +using osuTK; + +namespace osu.Game.Tests.Visual.Beatmaps +{ + public class TestSceneBeatmapCardDownloadButton : OsuTestScene + { + private TestDownloadButton downloadButton; + + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + + [Resolved] + private BeatmapManager beatmaps { get; set; } + + [Test] + public void TestDownloadableBeatmap() + { + createButton(true); + assertDownloadEnabled(true); + + AddStep("set downloading state", () => downloadButton.State.Value = DownloadState.Downloading); + AddStep("set progress to 30%", () => downloadButton.Progress.Value = 0.3f); + AddStep("set progress to 100%", () => downloadButton.Progress.Value = 1f); + AddStep("set importing state", () => downloadButton.State.Value = DownloadState.Importing); + AddStep("set locally available state", () => downloadButton.State.Value = DownloadState.LocallyAvailable); + } + + [Test] + public void TestUndownloadableBeatmap() + { + createButton(false); + assertDownloadEnabled(false); + } + + [Test] + public void TestDownloadState() + { + AddUntilStep("ensure manager loaded", () => beatmaps != null); + ensureSoleilyRemoved(); + createButtonWithBeatmap(createSoleily()); + AddAssert("button state not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded); + AddStep("import soleily", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport())); + + AddUntilStep("wait for beatmap import", () => beatmaps.GetAllUsableBeatmapSets().Any(b => b.OnlineBeatmapSetID == 241526)); + AddUntilStep("button state downloaded", () => downloadButton.State.Value == DownloadState.LocallyAvailable); + + createButtonWithBeatmap(createSoleily()); + AddUntilStep("button state downloaded", () => downloadButton.State.Value == DownloadState.LocallyAvailable); + ensureSoleilyRemoved(); + AddUntilStep("button state not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded); + } + + private void ensureSoleilyRemoved() + { + AddStep("remove soleily", () => + { + var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == 241526); + + if (beatmap != null) beatmaps.Delete(beatmap); + }); + } + + private void assertDownloadEnabled(bool enabled) + { + AddAssert($"button {(enabled ? "enabled" : "disabled")}", () => downloadButton.Download.IsPresent && downloadButton.Download.Enabled.Value == enabled); + } + + private APIBeatmapSet createSoleily() + { + return new APIBeatmapSet + { + OnlineID = 241526, + Availability = new BeatmapSetOnlineAvailability + { + DownloadDisabled = false, + ExternalLink = string.Empty, + }, + }; + } + + private void createButtonWithBeatmap(APIBeatmapSet beatmap) + { + AddStep("create button", () => + { + Child = downloadButton = new TestDownloadButton(beatmap) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(2) + }; + }); + } + + private void createButton(bool downloadable) + { + AddStep("create button", () => + { + Child = downloadButton = new TestDownloadButton(downloadable ? getDownloadableBeatmapSet() : getUndownloadableBeatmapSet()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(2) + }; + }); + } + + private APIBeatmapSet getDownloadableBeatmapSet() + { + var normal = CreateAPIBeatmapSet(new OsuRuleset().RulesetInfo); + normal.HasVideo = true; + normal.HasStoryboard = true; + + return normal; + } + + private APIBeatmapSet getUndownloadableBeatmapSet() + { + var beatmap = CreateAPIBeatmapSet(new OsuRuleset().RulesetInfo); + beatmap.Artist = "test"; + beatmap.Title = "undownloadable"; + beatmap.AuthorString = "test"; + + beatmap.HasVideo = true; + beatmap.HasStoryboard = true; + + beatmap.Availability = new BeatmapSetOnlineAvailability + { + DownloadDisabled = true, + ExternalLink = "https://osu.ppy.sh", + }; + + return beatmap; + } + + private class TestDownloadButton : DownloadButton + { + public readonly Bindable State = new Bindable(); + public readonly BindableNumber Progress = new BindableNumber(); + + public new BeatmapCardIconButton Download => base.Download; + public new BeatmapCardIconButton Play => base.Play; + + public TestDownloadButton(APIBeatmapSet beatmapSet) + : base(beatmapSet) + { + Tracker.State.BindTo(State); + Tracker.Progress.BindTo(Progress); + } + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 71376c28f1..3202ac4b3c 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -102,14 +102,14 @@ namespace osu.Game.Beatmaps.Drawables.Cards RelativeSizeAxes = Axes.Y, Origin = Anchor.TopRight, Anchor = Anchor.TopRight, - Child = new FillFlowContainer + Child = new FillFlowContainer { AutoSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, Direction = FillDirection.Vertical, Spacing = new Vector2(0, 14), - Children = new BeatmapCardIconButton[] + Children = new Drawable[] { new FavouriteButton(beatmapSet) { Current = favouriteState }, new DownloadButton(beatmapSet) diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs index 00c0ccc3ce..47475e5b31 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs @@ -1,16 +1,71 @@ // 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.Game.Online.API.Requests.Responses; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Online; +using osu.Game.Overlays; +using osuTK; namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { - public class DownloadButton : BeatmapCardIconButton + public class DownloadButton : CompositeDrawable { + protected readonly DownloadIcon Download; + protected readonly PlayIcon Play; + protected readonly BeatmapDownloadTracker Tracker; + + private readonly CircularProgress downloadProgress; + public DownloadButton(APIBeatmapSet beatmapSet) { - Icon.Icon = FontAwesome.Solid.FileDownload; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + Tracker = new BeatmapDownloadTracker(beatmapSet), + Download = new DownloadIcon(), + downloadProgress = new CircularProgress + { + Size = new Vector2(16), + InnerRadius = 0.1f, + }, + Play = new PlayIcon() + }; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + downloadProgress.Colour = colourProvider.Highlight1; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + ((IBindable)downloadProgress.Current).BindTo(Tracker.Progress); + } + + protected class DownloadIcon : BeatmapCardIconButton + { + public DownloadIcon() + { + Icon.Icon = FontAwesome.Solid.Download; + } + } + + protected class PlayIcon : BeatmapCardIconButton + { + public PlayIcon() + { + Icon.Icon = FontAwesome.Regular.PlayCircle; + } } // TODO: implement behaviour From a60cceeda6f55f45e5b158c847b0d578ecabec9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Oct 2021 15:27:04 +0200 Subject: [PATCH 0065/4398] Implement basic appearance of download button --- .../TestSceneBeatmapCardDownloadButton.cs | 41 +++++++++++------ .../Drawables/Cards/Buttons/DownloadButton.cs | 44 +++++++++++++------ 2 files changed, 58 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs index 88de1f8b98..1d7acb0e7b 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs @@ -6,12 +6,14 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables.Cards.Buttons; using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Ranking.Expanded.Accuracy; using osu.Game.Tests.Resources; using osuTK; @@ -31,13 +33,25 @@ namespace osu.Game.Tests.Visual.Beatmaps public void TestDownloadableBeatmap() { createButton(true); + + assertDownloadVisible(true); assertDownloadEnabled(true); + assertProgressVisible(false); + assertPlayVisible(false); AddStep("set downloading state", () => downloadButton.State.Value = DownloadState.Downloading); + assertDownloadVisible(false); + assertProgressVisible(true); + assertPlayVisible(false); + AddStep("set progress to 30%", () => downloadButton.Progress.Value = 0.3f); AddStep("set progress to 100%", () => downloadButton.Progress.Value = 1f); AddStep("set importing state", () => downloadButton.State.Value = DownloadState.Importing); + AddStep("set locally available state", () => downloadButton.State.Value = DownloadState.LocallyAvailable); + assertDownloadVisible(false); + assertProgressVisible(false); + assertPlayVisible(true); } [Test] @@ -75,23 +89,22 @@ namespace osu.Game.Tests.Visual.Beatmaps }); } - private void assertDownloadEnabled(bool enabled) - { - AddAssert($"button {(enabled ? "enabled" : "disabled")}", () => downloadButton.Download.IsPresent && downloadButton.Download.Enabled.Value == enabled); - } + private void assertDownloadVisible(bool visible) => AddUntilStep($"download {(visible ? "visible" : "not visible")}", () => downloadButton.Download.IsPresent == visible); + private void assertDownloadEnabled(bool enabled) => AddAssert($"download {(enabled ? "enabled" : "disabled")}", () => downloadButton.Download.Enabled.Value == enabled); - private APIBeatmapSet createSoleily() + private void assertProgressVisible(bool visible) => AddUntilStep($"progress {(visible ? "visible" : "not visible")}", () => downloadButton.ChildrenOfType().Single().IsPresent == visible); + + private void assertPlayVisible(bool visible) => AddUntilStep($"play {(visible ? "visible" : "not visible")}", () => downloadButton.Play.IsPresent == visible); + + private static APIBeatmapSet createSoleily() => new APIBeatmapSet { - return new APIBeatmapSet + OnlineID = 241526, + Availability = new BeatmapSetOnlineAvailability { - OnlineID = 241526, - Availability = new BeatmapSetOnlineAvailability - { - DownloadDisabled = false, - ExternalLink = string.Empty, - }, - }; - } + DownloadDisabled = false, + ExternalLink = string.Empty, + }, + }; private void createButtonWithBeatmap(APIBeatmapSet beatmap) { diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs index 47475e5b31..43c222c949 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs @@ -2,14 +2,14 @@ // 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.Game.Online.API.Requests.Responses; -using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; using osu.Game.Online; using osu.Game.Overlays; +using osu.Game.Screens.Ranking.Expanded.Accuracy; using osuTK; namespace osu.Game.Beatmaps.Drawables.Cards.Buttons @@ -20,36 +20,54 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons protected readonly PlayIcon Play; protected readonly BeatmapDownloadTracker Tracker; - private readonly CircularProgress downloadProgress; + private readonly SmoothCircularProgress downloadProgress; + + [Resolved] + private OsuColour colours { get; set; } + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } public DownloadButton(APIBeatmapSet beatmapSet) { Anchor = Anchor.Centre; Origin = Anchor.Centre; + AutoSizeAxes = Axes.Both; InternalChildren = new Drawable[] { Tracker = new BeatmapDownloadTracker(beatmapSet), Download = new DownloadIcon(), - downloadProgress = new CircularProgress + downloadProgress = new SmoothCircularProgress { - Size = new Vector2(16), - InnerRadius = 0.1f, + Size = new Vector2(12), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + InnerRadius = 0.4f, }, Play = new PlayIcon() }; } - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - downloadProgress.Colour = colourProvider.Highlight1; - } - protected override void LoadComplete() { base.LoadComplete(); - ((IBindable)downloadProgress.Current).BindTo(Tracker.Progress); + + Tracker.Progress.BindValueChanged(_ => updateState()); + Tracker.State.BindValueChanged(_ => updateState(), true); + FinishTransforms(true); + } + + private void updateState() + { + Download.FadeTo(Tracker.State.Value == DownloadState.NotDownloaded ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + + downloadProgress.FadeTo(Tracker.State.Value == DownloadState.Downloading || Tracker.State.Value == DownloadState.Importing ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + downloadProgress.FadeColour(Tracker.State.Value == DownloadState.Importing ? colours.Yellow : colourProvider.Highlight1, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + if (Tracker.State.Value == DownloadState.Downloading) + downloadProgress.FillTo(Tracker.Progress.Value, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + + Play.FadeTo(Tracker.State.Value == DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); } protected class DownloadIcon : BeatmapCardIconButton From 2186b51676b77ab5e96427fa46a29ad641c3174b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Oct 2021 15:51:36 +0200 Subject: [PATCH 0066/4398] Implement detailed download button behaviour --- .../TestSceneBeatmapCardDownloadButton.cs | 31 ++++++-- .../Drawables/Cards/Buttons/DownloadButton.cs | 70 ++++++++++++++++--- 2 files changed, 87 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs index 1d7acb0e7b..11eb93c5ac 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs @@ -9,9 +9,11 @@ using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables.Cards.Buttons; +using osu.Game.Configuration; using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; +using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Ranking.Expanded.Accuracy; using osu.Game.Tests.Resources; @@ -29,15 +31,20 @@ namespace osu.Game.Tests.Visual.Beatmaps [Resolved] private BeatmapManager beatmaps { get; set; } + [Resolved] + private OsuConfigManager config { get; set; } + [Test] public void TestDownloadableBeatmap() { + ensureSoleilyRemoved(); createButton(true); assertDownloadVisible(true); assertDownloadEnabled(true); assertProgressVisible(false); assertPlayVisible(false); + AddAssert("tooltip text correct", () => downloadButton.Download.TooltipText == BeatmapsetsStrings.PanelDownloadAll); AddStep("set downloading state", () => downloadButton.State.Value = DownloadState.Downloading); assertDownloadVisible(false); @@ -54,17 +61,30 @@ namespace osu.Game.Tests.Visual.Beatmaps assertPlayVisible(true); } + [Test] + public void TestDownloadableBeatmapWithVideo() + { + createButton(true, true); + assertDownloadEnabled(true); + + AddStep("prefer no video", () => config.SetValue(OsuSetting.PreferNoVideo, true)); + AddAssert("tooltip text correct", () => downloadButton.Download.TooltipText == BeatmapsetsStrings.PanelDownloadNoVideo); + + AddStep("prefer video", () => config.SetValue(OsuSetting.PreferNoVideo, false)); + AddAssert("tooltip text correct", () => downloadButton.Download.TooltipText == BeatmapsetsStrings.PanelDownloadVideo); + } + [Test] public void TestUndownloadableBeatmap() { createButton(false); assertDownloadEnabled(false); + AddAssert("tooltip text correct", () => downloadButton.Download.TooltipText == BeatmapsetsStrings.AvailabilityDisabled); } [Test] public void TestDownloadState() { - AddUntilStep("ensure manager loaded", () => beatmaps != null); ensureSoleilyRemoved(); createButtonWithBeatmap(createSoleily()); AddAssert("button state not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded); @@ -81,6 +101,7 @@ namespace osu.Game.Tests.Visual.Beatmaps private void ensureSoleilyRemoved() { + AddUntilStep("ensure manager loaded", () => beatmaps != null); AddStep("remove soleily", () => { var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == 241526); @@ -119,11 +140,11 @@ namespace osu.Game.Tests.Visual.Beatmaps }); } - private void createButton(bool downloadable) + private void createButton(bool downloadable, bool hasVideo = false) { AddStep("create button", () => { - Child = downloadButton = new TestDownloadButton(downloadable ? getDownloadableBeatmapSet() : getUndownloadableBeatmapSet()) + Child = downloadButton = new TestDownloadButton(downloadable ? getDownloadableBeatmapSet(hasVideo) : getUndownloadableBeatmapSet()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -132,10 +153,10 @@ namespace osu.Game.Tests.Visual.Beatmaps }); } - private APIBeatmapSet getDownloadableBeatmapSet() + private APIBeatmapSet getDownloadableBeatmapSet(bool hasVideo) { var normal = CreateAPIBeatmapSet(new OsuRuleset().RulesetInfo); - normal.HasVideo = true; + normal.HasVideo = hasVideo; normal.HasStoryboard = true; return normal; diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs index 43c222c949..aeb36b579e 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs @@ -1,14 +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 osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Online; using osu.Game.Overlays; +using osu.Game.Resources.Localisation.Web; using osu.Game.Screens.Ranking.Expanded.Accuracy; using osuTK; @@ -23,10 +28,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons private readonly SmoothCircularProgress downloadProgress; [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; [Resolved] - private OverlayColourProvider colourProvider { get; set; } + private OverlayColourProvider colourProvider { get; set; } = null!; public DownloadButton(APIBeatmapSet beatmapSet) { @@ -37,7 +42,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons InternalChildren = new Drawable[] { Tracker = new BeatmapDownloadTracker(beatmapSet), - Download = new DownloadIcon(), + Download = new DownloadIcon(beatmapSet), downloadProgress = new SmoothCircularProgress { Size = new Vector2(12), @@ -45,7 +50,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons Origin = Anchor.Centre, InnerRadius = 0.4f, }, - Play = new PlayIcon() + Play = new PlayIcon(beatmapSet) }; } @@ -65,27 +70,74 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons downloadProgress.FadeTo(Tracker.State.Value == DownloadState.Downloading || Tracker.State.Value == DownloadState.Importing ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); downloadProgress.FadeColour(Tracker.State.Value == DownloadState.Importing ? colours.Yellow : colourProvider.Highlight1, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); if (Tracker.State.Value == DownloadState.Downloading) - downloadProgress.FillTo(Tracker.Progress.Value, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + downloadProgress.FillTo(Tracker.Progress.Value, Tracker.Progress.Value > 0 ? BeatmapCard.TRANSITION_DURATION : 0, Easing.OutQuint); Play.FadeTo(Tracker.State.Value == DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); } protected class DownloadIcon : BeatmapCardIconButton { - public DownloadIcon() + private readonly APIBeatmapSet beatmapSet; + private Bindable preferNoVideo = null!; + + [Resolved] + private BeatmapManager beatmaps { get; set; } = null!; + + public DownloadIcon(APIBeatmapSet beatmapSet) { Icon.Icon = FontAwesome.Solid.Download; + + this.beatmapSet = beatmapSet; + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + preferNoVideo = config.GetBindable(OsuSetting.PreferNoVideo); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + preferNoVideo.BindValueChanged(_ => updateState(), true); + } + + private void updateState() + { + if (beatmapSet.Availability.DownloadDisabled) + { + Enabled.Value = false; + TooltipText = BeatmapsetsStrings.AvailabilityDisabled; + return; + } + + if (!beatmapSet.HasVideo) + TooltipText = BeatmapsetsStrings.PanelDownloadAll; + else + TooltipText = preferNoVideo.Value ? BeatmapsetsStrings.PanelDownloadNoVideo : BeatmapsetsStrings.PanelDownloadVideo; + + Action = () => beatmaps.Download(beatmapSet, preferNoVideo.Value); } } protected class PlayIcon : BeatmapCardIconButton { - public PlayIcon() + private readonly APIBeatmapSet beatmapSet; + + public PlayIcon(APIBeatmapSet beatmapSet) { + this.beatmapSet = beatmapSet; + Icon.Icon = FontAwesome.Regular.PlayCircle; + TooltipText = "Go to beatmap"; + } + + [BackgroundDependencyLoader(true)] + private void load(OsuGame? game) + { + if (game != null) + Action = () => game.PresentBeatmap(beatmapSet); } } - - // TODO: implement behaviour } } From 704ff47a0e3404693785ba587ff2a97b74776d3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Nov 2021 08:07:56 +0100 Subject: [PATCH 0067/4398] Swap icon for "go to beatmap" state --- osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs index aeb36b579e..df765e1e69 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs @@ -128,7 +128,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { this.beatmapSet = beatmapSet; - Icon.Icon = FontAwesome.Regular.PlayCircle; + Icon.Icon = FontAwesome.Solid.AngleDoubleRight; TooltipText = "Go to beatmap"; } From 71f3a64165f81d50b00cf4cb2b6a82ca8be6dba6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 10 Nov 2021 18:25:14 +0900 Subject: [PATCH 0068/4398] Fix APIPlaylistItem serialisation --- osu.Game/Online/Rooms/APIPlaylistItem.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/Rooms/APIPlaylistItem.cs b/osu.Game/Online/Rooms/APIPlaylistItem.cs index a9156f35ce..e9803a3a10 100644 --- a/osu.Game/Online/Rooms/APIPlaylistItem.cs +++ b/osu.Game/Online/Rooms/APIPlaylistItem.cs @@ -3,6 +3,7 @@ #nullable enable +using System; using System.Collections.Generic; using System.Linq; using MessagePack; @@ -10,6 +11,8 @@ using osu.Game.Online.API; namespace osu.Game.Online.Rooms { + [Serializable] + [MessagePackObject] public class APIPlaylistItem { [Key(0)] From c7381b4df0045817bed83abbbffd65c397055041 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 10 Nov 2021 18:36:21 +0900 Subject: [PATCH 0069/4398] Fix host check Although the underlying issue is that the host user is not populated. Not sure whether this should be fixed at all. --- .../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 4fcf60c0e8..aa936109c6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -387,7 +387,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { case QueueModes.HostOnly: AddOrEditPlaylistButton.Text = "Edit beatmap"; - AddOrEditPlaylistButton.Alpha = client.Room.Host?.User?.Equals(client.LocalUser?.User) == true ? 1 : 0; + AddOrEditPlaylistButton.Alpha = client.IsHost ? 1 : 0; break; case QueueModes.FreeForAll: From 02efe24e39d7d940dea388aa439878b7776b8199 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 10 Nov 2021 18:36:55 +0900 Subject: [PATCH 0070/4398] Fix checksum not passed to AddPlaylistItem --- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index df1e0c1fc2..40b086873d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -59,6 +59,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer client.AddPlaylistItem(new APIPlaylistItem { 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() From aad4b8ff809ff49f136dbf006f629d69dd0d860c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 10 Nov 2021 18:42:46 +0900 Subject: [PATCH 0071/4398] Fix ready button remaining enabled for expired items --- .../OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index b854b6d964..ce988e377f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -106,7 +106,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match break; } - bool enableButton = Room?.State == MultiplayerRoomState.Open && !operationInProgress.Value; + bool enableButton = + Room?.State == MultiplayerRoomState.Open + && Client.CurrentMatchPlayingItem.Value?.Expired == false + && !operationInProgress.Value; // When the local user is the host and spectating the match, the "start match" state should be enabled if any users are ready. if (localUser?.State == MultiplayerUserState.Spectating) From 78793d8624e6045903ace6a71785d16bf3d8f667 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 10 Nov 2021 19:58:25 +0900 Subject: [PATCH 0072/4398] Update interface to remove items --- osu.Game/Online/Multiplayer/IMultiplayerClient.cs | 4 ++-- osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs | 4 ++-- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 6 +++--- osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs | 6 +++--- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs index 81979c146c..4cadc5d606 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs @@ -116,8 +116,8 @@ namespace osu.Game.Online.Multiplayer /// /// Signals that an item has been removed from the playlist. /// - /// The removed item. - Task PlaylistItemRemoved(APIPlaylistItem item); + /// The removed item. + Task PlaylistItemRemoved(long playlistItemId); /// /// Signals that an item has been changed in the playlist. diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index 1f2e45b482..d6ff4f4993 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs @@ -86,7 +86,7 @@ namespace osu.Game.Online.Multiplayer /// /// Removes an item from the playlist. /// - /// The item to remove. - Task RemovePlaylistItem(APIPlaylistItem item); + /// The item to remove. + Task RemovePlaylistItem(long playlistItemId); } } diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index ed45662fec..ede42a8d1e 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -307,7 +307,7 @@ namespace osu.Game.Online.Multiplayer public abstract Task AddPlaylistItem(APIPlaylistItem item); - public abstract Task RemovePlaylistItem(APIPlaylistItem item); + public abstract Task RemovePlaylistItem(long playlistItemId); Task IMultiplayerClient.RoomStateChanged(MultiplayerRoomState state) { @@ -613,7 +613,7 @@ namespace osu.Game.Online.Multiplayer }).ConfigureAwait(false); } - public Task PlaylistItemRemoved(APIPlaylistItem item) + public Task PlaylistItemRemoved(long playlistItemId) { if (Room == null) return Task.CompletedTask; @@ -625,7 +625,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(APIRoom != null); - APIRoom.Playlist.RemoveAll(i => i.ID == item.ID); + APIRoom.Playlist.RemoveAll(i => i.ID == playlistItemId); RoomUpdated?.Invoke(); }); } diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 94225b6de9..2f0e1623e7 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -63,7 +63,7 @@ namespace osu.Game.Online.Multiplayer connection.On(nameof(IMultiplayerClient.MatchUserStateChanged), ((IMultiplayerClient)this).MatchUserStateChanged); connection.On(nameof(IMultiplayerClient.MatchEvent), ((IMultiplayerClient)this).MatchEvent); connection.On(nameof(IMultiplayerClient.PlaylistItemAdded), ((IMultiplayerClient)this).PlaylistItemAdded); - connection.On(nameof(IMultiplayerClient.PlaylistItemRemoved), ((IMultiplayerClient)this).PlaylistItemRemoved); + connection.On(nameof(IMultiplayerClient.PlaylistItemRemoved), ((IMultiplayerClient)this).PlaylistItemRemoved); connection.On(nameof(IMultiplayerClient.PlaylistItemChanged), ((IMultiplayerClient)this).PlaylistItemChanged); }; @@ -159,12 +159,12 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.AddPlaylistItem), item); } - public override Task RemovePlaylistItem(APIPlaylistItem item) + public override Task RemovePlaylistItem(long playlistItemId) { if (!IsConnected.Value) return Task.CompletedTask; - return connection.InvokeAsync(nameof(IMultiplayerServer.RemovePlaylistItem), item); + return connection.InvokeAsync(nameof(IMultiplayerServer.RemovePlaylistItem), playlistItemId); } protected override Task GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index d35e12b6a2..ba004f83f8 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -299,14 +299,14 @@ namespace osu.Game.Tests.Visual.Multiplayer await addPlaylistItem(item).ConfigureAwait(false); } - public override Task RemovePlaylistItem(APIPlaylistItem item) + public override Task RemovePlaylistItem(long playlistItemId) { Debug.Assert(Room != null); if (Room.Host?.UserID != LocalUser?.UserID) throw new InvalidOperationException("Local user is not the room host."); - return ((IMultiplayerClient)this).PlaylistItemRemoved(item); + return ((IMultiplayerClient)this).PlaylistItemRemoved(playlistItemId); } protected override Task GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default) From 1bc6a58528c85574b68aac5e13b98411089212e3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 10 Nov 2021 21:23:18 +0900 Subject: [PATCH 0073/4398] Fix RoomUpdated() not invoked on item changes --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index ede42a8d1e..cf61b83e08 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -656,6 +656,8 @@ namespace osu.Game.Online.Multiplayer // If the currently-selected item was the one that got replaced, update the selected item to the new one. if (CurrentMatchPlayingItem.Value == oldItem) CurrentMatchPlayingItem.Value = APIRoom.Playlist[index]; + + RoomUpdated?.Invoke(); }).ConfigureAwait(false); } From c51cb623158d60745e4275dcb288019932b041c9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 10 Nov 2021 21:27:20 +0900 Subject: [PATCH 0074/4398] Adjust TestMultiplayerClient for correctness --- .../Visual/Multiplayer/TestMultiplayerClient.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index ba004f83f8..d2fc403d1d 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -383,25 +383,20 @@ namespace osu.Game.Tests.Visual.Multiplayer Debug.Assert(Room != null); Debug.Assert(APIRoom != null); - long nextId = 0; + long nextId; switch (Room.Settings.QueueMode) { - case QueueModes.HostOnly: + default: // Pick the single non-expired playlist item. - nextId = APIRoom.Playlist.SingleOrDefault(i => !i.Expired)?.ID ?? 0; + nextId = APIRoom.Playlist.FirstOrDefault(i => !i.Expired)?.ID + ?? APIRoom.Playlist.LastOrDefault()?.ID + ?? 0; break; case QueueModes.FairRotate: // Group playlist items by (user_id -> count_expired), and select the first available playlist item from a user that has available beatmaps where count_expired is the lowest. throw new NotImplementedException(); - - case QueueModes.FreeForAll: - // Pick the first available non-expired playlist item, or default to the last. - nextId = APIRoom.Playlist.FirstOrDefault(i => !i.Expired)?.ID - ?? APIRoom.Playlist.LastOrDefault()?.ID - ?? 0; - break; } if (nextId != Room.Settings.PlaylistItemId) From f090e5ca7525edf51328dd892822941027ffd64f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 10 Nov 2021 15:24:36 +0100 Subject: [PATCH 0075/4398] Restyle card buttons to resemble buttons more --- .../Cards/BeatmapCardDownloadProgressBar.cs | 10 +++++ .../Cards/Buttons/BeatmapCardIconButton.cs | 44 ++++++++++++++----- 2 files changed, 44 insertions(+), 10 deletions(-) create mode 100644 osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs new file mode 100644 index 0000000000..56e23f920b --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs @@ -0,0 +1,10 @@ +// 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.Beatmaps.Drawables.Cards +{ + public class BeatmapCardDownloadProgressBar + { + + } +} diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs index 155259d859..ebd78938c7 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.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.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Containers; using osu.Game.Overlays; @@ -12,26 +16,45 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { public abstract class BeatmapCardIconButton : OsuHoverContainer { + protected override IEnumerable EffectTargets => background.Yield(); + + private readonly Box background; protected readonly SpriteIcon Icon; - private float size; + private float iconSize; - public new float Size + public float IconSize { - get => size; + get => iconSize; set { - size = value; - Icon.Size = new Vector2(size); + iconSize = value; + Icon.Size = new Vector2(iconSize); } } protected BeatmapCardIconButton() { - Add(Icon = new SpriteIcon()); + Child = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + Icon = new SpriteIcon + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre + } + } + }; - AutoSizeAxes = Axes.Both; - Size = 12; + Size = new Vector2(24); + IconSize = 12; } [BackgroundDependencyLoader] @@ -39,8 +62,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { Anchor = Origin = Anchor.Centre; - IdleColour = colourProvider.Light1; - HoverColour = colourProvider.Content1; + IdleColour = colourProvider.Background4; + HoverColour = colourProvider.Background1; + Icon.Colour = colourProvider.Content2; } } } From 91baaa73f37c42d0adaf95aad2893c902f7f1019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 10 Nov 2021 16:03:48 +0100 Subject: [PATCH 0076/4398] Show download progress on card body rather than in button --- .../Visual/Beatmaps/TestSceneBeatmapCard.cs | 17 ++++ .../TestSceneBeatmapCardDownloadButton.cs | 15 ++-- .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 60 +++++++++---- .../Cards/BeatmapCardDownloadProgressBar.cs | 84 ++++++++++++++++++- .../Drawables/Cards/Buttons/DownloadButton.cs | 19 +---- 5 files changed, 148 insertions(+), 47 deletions(-) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs index 0feaa8f480..29a1e349c4 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -27,6 +27,9 @@ namespace osu.Game.Tests.Visual.Beatmaps private APIBeatmapSet[] testCases; + [Resolved] + private BeatmapManager beatmaps { get; set; } + #region Test case generation [BackgroundDependencyLoader] @@ -35,6 +38,7 @@ namespace osu.Game.Tests.Visual.Beatmaps var normal = CreateAPIBeatmapSet(Ruleset.Value); normal.HasVideo = true; normal.HasStoryboard = true; + normal.OnlineID = 241526; var withStatistics = CreateAPIBeatmapSet(Ruleset.Value); withStatistics.Title = withStatistics.TitleUnicode = "play favourite stats"; @@ -180,6 +184,19 @@ namespace osu.Game.Tests.Visual.Beatmaps request.TriggerSuccess(); return true; }); + + ensureSoleilyRemoved(); + } + + private void ensureSoleilyRemoved() + { + AddUntilStep("ensure manager loaded", () => beatmaps != null); + AddStep("remove soleily", () => + { + var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == 241526); + + if (beatmap != null) beatmaps.Delete(beatmap); + }); } private Drawable createContent(OverlayColourScheme colourScheme, Func creationFunc) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs index 11eb93c5ac..82ab55cf0e 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs @@ -6,7 +6,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables.Cards.Buttons; using osu.Game.Configuration; @@ -15,7 +14,6 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets.Osu; -using osu.Game.Screens.Ranking.Expanded.Accuracy; using osu.Game.Tests.Resources; using osuTK; @@ -42,22 +40,21 @@ namespace osu.Game.Tests.Visual.Beatmaps assertDownloadVisible(true); assertDownloadEnabled(true); - assertProgressVisible(false); assertPlayVisible(false); AddAssert("tooltip text correct", () => downloadButton.Download.TooltipText == BeatmapsetsStrings.PanelDownloadAll); AddStep("set downloading state", () => downloadButton.State.Value = DownloadState.Downloading); - assertDownloadVisible(false); - assertProgressVisible(true); + assertDownloadVisible(true); + assertDownloadEnabled(false); assertPlayVisible(false); - AddStep("set progress to 30%", () => downloadButton.Progress.Value = 0.3f); - AddStep("set progress to 100%", () => downloadButton.Progress.Value = 1f); AddStep("set importing state", () => downloadButton.State.Value = DownloadState.Importing); + assertDownloadVisible(true); + assertDownloadEnabled(false); + assertPlayVisible(false); AddStep("set locally available state", () => downloadButton.State.Value = DownloadState.LocallyAvailable); assertDownloadVisible(false); - assertProgressVisible(false); assertPlayVisible(true); } @@ -113,8 +110,6 @@ namespace osu.Game.Tests.Visual.Beatmaps private void assertDownloadVisible(bool visible) => AddUntilStep($"download {(visible ? "visible" : "not visible")}", () => downloadButton.Download.IsPresent == visible); private void assertDownloadEnabled(bool enabled) => AddAssert($"download {(enabled ? "enabled" : "disabled")}", () => downloadButton.Download.Enabled.Value == enabled); - private void assertProgressVisible(bool visible) => AddUntilStep($"progress {(visible ? "visible" : "not visible")}", () => downloadButton.ChildrenOfType().Single().IsPresent == visible); - private void assertPlayVisible(bool visible) => AddUntilStep($"play {(visible ? "visible" : "not visible")}", () => downloadButton.Play.IsPresent == visible); private static APIBeatmapSet createSoleily() => new APIBeatmapSet diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 3202ac4b3c..fe25d3dc36 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -50,6 +50,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards private GridContainer artistContainer; private FillFlowContainer statisticsContainer; + private FillFlowContainer idleBottomContent; + private BeatmapCardDownloadProgressBar downloadProgressBar; + [Resolved] private OverlayColourProvider colourProvider { get; set; } @@ -218,34 +221,51 @@ namespace osu.Game.Beatmaps.Drawables.Cards } } }, - new FillFlowContainer + new Container { Name = @"Bottom content", RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Padding = new MarginPadding - { - Horizontal = 10, - Vertical = 4 - }, - Spacing = new Vector2(4, 0), Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Children = new Drawable[] { - new BeatmapSetOnlineStatusPill + idleBottomContent = new FillFlowContainer { - AutoSizeAxes = Axes.Both, - Status = beatmapSet.Status, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding + { + Horizontal = 10, + Vertical = 4 + }, + Spacing = new Vector2(4, 0), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Children = new Drawable[] + { + new BeatmapSetOnlineStatusPill + { + AutoSizeAxes = Axes.Both, + Status = beatmapSet.Status, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + }, + new DifficultySpectrumDisplay(beatmapSet) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + DotSize = new Vector2(6, 12) + } + } }, - new DifficultySpectrumDisplay(beatmapSet) + downloadProgressBar = new BeatmapCardDownloadProgressBar(beatmapSet) { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - DotSize = new Vector2(6, 12) + RelativeSizeAxes = Axes.X, + Height = 0, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft } } } @@ -283,7 +303,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards protected override void LoadComplete() { base.LoadComplete(); - updateState(); + + downloadProgressBar.IsActive.BindValueChanged(_ => updateState(), true); FinishTransforms(true); } @@ -335,6 +356,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards leftCover.FadeColour(IsHovered ? OsuColour.Gray(0.2f) : Color4.White, TRANSITION_DURATION, Easing.OutQuint); statisticsContainer.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); rightButtonArea.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); + + idleBottomContent.FadeTo(downloadProgressBar.IsActive.Value ? 0 : 1, TRANSITION_DURATION, Easing.OutQuint); + downloadProgressBar.ResizeHeightTo(downloadProgressBar.IsActive.Value ? 8 : 0, TRANSITION_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs index 56e23f920b..a016f99b20 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs @@ -1,10 +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 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; +using osu.Game.Online; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; + namespace osu.Game.Beatmaps.Drawables.Cards { - public class BeatmapCardDownloadProgressBar + public class BeatmapCardDownloadProgressBar : CompositeDrawable { - + private readonly BindableBool isActive = new BindableBool(); + public IBindable IsActive => isActive; + + public override bool IsPresent => true; + + private readonly BeatmapDownloadTracker tracker; + private readonly Box background; + private readonly Box foreground; + + [Resolved] + private OsuColour colours { get; set; } + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + public BeatmapCardDownloadProgressBar(APIBeatmapSet beatmapSet) + { + InternalChildren = new Drawable[] + { + tracker = new BeatmapDownloadTracker(beatmapSet), + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + foreground = new Box + { + RelativeSizeAxes = Axes.Both + } + }; + } + + [BackgroundDependencyLoader] + private void load() + { + background.Colour = colourProvider.Background6; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + tracker.State.BindValueChanged(_ => stateChanged(), true); + tracker.Progress.BindValueChanged(_ => progressChanged(), true); + } + + private void stateChanged() + { + switch (tracker.State.Value) + { + case DownloadState.Downloading: + FinishTransforms(true); + foreground.Colour = colourProvider.Highlight1; + isActive.Value = true; + break; + + case DownloadState.Importing: + foreground.FadeColour(colours.Yellow, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + isActive.Value = true; + break; + + default: + isActive.Value = false; + break; + } + } + + private void progressChanged() + { + double progress = tracker.Progress.Value; + foreground.ResizeWidthTo((float)progress, progress > 0 ? BeatmapCard.TRANSITION_DURATION : 0, Easing.OutQuint); + } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs index df765e1e69..85e2988614 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs @@ -14,8 +14,6 @@ using osu.Game.Graphics; using osu.Game.Online; using osu.Game.Overlays; using osu.Game.Resources.Localisation.Web; -using osu.Game.Screens.Ranking.Expanded.Accuracy; -using osuTK; namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { @@ -25,8 +23,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons protected readonly PlayIcon Play; protected readonly BeatmapDownloadTracker Tracker; - private readonly SmoothCircularProgress downloadProgress; - [Resolved] private OsuColour colours { get; set; } = null!; @@ -43,13 +39,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { Tracker = new BeatmapDownloadTracker(beatmapSet), Download = new DownloadIcon(beatmapSet), - downloadProgress = new SmoothCircularProgress - { - Size = new Vector2(12), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - InnerRadius = 0.4f, - }, Play = new PlayIcon(beatmapSet) }; } @@ -65,12 +54,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons private void updateState() { - Download.FadeTo(Tracker.State.Value == DownloadState.NotDownloaded ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - - downloadProgress.FadeTo(Tracker.State.Value == DownloadState.Downloading || Tracker.State.Value == DownloadState.Importing ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - downloadProgress.FadeColour(Tracker.State.Value == DownloadState.Importing ? colours.Yellow : colourProvider.Highlight1, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - if (Tracker.State.Value == DownloadState.Downloading) - downloadProgress.FillTo(Tracker.Progress.Value, Tracker.Progress.Value > 0 ? BeatmapCard.TRANSITION_DURATION : 0, Easing.OutQuint); + Download.FadeTo(Tracker.State.Value != DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + Download.Enabled.Value = Tracker.State.Value == DownloadState.NotDownloaded; Play.FadeTo(Tracker.State.Value == DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); } From 222846290ead013320a99fab4c55ca59a309a6bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 10 Nov 2021 18:38:52 +0100 Subject: [PATCH 0077/4398] Update download progress bar appearance to match intended design --- .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 72 +++++++++++-------- .../Cards/BeatmapCardDownloadProgressBar.cs | 31 +++++--- 2 files changed, 63 insertions(+), 40 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index fe25d3dc36..09ceb72a10 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -210,15 +210,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards d.AddText("mapped by ", t => t.Colour = colourProvider.Content2); d.AddUserLink(beatmapSet.Author); }), - statisticsContainer = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - Alpha = 0, - ChildrenEnumerable = createStatistics() - } } }, new Container @@ -228,44 +219,63 @@ namespace osu.Game.Beatmaps.Drawables.Cards AutoSizeAxes = Axes.Y, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, + Padding = new MarginPadding + { + Horizontal = 10, + Vertical = 4 + }, Children = new Drawable[] { idleBottomContent = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Padding = new MarginPadding - { - Horizontal = 10, - Vertical = 4 - }, - Spacing = new Vector2(4, 0), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 3), + AlwaysPresent = true, Children = new Drawable[] { - new BeatmapSetOnlineStatusPill + statisticsContainer = new FillFlowContainer { - AutoSizeAxes = Axes.Both, - Status = beatmapSet.Status, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Alpha = 0, + AlwaysPresent = true, + ChildrenEnumerable = createStatistics() }, - new DifficultySpectrumDisplay(beatmapSet) + new FillFlowContainer { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - DotSize = new Vector2(6, 12) + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(4, 0), + Children = new Drawable[] + { + new BeatmapSetOnlineStatusPill + { + AutoSizeAxes = Axes.Both, + Status = beatmapSet.Status, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + }, + new DifficultySpectrumDisplay(beatmapSet) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + DotSize = new Vector2(6, 12) + } + } } } }, downloadProgressBar = new BeatmapCardDownloadProgressBar(beatmapSet) { RelativeSizeAxes = Axes.X, - Height = 0, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft + Height = 6, + Anchor = Anchor.Centre, + Origin = Anchor.Centre } } } @@ -358,7 +368,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards rightButtonArea.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); idleBottomContent.FadeTo(downloadProgressBar.IsActive.Value ? 0 : 1, TRANSITION_DURATION, Easing.OutQuint); - downloadProgressBar.ResizeHeightTo(downloadProgressBar.IsActive.Value ? 8 : 0, TRANSITION_DURATION, Easing.OutQuint); + downloadProgressBar.FadeTo(downloadProgressBar.IsActive.Value ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs index a016f99b20..37a7e7d41d 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs @@ -21,8 +21,11 @@ namespace osu.Game.Beatmaps.Drawables.Cards public override bool IsPresent => true; private readonly BeatmapDownloadTracker tracker; - private readonly Box background; - private readonly Box foreground; + private readonly CircularContainer background; + private readonly CircularContainer foreground; + + private readonly Box backgroundFill; + private readonly Box foregroundFill; [Resolved] private OsuColour colours { get; set; } @@ -35,13 +38,23 @@ namespace osu.Game.Beatmaps.Drawables.Cards InternalChildren = new Drawable[] { tracker = new BeatmapDownloadTracker(beatmapSet), - background = new Box + background = new CircularContainer { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = backgroundFill = new Box + { + RelativeSizeAxes = Axes.Both, + } }, - foreground = new Box + foreground = new CircularContainer { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = foregroundFill = new Box + { + RelativeSizeAxes = Axes.Both, + } } }; } @@ -49,7 +62,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards [BackgroundDependencyLoader] private void load() { - background.Colour = colourProvider.Background6; + backgroundFill.Colour = colourProvider.Background6; } protected override void LoadComplete() @@ -66,12 +79,12 @@ namespace osu.Game.Beatmaps.Drawables.Cards { case DownloadState.Downloading: FinishTransforms(true); - foreground.Colour = colourProvider.Highlight1; + foregroundFill.Colour = colourProvider.Highlight1; isActive.Value = true; break; case DownloadState.Importing: - foreground.FadeColour(colours.Yellow, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + foregroundFill.FadeColour(colours.Yellow, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); isActive.Value = true; break; From 5c8c121446e44f92840ff74a783fc5c9861db4dc Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 11 Nov 2021 23:39:15 +0900 Subject: [PATCH 0078/4398] Update behaviour to match server (removing playlist items) + tests --- .../TestSceneFreeForAllQueueMode.cs | 6 +-- .../Multiplayer/TestMultiplayerClient.cs | 49 ++++++++++--------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneFreeForAllQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneFreeForAllQueueMode.cs index 906e528ac5..0cf330bbbb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneFreeForAllQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneFreeForAllQueueMode.cs @@ -73,9 +73,9 @@ namespace osu.Game.Tests.Visual.Multiplayer.QueueingModes RunGameplay(); AddStep("change queue mode", () => Client.ChangeSettings(queueMode: QueueModes.HostOnly)); - AddAssert("playlist has 1 item", () => Client.APIRoom?.Playlist.Count == 1); - AddAssert("playlist item is the same as last selected", () => Client.APIRoom?.Playlist[0].Beatmap.Value.OnlineID == OtherBeatmap.OnlineID); - AddAssert("playlist item is not expired", () => Client.APIRoom?.Playlist[0].Expired == false); + AddAssert("playlist has 2 items", () => Client.APIRoom?.Playlist.Count == 2); + AddAssert("playlist item is the other beatmap", () => Client.CurrentMatchPlayingItem.Value?.BeatmapID == OtherBeatmap.OnlineID); + AddAssert("playlist item is not expired", () => Client.APIRoom?.Playlist[1].Expired == false); } private void addItem(Func beatmap) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 0e0c9480dd..036afb26b4 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -209,29 +209,10 @@ namespace osu.Game.Tests.Visual.Multiplayer Debug.Assert(Room != null); Debug.Assert(APIRoom != null); - switch (Room.Settings.QueueMode, settings.QueueMode) - { - case (QueueModes.HostOnly, QueueModes.HostOnly): - break; + // Server is authoritative for the time being. + settings.PlaylistItemId = Room.Settings.PlaylistItemId; - // Host-only is incompatible with other queueing modes, so expire all non-expired items. - case (QueueModes.HostOnly, _): - case (_, QueueModes.HostOnly): - foreach (var playlistItem in APIRoom.Playlist.Where(i => !i.Expired).ToArray()) - { - playlistItem.Expired = true; - await ((IMultiplayerClient)this).PlaylistItemChanged(new APIPlaylistItem(playlistItem)).ConfigureAwait(false); - } - - break; - } - - await ((IMultiplayerClient)this).SettingsChanged(settings).ConfigureAwait(false); - - foreach (var user in Room.Users.Where(u => u.State == MultiplayerUserState.Ready)) - ChangeUserState(user.UserID, MultiplayerUserState.Idle); - - await changeMatchType(settings.MatchType).ConfigureAwait(false); + await changeSettings(settings).ConfigureAwait(false); } public override Task ChangeState(MultiplayerUserState newState) @@ -344,6 +325,28 @@ namespace osu.Game.Tests.Visual.Multiplayer return Task.FromResult(apiSet); } + private async Task changeSettings(MultiplayerRoomSettings settings) + { + Debug.Assert(Room != null); + Debug.Assert(APIRoom != null); + + if (settings.QueueMode == QueueModes.HostOnly) + { + foreach (var playlistItem in APIRoom.Playlist.Where(i => !i.Expired && i.ID != Room.Settings.PlaylistItemId).ToArray()) + { + APIRoom.Playlist.Remove(playlistItem); + await ((IMultiplayerClient)this).PlaylistItemRemoved(playlistItem.ID).ConfigureAwait(false); + } + } + + await ((IMultiplayerClient)this).SettingsChanged(settings).ConfigureAwait(false); + + foreach (var user in Room.Users.Where(u => u.State == MultiplayerUserState.Ready)) + ChangeUserState(user.UserID, MultiplayerUserState.Idle); + + await changeMatchType(settings.MatchType).ConfigureAwait(false); + } + private async Task changeMatchType(MatchType type) { Debug.Assert(Room != null); @@ -417,7 +420,7 @@ namespace osu.Game.Tests.Visual.Multiplayer if (nextId != Room.Settings.PlaylistItemId) { Room.Settings.PlaylistItemId = nextId; - await ChangeSettings(Room.Settings).ConfigureAwait(false); + await changeSettings(Room.Settings).ConfigureAwait(false); } } } From 131e64e56c14760ffc408993ca5416d44f863528 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Fri, 12 Nov 2021 21:29:51 +1100 Subject: [PATCH 0079/4398] 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 0080/4398] 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 0081/4398] 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 25202316cc62074b352eece649b50d29e02e1520 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 13 Nov 2021 00:22:48 +0900 Subject: [PATCH 0082/4398] Add user id to APIPlaylistItem --- osu.Game/Online/Rooms/APIPlaylistItem.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Rooms/APIPlaylistItem.cs b/osu.Game/Online/Rooms/APIPlaylistItem.cs index e9803a3a10..eddce69976 100644 --- a/osu.Game/Online/Rooms/APIPlaylistItem.cs +++ b/osu.Game/Online/Rooms/APIPlaylistItem.cs @@ -19,21 +19,24 @@ namespace osu.Game.Online.Rooms public long ID { get; set; } [Key(1)] - public int BeatmapID { get; set; } + public int UserID { get; set; } [Key(2)] - public string BeatmapChecksum { get; set; } = string.Empty; + public int BeatmapID { get; set; } [Key(3)] - public int RulesetID { get; set; } + public string BeatmapChecksum { get; set; } = string.Empty; [Key(4)] - public IEnumerable RequiredMods { get; set; } = Enumerable.Empty(); + public int RulesetID { get; set; } [Key(5)] - public IEnumerable AllowedMods { get; set; } = Enumerable.Empty(); + public IEnumerable RequiredMods { get; set; } = Enumerable.Empty(); [Key(6)] + public IEnumerable AllowedMods { get; set; } = Enumerable.Empty(); + + [Key(7)] public bool Expired { get; set; } public APIPlaylistItem() From 867403d94737cd85479199d44038231e19d95fad Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 13 Nov 2021 01:42:51 +0900 Subject: [PATCH 0083/4398] Update TestMultiplayerClient to be more in line with server --- .../Multiplayer/TestMultiplayerClient.cs | 132 +++++++++++------- 1 file changed, 79 insertions(+), 53 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 8f7e6d7f5c..7520740eb6 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -42,6 +42,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private BeatmapManager beatmaps { get; set; } = null!; private readonly TestMultiplayerRoomManager roomManager; + private PlaylistItem? currentItem; public TestMultiplayerClient(TestMultiplayerRoomManager roomManager) { @@ -138,7 +139,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ((IMultiplayerClient)this).ResultsReady(); - finishPlaylistItem().ContinueWith(_ => advanceToNextPlaylistItem()); + Task.Run(finishCurrentItem); } break; @@ -153,7 +154,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged(userId, newBeatmapAvailability); } - protected override Task JoinRoom(long roomId, string? password = null) + protected override async Task JoinRoom(long roomId, string? password = null) { var apiRoom = roomManager.ServerSideRooms.Single(r => r.RoomID.Value == roomId); @@ -165,13 +166,16 @@ namespace osu.Game.Tests.Visual.Multiplayer User = api.LocalUser.Value }; + await updateCurrentItem(apiRoom: apiRoom).ConfigureAwait(false); + Debug.Assert(currentItem != null); + var room = new MultiplayerRoom(roomId) { Settings = { Name = apiRoom.Name.Value, MatchType = apiRoom.Type.Value, - PlaylistItemId = apiRoom.Playlist.Single().ID, + PlaylistItemId = currentItem.ID, Password = password, QueueMode = apiRoom.QueueMode.Value }, @@ -182,7 +186,7 @@ namespace osu.Game.Tests.Visual.Multiplayer RoomSetupAction?.Invoke(room); RoomSetupAction = null; - return Task.FromResult(room); + return room; } protected override void OnRoomJoined() @@ -208,11 +212,19 @@ namespace osu.Game.Tests.Visual.Multiplayer { Debug.Assert(Room != null); Debug.Assert(APIRoom != null); + Debug.Assert(currentItem != null); // Server is authoritative for the time being. settings.PlaylistItemId = Room.Settings.PlaylistItemId; - await changeSettings(settings).ConfigureAwait(false); + await changeQueueMode(settings.QueueMode).ConfigureAwait(false); + + await ((IMultiplayerClient)this).SettingsChanged(settings).ConfigureAwait(false); + + foreach (var user in Room.Users.Where(u => u.State == MultiplayerUserState.Ready)) + ChangeUserState(user.UserID, MultiplayerUserState.Idle); + + await changeMatchType(settings.MatchType).ConfigureAwait(false); } public override Task ChangeState(MultiplayerUserState newState) @@ -282,17 +294,28 @@ namespace osu.Game.Tests.Visual.Multiplayer { Debug.Assert(Room != null); Debug.Assert(APIRoom != null); + Debug.Assert(currentItem != null); - if (Room.Settings.QueueMode == QueueModes.HostOnly && APIRoom.Playlist.Count > 0) + if (Room.Settings.QueueMode == QueueModes.HostOnly && Room.Host?.UserID != LocalUser?.UserID) + throw new InvalidOperationException("Local user is not the room host."); + + switch (Room.Settings.QueueMode) { - if (Room.Host?.UserID != LocalUser?.UserID) - throw new InvalidOperationException("Local user is not the room host."); + case QueueModes.HostOnly: + // In host-only mode, the current item is re-used. + item.ID = currentItem.ID; + await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); - item.ID = APIRoom.Playlist.Last().ID; - await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); + // Note: Unlike the server, this is the easiest way to update the current item at this point. + await updateCurrentItem(false).ConfigureAwait(false); + break; + + default: + item.ID = APIRoom.Playlist.Last().ID + 1; + await ((IMultiplayerClient)this).PlaylistItemAdded(item).ConfigureAwait(false); + await updateCurrentItem().ConfigureAwait(false); + break; } - else - await addPlaylistItem(item).ConfigureAwait(false); } public override Task RemovePlaylistItem(long playlistItemId) @@ -325,28 +348,6 @@ namespace osu.Game.Tests.Visual.Multiplayer return Task.FromResult(apiSet); } - private async Task changeSettings(MultiplayerRoomSettings settings) - { - Debug.Assert(Room != null); - Debug.Assert(APIRoom != null); - - if (settings.QueueMode == QueueModes.HostOnly) - { - foreach (var playlistItem in APIRoom.Playlist.Where(i => !i.Expired && i.ID != Room.Settings.PlaylistItemId).ToArray()) - { - APIRoom.Playlist.Remove(playlistItem); - await ((IMultiplayerClient)this).PlaylistItemRemoved(playlistItem.ID).ConfigureAwait(false); - } - } - - await ((IMultiplayerClient)this).SettingsChanged(settings).ConfigureAwait(false); - - foreach (var user in Room.Users.Where(u => u.State == MultiplayerUserState.Ready)) - ChangeUserState(user.UserID, MultiplayerUserState.Idle); - - await changeMatchType(settings.MatchType).ConfigureAwait(false); - } - private async Task changeMatchType(MatchType type) { Debug.Assert(Room != null); @@ -369,47 +370,69 @@ namespace osu.Game.Tests.Visual.Multiplayer } } - private async Task addPlaylistItem(APIPlaylistItem newItem) + private async Task changeQueueMode(QueueModes newMode) { - Debug.Assert(Room != null); Debug.Assert(APIRoom != null); + Debug.Assert(currentItem != null); - newItem.ID = (APIRoom.Playlist.LastOrDefault()?.ID ?? 0) + 1; - await ((IMultiplayerClient)this).PlaylistItemAdded(newItem).ConfigureAwait(false); + if (newMode == QueueModes.HostOnly) + { + foreach (var playlistItem in APIRoom.Playlist.Where(i => !i.Expired && i.ID != currentItem.ID).ToArray()) + await ((IMultiplayerClient)this).PlaylistItemRemoved(playlistItem.ID).ConfigureAwait(false); + } - // A more valid selection can occur as a result of adding a new playlist item (e.g. if all previous items were expired). - await advanceToNextPlaylistItem().ConfigureAwait(false); + // When changing modes, items could have been added (above) or the queueing order could have changed. + await updateCurrentItem().ConfigureAwait(false); } - private async Task finishPlaylistItem() + private async Task finishCurrentItem() { Debug.Assert(Room != null); Debug.Assert(APIRoom != null); - - var currentItem = APIRoom.Playlist.Single(i => i.ID == Room.Settings.PlaylistItemId); + Debug.Assert(currentItem != null); // Expire the current playlist item. await ((IMultiplayerClient)this).PlaylistItemChanged(new APIPlaylistItem(currentItem) { Expired = true }).ConfigureAwait(false); // In host-only mode, a duplicate playlist item will be used for the next round. if (Room.Settings.QueueMode == QueueModes.HostOnly) - await addPlaylistItem(new APIPlaylistItem(currentItem)).ConfigureAwait(false); + await duplicateCurrentItem().ConfigureAwait(false); + + await updateCurrentItem().ConfigureAwait(false); } - private async Task advanceToNextPlaylistItem() + private async Task duplicateCurrentItem() { Debug.Assert(Room != null); Debug.Assert(APIRoom != null); + Debug.Assert(currentItem != null); - long nextId; + var newItem = new PlaylistItem + { + ID = APIRoom.Playlist.Last().ID + 1, + BeatmapID = currentItem.BeatmapID, + RulesetID = currentItem.RulesetID, + }; - switch (Room.Settings.QueueMode) + newItem.AllowedMods.AddRange(currentItem.AllowedMods); + newItem.RequiredMods.AddRange(currentItem.AllowedMods); + + await ((IMultiplayerClient)this).PlaylistItemAdded(new APIPlaylistItem(newItem)).ConfigureAwait(false); + } + + private async Task updateCurrentItem(bool notify = true, Room? apiRoom = null) + { + if (apiRoom == null) + { + Debug.Assert(APIRoom != null); + apiRoom = APIRoom; + } + + switch (apiRoom.QueueMode.Value) { default: // Pick the single non-expired playlist item. - nextId = APIRoom.Playlist.FirstOrDefault(i => !i.Expired)?.ID - ?? APIRoom.Playlist.LastOrDefault()?.ID - ?? 0; + currentItem = apiRoom.Playlist.FirstOrDefault(i => !i.Expired) ?? apiRoom.Playlist.Last(); break; case QueueModes.FairRotate: @@ -417,10 +440,13 @@ namespace osu.Game.Tests.Visual.Multiplayer throw new NotImplementedException(); } - if (nextId != Room.Settings.PlaylistItemId) + if (Room != null) { - Room.Settings.PlaylistItemId = nextId; - await changeSettings(Room.Settings).ConfigureAwait(false); + long lastItem = Room.Settings.PlaylistItemId; + Room.Settings.PlaylistItemId = currentItem.ID; + + if (notify && currentItem.ID != lastItem) + await ((IMultiplayerClient)this).SettingsChanged(Room.Settings).ConfigureAwait(false); } } } From 060b4fa5266a9cd1e334111d968036141e379cd8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 13 Nov 2021 01:53:19 +0900 Subject: [PATCH 0084/4398] Fix failing multiplayer player test --- .../Visual/Multiplayer/TestSceneMultiplayerPlayer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs index a1543f99e1..5708b2f789 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs @@ -3,7 +3,9 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Game.Online.Multiplayer; using osu.Game.Rulesets.Osu; using osu.Game.Screens.OnlinePlay.Multiplayer; @@ -27,6 +29,9 @@ namespace osu.Game.Tests.Visual.Multiplayer { Stack.Push(player = new MultiplayerPlayer(Client.APIRoom, Client.CurrentMatchPlayingItem.Value, Client.Room?.Users.ToArray())); }); + + AddUntilStep("wait for player to be current", () => player.IsCurrentScreen() && player.IsLoaded); + AddStep("start gameplay", () => ((IMultiplayerClient)Client).MatchStarted()); } [Test] From 6fb2ccbb860513a17b84f99099e4bf1a7270a97d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 13 Nov 2021 01:53:58 +0900 Subject: [PATCH 0085/4398] Add failing test ensuring room is updated after join --- .../Multiplayer/TestSceneMultiplayer.cs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index e831d5b564..66c639ed62 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -19,11 +19,13 @@ using osu.Game.Database; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Multiplayer.Queueing; 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.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Match; @@ -579,6 +581,54 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for results", () => multiplayerScreenStack.CurrentScreen is ResultsScreen); } + [Test] + public void TestRoomSettingsReQueriedWhenJoiningRoom() + { + AddStep("create room", () => + { + roomManager.AddServerSideRoom(new Room + { + Name = { Value = "Test Room" }, + QueueMode = { Value = QueueModes.FreeForAll }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + } + } + }); + }); + + AddStep("refresh rooms", () => this.ChildrenOfType().Single().UpdateFilter()); + AddUntilStep("wait for room", () => this.ChildrenOfType().Any()); + AddStep("select room", () => InputManager.Key(Key.Down)); + + AddStep("disable polling", () => this.ChildrenOfType().Single().TimeBetweenPolls.Value = 0); + AddStep("change server-side settings", () => + { + roomManager.ServerSideRooms[0].Name.Value = "New name"; + roomManager.ServerSideRooms[0].Playlist.Add(new PlaylistItem + { + ID = 2, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + }); + }); + + AddStep("join room", () => InputManager.Key(Key.Enter)); + AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + AddUntilStep("wait for join", () => client.Room != null); + + AddAssert("local room has correct settings", () => + { + var localRoom = this.ChildrenOfType().Single().Room; + return localRoom.Name.Value == roomManager.ServerSideRooms[0].Name.Value + && localRoom.Playlist.SequenceEqual(roomManager.ServerSideRooms[0].Playlist); + }); + } + private MultiplayerReadyButton readyButton => this.ChildrenOfType().Single(); private void createRoom(Func room) From e31d7e8ad72321b9885c30d268e73b0a44d6beaa Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 13 Nov 2021 02:32:20 +0900 Subject: [PATCH 0086/4398] Fix new failing test --- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 7520740eb6..fc6f8189e3 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -191,10 +191,17 @@ namespace osu.Game.Tests.Visual.Multiplayer protected override void OnRoomJoined() { + Debug.Assert(APIRoom != null); Debug.Assert(Room != null); // emulate the server sending this after the join room. scheduler required to make sure the join room event is fired first (in Join). changeMatchType(Room.Settings.MatchType).Wait(); + + // emulate the server sending all playlist items after room join. + var serverSideRom = roomManager.ServerSideRooms.Single(r => r.RoomID.Value == APIRoom.RoomID.Value); + + foreach (var playlistItem in serverSideRom.Playlist) + ((IMultiplayerClient)this).PlaylistItemAdded(new APIPlaylistItem(playlistItem)).Wait(); } protected override Task LeaveRoomInternal() => Task.CompletedTask; From dbc23e224f08a528c7f84de85495e6fc61567873 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 13 Nov 2021 03:34:45 +0900 Subject: [PATCH 0087/4398] Fix further test failures --- .../Online/Multiplayer/MultiplayerClient.cs | 20 +++++++--- .../Multiplayer/TestMultiplayerClient.cs | 37 +++++++++++-------- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index cf61b83e08..43c1148ba9 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -442,7 +442,12 @@ namespace osu.Game.Online.Multiplayer return Task.CompletedTask; } - Task IMultiplayerClient.SettingsChanged(MultiplayerRoomSettings newSettings) => updateLocalRoomSettings(newSettings); + Task IMultiplayerClient.SettingsChanged(MultiplayerRoomSettings newSettings) + { + // Do not return this task, as it will cause tests to deadlock. + updateLocalRoomSettings(newSettings); + return Task.CompletedTask; + } Task IMultiplayerClient.UserStateChanged(int userId, MultiplayerUserState state) { @@ -601,16 +606,17 @@ namespace osu.Game.Online.Multiplayer var playlistItem = await createPlaylistItem(item).ConfigureAwait(false); - await scheduleAsync(() => + Scheduler.Add(() => { if (Room == null) return; Debug.Assert(APIRoom != null); + APIRoom.Playlist.RemoveAll(i => i.ID == playlistItem.ID); APIRoom.Playlist.Add(playlistItem); RoomUpdated?.Invoke(); - }).ConfigureAwait(false); + }); } public Task PlaylistItemRemoved(long playlistItemId) @@ -618,7 +624,7 @@ namespace osu.Game.Online.Multiplayer if (Room == null) return Task.CompletedTask; - return scheduleAsync(() => + Scheduler.Add(() => { if (Room == null) return; @@ -628,6 +634,8 @@ namespace osu.Game.Online.Multiplayer APIRoom.Playlist.RemoveAll(i => i.ID == playlistItemId); RoomUpdated?.Invoke(); }); + + return Task.CompletedTask; } public async Task PlaylistItemChanged(APIPlaylistItem item) @@ -637,7 +645,7 @@ namespace osu.Game.Online.Multiplayer var playlistItem = await createPlaylistItem(item).ConfigureAwait(false); - await scheduleAsync(() => + Scheduler.Add(() => { if (Room == null) return; @@ -658,7 +666,7 @@ namespace osu.Game.Online.Multiplayer CurrentMatchPlayingItem.Value = APIRoom.Playlist[index]; RoomUpdated?.Invoke(); - }).ConfigureAwait(false); + }); } /// diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index fc6f8189e3..5c779336ae 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -41,8 +41,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [Resolved] private BeatmapManager beatmaps { get; set; } = null!; + private readonly List playlistItems = new List(); private readonly TestMultiplayerRoomManager roomManager; - private PlaylistItem? currentItem; + private APIPlaylistItem? currentItem; public TestMultiplayerClient(TestMultiplayerRoomManager roomManager) { @@ -139,7 +140,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ((IMultiplayerClient)this).ResultsReady(); - Task.Run(finishCurrentItem); + finishCurrentItem().Wait(); } break; @@ -161,6 +162,8 @@ namespace osu.Game.Tests.Visual.Multiplayer if (password != apiRoom.Password.Value) throw new InvalidOperationException("Invalid password."); + playlistItems.AddRange(apiRoom.Playlist.Select(i => new APIPlaylistItem(i))); + var localUser = new MultiplayerRoomUser(api.LocalUser.Value.Id) { User = api.LocalUser.Value @@ -198,10 +201,8 @@ namespace osu.Game.Tests.Visual.Multiplayer changeMatchType(Room.Settings.MatchType).Wait(); // emulate the server sending all playlist items after room join. - var serverSideRom = roomManager.ServerSideRooms.Single(r => r.RoomID.Value == APIRoom.RoomID.Value); - - foreach (var playlistItem in serverSideRom.Playlist) - ((IMultiplayerClient)this).PlaylistItemAdded(new APIPlaylistItem(playlistItem)).Wait(); + var serverSideRoom = roomManager.ServerSideRooms.Single(r => r.RoomID.Value == APIRoom.RoomID.Value); + Task.WhenAll(serverSideRoom.Playlist.Select(i => ((IMultiplayerClient)this).PlaylistItemAdded(new APIPlaylistItem(i)))).Wait(); } protected override Task LeaveRoomInternal() => Task.CompletedTask; @@ -311,6 +312,7 @@ namespace osu.Game.Tests.Visual.Multiplayer case QueueModes.HostOnly: // In host-only mode, the current item is re-used. item.ID = currentItem.ID; + playlistItems[playlistItems.FindIndex(i => i == currentItem)] = item; await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); // Note: Unlike the server, this is the easiest way to update the current item at this point. @@ -318,7 +320,8 @@ namespace osu.Game.Tests.Visual.Multiplayer break; default: - item.ID = APIRoom.Playlist.Last().ID + 1; + item.ID = playlistItems.Last().ID + 1; + playlistItems.Add(item); await ((IMultiplayerClient)this).PlaylistItemAdded(item).ConfigureAwait(false); await updateCurrentItem().ConfigureAwait(false); break; @@ -384,7 +387,7 @@ namespace osu.Game.Tests.Visual.Multiplayer if (newMode == QueueModes.HostOnly) { - foreach (var playlistItem in APIRoom.Playlist.Where(i => !i.Expired && i.ID != currentItem.ID).ToArray()) + foreach (var playlistItem in playlistItems.Where(i => !i.Expired && i.ID != currentItem.ID).ToArray()) await ((IMultiplayerClient)this).PlaylistItemRemoved(playlistItem.ID).ConfigureAwait(false); } @@ -399,7 +402,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Debug.Assert(currentItem != null); // Expire the current playlist item. - await ((IMultiplayerClient)this).PlaylistItemChanged(new APIPlaylistItem(currentItem) { Expired = true }).ConfigureAwait(false); + currentItem.Expired = true; + await ((IMultiplayerClient)this).PlaylistItemChanged(currentItem).ConfigureAwait(false); // In host-only mode, a duplicate playlist item will be used for the next round. if (Room.Settings.QueueMode == QueueModes.HostOnly) @@ -414,17 +418,18 @@ namespace osu.Game.Tests.Visual.Multiplayer Debug.Assert(APIRoom != null); Debug.Assert(currentItem != null); - var newItem = new PlaylistItem + var newItem = new APIPlaylistItem { - ID = APIRoom.Playlist.Last().ID + 1, + ID = playlistItems.Last().ID + 1, BeatmapID = currentItem.BeatmapID, + BeatmapChecksum = currentItem.BeatmapChecksum, RulesetID = currentItem.RulesetID, + RequiredMods = currentItem.RequiredMods, + AllowedMods = currentItem.AllowedMods }; - newItem.AllowedMods.AddRange(currentItem.AllowedMods); - newItem.RequiredMods.AddRange(currentItem.AllowedMods); - - await ((IMultiplayerClient)this).PlaylistItemAdded(new APIPlaylistItem(newItem)).ConfigureAwait(false); + playlistItems.Add(newItem); + await ((IMultiplayerClient)this).PlaylistItemAdded(newItem).ConfigureAwait(false); } private async Task updateCurrentItem(bool notify = true, Room? apiRoom = null) @@ -439,7 +444,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { default: // Pick the single non-expired playlist item. - currentItem = apiRoom.Playlist.FirstOrDefault(i => !i.Expired) ?? apiRoom.Playlist.Last(); + currentItem = playlistItems.FirstOrDefault(i => !i.Expired) ?? playlistItems.Last(); break; case QueueModes.FairRotate: From 4aaf412ac9a3735034dfdfae9dbeb9714c9375ad Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 13 Nov 2021 03:35:41 +0900 Subject: [PATCH 0088/4398] Ensure one non-expired item when switching to host-only --- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 5c779336ae..a4175defa2 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -387,8 +387,13 @@ namespace osu.Game.Tests.Visual.Multiplayer if (newMode == QueueModes.HostOnly) { + // Remove all but the current and expired items. The current item may be re-used for host-only mode if it's non-expired. foreach (var playlistItem in playlistItems.Where(i => !i.Expired && i.ID != currentItem.ID).ToArray()) await ((IMultiplayerClient)this).PlaylistItemRemoved(playlistItem.ID).ConfigureAwait(false); + + // Always ensure that at least one non-expired item exists by duplicating the current item if required. + if (currentItem.Expired) + await duplicateCurrentItem().ConfigureAwait(false); } // When changing modes, items could have been added (above) or the queueing order could have changed. From ce47f456ec4a8f61b5c9842c6334e344fe172164 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 13 Nov 2021 04:42:14 +0900 Subject: [PATCH 0089/4398] Change API for retrieving playlist items on join --- osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs | 5 +++++ osu.Game/Online/Multiplayer/MultiplayerClient.cs | 4 ++++ osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs | 9 +++++++++ .../Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 10 ++++++---- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index d6ff4f4993..d00022d6f5 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs @@ -77,6 +77,11 @@ namespace osu.Game.Online.Multiplayer /// If an attempt to start the game occurs when the game's (or users') state disallows it. Task StartMatch(); + /// + /// Requests for all playlist items of the room to be sent to the client. + /// + Task RequestAllPlaylistItems(); + /// /// Adds an item to the playlist. /// diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 43c1148ba9..638eb30ee9 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -158,6 +158,8 @@ namespace osu.Game.Online.Multiplayer OnRoomJoined(); }, cancellationSource.Token).ConfigureAwait(false); + await RequestAllPlaylistItems().ConfigureAwait(false); + // Update room settings. await updateLocalRoomSettings(joinedRoom.Settings, cancellationSource.Token).ConfigureAwait(false); }, cancellationSource.Token).ConfigureAwait(false); @@ -305,6 +307,8 @@ namespace osu.Game.Online.Multiplayer public abstract Task StartMatch(); + public abstract Task RequestAllPlaylistItems(); + public abstract Task AddPlaylistItem(APIPlaylistItem item); public abstract Task RemovePlaylistItem(long playlistItemId); diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 2f0e1623e7..c7a751500e 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -3,6 +3,7 @@ #nullable enable +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -151,6 +152,14 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.StartMatch)); } + public override Task RequestAllPlaylistItems() + { + if (!IsConnected.Value) + return Task.FromResult(Array.Empty()); + + return connection.InvokeAsync(nameof(IMultiplayerServer.RequestAllPlaylistItems)); + } + public override Task AddPlaylistItem(APIPlaylistItem item) { if (!IsConnected.Value) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index a4175defa2..dc871de3f5 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -199,10 +199,6 @@ namespace osu.Game.Tests.Visual.Multiplayer // emulate the server sending this after the join room. scheduler required to make sure the join room event is fired first (in Join). changeMatchType(Room.Settings.MatchType).Wait(); - - // emulate the server sending all playlist items after room join. - var serverSideRoom = roomManager.ServerSideRooms.Single(r => r.RoomID.Value == APIRoom.RoomID.Value); - Task.WhenAll(serverSideRoom.Playlist.Select(i => ((IMultiplayerClient)this).PlaylistItemAdded(new APIPlaylistItem(i)))).Wait(); } protected override Task LeaveRoomInternal() => Task.CompletedTask; @@ -298,6 +294,12 @@ namespace osu.Game.Tests.Visual.Multiplayer return ((IMultiplayerClient)this).LoadRequested(); } + public override async Task RequestAllPlaylistItems() + { + foreach (var item in playlistItems) + await ((IMultiplayerClient)this).PlaylistItemAdded(item).ConfigureAwait(false); + } + public override async Task AddPlaylistItem(APIPlaylistItem item) { Debug.Assert(Room != null); From be3f225d4bb6dbe4f4bb2dfdd6453a7e08ee1f30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Nov 2021 15:01:37 +0100 Subject: [PATCH 0090/4398] Update in line with `Online{BeatmapSet -> }ID` changes --- osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs | 2 +- .../Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs index 29a1e349c4..addec15881 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -193,7 +193,7 @@ namespace osu.Game.Tests.Visual.Beatmaps AddUntilStep("ensure manager loaded", () => beatmaps != null); AddStep("remove soleily", () => { - var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == 241526); + var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineID == 241526); if (beatmap != null) beatmaps.Delete(beatmap); }); diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs index 82ab55cf0e..4a8bd7cff0 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Beatmaps AddAssert("button state not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded); AddStep("import soleily", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport())); - AddUntilStep("wait for beatmap import", () => beatmaps.GetAllUsableBeatmapSets().Any(b => b.OnlineBeatmapSetID == 241526)); + AddUntilStep("wait for beatmap import", () => beatmaps.GetAllUsableBeatmapSets().Any(b => b.OnlineID == 241526)); AddUntilStep("button state downloaded", () => downloadButton.State.Value == DownloadState.LocallyAvailable); createButtonWithBeatmap(createSoleily()); @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.Beatmaps AddUntilStep("ensure manager loaded", () => beatmaps != null); AddStep("remove soleily", () => { - var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == 241526); + var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineID == 241526); if (beatmap != null) beatmaps.Delete(beatmap); }); From b58fe2d80a88c90cb7f25accffd5be5dd62e8809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Nov 2021 15:38:04 +0100 Subject: [PATCH 0091/4398] Restructure card to use single tracker at the top level --- .../TestSceneBeatmapCardDownloadButton.cs | 5 +-- .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 16 ++++++--- .../Cards/BeatmapCardDownloadProgressBar.cs | 34 +++++++------------ .../Drawables/Cards/Buttons/DownloadButton.cs | 19 +++++++---- 4 files changed, 38 insertions(+), 36 deletions(-) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs index 4a8bd7cff0..67ad699615 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs @@ -178,8 +178,7 @@ namespace osu.Game.Tests.Visual.Beatmaps private class TestDownloadButton : DownloadButton { - public readonly Bindable State = new Bindable(); - public readonly BindableNumber Progress = new BindableNumber(); + public new Bindable State => base.State; public new BeatmapCardIconButton Download => base.Download; public new BeatmapCardIconButton Play => base.Play; @@ -187,8 +186,6 @@ namespace osu.Game.Tests.Visual.Beatmaps public TestDownloadButton(APIBeatmapSet beatmapSet) : base(beatmapSet) { - Tracker.State.BindTo(State); - Tracker.Progress.BindTo(Progress); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 09ceb72a10..23009cda5e 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -16,6 +16,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet; @@ -38,6 +39,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards private readonly APIBeatmapSet beatmapSet; private readonly Bindable favouriteState; + [Cached] + private readonly BeatmapDownloadTracker downloadTracker; + private UpdateableOnlineBeatmapSetCover leftCover; private FillFlowContainer leftIconArea; @@ -61,6 +65,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { this.beatmapSet = beatmapSet; favouriteState = new Bindable(new BeatmapSetFavouriteState(beatmapSet.HasFavourited, beatmapSet.FavouriteCount)); + downloadTracker = new BeatmapDownloadTracker(beatmapSet); } [BackgroundDependencyLoader] @@ -73,6 +78,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards InternalChildren = new Drawable[] { + downloadTracker, new Box { RelativeSizeAxes = Axes.Both, @@ -270,7 +276,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards } } }, - downloadProgressBar = new BeatmapCardDownloadProgressBar(beatmapSet) + downloadProgressBar = new BeatmapCardDownloadProgressBar { RelativeSizeAxes = Axes.X, Height = 6, @@ -314,7 +320,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { base.LoadComplete(); - downloadProgressBar.IsActive.BindValueChanged(_ => updateState(), true); + downloadTracker.State.BindValueChanged(_ => updateState(), true); FinishTransforms(true); } @@ -367,8 +373,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards statisticsContainer.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); rightButtonArea.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); - idleBottomContent.FadeTo(downloadProgressBar.IsActive.Value ? 0 : 1, TRANSITION_DURATION, Easing.OutQuint); - downloadProgressBar.FadeTo(downloadProgressBar.IsActive.Value ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); + bool showProgress = downloadTracker.State.Value == DownloadState.Downloading || downloadTracker.State.Value == DownloadState.Importing; + + idleBottomContent.FadeTo(showProgress ? 0 : 1, TRANSITION_DURATION, Easing.OutQuint); + downloadProgressBar.FadeTo(showProgress ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs index 37a7e7d41d..a94900b0c7 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs @@ -8,37 +8,33 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Online; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; namespace osu.Game.Beatmaps.Drawables.Cards { public class BeatmapCardDownloadProgressBar : CompositeDrawable { - private readonly BindableBool isActive = new BindableBool(); - public IBindable IsActive => isActive; - public override bool IsPresent => true; - private readonly BeatmapDownloadTracker tracker; - private readonly CircularContainer background; private readonly CircularContainer foreground; private readonly Box backgroundFill; private readonly Box foregroundFill; + private readonly Bindable state = new Bindable(); + private readonly BindableDouble progress = new BindableDouble(); + [Resolved] private OsuColour colours { get; set; } [Resolved] private OverlayColourProvider colourProvider { get; set; } - public BeatmapCardDownloadProgressBar(APIBeatmapSet beatmapSet) + public BeatmapCardDownloadProgressBar() { InternalChildren = new Drawable[] { - tracker = new BeatmapDownloadTracker(beatmapSet), - background = new CircularContainer + new CircularContainer { RelativeSizeAxes = Axes.Both, Masking = true, @@ -60,44 +56,40 @@ namespace osu.Game.Beatmaps.Drawables.Cards } [BackgroundDependencyLoader] - private void load() + private void load(BeatmapDownloadTracker downloadTracker) { backgroundFill.Colour = colourProvider.Background6; + + ((IBindable)state).BindTo(downloadTracker.State); + ((IBindable)progress).BindTo(downloadTracker.Progress); } protected override void LoadComplete() { base.LoadComplete(); - tracker.State.BindValueChanged(_ => stateChanged(), true); - tracker.Progress.BindValueChanged(_ => progressChanged(), true); + state.BindValueChanged(_ => stateChanged(), true); + progress.BindValueChanged(_ => progressChanged(), true); } private void stateChanged() { - switch (tracker.State.Value) + switch (state.Value) { case DownloadState.Downloading: FinishTransforms(true); foregroundFill.Colour = colourProvider.Highlight1; - isActive.Value = true; break; case DownloadState.Importing: foregroundFill.FadeColour(colours.Yellow, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - isActive.Value = true; - break; - - default: - isActive.Value = false; break; } } private void progressChanged() { - double progress = tracker.Progress.Value; - foreground.ResizeWidthTo((float)progress, progress > 0 ? BeatmapCard.TRANSITION_DURATION : 0, Easing.OutQuint); + foreground.ResizeWidthTo((float)progress.Value, progress.Value > 0 ? BeatmapCard.TRANSITION_DURATION : 0, Easing.OutQuint); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs index 85e2988614..0e6d63830c 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs @@ -21,7 +21,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { protected readonly DownloadIcon Download; protected readonly PlayIcon Play; - protected readonly BeatmapDownloadTracker Tracker; + protected readonly Bindable State = new Bindable(); [Resolved] private OsuColour colours { get; set; } = null!; @@ -37,27 +37,32 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons InternalChildren = new Drawable[] { - Tracker = new BeatmapDownloadTracker(beatmapSet), Download = new DownloadIcon(beatmapSet), Play = new PlayIcon(beatmapSet) }; } + [BackgroundDependencyLoader(true)] + private void load(BeatmapDownloadTracker? tracker) + { + if (tracker != null) + ((IBindable)State).BindTo(tracker.State); + } + protected override void LoadComplete() { base.LoadComplete(); - Tracker.Progress.BindValueChanged(_ => updateState()); - Tracker.State.BindValueChanged(_ => updateState(), true); + State.BindValueChanged(_ => updateState(), true); FinishTransforms(true); } private void updateState() { - Download.FadeTo(Tracker.State.Value != DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - Download.Enabled.Value = Tracker.State.Value == DownloadState.NotDownloaded; + Download.FadeTo(State.Value != DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + Download.Enabled.Value = State.Value == DownloadState.NotDownloaded; - Play.FadeTo(Tracker.State.Value == DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + Play.FadeTo(State.Value == DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); } protected class DownloadIcon : BeatmapCardIconButton From f743a3647f8b8feb0056ef2dd3331246ef60b838 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 15 Nov 2021 23:14:27 +0900 Subject: [PATCH 0092/4398] Rename APIPlaylistItem -> MultiplayerPlaylistItem --- osu.Game/Online/Multiplayer/IMultiplayerClient.cs | 4 ++-- osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs | 2 +- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 8 ++++---- osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs | 10 +++++----- .../{APIPlaylistItem.cs => MultiplayerPlaylistItem.cs} | 6 +++--- .../Multiplayer/MultiplayerMatchSongSelect.cs | 2 +- .../Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 10 +++++----- 7 files changed, 21 insertions(+), 21 deletions(-) rename osu.Game/Online/Rooms/{APIPlaylistItem.cs => MultiplayerPlaylistItem.cs} (90%) diff --git a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs index 4cadc5d606..3e6821b1cd 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs @@ -111,7 +111,7 @@ namespace osu.Game.Online.Multiplayer /// Signals that an item has been added to the playlist. /// /// The added item. - Task PlaylistItemAdded(APIPlaylistItem item); + Task PlaylistItemAdded(MultiplayerPlaylistItem item); /// /// Signals that an item has been removed from the playlist. @@ -123,6 +123,6 @@ namespace osu.Game.Online.Multiplayer /// Signals that an item has been changed in the playlist. /// /// The changed item. - Task PlaylistItemChanged(APIPlaylistItem item); + Task PlaylistItemChanged(MultiplayerPlaylistItem item); } } diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index d00022d6f5..cb80c93ef9 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs @@ -86,7 +86,7 @@ namespace osu.Game.Online.Multiplayer /// Adds an item to the playlist. /// /// The item to add. - Task AddPlaylistItem(APIPlaylistItem item); + Task AddPlaylistItem(MultiplayerPlaylistItem item); /// /// Removes an item from the playlist. diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 638eb30ee9..ebbd76f237 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -309,7 +309,7 @@ namespace osu.Game.Online.Multiplayer public abstract Task RequestAllPlaylistItems(); - public abstract Task AddPlaylistItem(APIPlaylistItem item); + public abstract Task AddPlaylistItem(MultiplayerPlaylistItem item); public abstract Task RemovePlaylistItem(long playlistItemId); @@ -603,7 +603,7 @@ namespace osu.Game.Online.Multiplayer return Task.CompletedTask; } - public async Task PlaylistItemAdded(APIPlaylistItem item) + public async Task PlaylistItemAdded(MultiplayerPlaylistItem item) { if (Room == null) return; @@ -642,7 +642,7 @@ namespace osu.Game.Online.Multiplayer return Task.CompletedTask; } - public async Task PlaylistItemChanged(APIPlaylistItem item) + public async Task PlaylistItemChanged(MultiplayerPlaylistItem item) { if (Room == null) return; @@ -703,7 +703,7 @@ namespace osu.Game.Online.Multiplayer CurrentMatchPlayingItem.Value = APIRoom.Playlist.SingleOrDefault(p => p.ID == settings.PlaylistItemId); }, cancellationToken); - private async Task createPlaylistItem(APIPlaylistItem item) + private async Task createPlaylistItem(MultiplayerPlaylistItem item) { var set = await GetOnlineBeatmapSet(item.BeatmapID).ConfigureAwait(false); diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index c7a751500e..5c60dae747 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -63,9 +63,9 @@ namespace osu.Game.Online.Multiplayer connection.On(nameof(IMultiplayerClient.MatchRoomStateChanged), ((IMultiplayerClient)this).MatchRoomStateChanged); connection.On(nameof(IMultiplayerClient.MatchUserStateChanged), ((IMultiplayerClient)this).MatchUserStateChanged); connection.On(nameof(IMultiplayerClient.MatchEvent), ((IMultiplayerClient)this).MatchEvent); - connection.On(nameof(IMultiplayerClient.PlaylistItemAdded), ((IMultiplayerClient)this).PlaylistItemAdded); + connection.On(nameof(IMultiplayerClient.PlaylistItemAdded), ((IMultiplayerClient)this).PlaylistItemAdded); connection.On(nameof(IMultiplayerClient.PlaylistItemRemoved), ((IMultiplayerClient)this).PlaylistItemRemoved); - connection.On(nameof(IMultiplayerClient.PlaylistItemChanged), ((IMultiplayerClient)this).PlaylistItemChanged); + connection.On(nameof(IMultiplayerClient.PlaylistItemChanged), ((IMultiplayerClient)this).PlaylistItemChanged); }; IsConnected.BindTo(connector.IsConnected); @@ -155,12 +155,12 @@ namespace osu.Game.Online.Multiplayer public override Task RequestAllPlaylistItems() { if (!IsConnected.Value) - return Task.FromResult(Array.Empty()); + return Task.FromResult(Array.Empty()); - return connection.InvokeAsync(nameof(IMultiplayerServer.RequestAllPlaylistItems)); + return connection.InvokeAsync(nameof(IMultiplayerServer.RequestAllPlaylistItems)); } - public override Task AddPlaylistItem(APIPlaylistItem item) + public override Task AddPlaylistItem(MultiplayerPlaylistItem item) { if (!IsConnected.Value) return Task.CompletedTask; diff --git a/osu.Game/Online/Rooms/APIPlaylistItem.cs b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs similarity index 90% rename from osu.Game/Online/Rooms/APIPlaylistItem.cs rename to osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs index eddce69976..a158be71a1 100644 --- a/osu.Game/Online/Rooms/APIPlaylistItem.cs +++ b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs @@ -13,7 +13,7 @@ namespace osu.Game.Online.Rooms { [Serializable] [MessagePackObject] - public class APIPlaylistItem + public class MultiplayerPlaylistItem { [Key(0)] public long ID { get; set; } @@ -39,11 +39,11 @@ namespace osu.Game.Online.Rooms [Key(7)] public bool Expired { get; set; } - public APIPlaylistItem() + public MultiplayerPlaylistItem() { } - public APIPlaylistItem(PlaylistItem item) + public MultiplayerPlaylistItem(PlaylistItem item) { ID = item.ID; BeatmapID = item.BeatmapID; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 40b086873d..e7998fc01c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -56,7 +56,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { loadingLayer.Show(); - client.AddPlaylistItem(new APIPlaylistItem + client.AddPlaylistItem(new MultiplayerPlaylistItem { BeatmapID = item.BeatmapID, BeatmapChecksum = item.Beatmap.Value.MD5Hash, diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index dc871de3f5..a88bf56cee 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -41,9 +41,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [Resolved] private BeatmapManager beatmaps { get; set; } = null!; - private readonly List playlistItems = new List(); + private readonly List playlistItems = new List(); private readonly TestMultiplayerRoomManager roomManager; - private APIPlaylistItem? currentItem; + private MultiplayerPlaylistItem? currentItem; public TestMultiplayerClient(TestMultiplayerRoomManager roomManager) { @@ -162,7 +162,7 @@ namespace osu.Game.Tests.Visual.Multiplayer if (password != apiRoom.Password.Value) throw new InvalidOperationException("Invalid password."); - playlistItems.AddRange(apiRoom.Playlist.Select(i => new APIPlaylistItem(i))); + playlistItems.AddRange(apiRoom.Playlist.Select(i => new MultiplayerPlaylistItem(i))); var localUser = new MultiplayerRoomUser(api.LocalUser.Value.Id) { @@ -300,7 +300,7 @@ namespace osu.Game.Tests.Visual.Multiplayer await ((IMultiplayerClient)this).PlaylistItemAdded(item).ConfigureAwait(false); } - public override async Task AddPlaylistItem(APIPlaylistItem item) + public override async Task AddPlaylistItem(MultiplayerPlaylistItem item) { Debug.Assert(Room != null); Debug.Assert(APIRoom != null); @@ -425,7 +425,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Debug.Assert(APIRoom != null); Debug.Assert(currentItem != null); - var newItem = new APIPlaylistItem + var newItem = new MultiplayerPlaylistItem { ID = playlistItems.Last().ID + 1, BeatmapID = currentItem.BeatmapID, From eb983ed548dbd55492e74d995994ff08831a5aac Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Nov 2021 00:02:38 +0900 Subject: [PATCH 0093/4398] Fix potential crash from playlist updating during async load --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index aa936109c6..9e689773af 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -44,6 +44,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public override string ShortTitle => "room"; + public OsuButton AddOrEditPlaylistButton { get; private set; } + [Resolved] private MultiplayerClient client { get; set; } @@ -55,7 +57,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [CanBeNull] private IDisposable readyClickOperation; - public OsuButton AddOrEditPlaylistButton { get; private set; } + private DrawableRoomPlaylist playlist; public MultiplayerMatchSubScreen(Room room) : base(room) @@ -73,6 +75,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true); UserMods.BindValueChanged(onUserModsChanged); + playlist.Items.BindTo(Room.Playlist); + playlist.SelectedItem.BindTo(SelectedItem); + client.LoadRequested += onLoadRequested; client.RoomUpdated += onRoomUpdated; @@ -149,11 +154,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer null, new Drawable[] { - new DrawableRoomPlaylist(false, false) + playlist = new DrawableRoomPlaylist(false, false) { RelativeSizeAxes = Axes.Both, - Items = { BindTarget = Room.Playlist }, - SelectedItem = { BindTarget = SelectedItem } }, }, new[] From 8e014ca17ad6bab674db6076e5c0c2bb0eee3c19 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Nov 2021 00:09:38 +0900 Subject: [PATCH 0094/4398] Reverse in-match playlist --- .../OnlinePlay/DrawableRoomPlaylist.cs | 25 ++++++++++++++++--- .../Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index a08d9edb34..296bb4bea5 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -19,10 +19,12 @@ namespace osu.Game.Screens.OnlinePlay private readonly bool allowEdit; private readonly bool allowSelection; - public DrawableRoomPlaylist(bool allowEdit, bool allowSelection) + public DrawableRoomPlaylist(bool allowEdit, bool allowSelection, bool reverse = false) { this.allowEdit = allowEdit; this.allowSelection = allowSelection; + + ((ReversibleFillFlowContainer)ListContainer).Reverse = reverse; } protected override void LoadComplete() @@ -47,10 +49,8 @@ namespace osu.Game.Screens.OnlinePlay d.ScrollbarVisible = false; }); - protected override FillFlowContainer> CreateListFillFlowContainer() => new FillFlowContainer> + protected override FillFlowContainer> CreateListFillFlowContainer() => new ReversibleFillFlowContainer { - LayoutDuration = 200, - LayoutEasing = Easing.OutQuint, Spacing = new Vector2(0, 2) }; @@ -72,5 +72,22 @@ namespace osu.Game.Screens.OnlinePlay Items.Remove(item); } + + private class ReversibleFillFlowContainer : FillFlowContainer> + { + private bool reverse; + + public bool Reverse + { + get => reverse; + set + { + reverse = value; + Invalidate(); + } + } + + public override IEnumerable FlowingChildren => Reverse ? base.FlowingChildren.OrderBy(d => -GetLayoutPosition(d)) : base.FlowingChildren; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 9e689773af..c99367851a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -154,7 +154,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer null, new Drawable[] { - playlist = new DrawableRoomPlaylist(false, false) + playlist = new DrawableRoomPlaylist(false, false, true) { RelativeSizeAxes = Axes.Both, }, From b9e38269e34a7fbfabf42a02b6d7fbe7e9f3b447 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Nov 2021 00:09:49 +0900 Subject: [PATCH 0095/4398] Prevent playlist mangling selection if not allowed --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index 296bb4bea5..f5522cd25d 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -1,7 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Collections.Specialized; +using System.Linq; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; @@ -37,7 +39,7 @@ namespace osu.Game.Screens.OnlinePlay switch (args.Action) { case NotifyCollectionChangedAction.Remove: - if (args.OldItems.Contains(SelectedItem)) + if (allowSelection && args.OldItems.Contains(SelectedItem)) SelectedItem.Value = null; break; } @@ -62,7 +64,7 @@ namespace osu.Game.Screens.OnlinePlay private void requestDeletion(PlaylistItem item) { - if (SelectedItem.Value == item) + if (allowSelection && SelectedItem.Value == item) { if (Items.Count == 1) SelectedItem.Value = null; From d27edb3a2536eb6d8c650bcefa633cc16fe2db77 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Nov 2021 00:31:35 +0900 Subject: [PATCH 0096/4398] Fix another potential crash from async load --- .../Match/MultiplayerMatchSettingsOverlay.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 3d95960bef..98a0cd3d37 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -102,6 +102,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private readonly Room room; private Drawable playlistContainer; + private DrawableRoomPlaylist drawablePlaylist; public MatchSettings(Room room) { @@ -256,12 +257,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match Spacing = new Vector2(5), Children = new Drawable[] { - new DrawableRoomPlaylist(false, false) + drawablePlaylist = new DrawableRoomPlaylist(false, false) { RelativeSizeAxes = Axes.X, - Height = DrawableRoomPlaylistItem.HEIGHT, - Items = { BindTarget = Playlist }, - SelectedItem = { BindTarget = SelectedItem } + Height = DrawableRoomPlaylistItem.HEIGHT }, new PurpleTriangleButton { @@ -348,6 +347,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match }); } + protected override void LoadComplete() + { + base.LoadComplete(); + + drawablePlaylist.Items.BindTo(Playlist); + drawablePlaylist.SelectedItem.BindTo(SelectedItem); + } + protected override void Update() { base.Update(); From 459e819a5db1bcd68ba9b806eefc7955e06a21ee Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Nov 2021 00:50:23 +0900 Subject: [PATCH 0097/4398] Update todo --- .../OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index e9085580f9..3152f50d3d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -184,7 +184,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants const double fade_time = 50; - // Todo: FIX THIS + // Todo: Should use the room's selected item to determine ruleset. var ruleset = rulesets.GetRuleset(0).CreateInstance(); int? currentModeRank = User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank; From f0593115b2b4dd27752b4b8637b846b5ac5661bd Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Nov 2021 12:08:53 +0900 Subject: [PATCH 0098/4398] Remove RemovePlaylistItem() server method for the time being --- osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs | 6 ------ osu.Game/Online/Multiplayer/MultiplayerClient.cs | 2 -- osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs | 8 -------- .../Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 10 ---------- 4 files changed, 26 deletions(-) diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index cb80c93ef9..afaf1e89de 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs @@ -87,11 +87,5 @@ namespace osu.Game.Online.Multiplayer /// /// The item to add. Task AddPlaylistItem(MultiplayerPlaylistItem item); - - /// - /// Removes an item from the playlist. - /// - /// The item to remove. - Task RemovePlaylistItem(long playlistItemId); } } diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index ebbd76f237..77a1b51b7d 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -311,8 +311,6 @@ namespace osu.Game.Online.Multiplayer public abstract Task AddPlaylistItem(MultiplayerPlaylistItem item); - public abstract Task RemovePlaylistItem(long playlistItemId); - Task IMultiplayerClient.RoomStateChanged(MultiplayerRoomState state) { if (Room == null) diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 5c60dae747..2609436890 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -168,14 +168,6 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.AddPlaylistItem), item); } - public override Task RemovePlaylistItem(long playlistItemId) - { - if (!IsConnected.Value) - return Task.CompletedTask; - - return connection.InvokeAsync(nameof(IMultiplayerServer.RemovePlaylistItem), playlistItemId); - } - protected override Task GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default) { var tcs = new TaskCompletionSource(); diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index a88bf56cee..e2dff61e7c 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -330,16 +330,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } } - public override Task RemovePlaylistItem(long playlistItemId) - { - Debug.Assert(Room != null); - - if (Room.Host?.UserID != LocalUser?.UserID) - throw new InvalidOperationException("Local user is not the room host."); - - return ((IMultiplayerClient)this).PlaylistItemRemoved(playlistItemId); - } - protected override Task GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default) { Debug.Assert(Room != null); From 9076519710aa245baa50743789211aa5a34caf76 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Nov 2021 13:49:06 +0900 Subject: [PATCH 0099/4398] Give MultiplayerRoom a playlist, remove RequestAllPlaylistItems() --- osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs | 5 ----- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 10 ++++++---- osu.Game/Online/Multiplayer/MultiplayerRoom.cs | 4 ++++ osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs | 9 --------- .../Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 6 ------ 5 files changed, 10 insertions(+), 24 deletions(-) diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index afaf1e89de..3e84e4b904 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs @@ -77,11 +77,6 @@ namespace osu.Game.Online.Multiplayer /// If an attempt to start the game occurs when the game's (or users') state disallows it. Task StartMatch(); - /// - /// Requests for all playlist items of the room to be sent to the client. - /// - Task RequestAllPlaylistItems(); - /// /// Adds an item to the playlist. /// diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 77a1b51b7d..07f827e748 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -139,6 +139,9 @@ namespace osu.Game.Online.Multiplayer var joinedRoom = await JoinRoom(room.RoomID.Value.Value, password ?? room.Password.Value).ConfigureAwait(false); Debug.Assert(joinedRoom != null); + // Populate playlist items. + var playlistItems = await Task.WhenAll(joinedRoom.Playlist.Select(createPlaylistItem)).ConfigureAwait(false); + // Populate users. Debug.Assert(joinedRoom.Users != null); await Task.WhenAll(joinedRoom.Users.Select(PopulateUser)).ConfigureAwait(false); @@ -149,6 +152,9 @@ namespace osu.Game.Online.Multiplayer Room = joinedRoom; APIRoom = room; + APIRoom.Playlist.Clear(); + APIRoom.Playlist.AddRange(playlistItems); + Debug.Assert(LocalUser != null); addUserToAPIRoom(LocalUser); @@ -158,8 +164,6 @@ namespace osu.Game.Online.Multiplayer OnRoomJoined(); }, cancellationSource.Token).ConfigureAwait(false); - await RequestAllPlaylistItems().ConfigureAwait(false); - // Update room settings. await updateLocalRoomSettings(joinedRoom.Settings, cancellationSource.Token).ConfigureAwait(false); }, cancellationSource.Token).ConfigureAwait(false); @@ -307,8 +311,6 @@ namespace osu.Game.Online.Multiplayer public abstract Task StartMatch(); - public abstract Task RequestAllPlaylistItems(); - public abstract Task AddPlaylistItem(MultiplayerPlaylistItem item); Task IMultiplayerClient.RoomStateChanged(MultiplayerRoomState state) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoom.cs b/osu.Game/Online/Multiplayer/MultiplayerRoom.cs index 175c0e0e27..a60e70dab3 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoom.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoom.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using MessagePack; using Newtonsoft.Json; +using osu.Game.Online.Rooms; namespace osu.Game.Online.Multiplayer { @@ -50,6 +51,9 @@ namespace osu.Game.Online.Multiplayer [Key(5)] public MatchRoomState? MatchState { get; set; } + [Key(6)] + public IList Playlist { get; set; } = new List(); + [JsonConstructor] [SerializationConstructor] public MultiplayerRoom(long roomId) diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 2609436890..7308c03ec3 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -3,7 +3,6 @@ #nullable enable -using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -152,14 +151,6 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.StartMatch)); } - public override Task RequestAllPlaylistItems() - { - if (!IsConnected.Value) - return Task.FromResult(Array.Empty()); - - return connection.InvokeAsync(nameof(IMultiplayerServer.RequestAllPlaylistItems)); - } - public override Task AddPlaylistItem(MultiplayerPlaylistItem item) { if (!IsConnected.Value) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index e2dff61e7c..dc86c20b4c 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -294,12 +294,6 @@ namespace osu.Game.Tests.Visual.Multiplayer return ((IMultiplayerClient)this).LoadRequested(); } - public override async Task RequestAllPlaylistItems() - { - foreach (var item in playlistItems) - await ((IMultiplayerClient)this).PlaylistItemAdded(item).ConfigureAwait(false); - } - public override async Task AddPlaylistItem(MultiplayerPlaylistItem item) { Debug.Assert(Room != null); From 41e46f158fea5e36205569dd2f9bc7b1e808397c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Nov 2021 14:37:29 +0900 Subject: [PATCH 0100/4398] Fix tests following playlist changes Also more closely follows the server implementation. --- .../Multiplayer/TestMultiplayerClient.cs | 80 ++++++++++--------- 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index dc86c20b4c..3f7ad4a710 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -41,9 +41,10 @@ namespace osu.Game.Tests.Visual.Multiplayer [Resolved] private BeatmapManager beatmaps { get; set; } = null!; - private readonly List playlistItems = new List(); + private MultiplayerPlaylistItem? currentItem => Room?.Playlist[currentIndex]; + private readonly TestMultiplayerRoomManager roomManager; - private MultiplayerPlaylistItem? currentItem; + private int currentIndex; public TestMultiplayerClient(TestMultiplayerRoomManager roomManager) { @@ -162,30 +163,27 @@ namespace osu.Game.Tests.Visual.Multiplayer if (password != apiRoom.Password.Value) throw new InvalidOperationException("Invalid password."); - playlistItems.AddRange(apiRoom.Playlist.Select(i => new MultiplayerPlaylistItem(i))); - var localUser = new MultiplayerRoomUser(api.LocalUser.Value.Id) { User = api.LocalUser.Value }; - await updateCurrentItem(apiRoom: apiRoom).ConfigureAwait(false); - Debug.Assert(currentItem != null); - var room = new MultiplayerRoom(roomId) { Settings = { Name = apiRoom.Name.Value, MatchType = apiRoom.Type.Value, - PlaylistItemId = currentItem.ID, Password = password, QueueMode = apiRoom.QueueMode.Value }, + Playlist = apiRoom.Playlist.Select(item => new MultiplayerPlaylistItem(item)).ToList(), Users = { localUser }, Host = localUser }; + await updateCurrentItem(room, false).ConfigureAwait(false); + RoomSetupAction?.Invoke(room); RoomSetupAction = null; @@ -308,28 +306,29 @@ namespace osu.Game.Tests.Visual.Multiplayer case QueueModes.HostOnly: // In host-only mode, the current item is re-used. item.ID = currentItem.ID; - playlistItems[playlistItems.FindIndex(i => i == currentItem)] = item; + + Room.Playlist[currentIndex] = item; await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); // Note: Unlike the server, this is the easiest way to update the current item at this point. - await updateCurrentItem(false).ConfigureAwait(false); + await updateCurrentItem(Room, false).ConfigureAwait(false); break; default: - item.ID = playlistItems.Last().ID + 1; - playlistItems.Add(item); + item.ID = Room.Playlist.Last().ID + 1; + + Room.Playlist.Add(item); await ((IMultiplayerClient)this).PlaylistItemAdded(item).ConfigureAwait(false); - await updateCurrentItem().ConfigureAwait(false); + + await updateCurrentItem(Room).ConfigureAwait(false); break; } } protected override Task GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default) { - Debug.Assert(Room != null); - - var apiRoom = roomManager.ServerSideRooms.Single(r => r.RoomID.Value == Room.RoomID); - IBeatmapSetInfo? set = apiRoom.Playlist.FirstOrDefault(p => p.BeatmapID == beatmapId)?.Beatmap.Value.BeatmapSet + IBeatmapSetInfo? set = roomManager.ServerSideRooms.SelectMany(r => r.Playlist) + .FirstOrDefault(p => p.BeatmapID == beatmapId)?.Beatmap.Value.BeatmapSet ?? beatmaps.QueryBeatmap(b => b.OnlineID == beatmapId)?.BeatmapSet; if (set == null) @@ -368,14 +367,24 @@ namespace osu.Game.Tests.Visual.Multiplayer private async Task changeQueueMode(QueueModes newMode) { + Debug.Assert(Room != null); Debug.Assert(APIRoom != null); Debug.Assert(currentItem != null); if (newMode == QueueModes.HostOnly) { // Remove all but the current and expired items. The current item may be re-used for host-only mode if it's non-expired. - foreach (var playlistItem in playlistItems.Where(i => !i.Expired && i.ID != currentItem.ID).ToArray()) - await ((IMultiplayerClient)this).PlaylistItemRemoved(playlistItem.ID).ConfigureAwait(false); + for (int i = 0; i < Room.Playlist.Count; i++) + { + var item = Room.Playlist[i]; + + if (item.Expired || item.ID == Room.Settings.PlaylistItemId) + continue; + + Room.Playlist.RemoveAt(i--); + + await ((IMultiplayerClient)this).PlaylistItemRemoved(item.ID).ConfigureAwait(false); + } // Always ensure that at least one non-expired item exists by duplicating the current item if required. if (currentItem.Expired) @@ -383,7 +392,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } // When changing modes, items could have been added (above) or the queueing order could have changed. - await updateCurrentItem().ConfigureAwait(false); + await updateCurrentItem(Room).ConfigureAwait(false); } private async Task finishCurrentItem() @@ -400,7 +409,7 @@ namespace osu.Game.Tests.Visual.Multiplayer if (Room.Settings.QueueMode == QueueModes.HostOnly) await duplicateCurrentItem().ConfigureAwait(false); - await updateCurrentItem().ConfigureAwait(false); + await updateCurrentItem(Room).ConfigureAwait(false); } private async Task duplicateCurrentItem() @@ -411,7 +420,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var newItem = new MultiplayerPlaylistItem { - ID = playlistItems.Last().ID + 1, + ID = Room.Playlist.Last().ID + 1, BeatmapID = currentItem.BeatmapID, BeatmapChecksum = currentItem.BeatmapChecksum, RulesetID = currentItem.RulesetID, @@ -419,23 +428,19 @@ namespace osu.Game.Tests.Visual.Multiplayer AllowedMods = currentItem.AllowedMods }; - playlistItems.Add(newItem); + Room.Playlist.Add(newItem); await ((IMultiplayerClient)this).PlaylistItemAdded(newItem).ConfigureAwait(false); } - private async Task updateCurrentItem(bool notify = true, Room? apiRoom = null) + private async Task updateCurrentItem(MultiplayerRoom room, bool notify = true) { - if (apiRoom == null) - { - Debug.Assert(APIRoom != null); - apiRoom = APIRoom; - } + MultiplayerPlaylistItem newItem; - switch (apiRoom.QueueMode.Value) + switch (room.Settings.QueueMode) { default: // Pick the single non-expired playlist item. - currentItem = playlistItems.FirstOrDefault(i => !i.Expired) ?? playlistItems.Last(); + newItem = room.Playlist.FirstOrDefault(i => !i.Expired) ?? room.Playlist.Last(); break; case QueueModes.FairRotate: @@ -443,14 +448,13 @@ namespace osu.Game.Tests.Visual.Multiplayer throw new NotImplementedException(); } - if (Room != null) - { - long lastItem = Room.Settings.PlaylistItemId; - Room.Settings.PlaylistItemId = currentItem.ID; + currentIndex = room.Playlist.IndexOf(newItem); - if (notify && currentItem.ID != lastItem) - await ((IMultiplayerClient)this).SettingsChanged(Room.Settings).ConfigureAwait(false); - } + long lastItem = room.Settings.PlaylistItemId; + room.Settings.PlaylistItemId = newItem.ID; + + if (notify && newItem.ID != lastItem) + await ((IMultiplayerClient)this).SettingsChanged(room.Settings).ConfigureAwait(false); } } } From f414877d00a32baa327040f4333fd44b587369d3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Nov 2021 14:37:54 +0900 Subject: [PATCH 0101/4398] Rename UserID -> OwnerID --- osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs index a158be71a1..6ca0b822f3 100644 --- a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs +++ b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs @@ -19,7 +19,7 @@ namespace osu.Game.Online.Rooms public long ID { get; set; } [Key(1)] - public int UserID { get; set; } + public int OwnerID { get; set; } [Key(2)] public int BeatmapID { get; set; } From 01f3649d7531689f0e3670c7bb4b5bb00764e45a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Nov 2021 14:44:47 +0900 Subject: [PATCH 0102/4398] Rename variables for readability --- .../Online/Multiplayer/MultiplayerClient.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 07f827e748..b1ec16ee8d 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -617,7 +617,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(APIRoom != null); - APIRoom.Playlist.RemoveAll(i => i.ID == playlistItem.ID); + APIRoom.Playlist.RemoveAll(existingItem => existingItem.ID == playlistItem.ID); APIRoom.Playlist.Add(playlistItem); RoomUpdated?.Invoke(); }); @@ -635,7 +635,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(APIRoom != null); - APIRoom.Playlist.RemoveAll(i => i.ID == playlistItemId); + APIRoom.Playlist.RemoveAll(item => item.ID == playlistItemId); RoomUpdated?.Invoke(); }); @@ -656,18 +656,18 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(APIRoom != null); - int index = APIRoom.Playlist.Select((i, index) => (i, index)).Single(kvp => kvp.i.ID == item.ID).index; - var oldItem = APIRoom.Playlist[index]; - if (oldItem.Equals(playlistItem)) + PlaylistItem existingItem = APIRoom.Playlist.Single(existingItem => existingItem.ID == item.ID); + int existingIndex = APIRoom.Playlist.IndexOf(existingItem); + if (existingItem.Equals(playlistItem)) return; // Replace the item. - APIRoom.Playlist.RemoveAt(index); - APIRoom.Playlist.Insert(index, playlistItem); + APIRoom.Playlist.RemoveAt(existingIndex); + APIRoom.Playlist.Insert(existingIndex, playlistItem); // If the currently-selected item was the one that got replaced, update the selected item to the new one. - if (CurrentMatchPlayingItem.Value == oldItem) - CurrentMatchPlayingItem.Value = APIRoom.Playlist[index]; + if (CurrentMatchPlayingItem.Value == existingItem) + CurrentMatchPlayingItem.Value = playlistItem; RoomUpdated?.Invoke(); }); From 5c3141d16afd3a4091e42ed4f1906a58d1d6fba4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Nov 2021 14:48:52 +0900 Subject: [PATCH 0103/4398] Fix ready button tooltip showing when locally available --- osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs index 5a4a0a0fba..9822ceaaf6 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs @@ -36,7 +36,10 @@ namespace osu.Game.Screens.OnlinePlay.Components if (Enabled.Value) return string.Empty; - return "Beatmap not downloaded"; + if (availability.Value.State != DownloadState.LocallyAvailable) + return "Beatmap not downloaded"; + + return string.Empty; } } } From 29d0d5badfc1951fe8f4fbac0791a90b73ec2868 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Nov 2021 14:53:10 +0900 Subject: [PATCH 0104/4398] Rename QueueModes -> QueueMode --- .../Multiplayer/QueueingModes/QueueModeTestScene.cs | 2 +- .../QueueingModes/TestSceneFreeForAllQueueMode.cs | 4 ++-- .../QueueingModes/TestSceneHostOnlyQueueMode.cs | 2 +- .../Visual/Multiplayer/TestSceneMultiplayer.cs | 2 +- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 2 +- .../Online/Multiplayer/MultiplayerRoomSettings.cs | 2 +- .../Queueing/{QueueModes.cs => QueueMode.cs} | 2 +- osu.Game/Online/Rooms/Room.cs | 4 ++-- .../Match/MultiplayerMatchSettingsOverlay.cs | 4 ++-- .../Multiplayer/MultiplayerMatchSubScreen.cs | 6 +++--- osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs | 2 +- .../Visual/Multiplayer/TestMultiplayerClient.cs | 12 ++++++------ 12 files changed, 22 insertions(+), 22 deletions(-) rename osu.Game/Online/Multiplayer/Queueing/{QueueModes.cs => QueueMode.cs} (94%) diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/QueueModeTestScene.cs index 82ad9f3afe..cdab83e463 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/QueueModeTestScene.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Multiplayer.QueueingModes { public abstract class QueueModeTestScene : ScreenTestScene { - protected abstract QueueModes Mode { get; } + protected abstract QueueMode Mode { get; } protected BeatmapInfo InitialBeatmap { get; private set; } protected BeatmapInfo OtherBeatmap { get; private set; } diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneFreeForAllQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneFreeForAllQueueMode.cs index 0cf330bbbb..8217eabcdb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneFreeForAllQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneFreeForAllQueueMode.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Multiplayer.QueueingModes { public class TestSceneFreeForAllQueueMode : QueueModeTestScene { - protected override QueueModes Mode => QueueModes.FreeForAll; + protected override QueueMode Mode => QueueMode.FreeForAll; [Test] public void TestFirstItemSelectedByDefault() @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Multiplayer.QueueingModes // Move to the "other" beatmap. RunGameplay(); - AddStep("change queue mode", () => Client.ChangeSettings(queueMode: QueueModes.HostOnly)); + AddStep("change queue mode", () => Client.ChangeSettings(queueMode: QueueMode.HostOnly)); AddAssert("playlist has 2 items", () => Client.APIRoom?.Playlist.Count == 2); AddAssert("playlist item is the other beatmap", () => Client.CurrentMatchPlayingItem.Value?.BeatmapID == OtherBeatmap.OnlineID); AddAssert("playlist item is not expired", () => Client.APIRoom?.Playlist[1].Expired == false); diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueMode.cs index fb11416140..d9e6ea878b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueMode.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Multiplayer.QueueingModes { public class TestSceneHostOnlyQueueMode : QueueModeTestScene { - protected override QueueModes Mode => QueueModes.HostOnly; + protected override QueueMode Mode => QueueMode.HostOnly; [Test] public void TestFirstItemSelectedByDefault() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 66c639ed62..1cc770b670 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -589,7 +589,7 @@ namespace osu.Game.Tests.Visual.Multiplayer roomManager.AddServerSideRoom(new Room { Name = { Value = "Test Room" }, - QueueMode = { Value = QueueModes.FreeForAll }, + QueueMode = { Value = QueueMode.FreeForAll }, Playlist = { new PlaylistItem diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index b1ec16ee8d..788cc1f40b 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -222,7 +222,7 @@ namespace osu.Game.Online.Multiplayer /// The new password, if any. /// The type of the match, if any. /// The new queue mode, if any. - public Task ChangeSettings(Optional name = default, Optional password = default, Optional matchType = default, Optional queueMode = default) + public Task ChangeSettings(Optional name = default, Optional password = default, Optional matchType = default, Optional queueMode = default) { if (Room == null) throw new InvalidOperationException("Must be joined to a match to change settings."); diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs index 70e884dcce..0f7c61bbc6 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs @@ -27,7 +27,7 @@ namespace osu.Game.Online.Multiplayer public MatchType MatchType { get; set; } = MatchType.HeadToHead; [Key(4)] - public QueueModes QueueMode { get; set; } = QueueModes.HostOnly; + public QueueMode QueueMode { get; set; } = QueueMode.HostOnly; public bool Equals(MultiplayerRoomSettings other) => Password.Equals(other.Password, StringComparison.Ordinal) diff --git a/osu.Game/Online/Multiplayer/Queueing/QueueModes.cs b/osu.Game/Online/Multiplayer/Queueing/QueueMode.cs similarity index 94% rename from osu.Game/Online/Multiplayer/Queueing/QueueModes.cs rename to osu.Game/Online/Multiplayer/Queueing/QueueMode.cs index 0ead8d2f06..169d238496 100644 --- a/osu.Game/Online/Multiplayer/Queueing/QueueModes.cs +++ b/osu.Game/Online/Multiplayer/Queueing/QueueMode.cs @@ -5,7 +5,7 @@ using System.ComponentModel; namespace osu.Game.Online.Multiplayer.Queueing { - public enum QueueModes + public enum QueueMode { // used for osu-web deserialization so names shouldn't be changed. diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index f3d591cde0..51f8bd7437 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -76,11 +76,11 @@ namespace osu.Game.Online.Rooms [Cached] [JsonIgnore] - public readonly Bindable QueueMode = new Bindable(); + public readonly Bindable QueueMode = new Bindable(); [JsonConverter(typeof(SnakeCaseStringEnumConverter))] [JsonProperty("queue_mode")] - private QueueModes queueMode + private QueueMode queueMode { get => QueueMode.Value; set => QueueMode.Value = value; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 98a0cd3d37..6bb698cd67 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -61,7 +61,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match public OsuTextBox NameField, MaxParticipantsField; public RoomAvailabilityPicker AvailabilityPicker; public MatchTypePicker TypePicker; - public OsuEnumDropdown QueueModeDropdown; + public OsuEnumDropdown QueueModeDropdown; public OsuTextBox PasswordTextBox; public TriangleButton ApplyButton; @@ -208,7 +208,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { RelativeSizeAxes = Axes.X, Height = 40, - Child = QueueModeDropdown = new OsuEnumDropdown + Child = QueueModeDropdown = new OsuEnumDropdown { RelativeSizeAxes = Axes.X } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index c99367851a..09ebfbc13c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -388,13 +388,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer switch (client.Room.Settings.QueueMode) { - case QueueModes.HostOnly: + case QueueMode.HostOnly: AddOrEditPlaylistButton.Text = "Edit beatmap"; AddOrEditPlaylistButton.Alpha = client.IsHost ? 1 : 0; break; - case QueueModes.FreeForAll: - case QueueModes.FairRotate: + case QueueMode.FreeForAll: + case QueueMode.FairRotate: AddOrEditPlaylistButton.Text = "Add beatmap"; AddOrEditPlaylistButton.Alpha = 1; break; diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index da82a77a13..b6f4abdbb6 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay protected Bindable Duration { get; private set; } [Resolved(typeof(Room), nameof(Room.QueueMode))] - protected Bindable QueueMode { get; private set; } + protected Bindable QueueMode { get; private set; } [Resolved(CanBeNull = true)] private IBindable subScreenSelectedItem { get; set; } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 3f7ad4a710..7965bba8a1 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -298,12 +298,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Debug.Assert(APIRoom != null); Debug.Assert(currentItem != null); - if (Room.Settings.QueueMode == QueueModes.HostOnly && Room.Host?.UserID != LocalUser?.UserID) + if (Room.Settings.QueueMode == QueueMode.HostOnly && Room.Host?.UserID != LocalUser?.UserID) throw new InvalidOperationException("Local user is not the room host."); switch (Room.Settings.QueueMode) { - case QueueModes.HostOnly: + case QueueMode.HostOnly: // In host-only mode, the current item is re-used. item.ID = currentItem.ID; @@ -365,13 +365,13 @@ namespace osu.Game.Tests.Visual.Multiplayer } } - private async Task changeQueueMode(QueueModes newMode) + private async Task changeQueueMode(QueueMode newMode) { Debug.Assert(Room != null); Debug.Assert(APIRoom != null); Debug.Assert(currentItem != null); - if (newMode == QueueModes.HostOnly) + if (newMode == QueueMode.HostOnly) { // Remove all but the current and expired items. The current item may be re-used for host-only mode if it's non-expired. for (int i = 0; i < Room.Playlist.Count; i++) @@ -406,7 +406,7 @@ namespace osu.Game.Tests.Visual.Multiplayer await ((IMultiplayerClient)this).PlaylistItemChanged(currentItem).ConfigureAwait(false); // In host-only mode, a duplicate playlist item will be used for the next round. - if (Room.Settings.QueueMode == QueueModes.HostOnly) + if (Room.Settings.QueueMode == QueueMode.HostOnly) await duplicateCurrentItem().ConfigureAwait(false); await updateCurrentItem(Room).ConfigureAwait(false); @@ -443,7 +443,7 @@ namespace osu.Game.Tests.Visual.Multiplayer newItem = room.Playlist.FirstOrDefault(i => !i.Expired) ?? room.Playlist.Last(); break; - case QueueModes.FairRotate: + case QueueMode.FairRotate: // Group playlist items by (user_id -> count_expired), and select the first available playlist item from a user that has available beatmaps where count_expired is the lowest. throw new NotImplementedException(); } From 00f0321f25dde61ff830aaf2284a9a5b1a569fbe Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Nov 2021 15:44:47 +0900 Subject: [PATCH 0105/4398] Add nullable equality comparers --- .../Multiplayer/MultiplayerRoomSettings.cs | 17 +++++++++++------ .../Online/Multiplayer/MultiplayerRoomUser.cs | 3 ++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs index 0f7c61bbc6..e84026529f 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs @@ -29,12 +29,17 @@ namespace osu.Game.Online.Multiplayer [Key(4)] public QueueMode QueueMode { get; set; } = QueueMode.HostOnly; - public bool Equals(MultiplayerRoomSettings other) - => Password.Equals(other.Password, StringComparison.Ordinal) - && Name.Equals(other.Name, StringComparison.Ordinal) - && PlaylistItemId == other.PlaylistItemId - && MatchType == other.MatchType - && QueueMode == other.QueueMode; + public bool Equals(MultiplayerRoomSettings? other) + { + if (ReferenceEquals(this, other)) return true; + if (other == null) return false; + + return Password.Equals(other.Password, StringComparison.Ordinal) + && Name.Equals(other.Name, StringComparison.Ordinal) + && PlaylistItemId == other.PlaylistItemId + && MatchType == other.MatchType + && QueueMode == other.QueueMode; + } public override string ToString() => $"Name:{Name}" + $" Password:{(string.IsNullOrEmpty(Password) ? "no" : "yes")}" diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs index 519bfff116..f0b7dcbff8 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs @@ -48,9 +48,10 @@ namespace osu.Game.Online.Multiplayer UserID = userId; } - public bool Equals(MultiplayerRoomUser other) + public bool Equals(MultiplayerRoomUser? other) { if (ReferenceEquals(this, other)) return true; + if (other == null) return false; return UserID == other.UserID; } From c8038df5096a2696411a93190fd3fe2fbe989cd4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Nov 2021 16:06:30 +0900 Subject: [PATCH 0106/4398] Fix CI inspections --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 788cc1f40b..09f5c6a458 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -617,7 +617,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(APIRoom != null); - APIRoom.Playlist.RemoveAll(existingItem => existingItem.ID == playlistItem.ID); + APIRoom.Playlist.RemoveAll(existing => existing.ID == playlistItem.ID); APIRoom.Playlist.Add(playlistItem); RoomUpdated?.Invoke(); }); @@ -635,7 +635,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(APIRoom != null); - APIRoom.Playlist.RemoveAll(item => item.ID == playlistItemId); + APIRoom.Playlist.RemoveAll(existing => existing.ID == playlistItemId); RoomUpdated?.Invoke(); }); @@ -656,7 +656,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(APIRoom != null); - PlaylistItem existingItem = APIRoom.Playlist.Single(existingItem => existingItem.ID == item.ID); + PlaylistItem existingItem = APIRoom.Playlist.Single(existing => existing.ID == item.ID); int existingIndex = APIRoom.Playlist.IndexOf(existingItem); if (existingItem.Equals(playlistItem)) return; From f2d05ea899ba2aaa93deb9680042c1ce6a5cba8f Mon Sep 17 00:00:00 2001 From: MBmasher Date: Wed, 17 Nov 2021 11:27:48 +1100 Subject: [PATCH 0107/4398] 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 0108/4398] 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 f5b95c9e6d73b8819ca80793a433ecb4d1548085 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 17 Nov 2021 11:30:47 +0900 Subject: [PATCH 0109/4398] Add ModUtils helper to instantiate mods from ruleset --- osu.Game/Utils/ModUtils.cs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index 7485950f47..eb5deef6ea 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -8,6 +8,7 @@ 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 @@ -185,5 +186,33 @@ namespace osu.Game.Utils return setting; } } + + /// + /// Verifies all proposed mods are valid for a given ruleset and returns instantiated s for further processing. + /// + /// The ruleset to verify mods against. + /// 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) + { + valid = new List(); + bool proposedWereValid = true; + + foreach (var apiMod in proposedMods) + { + try + { + // will throw if invalid + valid.Add(apiMod.ToMod(ruleset)); + } + catch + { + proposedWereValid = false; + } + } + + return proposedWereValid; + } } } From 97bc67245d8e4643b8144809454085ac74f170c5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 17 Nov 2021 11:46:06 +0900 Subject: [PATCH 0110/4398] Fix xmldoc reference --- osu.Game/Utils/ModUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index eb5deef6ea..cdca277dd8 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -193,7 +193,7 @@ namespace osu.Game.Utils /// The ruleset to verify mods against. /// The proposed mods. /// Mods instantiated from which were valid for the given . - /// Whether all were valid for the given . + /// Whether all were valid for the given . public static bool InstantiateValidModsForRuleset(Ruleset ruleset, IEnumerable proposedMods, out List valid) { valid = new List(); From 6a444b9edbc2c8b780e965b08dcef1ade102e990 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Thu, 18 Nov 2021 09:47:41 +1100 Subject: [PATCH 0111/4398] 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 0112/4398] 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 92cf447180144a3b2a00fff624aacd531eda8406 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Thu, 18 Nov 2021 10:32:41 +1100 Subject: [PATCH 0113/4398] 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 0114/4398] 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 0115/4398] 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 0116/4398] 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 761d1e45f24e6b0fc2286e491c0b8fe62d998bd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Nov 2021 15:40:47 +0100 Subject: [PATCH 0117/4398] Use lime background on right side of card to signify downloaded sets --- .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 23009cda5e..8bc234ec84 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -35,6 +35,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards private const float width = 408; private const float height = 100; private const float corner_radius = 10; + private const float icon_area_width = 30; private readonly APIBeatmapSet beatmapSet; private readonly Bindable favouriteState; @@ -45,7 +46,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards private UpdateableOnlineBeatmapSetCover leftCover; private FillFlowContainer leftIconArea; - private Container rightButtonArea; + private Container rightAreaBackground; + private Container rightAreaButtons; private Container mainContent; private BeatmapCardContentBackground mainContentBackground; @@ -57,6 +59,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards private FillFlowContainer idleBottomContent; private BeatmapCardDownloadProgressBar downloadProgressBar; + [Resolved] + private OsuColour colours { get; set; } + [Resolved] private OverlayColourProvider colourProvider { get; set; } @@ -79,10 +84,20 @@ namespace osu.Game.Beatmaps.Drawables.Cards InternalChildren = new Drawable[] { downloadTracker, - new Box + rightAreaBackground = new Container { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background3 + RelativeSizeAxes = Axes.Y, + Width = icon_area_width + 2 * corner_radius, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + // workaround for masking artifacts at the top & bottom of card, + // which become especially visible on downloaded beatmaps (when the icon area has a lime background). + Padding = new MarginPadding { Vertical = 1 }, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.White + }, }, new Container { @@ -104,7 +119,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards } } }, - rightButtonArea = new Container + rightAreaButtons = new Container { Name = @"Right (button) area", Width = 30, @@ -364,15 +379,16 @@ namespace osu.Game.Beatmaps.Drawables.Cards { float targetWidth = width - height; if (IsHovered) - targetWidth -= 20; + targetWidth = targetWidth - icon_area_width + corner_radius; mainContent.ResizeWidthTo(targetWidth, TRANSITION_DURATION, Easing.OutQuint); mainContentBackground.Dimmed.Value = IsHovered; leftCover.FadeColour(IsHovered ? OsuColour.Gray(0.2f) : Color4.White, TRANSITION_DURATION, Easing.OutQuint); statisticsContainer.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); - rightButtonArea.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); + rightAreaBackground.FadeColour(downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3, TRANSITION_DURATION, Easing.OutQuint); + rightAreaButtons.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); bool showProgress = downloadTracker.State.Value == DownloadState.Downloading || downloadTracker.State.Value == DownloadState.Importing; idleBottomContent.FadeTo(showProgress ? 0 : 1, TRANSITION_DURATION, Easing.OutQuint); From 412abf30d92531a0054f2e53d45ea8052a3c650c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 18 Nov 2021 22:28:17 +0100 Subject: [PATCH 0118/4398] Refactor buttons again to work with latest design guidelines --- .../TestSceneBeatmapCardDownloadButton.cs | 129 +++--------------- .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 39 ++++-- .../Cards/Buttons/BeatmapCardIconButton.cs | 71 ++++++++-- .../Drawables/Cards/Buttons/DownloadButton.cs | 112 ++++----------- .../Drawables/Cards/Buttons/PlayButton.cs | 50 +++++++ 5 files changed, 179 insertions(+), 222 deletions(-) create mode 100644 osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs index 67ad699615..d93209efe9 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs @@ -1,10 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; +using System; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables.Cards.Buttons; @@ -14,50 +13,20 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets.Osu; -using osu.Game.Tests.Resources; using osuTK; namespace osu.Game.Tests.Visual.Beatmaps { public class TestSceneBeatmapCardDownloadButton : OsuTestScene { - private TestDownloadButton downloadButton; + private DownloadButton downloadButton; [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); - [Resolved] - private BeatmapManager beatmaps { get; set; } - [Resolved] private OsuConfigManager config { get; set; } - [Test] - public void TestDownloadableBeatmap() - { - ensureSoleilyRemoved(); - createButton(true); - - assertDownloadVisible(true); - assertDownloadEnabled(true); - assertPlayVisible(false); - AddAssert("tooltip text correct", () => downloadButton.Download.TooltipText == BeatmapsetsStrings.PanelDownloadAll); - - AddStep("set downloading state", () => downloadButton.State.Value = DownloadState.Downloading); - assertDownloadVisible(true); - assertDownloadEnabled(false); - assertPlayVisible(false); - - AddStep("set importing state", () => downloadButton.State.Value = DownloadState.Importing); - assertDownloadVisible(true); - assertDownloadEnabled(false); - assertPlayVisible(false); - - AddStep("set locally available state", () => downloadButton.State.Value = DownloadState.LocallyAvailable); - assertDownloadVisible(false); - assertPlayVisible(true); - } - [Test] public void TestDownloadableBeatmapWithVideo() { @@ -65,10 +34,10 @@ namespace osu.Game.Tests.Visual.Beatmaps assertDownloadEnabled(true); AddStep("prefer no video", () => config.SetValue(OsuSetting.PreferNoVideo, true)); - AddAssert("tooltip text correct", () => downloadButton.Download.TooltipText == BeatmapsetsStrings.PanelDownloadNoVideo); + AddAssert("tooltip text correct", () => downloadButton.TooltipText == BeatmapsetsStrings.PanelDownloadNoVideo); AddStep("prefer video", () => config.SetValue(OsuSetting.PreferNoVideo, false)); - AddAssert("tooltip text correct", () => downloadButton.Download.TooltipText == BeatmapsetsStrings.PanelDownloadVideo); + AddAssert("tooltip text correct", () => downloadButton.TooltipText == BeatmapsetsStrings.PanelDownloadVideo); } [Test] @@ -76,74 +45,31 @@ namespace osu.Game.Tests.Visual.Beatmaps { createButton(false); assertDownloadEnabled(false); - AddAssert("tooltip text correct", () => downloadButton.Download.TooltipText == BeatmapsetsStrings.AvailabilityDisabled); + AddAssert("tooltip text correct", () => downloadButton.TooltipText == BeatmapsetsStrings.AvailabilityDisabled); } - [Test] - public void TestDownloadState() - { - ensureSoleilyRemoved(); - createButtonWithBeatmap(createSoleily()); - AddAssert("button state not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded); - AddStep("import soleily", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport())); - - AddUntilStep("wait for beatmap import", () => beatmaps.GetAllUsableBeatmapSets().Any(b => b.OnlineID == 241526)); - AddUntilStep("button state downloaded", () => downloadButton.State.Value == DownloadState.LocallyAvailable); - - createButtonWithBeatmap(createSoleily()); - AddUntilStep("button state downloaded", () => downloadButton.State.Value == DownloadState.LocallyAvailable); - ensureSoleilyRemoved(); - AddUntilStep("button state not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded); - } - - private void ensureSoleilyRemoved() - { - AddUntilStep("ensure manager loaded", () => beatmaps != null); - AddStep("remove soleily", () => - { - var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineID == 241526); - - if (beatmap != null) beatmaps.Delete(beatmap); - }); - } - - private void assertDownloadVisible(bool visible) => AddUntilStep($"download {(visible ? "visible" : "not visible")}", () => downloadButton.Download.IsPresent == visible); - private void assertDownloadEnabled(bool enabled) => AddAssert($"download {(enabled ? "enabled" : "disabled")}", () => downloadButton.Download.Enabled.Value == enabled); - - private void assertPlayVisible(bool visible) => AddUntilStep($"play {(visible ? "visible" : "not visible")}", () => downloadButton.Play.IsPresent == visible); - - private static APIBeatmapSet createSoleily() => new APIBeatmapSet - { - OnlineID = 241526, - Availability = new BeatmapSetOnlineAvailability - { - DownloadDisabled = false, - ExternalLink = string.Empty, - }, - }; - - private void createButtonWithBeatmap(APIBeatmapSet beatmap) - { - AddStep("create button", () => - { - Child = downloadButton = new TestDownloadButton(beatmap) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(2) - }; - }); - } + private void assertDownloadEnabled(bool enabled) => AddAssert($"download {(enabled ? "enabled" : "disabled")}", () => downloadButton.Enabled.Value == enabled); private void createButton(bool downloadable, bool hasVideo = false) { AddStep("create button", () => { - Child = downloadButton = new TestDownloadButton(downloadable ? getDownloadableBeatmapSet(hasVideo) : getUndownloadableBeatmapSet()) + var beatmapSet = downloadable ? getDownloadableBeatmapSet(hasVideo) : getUndownloadableBeatmapSet(); + var downloadTracker = new BeatmapDownloadTracker(beatmapSet); + + Child = new DependencyProvidingContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(2) + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] + { + (typeof(BeatmapDownloadTracker), downloadTracker) + }, + Child = downloadButton = new DownloadButton(downloadable ? getDownloadableBeatmapSet(hasVideo) : getUndownloadableBeatmapSet()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(2) + } }; }); } @@ -175,18 +101,5 @@ namespace osu.Game.Tests.Visual.Beatmaps return beatmap; } - - private class TestDownloadButton : DownloadButton - { - public new Bindable State => base.State; - - public new BeatmapCardIconButton Download => base.Download; - public new BeatmapCardIconButton Play => base.Play; - - public TestDownloadButton(APIBeatmapSet beatmapSet) - : base(beatmapSet) - { - } - } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 8bc234ec84..e48b21cdb6 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -25,6 +25,7 @@ using osu.Game.Overlays.BeatmapListing.Panels; using osu.Game.Resources.Localisation.Web; using osuTK.Graphics; using DownloadButton = osu.Game.Beatmaps.Drawables.Cards.Buttons.DownloadButton; +using PlayButton = osu.Game.Beatmaps.Drawables.Cards.Buttons.PlayButton; namespace osu.Game.Beatmaps.Drawables.Cards { @@ -47,7 +48,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards private FillFlowContainer leftIconArea; private Container rightAreaBackground; - private Container rightAreaButtons; + private Container rightAreaButtons; private Container mainContent; private BeatmapCardContentBackground mainContentBackground; @@ -119,24 +120,35 @@ namespace osu.Game.Beatmaps.Drawables.Cards } } }, - rightAreaButtons = new Container + new Container { Name = @"Right (button) area", Width = 30, RelativeSizeAxes = Axes.Y, Origin = Anchor.TopRight, Anchor = Anchor.TopRight, - Child = new FillFlowContainer + Padding = new MarginPadding { Vertical = 17.5f }, + Child = rightAreaButtons = new Container { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 14), - Children = new Drawable[] + RelativeSizeAxes = Axes.Both, + Children = new BeatmapCardIconButton[] { - new FavouriteButton(beatmapSet) { Current = favouriteState }, + new FavouriteButton(beatmapSet) + { + Current = favouriteState, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + }, new DownloadButton(beatmapSet) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre + }, + new PlayButton(beatmapSet) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre + } } } }, @@ -389,6 +401,13 @@ namespace osu.Game.Beatmaps.Drawables.Cards rightAreaBackground.FadeColour(downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3, TRANSITION_DURATION, Easing.OutQuint); rightAreaButtons.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); + + foreach (var button in rightAreaButtons) + { + button.IdleColour = downloadTracker.State.Value != DownloadState.LocallyAvailable ? colourProvider.Light1 : colourProvider.Background3; + button.HoverColour = downloadTracker.State.Value != DownloadState.LocallyAvailable ? colourProvider.Content1 : colourProvider.Foreground1; + } + bool showProgress = downloadTracker.State.Value == DownloadState.Downloading || downloadTracker.State.Value == DownloadState.Importing; idleBottomContent.FadeTo(showProgress ? 0 : 1, TRANSITION_DURATION, Easing.OutQuint); diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs index ebd78938c7..f0f7d9aff0 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs @@ -1,25 +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 System.Collections.Generic; using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; 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.Containers; using osu.Game.Overlays; using osuTK; namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { - public abstract class BeatmapCardIconButton : OsuHoverContainer + public abstract class BeatmapCardIconButton : OsuClickableContainer { - protected override IEnumerable EffectTargets => background.Yield(); + private Colour4 idleColour; - private readonly Box background; - protected readonly SpriteIcon Icon; + public Colour4 IdleColour + { + get => idleColour; + set + { + idleColour = value; + if (IsLoaded) + updateState(); + } + } + + private Colour4 hoverColour; + + public Colour4 HoverColour + { + get => hoverColour; + set + { + hoverColour = value; + if (IsLoaded) + updateState(); + } + } private float iconSize; @@ -33,18 +52,18 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons } } + protected readonly SpriteIcon Icon; + protected BeatmapCardIconButton() { + Anchor = Origin = Anchor.Centre; + Child = new CircularContainer { RelativeSizeAxes = Axes.Both, Masking = true, Children = new Drawable[] { - background = new Box - { - RelativeSizeAxes = Axes.Both - }, Icon = new SpriteIcon { Origin = Anchor.Centre, @@ -60,11 +79,33 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - Anchor = Origin = Anchor.Centre; + IdleColour = colourProvider.Light1; + HoverColour = colourProvider.Content1; + } - IdleColour = colourProvider.Background4; - HoverColour = colourProvider.Background1; - Icon.Colour = colourProvider.Content2; + protected override void LoadComplete() + { + base.LoadComplete(); + + Enabled.BindValueChanged(_ => updateState(), true); + FinishTransforms(true); + } + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + updateState(); + } + + private void updateState() + { + Content.FadeColour(IsHovered && Enabled.Value ? HoverColour : IdleColour, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs index 0e6d63830c..737c4c4697 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs @@ -6,128 +6,62 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Configuration; -using osu.Game.Graphics; using osu.Game.Online; -using osu.Game.Overlays; using osu.Game.Resources.Localisation.Web; namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { - public class DownloadButton : CompositeDrawable + public class DownloadButton : BeatmapCardIconButton { - protected readonly DownloadIcon Download; - protected readonly PlayIcon Play; - protected readonly Bindable State = new Bindable(); + private readonly APIBeatmapSet beatmapSet; + private Bindable preferNoVideo = null!; + private Bindable downloadState = new Bindable(); [Resolved] - private OsuColour colours { get; set; } = null!; - - [Resolved] - private OverlayColourProvider colourProvider { get; set; } = null!; + private BeatmapManager beatmaps { get; set; } = null!; public DownloadButton(APIBeatmapSet beatmapSet) { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - AutoSizeAxes = Axes.Both; + Icon.Icon = FontAwesome.Solid.Download; - InternalChildren = new Drawable[] - { - Download = new DownloadIcon(beatmapSet), - Play = new PlayIcon(beatmapSet) - }; + this.beatmapSet = beatmapSet; } - [BackgroundDependencyLoader(true)] - private void load(BeatmapDownloadTracker? tracker) + [BackgroundDependencyLoader] + private void load(OsuConfigManager config, BeatmapDownloadTracker downloadTracker) { - if (tracker != null) - ((IBindable)State).BindTo(tracker.State); + preferNoVideo = config.GetBindable(OsuSetting.PreferNoVideo); + ((IBindable)downloadState).BindTo(downloadTracker.State); } protected override void LoadComplete() { base.LoadComplete(); - - State.BindValueChanged(_ => updateState(), true); + preferNoVideo.BindValueChanged(_ => updateState()); + downloadState.BindValueChanged(_ => updateState(), true); FinishTransforms(true); } private void updateState() { - Download.FadeTo(State.Value != DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - Download.Enabled.Value = State.Value == DownloadState.NotDownloaded; + this.FadeTo(downloadState.Value != DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - Play.FadeTo(State.Value == DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - } - - protected class DownloadIcon : BeatmapCardIconButton - { - private readonly APIBeatmapSet beatmapSet; - private Bindable preferNoVideo = null!; - - [Resolved] - private BeatmapManager beatmaps { get; set; } = null!; - - public DownloadIcon(APIBeatmapSet beatmapSet) + if (beatmapSet.Availability.DownloadDisabled) { - Icon.Icon = FontAwesome.Solid.Download; - - this.beatmapSet = beatmapSet; + Enabled.Value = false; + TooltipText = BeatmapsetsStrings.AvailabilityDisabled; + return; } - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - preferNoVideo = config.GetBindable(OsuSetting.PreferNoVideo); - } + if (!beatmapSet.HasVideo) + TooltipText = BeatmapsetsStrings.PanelDownloadAll; + else + TooltipText = preferNoVideo.Value ? BeatmapsetsStrings.PanelDownloadNoVideo : BeatmapsetsStrings.PanelDownloadVideo; - protected override void LoadComplete() - { - base.LoadComplete(); - preferNoVideo.BindValueChanged(_ => updateState(), true); - } - - private void updateState() - { - if (beatmapSet.Availability.DownloadDisabled) - { - Enabled.Value = false; - TooltipText = BeatmapsetsStrings.AvailabilityDisabled; - return; - } - - if (!beatmapSet.HasVideo) - TooltipText = BeatmapsetsStrings.PanelDownloadAll; - else - TooltipText = preferNoVideo.Value ? BeatmapsetsStrings.PanelDownloadNoVideo : BeatmapsetsStrings.PanelDownloadVideo; - - Action = () => beatmaps.Download(beatmapSet, preferNoVideo.Value); - } - } - - protected class PlayIcon : BeatmapCardIconButton - { - private readonly APIBeatmapSet beatmapSet; - - public PlayIcon(APIBeatmapSet beatmapSet) - { - this.beatmapSet = beatmapSet; - - Icon.Icon = FontAwesome.Solid.AngleDoubleRight; - TooltipText = "Go to beatmap"; - } - - [BackgroundDependencyLoader(true)] - private void load(OsuGame? game) - { - if (game != null) - Action = () => game.PresentBeatmap(beatmapSet); - } + Action = () => beatmaps.Download(beatmapSet, preferNoVideo.Value); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs new file mode 100644 index 0000000000..7f3dbf4502 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs @@ -0,0 +1,50 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Online; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Beatmaps.Drawables.Cards.Buttons +{ + public class PlayButton : BeatmapCardIconButton + { + private readonly APIBeatmapSet beatmapSet; + private readonly Bindable downloadState = new Bindable(); + + public PlayButton(APIBeatmapSet beatmapSet) + { + this.beatmapSet = beatmapSet; + + Icon.Icon = FontAwesome.Solid.AngleDoubleRight; + TooltipText = "Go to beatmap"; + } + + [BackgroundDependencyLoader(true)] + private void load(OsuGame? game, BeatmapDownloadTracker downloadTracker) + { + if (game != null) + Action = () => game.PresentBeatmap(beatmapSet); + + ((IBindable)downloadState).BindTo(downloadTracker.State); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + downloadState.BindValueChanged(_ => updateState(), true); + FinishTransforms(true); + } + + private void updateState() + { + this.FadeTo(downloadState.Value == DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + } + } +} From 0eaf450204c397fa58c7b310ccefe574f9e851cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 18 Nov 2021 22:50:41 +0100 Subject: [PATCH 0119/4398] Make field readonly --- osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs index 737c4c4697..325181598d 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs @@ -17,8 +17,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons public class DownloadButton : BeatmapCardIconButton { private readonly APIBeatmapSet beatmapSet; + private readonly Bindable downloadState = new Bindable(); + private Bindable preferNoVideo = null!; - private Bindable downloadState = new Bindable(); [Resolved] private BeatmapManager beatmaps { get; set; } = null!; From 0c289bf8e5b63f49a7ce328958d018d16bafa077 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Nov 2021 14:46:53 +0900 Subject: [PATCH 0120/4398] Remove pointless namespace --- .../Visual/Multiplayer/QueueingModes/QueueModeTestScene.cs | 1 - .../Multiplayer/QueueingModes/TestSceneFreeForAllQueueMode.cs | 2 +- .../Multiplayer/QueueingModes/TestSceneHostOnlyQueueMode.cs | 2 +- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 1 - osu.Game/Online/Multiplayer/MultiplayerClient.cs | 1 - osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs | 1 - osu.Game/Online/Multiplayer/{Queueing => }/QueueMode.cs | 2 +- osu.Game/Online/Rooms/Room.cs | 2 +- .../Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs | 1 - .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 1 - osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs | 2 +- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 1 - 12 files changed, 5 insertions(+), 12 deletions(-) rename osu.Game/Online/Multiplayer/{Queueing => }/QueueMode.cs (90%) diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/QueueModeTestScene.cs index cdab83e463..97d24f2d8c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/QueueModeTestScene.cs @@ -11,7 +11,6 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Online.Multiplayer; -using osu.Game.Online.Multiplayer.Queueing; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneFreeForAllQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneFreeForAllQueueMode.cs index 8217eabcdb..9de589b909 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneFreeForAllQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneFreeForAllQueueMode.cs @@ -6,7 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Online.Multiplayer.Queueing; +using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer; using osuTK.Input; diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueMode.cs index d9e6ea878b..8d162b875f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueMode.cs @@ -6,7 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Online.Multiplayer.Queueing; +using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer; using osuTK.Input; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 1cc770b670..cf14bfa312 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -19,7 +19,6 @@ using osu.Game.Database; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; -using osu.Game.Online.Multiplayer.Queueing; using osu.Game.Online.Rooms; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 09f5c6a458..d757781a0a 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Database; using osu.Game.Online.API; -using osu.Game.Online.Multiplayer.Queueing; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Online.Rooms.RoomStatuses; diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs index e84026529f..c392260a22 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs @@ -5,7 +5,6 @@ using System; using MessagePack; -using osu.Game.Online.Multiplayer.Queueing; using osu.Game.Online.Rooms; namespace osu.Game.Online.Multiplayer diff --git a/osu.Game/Online/Multiplayer/Queueing/QueueMode.cs b/osu.Game/Online/Multiplayer/QueueMode.cs similarity index 90% rename from osu.Game/Online/Multiplayer/Queueing/QueueMode.cs rename to osu.Game/Online/Multiplayer/QueueMode.cs index 169d238496..3a9ea93658 100644 --- a/osu.Game/Online/Multiplayer/Queueing/QueueMode.cs +++ b/osu.Game/Online/Multiplayer/QueueMode.cs @@ -3,7 +3,7 @@ using System.ComponentModel; -namespace osu.Game.Online.Multiplayer.Queueing +namespace osu.Game.Online.Multiplayer { public enum QueueMode { diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 51f8bd7437..c87411c3c0 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -7,8 +7,8 @@ using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.IO.Serialization.Converters; -using osu.Game.Online.Multiplayer.Queueing; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Utils; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 6bb698cd67..947c9d3868 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -18,7 +18,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; -using osu.Game.Online.Multiplayer.Queueing; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Rulesets; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 09ebfbc13c..7a840a05cd 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -17,7 +17,6 @@ using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.Multiplayer; -using osu.Game.Online.Multiplayer.Queueing; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index b6f4abdbb6..2fd2af08ef 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -6,8 +6,8 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; -using osu.Game.Online.Multiplayer.Queueing; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Match; diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 7965bba8a1..7e0312e566 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -16,7 +16,6 @@ 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.Multiplayer.Queueing; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Mods; using APIUser = osu.Game.Online.API.Requests.Responses.APIUser; From 741c27c1f2b92378ac4a8f63d3bf3edca6e129b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Nov 2021 15:27:38 +0900 Subject: [PATCH 0121/4398] Always populate action to make button behave better in testing scenarios --- osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs index 7f3dbf4502..be964e306a 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs @@ -28,8 +28,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons [BackgroundDependencyLoader(true)] private void load(OsuGame? game, BeatmapDownloadTracker downloadTracker) { - if (game != null) - Action = () => game.PresentBeatmap(beatmapSet); + Action = () => game?.PresentBeatmap(beatmapSet); ((IBindable)downloadState).BindTo(downloadTracker.State); } From 6e5918a100c6f212f8ab590ed7b06ec2d2444ede Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Nov 2021 15:27:51 +0900 Subject: [PATCH 0122/4398] Add slight scale to icon buttons --- .../Cards/Buttons/BeatmapCardIconButton.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs index f0f7d9aff0..ad9caf7e34 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs @@ -54,14 +54,19 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons protected readonly SpriteIcon Icon; + private readonly Container content; + protected BeatmapCardIconButton() { - Anchor = Origin = Anchor.Centre; + Origin = Anchor.Centre; + Anchor = Anchor.Centre; - Child = new CircularContainer + Child = content = new Container { RelativeSizeAxes = Axes.Both, Masking = true, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, Children = new Drawable[] { Icon = new SpriteIcon @@ -105,7 +110,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons private void updateState() { - Content.FadeColour(IsHovered && Enabled.Value ? HoverColour : IdleColour, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + bool isHovered = IsHovered && Enabled.Value; + + content.ScaleTo(isHovered ? 1.2f : 1, 500, Easing.OutQuint); + content.FadeColour(isHovered ? HoverColour : IdleColour, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); } } } From a6a786b167a345aae17e7bec6916f65a1856f293 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 19 Nov 2021 15:43:11 +0900 Subject: [PATCH 0123/4398] Remove namespacing of queue tests --- .../Multiplayer/{QueueingModes => }/QueueModeTestScene.cs | 2 +- .../{QueueingModes => }/TestSceneFreeForAllQueueMode.cs | 2 +- .../{QueueingModes => }/TestSceneHostOnlyQueueMode.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename osu.Game.Tests/Visual/Multiplayer/{QueueingModes => }/QueueModeTestScene.cs (98%) rename osu.Game.Tests/Visual/Multiplayer/{QueueingModes => }/TestSceneFreeForAllQueueMode.cs (98%) rename osu.Game.Tests/Visual/Multiplayer/{QueueingModes => }/TestSceneHostOnlyQueueMode.cs (98%) diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs similarity index 98% rename from osu.Game.Tests/Visual/Multiplayer/QueueingModes/QueueModeTestScene.cs rename to osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 97d24f2d8c..e94e91dfe3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -21,7 +21,7 @@ using osu.Game.Screens.Play; using osu.Game.Tests.Resources; using osuTK.Input; -namespace osu.Game.Tests.Visual.Multiplayer.QueueingModes +namespace osu.Game.Tests.Visual.Multiplayer { public abstract class QueueModeTestScene : ScreenTestScene { diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneFreeForAllQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeForAllQueueMode.cs similarity index 98% rename from osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneFreeForAllQueueMode.cs rename to osu.Game.Tests/Visual/Multiplayer/TestSceneFreeForAllQueueMode.cs index 9de589b909..967b9221bb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneFreeForAllQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeForAllQueueMode.cs @@ -10,7 +10,7 @@ using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer; using osuTK.Input; -namespace osu.Game.Tests.Visual.Multiplayer.QueueingModes +namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneFreeForAllQueueMode : QueueModeTestScene { diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs similarity index 98% rename from osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueMode.cs rename to osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index 8d162b875f..2c60a98014 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueingModes/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -10,7 +10,7 @@ using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer; using osuTK.Input; -namespace osu.Game.Tests.Visual.Multiplayer.QueueingModes +namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneHostOnlyQueueMode : QueueModeTestScene { From a922ce2fd9103b5c978300057345b3ac11cef0a6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 19 Nov 2021 15:45:45 +0900 Subject: [PATCH 0124/4398] Remove unnecessary nameofs --- osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 2fd2af08ef..6b111d76a5 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -60,13 +60,13 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable Availability { get; private set; } - [Resolved(typeof(Room), nameof(Room.Password))] + [Resolved(typeof(Room))] public Bindable Password { get; private set; } [Resolved(typeof(Room))] protected Bindable Duration { get; private set; } - [Resolved(typeof(Room), nameof(Room.QueueMode))] + [Resolved(typeof(Room))] protected Bindable QueueMode { get; private set; } [Resolved(CanBeNull = true)] From 12e1142b070fd710fda023ada912090b6695b99f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 19 Nov 2021 15:47:38 +0900 Subject: [PATCH 0125/4398] Rename settings section --- .../Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 947c9d3868..8a9a395222 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -201,7 +201,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match }, }, }, - new Section("Picking mode") + new Section("Queue mode") { Child = new Container { From abb8b0de47e0e3cc5a71811b0fdc5737d993e7c8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 19 Nov 2021 16:35:42 +0900 Subject: [PATCH 0126/4398] Also mutate multiplayer room playlist on callbacks --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index d757781a0a..daadef1db5 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -616,8 +616,9 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(APIRoom != null); - APIRoom.Playlist.RemoveAll(existing => existing.ID == playlistItem.ID); + Room.Playlist.Add(item); APIRoom.Playlist.Add(playlistItem); + RoomUpdated?.Invoke(); }); } @@ -634,7 +635,9 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(APIRoom != null); + Room.Playlist.Remove(Room.Playlist.Single(existing => existing.ID == playlistItemId)); APIRoom.Playlist.RemoveAll(existing => existing.ID == playlistItemId); + RoomUpdated?.Invoke(); }); @@ -655,17 +658,14 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(APIRoom != null); - PlaylistItem existingItem = APIRoom.Playlist.Single(existing => existing.ID == item.ID); - int existingIndex = APIRoom.Playlist.IndexOf(existingItem); - if (existingItem.Equals(playlistItem)) - return; + Room.Playlist[Room.Playlist.IndexOf(Room.Playlist.First(existing => existing.ID == item.ID))] = item; - // Replace the item. + int existingIndex = APIRoom.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID)); APIRoom.Playlist.RemoveAt(existingIndex); APIRoom.Playlist.Insert(existingIndex, playlistItem); // If the currently-selected item was the one that got replaced, update the selected item to the new one. - if (CurrentMatchPlayingItem.Value == existingItem) + if (CurrentMatchPlayingItem.Value?.ID == playlistItem.ID) CurrentMatchPlayingItem.Value = playlistItem; RoomUpdated?.Invoke(); From f3f8ac2c438f8633eb6c4de5cbd94510c0b4cc78 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 19 Nov 2021 16:36:32 +0900 Subject: [PATCH 0127/4398] Use single --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index daadef1db5..ad3c1f6781 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -658,7 +658,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(APIRoom != null); - Room.Playlist[Room.Playlist.IndexOf(Room.Playlist.First(existing => existing.ID == item.ID))] = item; + Room.Playlist[Room.Playlist.IndexOf(Room.Playlist.Single(existing => existing.ID == item.ID))] = item; int existingIndex = APIRoom.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID)); APIRoom.Playlist.RemoveAt(existingIndex); From 4e625b78e20fde95beefef4de8c67b76ce1bb0fb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 19 Nov 2021 18:28:43 +0900 Subject: [PATCH 0128/4398] Update queue mode names --- ...ForAllQueueMode.cs => TestSceneAllPlayersQueueMode.cs} | 4 ++-- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 2 +- osu.Game/Online/Multiplayer/QueueMode.cs | 8 ++++---- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 4 ++-- .../Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) rename osu.Game.Tests/Visual/Multiplayer/{TestSceneFreeForAllQueueMode.cs => TestSceneAllPlayersQueueMode.cs} (96%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeForAllQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs similarity index 96% rename from osu.Game.Tests/Visual/Multiplayer/TestSceneFreeForAllQueueMode.cs rename to osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index 967b9221bb..05cfd784eb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeForAllQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -12,9 +12,9 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneFreeForAllQueueMode : QueueModeTestScene + public class TestSceneAllPlayersQueueMode : QueueModeTestScene { - protected override QueueMode Mode => QueueMode.FreeForAll; + protected override QueueMode Mode => QueueMode.AllPlayers; [Test] public void TestFirstItemSelectedByDefault() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index cf14bfa312..b828379848 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -588,7 +588,7 @@ namespace osu.Game.Tests.Visual.Multiplayer roomManager.AddServerSideRoom(new Room { Name = { Value = "Test Room" }, - QueueMode = { Value = QueueMode.FreeForAll }, + QueueMode = { Value = QueueMode.AllPlayers }, Playlist = { new PlaylistItem diff --git a/osu.Game/Online/Multiplayer/QueueMode.cs b/osu.Game/Online/Multiplayer/QueueMode.cs index 3a9ea93658..026025890b 100644 --- a/osu.Game/Online/Multiplayer/QueueMode.cs +++ b/osu.Game/Online/Multiplayer/QueueMode.cs @@ -12,10 +12,10 @@ namespace osu.Game.Online.Multiplayer [Description("Host only")] HostOnly, - [Description("Free-for-all")] - FreeForAll, + [Description("All players")] + AllPlayers, - [Description("Fair rotate")] - FairRotate + [Description("All players (round robin)")] + AllPlayersRR } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 7a840a05cd..142beab024 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -392,8 +392,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer AddOrEditPlaylistButton.Alpha = client.IsHost ? 1 : 0; break; - case QueueMode.FreeForAll: - case QueueMode.FairRotate: + case QueueMode.AllPlayers: + case QueueMode.AllPlayersRR: AddOrEditPlaylistButton.Text = "Add beatmap"; AddOrEditPlaylistButton.Alpha = 1; break; diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 7e0312e566..e617ec3562 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -442,7 +442,7 @@ namespace osu.Game.Tests.Visual.Multiplayer newItem = room.Playlist.FirstOrDefault(i => !i.Expired) ?? room.Playlist.Last(); break; - case QueueMode.FairRotate: + case QueueMode.AllPlayersRR: // Group playlist items by (user_id -> count_expired), and select the first available playlist item from a user that has available beatmaps where count_expired is the lowest. throw new NotImplementedException(); } From 4c8c34b43f0b1a33e6d39a2ae2a00b605dc7fac0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 19 Nov 2021 18:42:34 +0900 Subject: [PATCH 0129/4398] Use full name for round robin --- osu.Game/Online/Multiplayer/QueueMode.cs | 2 +- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Multiplayer/QueueMode.cs b/osu.Game/Online/Multiplayer/QueueMode.cs index 026025890b..3d113c028e 100644 --- a/osu.Game/Online/Multiplayer/QueueMode.cs +++ b/osu.Game/Online/Multiplayer/QueueMode.cs @@ -16,6 +16,6 @@ namespace osu.Game.Online.Multiplayer AllPlayers, [Description("All players (round robin)")] - AllPlayersRR + AllPlayersRoundRobin } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 142beab024..1e3cfdbcbb 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -393,7 +393,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer break; case QueueMode.AllPlayers: - case QueueMode.AllPlayersRR: + case QueueMode.AllPlayersRoundRobin: AddOrEditPlaylistButton.Text = "Add beatmap"; AddOrEditPlaylistButton.Alpha = 1; break; diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index e617ec3562..4deeef331d 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -442,7 +442,7 @@ namespace osu.Game.Tests.Visual.Multiplayer newItem = room.Playlist.FirstOrDefault(i => !i.Expired) ?? room.Playlist.Last(); break; - case QueueMode.AllPlayersRR: + case QueueMode.AllPlayersRoundRobin: // Group playlist items by (user_id -> count_expired), and select the first available playlist item from a user that has available beatmaps where count_expired is the lowest. throw new NotImplementedException(); } From eecf6ad558983786890f5905370142fcff4d5a75 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Nov 2021 21:53:40 +0900 Subject: [PATCH 0130/4398] Add `IsManaged` helper method to EF classes to match realm implementation --- osu.Game/Beatmaps/BeatmapDifficulty.cs | 2 ++ osu.Game/Beatmaps/BeatmapInfo.cs | 2 ++ osu.Game/Beatmaps/BeatmapMetadata.cs | 2 ++ osu.Game/Beatmaps/BeatmapSetFileInfo.cs | 2 ++ osu.Game/Beatmaps/BeatmapSetInfo.cs | 2 ++ osu.Game/Configuration/DatabasedSetting.cs | 2 ++ osu.Game/Database/IHasPrimaryKey.cs | 2 ++ osu.Game/IO/FileInfo.cs | 2 ++ osu.Game/Scoring/ScoreFileInfo.cs | 2 ++ osu.Game/Scoring/ScoreInfo.cs | 2 ++ osu.Game/Skinning/SkinFileInfo.cs | 2 ++ osu.Game/Skinning/SkinInfo.cs | 2 ++ 12 files changed, 24 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index dfd21469fa..65d1fb8286 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -15,6 +15,8 @@ namespace osu.Game.Beatmaps public int ID { get; set; } + public bool IsManaged => ID > 0; + public float DrainRate { get; set; } = DEFAULT_DIFFICULTY; public float CircleSize { get; set; } = DEFAULT_DIFFICULTY; public float OverallDifficulty { get; set; } = DEFAULT_DIFFICULTY; diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index d2b322a843..7359de0cd7 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -21,6 +21,8 @@ namespace osu.Game.Beatmaps { public int ID { get; set; } + public bool IsManaged => ID > 0; + public int BeatmapVersion; private int? onlineID; diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index b395f16c24..5da0264893 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -20,6 +20,8 @@ namespace osu.Game.Beatmaps { public int ID { get; set; } + public bool IsManaged => ID > 0; + public string Title { get; set; } = string.Empty; [JsonProperty("title_unicode")] diff --git a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs index ce50463f05..29dcf4d6aa 100644 --- a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs @@ -11,6 +11,8 @@ namespace osu.Game.Beatmaps { public int ID { get; set; } + public bool IsManaged => ID > 0; + public int BeatmapSetInfoID { get; set; } public int FileInfoID { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index a0de50a311..db5c3bf15a 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -16,6 +16,8 @@ namespace osu.Game.Beatmaps { public int ID { get; set; } + public bool IsManaged => ID > 0; + private int? onlineID; [Column("OnlineBeatmapSetID")] diff --git a/osu.Game/Configuration/DatabasedSetting.cs b/osu.Game/Configuration/DatabasedSetting.cs index fe1d51d57f..65d9f7799d 100644 --- a/osu.Game/Configuration/DatabasedSetting.cs +++ b/osu.Game/Configuration/DatabasedSetting.cs @@ -11,6 +11,8 @@ namespace osu.Game.Configuration { public int ID { get; set; } + public bool IsManaged => ID > 0; + public int? RulesetID { get; set; } public int? Variant { get; set; } diff --git a/osu.Game/Database/IHasPrimaryKey.cs b/osu.Game/Database/IHasPrimaryKey.cs index 3c0fc94418..51a49948fe 100644 --- a/osu.Game/Database/IHasPrimaryKey.cs +++ b/osu.Game/Database/IHasPrimaryKey.cs @@ -11,5 +11,7 @@ namespace osu.Game.Database [JsonIgnore] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] int ID { get; set; } + + bool IsManaged { get; } } } diff --git a/osu.Game/IO/FileInfo.cs b/osu.Game/IO/FileInfo.cs index 331546f9f8..360f8440f1 100644 --- a/osu.Game/IO/FileInfo.cs +++ b/osu.Game/IO/FileInfo.cs @@ -10,6 +10,8 @@ namespace osu.Game.IO { public int ID { get; set; } + public bool IsManaged => ID > 0; + public string Hash { get; set; } public string StoragePath => Path.Combine(Hash.Remove(1), Hash.Remove(2), Hash); diff --git a/osu.Game/Scoring/ScoreFileInfo.cs b/osu.Game/Scoring/ScoreFileInfo.cs index 9075fdec5b..d98ef9fdc6 100644 --- a/osu.Game/Scoring/ScoreFileInfo.cs +++ b/osu.Game/Scoring/ScoreFileInfo.cs @@ -11,6 +11,8 @@ namespace osu.Game.Scoring { public int ID { get; set; } + public bool IsManaged => ID > 0; + public int FileInfoID { get; set; } public FileInfo FileInfo { get; set; } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 736a939a59..2b02dfef9f 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -22,6 +22,8 @@ namespace osu.Game.Scoring { public int ID { get; set; } + public bool IsManaged => ID > 0; + public ScoreRank Rank { get; set; } public long TotalScore { get; set; } diff --git a/osu.Game/Skinning/SkinFileInfo.cs b/osu.Game/Skinning/SkinFileInfo.cs index 8a7019e1a3..06d0d5e82e 100644 --- a/osu.Game/Skinning/SkinFileInfo.cs +++ b/osu.Game/Skinning/SkinFileInfo.cs @@ -11,6 +11,8 @@ namespace osu.Game.Skinning { public int ID { get; set; } + public bool IsManaged => ID > 0; + public int SkinInfoID { get; set; } public int FileInfoID { get; set; } diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 3b34e23d57..307bb2f9cd 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -18,6 +18,8 @@ namespace osu.Game.Skinning public int ID { get; set; } + public bool IsManaged => ID > 0; + public string Name { get; set; } = string.Empty; public string Creator { get; set; } = string.Empty; From 83b4625bd54cff98c758e82daf4fd8fc5ec27e46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Nov 2021 22:13:07 +0900 Subject: [PATCH 0131/4398] Replace existing cases with new helper method --- .../Visual/Editing/TestSceneEditorBeatmapCreation.cs | 2 +- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 2 +- osu.Game/Database/ArchiveModelManager.cs | 6 +++--- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 4 ++-- osu.Game/Screens/Select/SongSelect.cs | 4 ++-- osu.Game/Skinning/SkinManager.cs | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 92c8131568..db20d3c7ba 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Editing public void TestCreateNewBeatmap() { AddStep("save beatmap", () => Editor.Save()); - AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.ID > 0); + AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.IsManaged); AddAssert("new beatmap in database", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == false); } diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 5f20bf49b9..17d64bce4d 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -131,7 +131,7 @@ namespace osu.Game.Beatmaps var localRulesetInfo = rulesetInfo as RulesetInfo; // Difficulty can only be computed if the beatmap and ruleset are locally available. - if (localBeatmapInfo == null || localBeatmapInfo.ID == 0 || localRulesetInfo == null) + if (localBeatmapInfo?.IsManaged != true || localRulesetInfo == null) { // If not, fall back to the existing star difficulty (e.g. from an online source). return Task.FromResult(new StarDifficulty(beatmapInfo.StarRating, (beatmapInfo as IBeatmapOnlineInfo)?.MaxCombo ?? 0)); diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index e8b6996869..5f9304448a 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -516,7 +516,7 @@ namespace osu.Game.Database { Files.Dereference(file.FileInfo); - if (file.ID > 0) + if (file.IsManaged) { // This shouldn't be required, but here for safety in case the provided TModel is not being change tracked // Definitely can be removed once we rework the database backend. @@ -545,7 +545,7 @@ namespace osu.Game.Database }); } - if (model.ID > 0) + if (model.IsManaged) Update(model); } @@ -811,7 +811,7 @@ namespace osu.Game.Database /// The usable items present in the store. /// Whether the exists. protected virtual bool CheckLocalAvailability(TModel model, IQueryable items) - => model.ID > 0 && items.Any(i => i.ID == model.ID && i.Files.Any()); + => model.IsManaged && items.Any(i => i.ID == model.ID && i.Files.Any()); /// /// Whether import can be skipped after finding an existing import early in the process. diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 0714b28b47..e17abb5936 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -45,7 +45,7 @@ namespace osu.Game.Overlays.Settings.Sections { get { - int index = skinItems.FindIndex(s => s.ID > 0); + int index = skinItems.FindIndex(s => s.IsManaged); if (index < 0) index = skinItems.Count; @@ -176,7 +176,7 @@ namespace osu.Game.Overlays.Settings.Sections Action = export; currentSkin = skins.CurrentSkin.GetBoundCopy(); - currentSkin.BindValueChanged(skin => Enabled.Value = skin.NewValue.SkinInfo.ID > 0, true); + currentSkin.BindValueChanged(skin => Enabled.Value = skin.NewValue.SkinInfo.IsManaged, true); } private void export() diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 2c36bf5fc8..969a6e0290 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -805,14 +805,14 @@ namespace osu.Game.Screens.Select private void delete(BeatmapSetInfo beatmap) { - if (beatmap == null || beatmap.ID <= 0) return; + if (beatmap == null || !beatmap.IsManaged) return; dialogOverlay?.Push(new BeatmapDeleteDialog(beatmap)); } private void clearScores(BeatmapInfo beatmapInfo) { - if (beatmapInfo == null || beatmapInfo.ID <= 0) return; + if (beatmapInfo == null || !beatmapInfo.IsManaged) return; dialogOverlay?.Push(new BeatmapClearScoresDialog(beatmapInfo, () => // schedule done here rather than inside the dialog as the dialog may fade out and never callback. diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 0739026544..2887d03029 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -307,7 +307,7 @@ namespace osu.Game.Skinning public void Save(Skin skin) { - if (skin.SkinInfo.ID <= 0) + if (!skin.SkinInfo.IsManaged) throw new InvalidOperationException($"Attempting to save a skin which is not yet tracked. Call {nameof(EnsureMutableSkin)} first."); foreach (var drawableInfo in skin.DrawableComponentInfo) From 0342923408b83b2300ff70dbf8baa3308df63a07 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Nov 2021 19:03:52 +0300 Subject: [PATCH 0132/4398] Fix SignalR messagepack formatter potentially initializing on iOS --- osu.Game/Online/SignalRUnionWorkaroundResolver.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/SignalRUnionWorkaroundResolver.cs b/osu.Game/Online/SignalRUnionWorkaroundResolver.cs index 21413f8285..89052ad052 100644 --- a/osu.Game/Online/SignalRUnionWorkaroundResolver.cs +++ b/osu.Game/Online/SignalRUnionWorkaroundResolver.cs @@ -35,7 +35,9 @@ namespace osu.Game.Online typeof(TeamVersusUserState), }; - private static readonly IReadOnlyDictionary formatter_map = new Dictionary + private static IReadOnlyDictionary formatterMapBacking; + + private static IReadOnlyDictionary formatterMap => formatterMapBacking ??= new Dictionary { { typeof(TeamVersusUserState), new TypeRedirectingFormatter() }, { typeof(TeamVersusRoomState), new TypeRedirectingFormatter() }, @@ -51,7 +53,7 @@ namespace osu.Game.Online public IMessagePackFormatter GetFormatter() { - if (formatter_map.TryGetValue(typeof(T), out var formatter)) + if (formatterMap.TryGetValue(typeof(T), out var formatter)) return (IMessagePackFormatter)formatter; return StandardResolver.Instance.GetFormatter(); From 8b28bf31f69e44a1922ddc7a649c3f6d63875a48 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 20 Nov 2021 15:11:02 +0300 Subject: [PATCH 0133/4398] Separate SignalR workaround types away from resolver --- ...gnalRDerivedTypeWorkaroundJsonConverter.cs | 7 +-- .../Online/SignalRUnionWorkaroundResolver.cs | 45 +++++++------------ osu.Game/Online/SignalRWorkaroundTypes.cs | 26 +++++++++++ 3 files changed, 45 insertions(+), 33 deletions(-) create mode 100644 osu.Game/Online/SignalRWorkaroundTypes.cs diff --git a/osu.Game/Online/SignalRDerivedTypeWorkaroundJsonConverter.cs b/osu.Game/Online/SignalRDerivedTypeWorkaroundJsonConverter.cs index 55516d2223..5643c5ca74 100644 --- a/osu.Game/Online/SignalRDerivedTypeWorkaroundJsonConverter.cs +++ b/osu.Game/Online/SignalRDerivedTypeWorkaroundJsonConverter.cs @@ -17,8 +17,9 @@ namespace osu.Game.Online public class SignalRDerivedTypeWorkaroundJsonConverter : JsonConverter { public override bool CanConvert(Type objectType) => - SignalRUnionWorkaroundResolver.BASE_TYPES.Contains(objectType) || - SignalRUnionWorkaroundResolver.DERIVED_TYPES.Contains(objectType); + SignalRWorkaroundTypes.BASE_DERIVED.Any(t => + objectType == t.baseType || + objectType == t.derivedType); public override object? ReadJson(JsonReader reader, Type objectType, object? o, JsonSerializer jsonSerializer) { @@ -29,7 +30,7 @@ namespace osu.Game.Online string type = (string)obj[@"$dtype"]!; - var resolvedType = SignalRUnionWorkaroundResolver.DERIVED_TYPES.Single(t => t.Name == type); + var resolvedType = SignalRWorkaroundTypes.BASE_DERIVED.Select(t => t.derivedType).Single(t => t.Name == type); object? instance = Activator.CreateInstance(resolvedType); diff --git a/osu.Game/Online/SignalRUnionWorkaroundResolver.cs b/osu.Game/Online/SignalRUnionWorkaroundResolver.cs index 89052ad052..01e3c24c87 100644 --- a/osu.Game/Online/SignalRUnionWorkaroundResolver.cs +++ b/osu.Game/Online/SignalRUnionWorkaroundResolver.cs @@ -3,11 +3,10 @@ using System; using System.Collections.Generic; +using System.Linq; using MessagePack; using MessagePack.Formatters; using MessagePack.Resolvers; -using osu.Game.Online.Multiplayer; -using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; namespace osu.Game.Online { @@ -20,40 +19,26 @@ namespace osu.Game.Online public static readonly MessagePackSerializerOptions OPTIONS = MessagePackSerializerOptions.Standard.WithResolver(new SignalRUnionWorkaroundResolver()); - public static readonly IReadOnlyList BASE_TYPES = new[] + private static readonly IReadOnlyDictionary formatter_map = createFormatterMap(); + + private static IReadOnlyDictionary createFormatterMap() { - typeof(MatchServerEvent), - typeof(MatchUserRequest), - typeof(MatchRoomState), - typeof(MatchUserState), - }; + IEnumerable<(Type derivedType, Type baseType)> baseDerived = SignalRWorkaroundTypes.BASE_DERIVED; - public static readonly IReadOnlyList DERIVED_TYPES = new[] - { - typeof(ChangeTeamRequest), - typeof(TeamVersusRoomState), - typeof(TeamVersusUserState), - }; - - private static IReadOnlyDictionary formatterMapBacking; - - private static IReadOnlyDictionary formatterMap => formatterMapBacking ??= new Dictionary - { - { typeof(TeamVersusUserState), new TypeRedirectingFormatter() }, - { typeof(TeamVersusRoomState), new TypeRedirectingFormatter() }, - { typeof(ChangeTeamRequest), new TypeRedirectingFormatter() }, - - // These should not be required. The fallback should work. But something is weird with the way caching is done. + // 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. - { typeof(MatchUserState), new TypeRedirectingFormatter() }, - { typeof(MatchRoomState), new TypeRedirectingFormatter() }, - { typeof(MatchUserRequest), new TypeRedirectingFormatter() }, - { typeof(MatchServerEvent), new TypeRedirectingFormatter() }, - }; + baseDerived = baseDerived.Concat(baseDerived.Select(t => (t.baseType, t.baseType))).Distinct(); + + return new Dictionary(baseDerived.Select(t => + { + var formatter = (IMessagePackFormatter)Activator.CreateInstance(typeof(TypeRedirectingFormatter<,>).MakeGenericType(t.derivedType, t.baseType)); + return new KeyValuePair(t.derivedType, formatter); + })); + } public IMessagePackFormatter GetFormatter() { - if (formatterMap.TryGetValue(typeof(T), out var formatter)) + if (formatter_map.TryGetValue(typeof(T), out var formatter)) return (IMessagePackFormatter)formatter; return StandardResolver.Instance.GetFormatter(); diff --git a/osu.Game/Online/SignalRWorkaroundTypes.cs b/osu.Game/Online/SignalRWorkaroundTypes.cs new file mode 100644 index 0000000000..ae2d8b997c --- /dev/null +++ b/osu.Game/Online/SignalRWorkaroundTypes.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 System; +using System.Collections.Generic; +using osu.Game.Online.Multiplayer; +using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; + +namespace osu.Game.Online +{ + /// + /// A static class providing the list of types requiring workarounds for serialisation in SignalR. + /// + /// + /// + internal static class SignalRWorkaroundTypes + { + internal static readonly IReadOnlyList<(Type derivedType, Type baseType)> BASE_DERIVED = new[] + { + (typeof(ChangeTeamRequest), typeof(MatchUserRequest)), + (typeof(TeamVersusRoomState), typeof(MatchRoomState)), + (typeof(TeamVersusUserState), typeof(MatchUserState)), + (typeof(MatchServerEvent), typeof(MatchServerEvent)), + }; + } +} From 15feb17da8c61563e3b8cc566aee2062b6df7173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 20 Nov 2021 16:54:58 +0100 Subject: [PATCH 0134/4398] Change difficulty cache storage type to nullable The recent changes related to adding support for working beatmap load cancellation exposed a flaw in the beatmap difficulty cache. With the way the difficulty computation logic was written, any error in the calculation process (including beatmap load timeout, or cancellation) would result in a 0.00 star rating being permanently cached in memory for the given beatmap. To resolve, change the difficulty cache's return type to nullable. In failure scenarios, `null` is returned, rather than `default(StarDifficulty)` as done previously. --- .../TestSceneBeatmapDifficultyCache.cs | 4 +-- .../TestSceneBeatmapMetadataDisplay.cs | 2 +- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 26 ++++++++++++------- osu.Game/Scoring/ScoreManager.cs | 7 ++++- osu.Game/Scoring/ScorePerformanceCache.cs | 4 +-- .../Expanded/ExpandedPanelMiddleContent.cs | 17 ++++++------ .../Screens/Select/Details/AdvancedStats.cs | 5 +++- 7 files changed, 40 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs index dcfeea5db8..f84dbca0be 100644 --- a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs +++ b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs @@ -164,9 +164,9 @@ namespace osu.Game.Tests.Beatmaps { public Func ComputeDifficulty { get; set; } - protected override Task ComputeValueAsync(DifficultyCacheLookup lookup, CancellationToken token = default) + protected override Task ComputeValueAsync(DifficultyCacheLookup lookup, CancellationToken token = default) { - return Task.FromResult(ComputeDifficulty?.Invoke(lookup) ?? new StarDifficulty(BASE_STARS, 0)); + return Task.FromResult(ComputeDifficulty?.Invoke(lookup) ?? new StarDifficulty(BASE_STARS, 0)); } } } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index 11d589f7bc..e573c96ce9 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -140,7 +140,7 @@ namespace osu.Game.Tests.Visual.SongSelect } } - public override async Task GetDifficultyAsync(IBeatmapInfo beatmapInfo, IRulesetInfo rulesetInfo = null, IEnumerable mods = null, CancellationToken cancellationToken = default) + public override async Task GetDifficultyAsync(IBeatmapInfo beatmapInfo, IRulesetInfo rulesetInfo = null, IEnumerable mods = null, CancellationToken cancellationToken = default) { if (blockCalculation) await calculationBlocker.Task.ConfigureAwait(false); diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 5f20bf49b9..9106408e23 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -27,7 +27,7 @@ namespace osu.Game.Beatmaps /// A component which performs and acts as a central cache for difficulty calculations of beatmap/ruleset/mod combinations. /// Currently not persisted between game sessions. /// - public class BeatmapDifficultyCache : MemoryCachingComponent + public class BeatmapDifficultyCache : MemoryCachingComponent { // Too many simultaneous updates can lead to stutters. One thread seems to work fine for song select display purposes. private readonly ThreadedTaskScheduler updateScheduler = new ThreadedTaskScheduler(1, nameof(BeatmapDifficultyCache)); @@ -120,9 +120,13 @@ namespace osu.Game.Beatmaps /// The to get the difficulty with. /// The s to get the difficulty with. /// An optional which stops computing the star difficulty. - /// The . - public virtual Task GetDifficultyAsync([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo rulesetInfo = null, - [CanBeNull] IEnumerable mods = null, CancellationToken cancellationToken = default) + /// + /// The requested , if non-. + /// A return value indicates that the difficulty process failed or was interrupted early, + /// and as such there is no usable star difficulty value to be returned. + /// + public virtual Task GetDifficultyAsync([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo rulesetInfo = null, + [CanBeNull] IEnumerable mods = null, CancellationToken cancellationToken = default) { // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. rulesetInfo ??= beatmapInfo.Ruleset; @@ -134,13 +138,13 @@ namespace osu.Game.Beatmaps if (localBeatmapInfo == null || localBeatmapInfo.ID == 0 || localRulesetInfo == null) { // If not, fall back to the existing star difficulty (e.g. from an online source). - return Task.FromResult(new StarDifficulty(beatmapInfo.StarRating, (beatmapInfo as IBeatmapOnlineInfo)?.MaxCombo ?? 0)); + return Task.FromResult(new StarDifficulty(beatmapInfo.StarRating, (beatmapInfo as IBeatmapOnlineInfo)?.MaxCombo ?? 0)); } return GetAsync(new DifficultyCacheLookup(localBeatmapInfo, localRulesetInfo, mods), cancellationToken); } - protected override Task ComputeValueAsync(DifficultyCacheLookup lookup, CancellationToken cancellationToken = default) + protected override Task ComputeValueAsync(DifficultyCacheLookup lookup, CancellationToken cancellationToken = default) { return Task.Factory.StartNew(() => { @@ -151,6 +155,8 @@ namespace osu.Game.Beatmaps }, cancellationToken, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); } + protected override bool CacheNullValues => false; + public Task> GetTimedDifficultyAttributesAsync(IWorkingBeatmap beatmap, Ruleset ruleset, Mod[] mods, CancellationToken cancellationToken = default) { return Task.Factory.StartNew(() => ruleset.CreateDifficultyCalculator(beatmap).CalculateTimed(mods, cancellationToken), @@ -260,7 +266,7 @@ namespace osu.Game.Beatmaps // We're on a threadpool thread, but we should exit back to the update thread so consumers can safely handle value-changed events. Schedule(() => { - if (!cancellationToken.IsCancellationRequested) + if (!cancellationToken.IsCancellationRequested && t.Result != null) bindable.Value = t.Result; }); }, cancellationToken); @@ -272,7 +278,7 @@ namespace osu.Game.Beatmaps /// The that defines the computation parameters. /// The cancellation token. /// The . - private StarDifficulty computeDifficulty(in DifficultyCacheLookup key, CancellationToken cancellationToken = default) + private StarDifficulty? computeDifficulty(in DifficultyCacheLookup key, CancellationToken cancellationToken = default) { // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. var beatmapInfo = key.BeatmapInfo; @@ -293,11 +299,11 @@ namespace osu.Game.Beatmaps if (rulesetInfo.Equals(beatmapInfo.Ruleset)) Logger.Error(e, $"Failed to convert {beatmapInfo.OnlineID} to the beatmap's default ruleset ({beatmapInfo.Ruleset})."); - return new StarDifficulty(); + return null; } catch { - return new StarDifficulty(); + return null; } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 9b4216084e..29144e7bdc 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -162,7 +162,12 @@ namespace osu.Game.Scoring // We can compute the max combo locally after the async beatmap difficulty computation. var difficulty = await difficulties().GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false); - beatmapMaxCombo = difficulty.MaxCombo; + + // Something failed during difficulty calculation. Fall back to provided score. + if (difficulty == null) + return score.TotalScore; + + beatmapMaxCombo = difficulty.Value.MaxCombo; } } else diff --git a/osu.Game/Scoring/ScorePerformanceCache.cs b/osu.Game/Scoring/ScorePerformanceCache.cs index 82685e9a04..79fb47f4b0 100644 --- a/osu.Game/Scoring/ScorePerformanceCache.cs +++ b/osu.Game/Scoring/ScorePerformanceCache.cs @@ -37,12 +37,12 @@ namespace osu.Game.Scoring var attributes = await difficultyCache.GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, token).ConfigureAwait(false); // Performance calculation requires the beatmap and ruleset to be locally available. If not, return a default value. - if (attributes.Attributes == null) + if (attributes?.Attributes == null) return null; token.ThrowIfCancellationRequested(); - var calculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator(attributes.Attributes, score); + var calculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator(attributes.Value.Attributes, score); return calculator?.Calculate(); } diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index e31a182a49..3ab2658f97 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -143,14 +143,6 @@ namespace osu.Game.Screens.Ranking.Expanded Origin = Anchor.TopCentre, AutoSizeAxes = Axes.Both, Spacing = new Vector2(5, 0), - Children = new Drawable[] - { - new StarRatingDisplay(starDifficulty) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft - }, - } }, new FillFlowContainer { @@ -231,6 +223,15 @@ namespace osu.Game.Screens.Ranking.Expanded if (score.Date != default) AddInternal(new PlayedOnText(score.Date)); + if (starDifficulty != null) + { + starAndModDisplay.Add(new StarRatingDisplay(starDifficulty.Value) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + }); + } + if (score.Mods.Any()) { starAndModDisplay.Add(new ModDisplay diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 91c8ce441e..edbaba40bc 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -152,7 +152,10 @@ namespace osu.Game.Screens.Select.Details Task.WhenAll(normalStarDifficulty, moddedStarDifficulty).ContinueWith(_ => Schedule(() => { - starDifficulty.Value = ((float)normalStarDifficulty.Result.Stars, (float)moddedStarDifficulty.Result.Stars); + if (normalStarDifficulty.Result == null || moddedStarDifficulty.Result == null) + return; + + starDifficulty.Value = ((float)normalStarDifficulty.Result.Value.Stars, (float)moddedStarDifficulty.Result.Value.Stars); }), starDifficultyCancellationSource.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current); } From 6100bf66a694dc6cac148c67689b3c4555418a83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 20 Nov 2021 17:23:55 +0100 Subject: [PATCH 0135/4398] Clean up cancellation handling in `WorkingBeatmap` After the recent changes introducing cancellation support to `WorkingBeatmap`, it turned out that if the cancellation support was used, `GetPlayableBeatmap()` would raise timeout exceptions rather than the expected `OperationCanceledException`. To that end, split off a separate overload for the typical usage, that catches `OperationCanceledException` and converts them to beatmap load timeout exceptions, and use normal `OperationCanceledException`s in the overload that requires a cancellation token to work. --- osu.Game.Tests/Beatmaps/WorkingBeatmapTest.cs | 5 +- osu.Game/Beatmaps/IWorkingBeatmap.cs | 23 +++++++- osu.Game/Beatmaps/WorkingBeatmap.cs | 53 ++++++++----------- .../Play/HUD/PerformancePointsCounter.cs | 2 +- 4 files changed, 48 insertions(+), 35 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/WorkingBeatmapTest.cs b/osu.Game.Tests/Beatmaps/WorkingBeatmapTest.cs index 6ad63a5fbb..5f70f08413 100644 --- a/osu.Game.Tests/Beatmaps/WorkingBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/WorkingBeatmapTest.cs @@ -9,6 +9,7 @@ using Moq; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; @@ -40,7 +41,7 @@ namespace osu.Game.Tests.Beatmaps Task.Factory.StartNew(() => { loadStarted.Set(); - Assert.Throws(() => working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, cancellationToken: cts.Token)); + Assert.Throws(() => working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty(), cts.Token)); loadCompleted.Set(); }, TaskCreationOptions.LongRunning); @@ -58,7 +59,7 @@ namespace osu.Game.Tests.Beatmaps { var working = new TestNeverLoadsWorkingBeatmap(); - Assert.Throws(() => working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo)); + Assert.Throws(Is.InstanceOf(), () => working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo)); working.ResetEvent.Set(); } diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index da977bb9d5..6cebce3c59 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -90,12 +90,31 @@ namespace osu.Game.Beatmaps /// have been applied, and s have been fully constructed. /// /// + /// + /// By default, the beatmap load process will be interrupted after 10 seconds. + /// For finer-grained control over the load process, use the + /// + /// overload instead. + /// /// The to create a playable for. /// The s to apply to the . - /// Cancellation token that cancels the beatmap loading process. If not provided, a default timeout of 10,000ms will be applied to the load process. /// The converted . /// If could not be converted to . - IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList mods = null, CancellationToken? cancellationToken = null); + IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, params Mod[] mods); + + /// + /// Constructs a playable from using the applicable converters for a specific . + /// + /// The returned is in a playable state - all and s + /// have been applied, and s have been fully constructed. + /// + /// + /// The to create a playable for. + /// The s to apply to the . + /// Cancellation token that cancels the beatmap loading process. + /// The converted . + /// If could not be converted to . + IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList mods, CancellationToken cancellationToken); /// /// Load a new audio track instance for this beatmap. This should be called once before accessing . diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index d2912229c6..a6f9f4e5f5 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -79,14 +79,24 @@ namespace osu.Game.Beatmaps /// The applicable . protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap); - public virtual IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList mods = null, CancellationToken? cancellationToken = null) + public IBeatmap GetPlayableBeatmap([NotNull] IRulesetInfo ruleset, params Mod[] mods) { - var token = cancellationToken ?? - // don't apply the default timeout when debugger is attached (may be breakpointing / debugging). - (Debugger.IsAttached ? new CancellationToken() : new CancellationTokenSource(10000).Token); - - mods ??= Array.Empty(); + try + { + using (var cancellationTokenSource = new CancellationTokenSource(10_000)) + { + // don't apply the default timeout when debugger is attached (may be breakpointing / debugging). + return GetPlayableBeatmap(ruleset, mods, Debugger.IsAttached ? new CancellationToken() : cancellationTokenSource.Token); + } + } + catch (OperationCanceledException) + { + throw new BeatmapLoadTimeoutException(BeatmapInfo); + } + } + public virtual IBeatmap GetPlayableBeatmap([NotNull] IRulesetInfo ruleset, [NotNull] IReadOnlyList mods, CancellationToken token) + { var rulesetInstance = ruleset.CreateInstance(); if (rulesetInstance == null) @@ -101,9 +111,7 @@ namespace osu.Game.Beatmaps // Apply conversion mods foreach (var mod in mods.OfType()) { - if (token.IsCancellationRequested) - throw new BeatmapLoadTimeoutException(BeatmapInfo); - + token.ThrowIfCancellationRequested(); mod.ApplyToBeatmapConverter(converter); } @@ -113,9 +121,7 @@ namespace osu.Game.Beatmaps // Apply conversion mods to the result foreach (var mod in mods.OfType()) { - if (token.IsCancellationRequested) - throw new BeatmapLoadTimeoutException(BeatmapInfo); - + token.ThrowIfCancellationRequested(); mod.ApplyToBeatmap(converted); } @@ -124,9 +130,7 @@ namespace osu.Game.Beatmaps { foreach (var mod in mods.OfType()) { - if (token.IsCancellationRequested) - throw new BeatmapLoadTimeoutException(BeatmapInfo); - + token.ThrowIfCancellationRequested(); mod.ApplyToDifficulty(converted.Difficulty); } } @@ -139,28 +143,17 @@ namespace osu.Game.Beatmaps processor?.PreProcess(); // Compute default values for hitobjects, including creating nested hitobjects in-case they're needed - try + foreach (var obj in converted.HitObjects) { - foreach (var obj in converted.HitObjects) - { - if (token.IsCancellationRequested) - throw new BeatmapLoadTimeoutException(BeatmapInfo); - - obj.ApplyDefaults(converted.ControlPointInfo, converted.Difficulty, token); - } - } - catch (OperationCanceledException) - { - throw new BeatmapLoadTimeoutException(BeatmapInfo); + token.ThrowIfCancellationRequested(); + obj.ApplyDefaults(converted.ControlPointInfo, converted.Difficulty, token); } foreach (var mod in mods.OfType()) { foreach (var obj in converted.HitObjects) { - if (token.IsCancellationRequested) - throw new BeatmapLoadTimeoutException(BeatmapInfo); - + token.ThrowIfCancellationRequested(); mod.ApplyToHitObject(obj); } } diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 9cb5d3cd91..854f255269 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -216,7 +216,7 @@ namespace osu.Game.Screens.Play.HUD this.gameplayBeatmap = gameplayBeatmap; } - public override IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList mods = null, CancellationToken? cancellationToken = null) + public override IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList mods, CancellationToken cancellationToken) => gameplayBeatmap; protected override IBeatmap GetBeatmap() => gameplayBeatmap; From a7e45a9098209000a179861ad4c77531f996e450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 20 Nov 2021 17:32:40 +0100 Subject: [PATCH 0136/4398] Log all non-cancellation errors in difficulty cache --- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 9106408e23..f444b597c9 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -294,15 +294,22 @@ namespace osu.Game.Beatmaps return new StarDifficulty(attributes); } - catch (BeatmapInvalidForRulesetException e) + catch (OperationCanceledException) + { + // no need to log, cancellations are expected as part of normal operation. + return null; + } + catch (BeatmapInvalidForRulesetException invalidForRuleset) { if (rulesetInfo.Equals(beatmapInfo.Ruleset)) - Logger.Error(e, $"Failed to convert {beatmapInfo.OnlineID} to the beatmap's default ruleset ({beatmapInfo.Ruleset})."); + Logger.Error(invalidForRuleset, $"Failed to convert {beatmapInfo.OnlineID} to the beatmap's default ruleset ({beatmapInfo.Ruleset})."); return null; } - catch + catch (Exception unknownException) { + Logger.Error(unknownException, "Failed to calculate beatmap difficulty"); + return null; } } From b3606f4a21334bea4fab2b5e22a31de52759b24c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 20 Nov 2021 18:36:39 +0100 Subject: [PATCH 0137/4398] Rename `{Play -> GoToBeatmap}Button` --- osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs | 3 +-- .../Cards/Buttons/{PlayButton.cs => GoToBeatmapButton.cs} | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) rename osu.Game/Beatmaps/Drawables/Cards/Buttons/{PlayButton.cs => GoToBeatmapButton.cs} (92%) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index e48b21cdb6..48f8e7b9c5 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -25,7 +25,6 @@ using osu.Game.Overlays.BeatmapListing.Panels; using osu.Game.Resources.Localisation.Web; using osuTK.Graphics; using DownloadButton = osu.Game.Beatmaps.Drawables.Cards.Buttons.DownloadButton; -using PlayButton = osu.Game.Beatmaps.Drawables.Cards.Buttons.PlayButton; namespace osu.Game.Beatmaps.Drawables.Cards { @@ -144,7 +143,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre }, - new PlayButton(beatmapSet) + new GoToBeatmapButton(beatmapSet) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/GoToBeatmapButton.cs similarity index 92% rename from osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs rename to osu.Game/Beatmaps/Drawables/Cards/Buttons/GoToBeatmapButton.cs index be964e306a..793c02d9c4 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/GoToBeatmapButton.cs @@ -12,12 +12,12 @@ using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { - public class PlayButton : BeatmapCardIconButton + public class GoToBeatmapButton : BeatmapCardIconButton { private readonly APIBeatmapSet beatmapSet; private readonly Bindable downloadState = new Bindable(); - public PlayButton(APIBeatmapSet beatmapSet) + public GoToBeatmapButton(APIBeatmapSet beatmapSet) { this.beatmapSet = beatmapSet; From 297de27a6a6d1533afcf4f9d565a1bd40b70bfa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 20 Nov 2021 18:45:24 +0100 Subject: [PATCH 0138/4398] Use `BindTarget` from card rather than caching & resolving download tracker --- .../TestSceneBeatmapCardDownloadButton.cs | 21 ++++--------------- .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 10 ++++++--- .../Cards/BeatmapCardDownloadProgressBar.cs | 14 ++++++------- .../Drawables/Cards/Buttons/DownloadButton.cs | 11 +++++----- .../Cards/Buttons/GoToBeatmapButton.cs | 12 +++++------ 5 files changed, 30 insertions(+), 38 deletions(-) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs index d93209efe9..068d2bdcbd 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs @@ -1,14 +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; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables.Cards.Buttons; using osu.Game.Configuration; -using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Resources.Localisation.Web; @@ -54,22 +52,11 @@ namespace osu.Game.Tests.Visual.Beatmaps { AddStep("create button", () => { - var beatmapSet = downloadable ? getDownloadableBeatmapSet(hasVideo) : getUndownloadableBeatmapSet(); - var downloadTracker = new BeatmapDownloadTracker(beatmapSet); - - Child = new DependencyProvidingContainer + Child = downloadButton = new DownloadButton(downloadable ? getDownloadableBeatmapSet(hasVideo) : getUndownloadableBeatmapSet()) { - RelativeSizeAxes = Axes.Both, - CachedDependencies = new (Type, object)[] - { - (typeof(BeatmapDownloadTracker), downloadTracker) - }, - Child = downloadButton = new DownloadButton(downloadable ? getDownloadableBeatmapSet(hasVideo) : getUndownloadableBeatmapSet()) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(2) - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(2) }; }); } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 48f8e7b9c5..b55fb91e92 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -141,12 +141,14 @@ namespace osu.Game.Beatmaps.Drawables.Cards new DownloadButton(beatmapSet) { Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre + Origin = Anchor.BottomCentre, + State = { BindTarget = downloadTracker.State } }, new GoToBeatmapButton(beatmapSet) { Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre + Origin = Anchor.BottomCentre, + State = { BindTarget = downloadTracker.State } } } } @@ -307,7 +309,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards RelativeSizeAxes = Axes.X, Height = 6, Anchor = Anchor.Centre, - Origin = Anchor.Centre + Origin = Anchor.Centre, + State = { BindTarget = downloadTracker.State }, + Progress = { BindTarget = downloadTracker.Progress } } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs index a94900b0c7..ffb4e0c540 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs @@ -14,6 +14,12 @@ namespace osu.Game.Beatmaps.Drawables.Cards { public class BeatmapCardDownloadProgressBar : CompositeDrawable { + public IBindable State => state; + private readonly Bindable state = new Bindable(); + + public IBindable Progress => progress; + private readonly BindableDouble progress = new BindableDouble(); + public override bool IsPresent => true; private readonly CircularContainer foreground; @@ -21,9 +27,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards private readonly Box backgroundFill; private readonly Box foregroundFill; - private readonly Bindable state = new Bindable(); - private readonly BindableDouble progress = new BindableDouble(); - [Resolved] private OsuColour colours { get; set; } @@ -56,12 +59,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards } [BackgroundDependencyLoader] - private void load(BeatmapDownloadTracker downloadTracker) + private void load() { backgroundFill.Colour = colourProvider.Background6; - - ((IBindable)state).BindTo(downloadTracker.State); - ((IBindable)progress).BindTo(downloadTracker.Progress); } protected override void LoadComplete() diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs index 325181598d..7430fce1c8 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs @@ -16,8 +16,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { public class DownloadButton : BeatmapCardIconButton { + public IBindable State => state; + private readonly Bindable state = new Bindable(); + private readonly APIBeatmapSet beatmapSet; - private readonly Bindable downloadState = new Bindable(); private Bindable preferNoVideo = null!; @@ -32,23 +34,22 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons } [BackgroundDependencyLoader] - private void load(OsuConfigManager config, BeatmapDownloadTracker downloadTracker) + private void load(OsuConfigManager config) { preferNoVideo = config.GetBindable(OsuSetting.PreferNoVideo); - ((IBindable)downloadState).BindTo(downloadTracker.State); } protected override void LoadComplete() { base.LoadComplete(); preferNoVideo.BindValueChanged(_ => updateState()); - downloadState.BindValueChanged(_ => updateState(), true); + state.BindValueChanged(_ => updateState(), true); FinishTransforms(true); } private void updateState() { - this.FadeTo(downloadState.Value != DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + this.FadeTo(state.Value != DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); if (beatmapSet.Availability.DownloadDisabled) { diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/GoToBeatmapButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/GoToBeatmapButton.cs index 793c02d9c4..9a6a3c01b7 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/GoToBeatmapButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/GoToBeatmapButton.cs @@ -14,8 +14,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { public class GoToBeatmapButton : BeatmapCardIconButton { + public IBindable State => state; + private readonly Bindable state = new Bindable(); + private readonly APIBeatmapSet beatmapSet; - private readonly Bindable downloadState = new Bindable(); public GoToBeatmapButton(APIBeatmapSet beatmapSet) { @@ -26,24 +28,22 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons } [BackgroundDependencyLoader(true)] - private void load(OsuGame? game, BeatmapDownloadTracker downloadTracker) + private void load(OsuGame? game) { Action = () => game?.PresentBeatmap(beatmapSet); - - ((IBindable)downloadState).BindTo(downloadTracker.State); } protected override void LoadComplete() { base.LoadComplete(); - downloadState.BindValueChanged(_ => updateState(), true); + state.BindValueChanged(_ => updateState(), true); FinishTransforms(true); } private void updateState() { - this.FadeTo(downloadState.Value == DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + this.FadeTo(state.Value == DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); } } } From 915bde6f9638dd7ece25f1e5cbb51dbc4cacd9e5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 21 Nov 2021 06:09:45 +0300 Subject: [PATCH 0139/4398] Remove unnecessary `MatchServerEvent` mapping --- osu.Game/Online/SignalRWorkaroundTypes.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Online/SignalRWorkaroundTypes.cs b/osu.Game/Online/SignalRWorkaroundTypes.cs index ae2d8b997c..ae1d9f9c6e 100644 --- a/osu.Game/Online/SignalRWorkaroundTypes.cs +++ b/osu.Game/Online/SignalRWorkaroundTypes.cs @@ -20,7 +20,6 @@ namespace osu.Game.Online (typeof(ChangeTeamRequest), typeof(MatchUserRequest)), (typeof(TeamVersusRoomState), typeof(MatchRoomState)), (typeof(TeamVersusUserState), typeof(MatchUserState)), - (typeof(MatchServerEvent), typeof(MatchServerEvent)), }; } } From 9f688f629152679a2dc18d14a83d0496d2e638a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 21 Nov 2021 12:12:24 +0900 Subject: [PATCH 0140/4398] Stop persisting `Skill`s in `DifficultyAttributes` --- .../EmptyFreeformDifficultyCalculator.cs | 2 +- .../PippidonDifficultyCalculator.cs | 2 +- .../EmptyScrollingDifficultyCalculator.cs | 2 +- .../PippidonDifficultyCalculator.cs | 2 +- .../Difficulty/CatchDifficultyCalculator.cs | 3 +-- .../Difficulty/ManiaDifficultyCalculator.cs | 3 +-- .../Difficulty/OsuDifficultyCalculator.cs | 3 +-- .../Difficulty/TaikoDifficultyCalculator.cs | 3 +-- osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs | 10 +--------- 9 files changed, 9 insertions(+), 21 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformDifficultyCalculator.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformDifficultyCalculator.cs index 79be2b27da..fae3784f5e 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformDifficultyCalculator.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformDifficultyCalculator.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.EmptyFreeform protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { - return new DifficultyAttributes(mods, skills, 0); + return new DifficultyAttributes(mods, 0); } protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty(); diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs index c612512938..ca64636076 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Pippidon protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { - return new DifficultyAttributes(mods, skills, 0); + return new DifficultyAttributes(mods, 0); } protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty(); diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingDifficultyCalculator.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingDifficultyCalculator.cs index 4628e6696b..63a8b48b3c 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingDifficultyCalculator.cs +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingDifficultyCalculator.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.EmptyScrolling protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { - return new DifficultyAttributes(mods, skills, 0); + return new DifficultyAttributes(mods, 0); } protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty(); diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs index c612512938..ca64636076 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Pippidon protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { - return new DifficultyAttributes(mods, skills, 0); + return new DifficultyAttributes(mods, 0); } protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty(); diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 77a783a10d..0054047573 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { if (beatmap.HitObjects.Count == 0) - return new CatchDifficultyAttributes { Mods = mods, Skills = skills }; + return new CatchDifficultyAttributes { Mods = mods }; // this is the same as osu!, so there's potential to share the implementation... maybe double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; @@ -42,7 +42,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty Mods = mods, ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0, MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)), - Skills = skills }; } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 0040df72aa..1f82eb7ccd 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { if (beatmap.HitObjects.Count == 0) - return new ManiaDifficultyAttributes { Mods = mods, Skills = skills }; + return new ManiaDifficultyAttributes { Mods = mods }; HitWindows hitWindows = new ManiaHitWindows(); hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty); @@ -51,7 +51,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty GreatHitWindow = Math.Ceiling(getHitWindow300(mods) / clockRate), ScoreMultiplier = getScoreMultiplier(mods), MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1), - Skills = skills }; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 3b4e85d0d3..ed42f333c0 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { if (beatmap.HitObjects.Count == 0) - return new OsuDifficultyAttributes { Mods = mods, Skills = skills }; + return new OsuDifficultyAttributes { Mods = mods }; double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; @@ -85,7 +85,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty HitCircleCount = hitCirclesCount, SliderCount = sliderCount, SpinnerCount = spinnerCount, - Skills = skills }; } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 606afdbabc..e84bee3d28 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { if (beatmap.HitObjects.Count == 0) - return new TaikoDifficultyAttributes { Mods = mods, Skills = skills }; + return new TaikoDifficultyAttributes { Mods = mods }; var colour = (Colour)skills[0]; var rhythm = (Rhythm)skills[1]; @@ -96,7 +96,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty ColourStrain = colourRating, GreatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate, MaxCombo = beatmap.HitObjects.Count(h => h is Hit), - Skills = skills }; } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs index e0d27799ce..47736ee033 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; -using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Difficulty @@ -31,11 +30,6 @@ namespace osu.Game.Rulesets.Difficulty /// public Mod[] Mods { get; set; } - /// - /// The skills resulting from the difficulty calculation. - /// - public Skill[] Skills { get; set; } - /// /// The combined star rating of all skill. /// @@ -59,12 +53,10 @@ namespace osu.Game.Rulesets.Difficulty /// Creates new . /// /// The mods which were applied to the beatmap. - /// The skills resulting from the difficulty calculation. /// The combined star rating of all skills. - public DifficultyAttributes(Mod[] mods, Skill[] skills, double starRating) + public DifficultyAttributes(Mod[] mods, double starRating) { Mods = mods; - Skills = skills; StarRating = starRating; } From fd3e5d333edc33408838fddab8b36114e83e096b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 21 Nov 2021 12:12:38 +0900 Subject: [PATCH 0141/4398] Add xmldoc note about `Skill` potentially being memory expensive --- osu.Game/Rulesets/Difficulty/Skills/Skill.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs index 9f0fb987a7..b5c94ad806 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs @@ -11,6 +11,10 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// /// A bare minimal abstract skill for fully custom skill implementations. /// + /// + /// This class should be considered a "processing" class and not persisted, as it keeps references to + /// gameplay objects after processing is run (see ). + /// public abstract class Skill { /// From af01b0ed4825547706c80e1b1dc7e41078635d83 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 21 Nov 2021 06:30:14 +0300 Subject: [PATCH 0142/4398] `BASE_DERIVED` -> `BASE_TYPE_MAPPING` --- .../Online/SignalRDerivedTypeWorkaroundJsonConverter.cs | 4 ++-- osu.Game/Online/SignalRUnionWorkaroundResolver.cs | 6 +++--- osu.Game/Online/SignalRWorkaroundTypes.cs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/SignalRDerivedTypeWorkaroundJsonConverter.cs b/osu.Game/Online/SignalRDerivedTypeWorkaroundJsonConverter.cs index 5643c5ca74..ab04e046d3 100644 --- a/osu.Game/Online/SignalRDerivedTypeWorkaroundJsonConverter.cs +++ b/osu.Game/Online/SignalRDerivedTypeWorkaroundJsonConverter.cs @@ -17,7 +17,7 @@ namespace osu.Game.Online public class SignalRDerivedTypeWorkaroundJsonConverter : JsonConverter { public override bool CanConvert(Type objectType) => - SignalRWorkaroundTypes.BASE_DERIVED.Any(t => + SignalRWorkaroundTypes.BASE_TYPE_MAPPING.Any(t => objectType == t.baseType || objectType == t.derivedType); @@ -30,7 +30,7 @@ namespace osu.Game.Online string type = (string)obj[@"$dtype"]!; - var resolvedType = SignalRWorkaroundTypes.BASE_DERIVED.Select(t => t.derivedType).Single(t => t.Name == type); + var resolvedType = SignalRWorkaroundTypes.BASE_TYPE_MAPPING.Select(t => t.derivedType).Single(t => t.Name == type); object? instance = Activator.CreateInstance(resolvedType); diff --git a/osu.Game/Online/SignalRUnionWorkaroundResolver.cs b/osu.Game/Online/SignalRUnionWorkaroundResolver.cs index 01e3c24c87..e64f9ed91c 100644 --- a/osu.Game/Online/SignalRUnionWorkaroundResolver.cs +++ b/osu.Game/Online/SignalRUnionWorkaroundResolver.cs @@ -23,13 +23,13 @@ namespace osu.Game.Online private static IReadOnlyDictionary createFormatterMap() { - IEnumerable<(Type derivedType, Type baseType)> baseDerived = SignalRWorkaroundTypes.BASE_DERIVED; + IEnumerable<(Type derivedType, Type baseType)> baseMap = SignalRWorkaroundTypes.BASE_TYPE_MAPPING; // 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. - baseDerived = baseDerived.Concat(baseDerived.Select(t => (t.baseType, t.baseType))).Distinct(); + baseMap = baseMap.Concat(baseMap.Select(t => (t.baseType, t.baseType))); - return new Dictionary(baseDerived.Select(t => + return new Dictionary(baseMap.Select(t => { var formatter = (IMessagePackFormatter)Activator.CreateInstance(typeof(TypeRedirectingFormatter<,>).MakeGenericType(t.derivedType, t.baseType)); return new KeyValuePair(t.derivedType, formatter); diff --git a/osu.Game/Online/SignalRWorkaroundTypes.cs b/osu.Game/Online/SignalRWorkaroundTypes.cs index ae1d9f9c6e..f69d23d81c 100644 --- a/osu.Game/Online/SignalRWorkaroundTypes.cs +++ b/osu.Game/Online/SignalRWorkaroundTypes.cs @@ -15,7 +15,7 @@ namespace osu.Game.Online /// internal static class SignalRWorkaroundTypes { - internal static readonly IReadOnlyList<(Type derivedType, Type baseType)> BASE_DERIVED = new[] + internal static readonly IReadOnlyList<(Type derivedType, Type baseType)> BASE_TYPE_MAPPING = new[] { (typeof(ChangeTeamRequest), typeof(MatchUserRequest)), (typeof(TeamVersusRoomState), typeof(MatchRoomState)), From bb8e8bc4f084e19a536b2eb4239f97d41afbe3e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 21 Nov 2021 11:30:45 +0100 Subject: [PATCH 0143/4398] Use consistent type for mod collection in all overloads --- osu.Game/Beatmaps/IWorkingBeatmap.cs | 2 +- osu.Game/Beatmaps/WorkingBeatmap.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index 6cebce3c59..717162fd7f 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -100,7 +100,7 @@ namespace osu.Game.Beatmaps /// The s to apply to the . /// The converted . /// If could not be converted to . - IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, params Mod[] mods); + IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList mods = null); /// /// Constructs a playable from using the applicable converters for a specific . diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index a6f9f4e5f5..a17f247d9b 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -79,14 +79,14 @@ namespace osu.Game.Beatmaps /// The applicable . protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap); - public IBeatmap GetPlayableBeatmap([NotNull] IRulesetInfo ruleset, params Mod[] mods) + public IBeatmap GetPlayableBeatmap([NotNull] IRulesetInfo ruleset, IReadOnlyList mods = null) { try { using (var cancellationTokenSource = new CancellationTokenSource(10_000)) { // don't apply the default timeout when debugger is attached (may be breakpointing / debugging). - return GetPlayableBeatmap(ruleset, mods, Debugger.IsAttached ? new CancellationToken() : cancellationTokenSource.Token); + return GetPlayableBeatmap(ruleset, mods ?? Array.Empty(), Debugger.IsAttached ? new CancellationToken() : cancellationTokenSource.Token); } } catch (OperationCanceledException) From 975744d26c881940f65d980ce421c4091715fab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 21 Nov 2021 13:19:16 +0100 Subject: [PATCH 0144/4398] Remove no longer used `[Cached]` attribute --- osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index b55fb91e92..e3af253db9 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -40,7 +40,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards private readonly APIBeatmapSet beatmapSet; private readonly Bindable favouriteState; - [Cached] private readonly BeatmapDownloadTracker downloadTracker; private UpdateableOnlineBeatmapSetCover leftCover; From afbec941249b1d309ed31be7fcd0e84e180805f6 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Sun, 21 Nov 2021 23:40:15 +1100 Subject: [PATCH 0145/4398] 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 0146/4398] 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 0147/4398] 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 0148/4398] 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 0149/4398] 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 0150/4398] 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 7560d3de0435408c62d7e8ad9fbedfcb1e211747 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Mon, 22 Nov 2021 10:52:04 +1100 Subject: [PATCH 0151/4398] Remove decay factor in Flashlight skill --- 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 466f0556ab..68434fd3d0 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills { } - private double skillMultiplier => 0.15; + 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. @@ -58,7 +58,7 @@ 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; + result += stackNerf * scalingFactor * jumpDistance / cumulativeStrainTime; } } From 642097166033bbcce457b09668e5c6fcdd51d5ce Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 22 Nov 2021 11:10:08 +0900 Subject: [PATCH 0152/4398] Adjust test client with new queue-changing logic --- .../Multiplayer/TestMultiplayerClient.cs | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 4deeef331d..20226bdb4f 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -370,25 +370,9 @@ namespace osu.Game.Tests.Visual.Multiplayer Debug.Assert(APIRoom != null); Debug.Assert(currentItem != null); - if (newMode == QueueMode.HostOnly) - { - // Remove all but the current and expired items. The current item may be re-used for host-only mode if it's non-expired. - for (int i = 0; i < Room.Playlist.Count; i++) - { - var item = Room.Playlist[i]; - - if (item.Expired || item.ID == Room.Settings.PlaylistItemId) - continue; - - Room.Playlist.RemoveAt(i--); - - await ((IMultiplayerClient)this).PlaylistItemRemoved(item.ID).ConfigureAwait(false); - } - - // Always ensure that at least one non-expired item exists by duplicating the current item if required. - if (currentItem.Expired) - await duplicateCurrentItem().ConfigureAwait(false); - } + // When changing to host-only mode, ensure that at least one non-expired playlist item exists by duplicating the current item. + if (newMode == QueueMode.HostOnly && Room.Playlist.All(item => item.Expired)) + await duplicateCurrentItem().ConfigureAwait(false); // When changing modes, items could have been added (above) or the queueing order could have changed. await updateCurrentItem(Room).ConfigureAwait(false); From 53dbbd6d64e02f84597816347f9fb97acaf9fd82 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 22 Nov 2021 11:16:29 +0900 Subject: [PATCH 0153/4398] Compare playlist item IDs instead in tests --- .../Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs | 10 +++++----- .../Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index 05cfd784eb..8e44c07a09 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestFirstItemSelectedByDefault() { - AddAssert("first item selected", () => Client.CurrentMatchPlayingItem.Value == Client.APIRoom?.Playlist[0]); + AddAssert("first item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID); } [Test] @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("playlist has 3 items", () => Client.APIRoom?.Playlist.Count == 3); AddAssert("last playlist item is different", () => Client.APIRoom?.Playlist[2].Beatmap.Value.OnlineID == InitialBeatmap.OnlineID); - AddAssert("first item still selected", () => Client.CurrentMatchPlayingItem.Value == Client.APIRoom?.Playlist[0]); + AddAssert("first item still selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID); } [Test] @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Multiplayer 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.CurrentMatchPlayingItem.Value == Client.APIRoom?.Playlist[0]); + AddAssert("last item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID); } [Test] @@ -55,12 +55,12 @@ namespace osu.Game.Tests.Visual.Multiplayer RunGameplay(); AddAssert("first item expired", () => Client.APIRoom?.Playlist[0].Expired == true); - AddAssert("next item selected", () => Client.CurrentMatchPlayingItem.Value == Client.APIRoom?.Playlist[1]); + AddAssert("next item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[1].ID); RunGameplay(); AddAssert("second item expired", () => Client.APIRoom?.Playlist[1].Expired == true); - AddAssert("next item selected", () => Client.CurrentMatchPlayingItem.Value == Client.APIRoom?.Playlist[2]); + AddAssert("next item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[2].ID); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index 2c60a98014..507292652f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestFirstItemSelectedByDefault() { - AddAssert("first item selected", () => Client.CurrentMatchPlayingItem.Value == Client.APIRoom?.Playlist[0]); + AddAssert("first item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID); } [Test] @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { selectNewItem(() => InitialBeatmap); - AddAssert("playlist item still selected", () => Client.CurrentMatchPlayingItem.Value == Client.APIRoom?.Playlist[0]); + AddAssert("playlist item still selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID); } [Test] @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { selectNewItem(() => OtherBeatmap); - AddAssert("playlist item still selected", () => Client.CurrentMatchPlayingItem.Value == Client.APIRoom?.Playlist[0]); + AddAssert("playlist item still selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID); } [Test] @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Multiplayer 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.CurrentMatchPlayingItem.Value == Client.APIRoom?.Playlist[1]); + AddAssert("second playlist item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[1].ID); } [Test] From 10dc08a8553be586d6b1946f3900723918d3f4fa Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 22 Nov 2021 11:18:21 +0900 Subject: [PATCH 0154/4398] Store server-side playlist instead of mutating client-side version --- .../Multiplayer/TestMultiplayerClient.cs | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 20226bdb4f..6d50188161 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -43,6 +43,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private MultiplayerPlaylistItem? currentItem => Room?.Playlist[currentIndex]; private readonly TestMultiplayerRoomManager roomManager; + private readonly List serverSidePlaylist = new List(); private int currentIndex; public TestMultiplayerClient(TestMultiplayerRoomManager roomManager) @@ -162,6 +163,9 @@ namespace osu.Game.Tests.Visual.Multiplayer if (password != apiRoom.Password.Value) throw new InvalidOperationException("Invalid password."); + serverSidePlaylist.Clear(); + serverSidePlaylist.AddRange(apiRoom.Playlist.Select(item => new MultiplayerPlaylistItem(item))); + var localUser = new MultiplayerRoomUser(api.LocalUser.Value.Id) { User = api.LocalUser.Value @@ -176,7 +180,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Password = password, QueueMode = apiRoom.QueueMode.Value }, - Playlist = apiRoom.Playlist.Select(item => new MultiplayerPlaylistItem(item)).ToList(), + Playlist = serverSidePlaylist.ToList(), Users = { localUser }, Host = localUser }; @@ -306,7 +310,7 @@ namespace osu.Game.Tests.Visual.Multiplayer // In host-only mode, the current item is re-used. item.ID = currentItem.ID; - Room.Playlist[currentIndex] = item; + serverSidePlaylist[currentIndex] = item; await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); // Note: Unlike the server, this is the easiest way to update the current item at this point. @@ -314,9 +318,9 @@ namespace osu.Game.Tests.Visual.Multiplayer break; default: - item.ID = Room.Playlist.Last().ID + 1; + item.ID = serverSidePlaylist.Last().ID + 1; - Room.Playlist.Add(item); + serverSidePlaylist.Add(item); await ((IMultiplayerClient)this).PlaylistItemAdded(item).ConfigureAwait(false); await updateCurrentItem(Room).ConfigureAwait(false); @@ -371,7 +375,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Debug.Assert(currentItem != null); // When changing to host-only mode, ensure that at least one non-expired playlist item exists by duplicating the current item. - if (newMode == QueueMode.HostOnly && Room.Playlist.All(item => item.Expired)) + if (newMode == QueueMode.HostOnly && serverSidePlaylist.All(item => item.Expired)) await duplicateCurrentItem().ConfigureAwait(false); // When changing modes, items could have been added (above) or the queueing order could have changed. @@ -403,7 +407,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var newItem = new MultiplayerPlaylistItem { - ID = Room.Playlist.Last().ID + 1, + ID = serverSidePlaylist.Last().ID + 1, BeatmapID = currentItem.BeatmapID, BeatmapChecksum = currentItem.BeatmapChecksum, RulesetID = currentItem.RulesetID, @@ -411,7 +415,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AllowedMods = currentItem.AllowedMods }; - Room.Playlist.Add(newItem); + serverSidePlaylist.Add(newItem); await ((IMultiplayerClient)this).PlaylistItemAdded(newItem).ConfigureAwait(false); } @@ -423,7 +427,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { default: // Pick the single non-expired playlist item. - newItem = room.Playlist.FirstOrDefault(i => !i.Expired) ?? room.Playlist.Last(); + newItem = serverSidePlaylist.FirstOrDefault(i => !i.Expired) ?? serverSidePlaylist.Last(); break; case QueueMode.AllPlayersRoundRobin: @@ -431,7 +435,7 @@ namespace osu.Game.Tests.Visual.Multiplayer throw new NotImplementedException(); } - currentIndex = room.Playlist.IndexOf(newItem); + currentIndex = serverSidePlaylist.IndexOf(newItem); long lastItem = room.Settings.PlaylistItemId; room.Settings.PlaylistItemId = newItem.ID; From cad6d1d25d01dab516c1f395bf93e4bd4f6a4573 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 22 Nov 2021 11:20:18 +0900 Subject: [PATCH 0155/4398] Adjust test to match new logic --- .../Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index 8e44c07a09..dcb01b83cc 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - public void TestItemsClearedWhenSwitchToHostOnlyMode() + public void TestItemsNotClearedWhenSwitchToHostOnlyMode() { addItem(() => OtherBeatmap); addItem(() => InitialBeatmap); @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Multiplayer RunGameplay(); AddStep("change queue mode", () => Client.ChangeSettings(queueMode: QueueMode.HostOnly)); - AddAssert("playlist has 2 items", () => Client.APIRoom?.Playlist.Count == 2); + AddAssert("playlist has 3 items", () => Client.APIRoom?.Playlist.Count == 3); AddAssert("playlist item is the other beatmap", () => Client.CurrentMatchPlayingItem.Value?.BeatmapID == OtherBeatmap.OnlineID); AddAssert("playlist item is not expired", () => Client.APIRoom?.Playlist[1].Expired == false); } From f3ba62d2c210ff5e855289ec0c6843536f0f1c75 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 22 Nov 2021 11:26:41 +0900 Subject: [PATCH 0156/4398] Add xmldoc to serverSidePlaylist --- .../Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 6d50188161..b54e243bde 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -40,10 +40,14 @@ namespace osu.Game.Tests.Visual.Multiplayer [Resolved] private BeatmapManager beatmaps { get; set; } = null!; - private MultiplayerPlaylistItem? currentItem => Room?.Playlist[currentIndex]; - private readonly TestMultiplayerRoomManager roomManager; + + /// + /// Guaranteed up-to-date playlist. + /// private readonly List serverSidePlaylist = new List(); + + private MultiplayerPlaylistItem? currentItem => Room?.Playlist[currentIndex]; private int currentIndex; public TestMultiplayerClient(TestMultiplayerRoomManager roomManager) From f64d20ed718555029a6a77ee0e8a652451046ab7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 22 Nov 2021 13:46:01 +0900 Subject: [PATCH 0157/4398] Fix APIRoom queue mode not updated on change --- .../Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs | 11 +++++++++++ osu.Game/Online/Multiplayer/MultiplayerClient.cs | 1 + 2 files changed, 12 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index 507292652f..efa5c481ed 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -63,6 +63,17 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("second playlist item changed", () => Client.APIRoom?.Playlist[1].Beatmap.Value != firstBeatmap); } + [Test] + public void TestSettingsUpdatedWhenChangingQueueMode() + { + AddStep("change queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings + { + QueueMode = QueueMode.AllPlayers + })); + + AddUntilStep("api room updated", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + } + private void selectNewItem(Func beatmap) { AddStep("click edit button", () => diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index ad3c1f6781..bd32687c5e 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -697,6 +697,7 @@ namespace osu.Game.Online.Multiplayer Room.Settings = settings; APIRoom.Name.Value = Room.Settings.Name; APIRoom.Password.Value = Room.Settings.Password; + APIRoom.QueueMode.Value = Room.Settings.QueueMode; RoomUpdated?.Invoke(); CurrentMatchPlayingItem.Value = APIRoom.Playlist.SingleOrDefault(p => p.ID == settings.PlaylistItemId); From ff13a980770b8ac162eb6663b80556e07c2679e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Nov 2021 15:23:16 +0900 Subject: [PATCH 0158/4398] Reformat realm migrations list for legibility --- 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 42ae986921..e235effc64 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -33,10 +33,10 @@ namespace osu.Game.Database /// /// Version history: - /// 6 First tracked version (~20211018) - /// 7 Changed OnlineID fields to non-nullable to add indexing support (20211018) - /// 8 Rebind scroll adjust keys to not have control modifier (20211029) - /// 9 Converted BeatmapMetadata.Author from string to RealmUser (20211104) + /// 6 ~2021-10-18 First tracked version. + /// 7 2021-10-18 Changed OnlineID fields to non-nullable to add indexing support. + /// 8 2021-10-29 Rebind scroll adjust keys to not have control modifier. + /// 9 2021-11-04 Converted BeatmapMetadata.Author from string to RealmUser. /// private const int schema_version = 9; From 1cdfa6d9a098fd2db3725227c43bc64395fb1689 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Nov 2021 15:30:11 +0900 Subject: [PATCH 0159/4398] Add helper property to access a realm beatmap's beatmap file --- .../Database/BeatmapImporterTests.cs | 18 ++++++++++++++++++ osu.Game/Models/RealmBeatmap.cs | 3 +++ 2 files changed, 21 insertions(+) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 75eb4a043a..71e5e9081c 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -74,6 +74,24 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestAccessFileAfterImport() + { + RunTestWithRealmAsync(async (realmFactory, storage) => + { + using var importer = new BeatmapImporter(realmFactory, storage); + using var store = new RealmRulesetStore(realmFactory, storage); + + var imported = await LoadOszIntoStore(importer, realmFactory.Context); + + var beatmap = imported.Beatmaps.First(); + var file = beatmap.File; + + Assert.NotNull(file); + Assert.AreEqual(beatmap.Hash, file!.File.Hash); + }); + } + [Test] public void TestImportThenDelete() { diff --git a/osu.Game/Models/RealmBeatmap.cs b/osu.Game/Models/RealmBeatmap.cs index 1a25d55d04..d190eb0c3b 100644 --- a/osu.Game/Models/RealmBeatmap.cs +++ b/osu.Game/Models/RealmBeatmap.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.Testing; @@ -35,6 +36,8 @@ namespace osu.Game.Models public RealmBeatmapSet? BeatmapSet { get; set; } + public RealmNamedFileUsage? File => BeatmapSet?.Files.First(f => f.File.Hash == Hash); + public BeatmapSetOnlineStatus Status { get => (BeatmapSetOnlineStatus)StatusInt; From 0633f3bcfe23513d671c0d5dc7bf7efcb603ba44 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 22 Nov 2021 16:35:58 +0900 Subject: [PATCH 0160/4398] Add owner id to playlist items --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 1 + osu.Game/Online/Rooms/PlaylistItem.cs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index ad3c1f6781..df16fb3042 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -720,6 +720,7 @@ namespace osu.Game.Online.Multiplayer var playlistItem = new PlaylistItem { ID = item.ID, + OwnerID = item.OwnerID, Beatmap = { Value = beatmap }, Ruleset = { Value = ruleset }, Expired = item.Expired diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs index c889dc514b..a1480865b8 100644 --- a/osu.Game/Online/Rooms/PlaylistItem.cs +++ b/osu.Game/Online/Rooms/PlaylistItem.cs @@ -18,6 +18,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("id")] public long ID { get; set; } + [JsonProperty("owner_id")] + public int OwnerID { get; set; } + [JsonProperty("beatmap_id")] public int BeatmapID { get; set; } From b9923e53969e3a2ebd267de46086f5c1a7cada7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Nov 2021 16:39:17 +0900 Subject: [PATCH 0161/4398] Remove subclassing of `RulesetInfo` --- .../Gameplay/TestSceneReplayRecorder.cs | 4 +- .../Gameplay/TestSceneReplayRecording.cs | 4 +- .../Gameplay/TestSceneSpectatorPlayback.cs | 4 +- .../UserInterface/TestSceneModSettings.cs | 48 +++++++--------- osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 57 +++++++++---------- 5 files changed, 52 insertions(+), 65 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index 159d583fc0..dcc193669b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.Gameplay { new Drawable[] { - recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { Recorder = recorder = new TestReplayRecorder(new Score { @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Gameplay }, new Drawable[] { - playbackManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + playbackManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { ReplayInputHandler = new TestFramedReplayInputHandler(replay) { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs index 1d4245308d..3f7155f1e2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Gameplay { new Drawable[] { - recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { Recorder = new TestReplayRecorder(new Score { @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Gameplay }, new Drawable[] { - playbackManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + playbackManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { ReplayInputHandler = new TestFramedReplayInputHandler(replay) { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index ef870a32a9..5fbccd54c8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -109,7 +109,7 @@ namespace osu.Game.Tests.Visual.Gameplay { new Drawable[] { - recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { Recorder = recorder = new TestReplayRecorder { @@ -139,7 +139,7 @@ namespace osu.Game.Tests.Visual.Gameplay }, new Drawable[] { - playbackManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + playbackManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { Clock = new FramedClock(manualClock), ReplayInputHandler = replayHandler = new TestFramedReplayInputHandler(replay) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index 4bb5e29589..9a3083e8db 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.UserInterface public void SetUp() => Schedule(() => { SelectedMods.Value = Array.Empty(); - Ruleset.Value = new TestRulesetInfo(); + Ruleset.Value = CreateTestRulesetInfo(); }); [Test] @@ -170,40 +170,32 @@ namespace osu.Game.Tests.Visual.UserInterface ModSettingsContainer.Parent.Width = newWidth; } - public class TestRulesetInfo : RulesetInfo + public static RulesetInfo CreateTestRulesetInfo() => new TestCustomisableModRuleset().RulesetInfo; + + public class TestCustomisableModRuleset : Ruleset { - public override Ruleset CreateInstance() => new TestCustomisableModRuleset(); - - public TestRulesetInfo() + public override IEnumerable GetModsFor(ModType type) { - Available = true; - } - - public class TestCustomisableModRuleset : Ruleset - { - public override IEnumerable GetModsFor(ModType type) + if (type == ModType.Conversion) { - if (type == ModType.Conversion) + return new Mod[] { - return new Mod[] - { - new TestModCustomisable1(), - new TestModCustomisable2() - }; - } - - return Array.Empty(); + new TestModCustomisable1(), + new TestModCustomisable2() + }; } - 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; } = "test"; - public override string ShortName { get; } = "tst"; + return Array.Empty(); } + + 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; } = "test"; + public override string ShortName { get; } = "tst"; } private class TestModCustomisable1 : TestModCustomisable diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index fcb44c462d..9ea8517764 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -38,7 +38,7 @@ namespace osu.Game.Beatmaps CircleSize = 0, OverallDifficulty = 0, }, - Ruleset = new DummyRulesetInfo() + Ruleset = new DummyRuleset().RulesetInfo }, audio) { this.textures = textures; @@ -54,42 +54,37 @@ namespace osu.Game.Beatmaps public override Stream GetStream(string storagePath) => null; - private class DummyRulesetInfo : RulesetInfo + private class DummyRuleset : Ruleset { - public override Ruleset CreateInstance() => new DummyRuleset(); + public override IEnumerable GetModsFor(ModType type) => Array.Empty(); - private class DummyRuleset : Ruleset + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) { - public override IEnumerable GetModsFor(ModType type) => Array.Empty(); + throw new NotImplementedException(); + } - public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new DummyBeatmapConverter { Beatmap = beatmap }; + + public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null; + + public override string Description => "dummy"; + + public override string ShortName => "dummy"; + + private class DummyBeatmapConverter : IBeatmapConverter + { + public event Action> ObjectConverted; + + public IBeatmap Beatmap { get; set; } + + public bool CanConvert() => true; + + public IBeatmap Convert(CancellationToken cancellationToken = default) { - throw new NotImplementedException(); - } + foreach (var obj in Beatmap.HitObjects) + ObjectConverted?.Invoke(obj, obj.Yield()); - public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new DummyBeatmapConverter { Beatmap = beatmap }; - - public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null; - - public override string Description => "dummy"; - - public override string ShortName => "dummy"; - - private class DummyBeatmapConverter : IBeatmapConverter - { - public event Action> ObjectConverted; - - public IBeatmap Beatmap { get; set; } - - public bool CanConvert() => true; - - public IBeatmap Convert(CancellationToken cancellationToken = default) - { - foreach (var obj in Beatmap.HitObjects) - ObjectConverted?.Invoke(obj, obj.Yield()); - - return Beatmap; - } + return Beatmap; } } } From cb5b6911e6d3263e01f11f49258214cf256f705b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Nov 2021 16:39:26 +0900 Subject: [PATCH 0162/4398] Seal `RulesetInfo` and remove `virtual` methods --- osu.Game/Rulesets/RulesetInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 8083041a3b..ccb614fe91 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -10,7 +10,7 @@ using osu.Framework.Testing; namespace osu.Game.Rulesets { [ExcludeFromDynamicCompile] - public class RulesetInfo : IEquatable, IRulesetInfo + public sealed class RulesetInfo : IEquatable, IRulesetInfo { public int? ID { get; set; } @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets public bool Available { get; set; } // TODO: this should probably be moved to RulesetStore. - public virtual Ruleset CreateInstance() + public Ruleset CreateInstance() { if (!Available) return null; From a8bc1ab052dbc75aa9461cf65545d228b8706a95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Nov 2021 16:45:55 +0900 Subject: [PATCH 0163/4398] Attempt to fix fody issues by ignoring new property --- osu.Game/Models/RealmBeatmap.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Models/RealmBeatmap.cs b/osu.Game/Models/RealmBeatmap.cs index d190eb0c3b..4e3f96ee5f 100644 --- a/osu.Game/Models/RealmBeatmap.cs +++ b/osu.Game/Models/RealmBeatmap.cs @@ -36,6 +36,7 @@ namespace osu.Game.Models public RealmBeatmapSet? BeatmapSet { get; set; } + [Ignored] public RealmNamedFileUsage? File => BeatmapSet?.Files.First(f => f.File.Hash == Hash); public BeatmapSetOnlineStatus Status From 377ba2673aa1f6869e7bca74de95df2e99717365 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Nov 2021 16:52:54 +0900 Subject: [PATCH 0164/4398] Use `Ruleset`'s `ShortName` for mod caching purposes --- osu.Game/Rulesets/Ruleset.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index ade763eed8..a252bd4786 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets { public RulesetInfo RulesetInfo { get; internal set; } - private static readonly ConcurrentDictionary mod_reference_cache = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary mod_reference_cache = new ConcurrentDictionary(); /// /// A queryable source containing all available mods. @@ -49,11 +49,8 @@ namespace osu.Game.Rulesets { get { - if (!(RulesetInfo.ID is int id)) - return CreateAllMods(); - - if (!mod_reference_cache.TryGetValue(id, out var mods)) - mod_reference_cache[id] = mods = CreateAllMods().Cast().ToArray(); + if (!mod_reference_cache.TryGetValue(ShortName, out var mods)) + mod_reference_cache[ShortName] = mods = CreateAllMods().Cast().ToArray(); return mods; } From 001f7c36f36fe8151d422d45de86d7328acfaa72 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Nov 2021 17:15:26 +0900 Subject: [PATCH 0165/4398] Use `Equals` in carousel tests for better realm compatibility --- .../SongSelect/TestSceneBeatmapCarousel.cs | 25 ++++++++++--------- .../SongSelect/TestScenePlaySongSelect.cs | 8 +++--- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 534442c8b6..5c40a3dd94 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -78,9 +78,9 @@ namespace osu.Game.Tests.Visual.SongSelect { AddStep("store selection", () => selection = carousel.SelectedBeatmapInfo); if (isIterating) - AddUntilStep("selection changed", () => carousel.SelectedBeatmapInfo != selection); + AddUntilStep("selection changed", () => !carousel.SelectedBeatmapInfo.Equals(selection)); else - AddUntilStep("selection not changed", () => carousel.SelectedBeatmapInfo == selection); + AddUntilStep("selection not changed", () => carousel.SelectedBeatmapInfo.Equals(selection)); } } } @@ -412,7 +412,8 @@ namespace osu.Game.Tests.Visual.SongSelect for (int i = 0; i < 20; i++) { - var set = createTestBeatmapSet(i); + // index + 1 because we are using OnlineID which should never be zero. + var set = createTestBeatmapSet(i + 1); set.Metadata.Artist = "same artist"; set.Metadata.Title = "same title"; sets.Add(set); @@ -421,10 +422,10 @@ namespace osu.Game.Tests.Visual.SongSelect loadBeatmaps(sets); AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false)); - AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.ID == index).All(b => b)); + AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == index + 1).All(b => b)); AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false)); - AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.ID == index).All(b => b)); + AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == index + 1).All(b => b)); } [Test] @@ -443,12 +444,12 @@ namespace osu.Game.Tests.Visual.SongSelect loadBeatmaps(sets); AddStep("Filter to normal", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Normal" }, false)); - AddAssert("Check first set at end", () => carousel.BeatmapSets.First() == sets.Last()); - AddAssert("Check last set at start", () => carousel.BeatmapSets.Last() == sets.First()); + AddAssert("Check first set at end", () => carousel.BeatmapSets.First().Equals(sets.Last())); + AddAssert("Check last set at start", () => carousel.BeatmapSets.Last().Equals(sets.First())); AddStep("Filter to insane", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Insane" }, false)); - AddAssert("Check first set at start", () => carousel.BeatmapSets.First() == sets.First()); - AddAssert("Check last set at end", () => carousel.BeatmapSets.Last() == sets.Last()); + AddAssert("Check first set at start", () => carousel.BeatmapSets.First().Equals(sets.First())); + AddAssert("Check last set at end", () => carousel.BeatmapSets.Last().Equals(sets.Last())); } [Test] @@ -662,7 +663,7 @@ namespace osu.Game.Tests.Visual.SongSelect eagerSelectedIDs.Add(carousel.SelectedBeatmapSet.ID); }); - AddAssert("selection changed", () => carousel.SelectedBeatmapInfo != manySets.First().Beatmaps.First()); + AddAssert("selection changed", () => !carousel.SelectedBeatmapInfo.Equals(manySets.First().Beatmaps.First())); } AddAssert("Selection was random", () => eagerSelectedIDs.Count > 2); @@ -759,13 +760,13 @@ namespace osu.Game.Tests.Visual.SongSelect } private void ensureRandomFetchSuccess() => - AddAssert("ensure prev random fetch worked", () => selectedSets.Peek() == carousel.SelectedBeatmapSet); + AddAssert("ensure prev random fetch worked", () => selectedSets.Peek().Equals(carousel.SelectedBeatmapSet)); private void waitForSelection(int set, int? diff = null) => AddUntilStep($"selected is set{set}{(diff.HasValue ? $" diff{diff.Value}" : "")}", () => { if (diff != null) - return carousel.SelectedBeatmapInfo == carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff.Value - 1).First(); + return carousel.SelectedBeatmapInfo.Equals(carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff.Value - 1).First()); return carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Contains(carousel.SelectedBeatmapInfo); }); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 0437c1b25b..f09dc38378 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -148,7 +148,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select next and enter", () => { InputManager.MoveMouseTo(songSelect.Carousel.ChildrenOfType() - .First(b => ((CarouselBeatmap)b.Item).BeatmapInfo != songSelect.Carousel.SelectedBeatmapInfo)); + .First(b => !((CarouselBeatmap)b.Item).BeatmapInfo.Equals(songSelect.Carousel.SelectedBeatmapInfo))); InputManager.Click(MouseButton.Left); @@ -175,7 +175,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select next and enter", () => { InputManager.MoveMouseTo(songSelect.Carousel.ChildrenOfType() - .First(b => ((CarouselBeatmap)b.Item).BeatmapInfo != songSelect.Carousel.SelectedBeatmapInfo)); + .First(b => !((CarouselBeatmap)b.Item).BeatmapInfo.Equals(songSelect.Carousel.SelectedBeatmapInfo))); InputManager.PressButton(MouseButton.Left); @@ -644,7 +644,7 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.Click(MouseButton.Left); }); - AddAssert("Selected beatmap correct", () => songSelect.Carousel.SelectedBeatmapInfo == filteredBeatmap); + AddAssert("Selected beatmap correct", () => songSelect.Carousel.SelectedBeatmapInfo.Equals(filteredBeatmap)); } [Test] @@ -858,7 +858,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("wait for difficulty panels visible", () => songSelect.Carousel.ChildrenOfType().Any()); } - private int getBeatmapIndex(BeatmapSetInfo set, BeatmapInfo info) => set.Beatmaps.FindIndex(b => b == info); + private int getBeatmapIndex(BeatmapSetInfo set, BeatmapInfo info) => set.Beatmaps.IndexOf(info); private int getCurrentBeatmapIndex() => getBeatmapIndex(songSelect.Carousel.SelectedBeatmapSet, songSelect.Carousel.SelectedBeatmapInfo); From f1926c6d275c082f2f0fa8df20aa6e6090982f75 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Nov 2021 17:40:43 +0900 Subject: [PATCH 0166/4398] Store preferred ruleset to configuration using `ShortName` instead of `ID` --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/OsuGame.cs | 64 +++++++++++----------- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 1beef89b51..84da3f666d 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -26,7 +26,7 @@ namespace osu.Game.Configuration protected override void InitialiseDefaults() { // UI/selection defaults - SetDefault(OsuSetting.Ruleset, 0, 0, int.MaxValue); + SetDefault(OsuSetting.Ruleset, string.Empty); SetDefault(OsuSetting.Skin, 0, -1, int.MaxValue); SetDefault(OsuSetting.BeatmapDetailTab, PlayBeatmapDetailArea.TabType.Details); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1274d8d867..574a5e5393 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -4,59 +4,58 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using osu.Framework.Configuration; -using osu.Framework.Screens; -using osu.Game.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Overlays; -using osu.Framework.Logging; -using osu.Framework.Allocation; -using osu.Game.Overlays.Toolbar; -using osu.Game.Screens; -using osu.Game.Screens.Menu; using System.Linq; using System.Threading; using System.Threading.Tasks; using Humanizer; using JetBrains.Annotations; +using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; +using osu.Framework.Configuration; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Logging; +using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Collections; +using osu.Game.Configuration; +using osu.Game.Database; +using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input; -using osu.Game.Overlays.Notifications; using osu.Game.Input.Bindings; -using osu.Game.Online.Chat; -using osu.Game.Overlays.Music; -using osu.Game.Skinning; -using osuTK.Graphics; -using osu.Game.Overlays.Volume; -using osu.Game.Rulesets.Mods; -using osu.Game.Scoring; -using osu.Game.Screens.Play; -using osu.Game.Screens.Ranking; -using osu.Game.Screens.Select; -using osu.Game.Updater; -using osu.Game.Utils; -using LogLevel = osu.Framework.Logging.LogLevel; -using osu.Game.Database; -using osu.Game.Extensions; using osu.Game.IO; using osu.Game.Localisation; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Chat; +using osu.Game.Overlays; +using osu.Game.Overlays.Music; +using osu.Game.Overlays.Notifications; +using osu.Game.Overlays.Toolbar; +using osu.Game.Overlays.Volume; using osu.Game.Performance; +using osu.Game.Rulesets.Mods; +using osu.Game.Scoring; +using osu.Game.Screens; +using osu.Game.Screens.Menu; +using osu.Game.Screens.Play; +using osu.Game.Screens.Ranking; +using osu.Game.Screens.Select; +using osu.Game.Skinning; using osu.Game.Skinning.Editor; +using osu.Game.Updater; using osu.Game.Users; +using osu.Game.Utils; +using osuTK.Graphics; namespace osu.Game { @@ -158,7 +157,7 @@ namespace osu.Game [CanBeNull] private IntroScreen introScreen; - private Bindable configRuleset; + private Bindable configRuleset; private Bindable uiScale; @@ -222,10 +221,13 @@ namespace osu.Game dependencies.Cache(osuLogo = new OsuLogo { Alpha = 0 }); // bind config int to database RulesetInfo - configRuleset = LocalConfig.GetBindable(OsuSetting.Ruleset); + configRuleset = LocalConfig.GetBindable(OsuSetting.Ruleset); uiScale = LocalConfig.GetBindable(OsuSetting.UIScale); - var preferredRuleset = RulesetStore.GetRuleset(configRuleset.Value); + var preferredRuleset = int.TryParse(configRuleset.Value, out int rulesetId) + // int parsing can be removed 20220522 + ? RulesetStore.GetRuleset(rulesetId) + : RulesetStore.GetRuleset(configRuleset.Value); try { @@ -238,7 +240,7 @@ namespace osu.Game Ruleset.Value = RulesetStore.AvailableRulesets.First(); } - Ruleset.ValueChanged += r => configRuleset.Value = r.NewValue.ID ?? 0; + Ruleset.ValueChanged += r => configRuleset.Value = r.NewValue.ShortName; // bind config int to database SkinInfo configSkin = LocalConfig.GetBindable(OsuSetting.Skin); From 361cb7888071da026f7cacdc0486d88e05708598 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Nov 2021 18:37:28 +0900 Subject: [PATCH 0167/4398] Fix realm applying migrations from one version too early --- osu.Game/Database/RealmContextFactory.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 42ae986921..0bb46a379a 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -153,13 +153,13 @@ namespace osu.Game.Database private void onMigration(Migration migration, ulong lastSchemaVersion) { - for (ulong i = lastSchemaVersion; i <= schema_version; i++) + for (ulong i = lastSchemaVersion + 1; i <= schema_version; i++) applyMigrationsForVersion(migration, i); } - private void applyMigrationsForVersion(Migration migration, ulong version) + private void applyMigrationsForVersion(Migration migration, ulong targetVersion) { - switch (version) + switch (targetVersion) { case 7: convertOnlineIDs(); From d2062ff97fda0507ca1a1c12f853eb6713f548c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Nov 2021 15:23:16 +0900 Subject: [PATCH 0168/4398] Reformat realm migrations list for legibility --- 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 0bb46a379a..e773ea9767 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -33,10 +33,10 @@ namespace osu.Game.Database /// /// Version history: - /// 6 First tracked version (~20211018) - /// 7 Changed OnlineID fields to non-nullable to add indexing support (20211018) - /// 8 Rebind scroll adjust keys to not have control modifier (20211029) - /// 9 Converted BeatmapMetadata.Author from string to RealmUser (20211104) + /// 6 ~2021-10-18 First tracked version. + /// 7 2021-10-18 Changed OnlineID fields to non-nullable to add indexing support. + /// 8 2021-10-29 Rebind scroll adjust keys to not have control modifier. + /// 9 2021-11-04 Converted BeatmapMetadata.Author from string to RealmUser. /// private const int schema_version = 9; From ca26b6c5408309256ebbc4d89324cc52d472823a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Nov 2021 18:51:37 +0900 Subject: [PATCH 0169/4398] Provide `RealmContextFactory` with the EF `RulesetStore` for migration purposes --- osu.Game/Database/RealmContextFactory.cs | 6 +++++- osu.Game/OsuGameBase.cs | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index e773ea9767..1d0da3ea5c 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -13,6 +13,7 @@ using osu.Framework.Platform; using osu.Framework.Statistics; using osu.Game.Input.Bindings; using osu.Game.Models; +using osu.Game.Rulesets; using Realms; #nullable enable @@ -31,6 +32,8 @@ namespace osu.Game.Database /// public readonly string Filename; + private readonly RulesetStore? rulesets; + /// /// Version history: /// 6 ~2021-10-18 First tracked version. @@ -72,9 +75,10 @@ namespace osu.Game.Database } } - public RealmContextFactory(Storage storage, string filename) + public RealmContextFactory(Storage storage, string filename, RulesetStore? rulesets = null) { this.storage = storage; + this.rulesets = rulesets; Filename = filename; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index e207d9ce3b..3b2f397a72 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -187,8 +187,9 @@ namespace osu.Game Resources.AddStore(new DllResourceStore(OsuResources.ResourceAssembly)); dependencies.Cache(contextFactory = new DatabaseContextFactory(Storage)); + dependencies.Cache(RulesetStore = new RulesetStore(contextFactory, Storage)); - dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client")); + dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client", RulesetStore)); dependencies.CacheAs(Storage); @@ -227,7 +228,6 @@ namespace osu.Game var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures); - dependencies.Cache(RulesetStore = new RulesetStore(contextFactory, Storage)); dependencies.Cache(fileStore = new FileStore(contextFactory, Storage)); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() From 329bae50b0e99b3450fc84daa9f3a4828c433c11 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Nov 2021 18:07:28 +0900 Subject: [PATCH 0170/4398] Switch realm ruleset configuration to use ruleset's `ShortName` as key --- osu.Game/Configuration/RealmRulesetSetting.cs | 9 ++----- osu.Game/Database/RealmContextFactory.cs | 26 ++++++++++++++++++- osu.Game/OsuGameBase.cs | 3 ++- .../Configuration/RulesetConfigManager.cs | 13 ++++------ 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/osu.Game/Configuration/RealmRulesetSetting.cs b/osu.Game/Configuration/RealmRulesetSetting.cs index 07e56ad8dd..3fea35ee9d 100644 --- a/osu.Game/Configuration/RealmRulesetSetting.cs +++ b/osu.Game/Configuration/RealmRulesetSetting.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 osu.Game.Database; using Realms; #nullable enable @@ -10,13 +8,10 @@ using Realms; namespace osu.Game.Configuration { [MapTo(@"RulesetSetting")] - public class RealmRulesetSetting : RealmObject, IHasGuidPrimaryKey + public class RealmRulesetSetting : RealmObject { - [PrimaryKey] - public Guid ID { get; set; } = Guid.NewGuid(); - [Indexed] - public int RulesetID { get; set; } + public string RulesetName { get; set; } = string.Empty; [Indexed] public int Variant { get; set; } diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 1d0da3ea5c..70ea24b581 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -11,6 +11,7 @@ using osu.Framework.Input.Bindings; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Statistics; +using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Models; using osu.Game.Rulesets; @@ -40,8 +41,9 @@ namespace osu.Game.Database /// 7 2021-10-18 Changed OnlineID fields to non-nullable to add indexing support. /// 8 2021-10-29 Rebind scroll adjust keys to not have control modifier. /// 9 2021-11-04 Converted BeatmapMetadata.Author from string to RealmUser. + /// 10 2021-11-22 Use ShortName instead of RulesetID for ruleset settings. /// - private const int schema_version = 9; + private const int schema_version = 10; /// /// Lock object which is held during sections, blocking context creation during blocking periods. @@ -236,6 +238,28 @@ namespace osu.Game.Database }; } + break; + + case 10: + string rulesetSettingClassName = getMappedOrOriginalName(typeof(RealmRulesetSetting)); + + var oldSettings = migration.OldRealm.DynamicApi.All(rulesetSettingClassName); + var newSettings = migration.NewRealm.All().ToList(); + + for (int i = 0; i < newSettings.Count; i++) + { + dynamic? oldItem = oldSettings.ElementAt(i); + var newItem = newSettings.ElementAt(i); + + long rulesetId = oldItem.RulesetID; + string? rulesetName = rulesets?.GetRuleset((int)rulesetId)?.ShortName; + + if (string.IsNullOrEmpty(rulesetName)) + migration.NewRealm.Remove(newItem); + else + newItem.RulesetName = rulesetName; + } + break; } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 3b2f397a72..d8050353c5 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -456,7 +456,8 @@ namespace osu.Game { Key = dkb.Key, Value = dkb.StringValue, - RulesetID = dkb.RulesetID.Value, + // important: this RulesetStore must be the EF one. + RulesetName = RulesetStore.GetRuleset(dkb.RulesetID.Value).ShortName, Variant = dkb.Variant ?? 0, }); } diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index eec71a3623..17678775e9 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -20,16 +20,13 @@ namespace osu.Game.Rulesets.Configuration private List databasedSettings = new List(); - private readonly int rulesetId; + private readonly string rulesetName; protected RulesetConfigManager(SettingsStore store, RulesetInfo ruleset, int? variant = null) { realmFactory = store?.Realm; - if (realmFactory != null && !ruleset.ID.HasValue) - throw new InvalidOperationException("Attempted to add databased settings for a non-databased ruleset"); - - rulesetId = ruleset.ID ?? -1; + rulesetName = ruleset.ShortName; this.variant = variant ?? 0; @@ -43,7 +40,7 @@ namespace osu.Game.Rulesets.Configuration if (realmFactory != null) { // As long as RulesetConfigCache exists, there is no need to subscribe to realm events. - databasedSettings = realmFactory.Context.All().Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList(); + databasedSettings = realmFactory.Context.All().Where(b => b.RulesetName == rulesetName && b.Variant == variant).ToList(); } } @@ -68,7 +65,7 @@ namespace osu.Game.Rulesets.Configuration { foreach (var c in changed) { - var setting = realm.All().First(s => s.RulesetID == rulesetId && 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(); } @@ -94,7 +91,7 @@ namespace osu.Game.Rulesets.Configuration { Key = lookup.ToString(), Value = bindable.Value.ToString(), - RulesetID = rulesetId, + RulesetName = rulesetName, Variant = variant, }; From d94b27a8a22bae840d5a1eaac09468946ac8dec7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Nov 2021 18:34:04 +0900 Subject: [PATCH 0171/4398] Switch realm ruleset key bindings to use ruleset's `ShortName` as key --- osu.Game/Database/RealmContextFactory.cs | 30 ++++++++++++++++++- .../Bindings/DatabasedKeyBindingContainer.cs | 25 +++++++--------- osu.Game/Input/Bindings/RealmKeyBinding.cs | 6 ++-- osu.Game/Input/RealmKeyBindingStore.cs | 10 +++---- .../Settings/Sections/Input/KeyBindingRow.cs | 2 +- .../Sections/Input/KeyBindingsSubsection.cs | 4 +-- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 2 +- 7 files changed, 53 insertions(+), 26 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 70ea24b581..2a2c94ee7d 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -42,8 +42,9 @@ namespace osu.Game.Database /// 8 2021-10-29 Rebind scroll adjust keys to not have control modifier. /// 9 2021-11-04 Converted BeatmapMetadata.Author from string to RealmUser. /// 10 2021-11-22 Use ShortName instead of RulesetID for ruleset settings. + /// 11 2021-11-22 Use ShortName instead of RulesetID for ruleset key bindings. /// - private const int schema_version = 10; + private const int schema_version = 11; /// /// Lock object which is held during sections, blocking context creation during blocking periods. @@ -260,6 +261,33 @@ namespace osu.Game.Database newItem.RulesetName = rulesetName; } + break; + + case 11: + + string keyBindingClassName = getMappedOrOriginalName(typeof(RealmKeyBinding)); + + var oldKeyBindings = migration.OldRealm.DynamicApi.All(keyBindingClassName); + var newKeyBindings = migration.NewRealm.All().ToList(); + + for (int i = 0; i < newKeyBindings.Count; i++) + { + dynamic? oldItem = oldKeyBindings.ElementAt(i); + var newItem = newKeyBindings.ElementAt(i); + + if (oldItem.RulesetID == null) + continue; + + long rulesetId = oldItem.RulesetID; + + string? rulesetName = rulesets?.GetRuleset((int)rulesetId)?.ShortName; + + if (string.IsNullOrEmpty(rulesetName)) + migration.NewRealm.Remove(newItem); + else + newItem.RulesetName = rulesetName; + } + break; } } diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 5dced23614..baa5b9ff9c 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -50,23 +50,20 @@ namespace osu.Game.Input.Bindings protected override void LoadComplete() { - if (ruleset == null || ruleset.ID.HasValue) - { - int? rulesetId = ruleset?.ID; + string rulesetName = ruleset?.ShortName; - realmKeyBindings = realmFactory.Context.All() - .Where(b => b.RulesetID == rulesetId && b.Variant == variant); + realmKeyBindings = realmFactory.Context.All() + .Where(b => b.RulesetName == rulesetName && b.Variant == variant); - realmSubscription = realmKeyBindings - .SubscribeForNotifications((sender, changes, error) => - { - // first subscription ignored as we are handling this in LoadComplete. - if (changes == null) - return; + realmSubscription = realmKeyBindings + .SubscribeForNotifications((sender, changes, error) => + { + // first subscription ignored as we are handling this in LoadComplete. + if (changes == null) + return; - ReloadMappings(); - }); - } + ReloadMappings(); + }); base.LoadComplete(); } diff --git a/osu.Game/Input/Bindings/RealmKeyBinding.cs b/osu.Game/Input/Bindings/RealmKeyBinding.cs index 334d2da427..6a408847fe 100644 --- a/osu.Game/Input/Bindings/RealmKeyBinding.cs +++ b/osu.Game/Input/Bindings/RealmKeyBinding.cs @@ -6,6 +6,8 @@ using osu.Framework.Input.Bindings; using osu.Game.Database; using Realms; +#nullable enable + namespace osu.Game.Input.Bindings { [MapTo(nameof(KeyBinding))] @@ -14,7 +16,7 @@ namespace osu.Game.Input.Bindings [PrimaryKey] public Guid ID { get; set; } = Guid.NewGuid(); - public int? RulesetID { get; set; } + public string? RulesetName { get; set; } public int? Variant { get; set; } @@ -34,6 +36,6 @@ namespace osu.Game.Input.Bindings public int ActionInt { get; set; } [MapTo(nameof(KeyCombination))] - public string KeyCombinationString { get; set; } + public string KeyCombinationString { get; set; } = string.Empty; } } diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 046969579c..3bdb0a180d 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -36,7 +36,7 @@ namespace osu.Game.Input using (var context = realmFactory.CreateContext()) { - foreach (var action in context.All().Where(b => b.RulesetID == null && (GlobalAction)b.ActionInt == globalAction)) + foreach (var action in context.All().Where(b => string.IsNullOrEmpty(b.RulesetName) && (GlobalAction)b.ActionInt == globalAction)) { string str = keyCombinationProvider.GetReadableString(action.KeyCombination); @@ -69,20 +69,20 @@ namespace osu.Game.Input { var instance = ruleset.CreateInstance(); foreach (int variant in instance.AvailableVariants) - insertDefaults(realm, existingBindings, instance.GetDefaultKeyBindings(variant), ruleset.ID, variant); + insertDefaults(realm, existingBindings, instance.GetDefaultKeyBindings(variant), ruleset.ShortName, variant); } transaction.Commit(); } } - private void insertDefaults(Realm realm, List existingBindings, IEnumerable defaults, int? rulesetId = null, int? variant = null) + private void insertDefaults(Realm realm, List existingBindings, IEnumerable defaults, string? rulesetName = null, int? variant = null) { // compare counts in database vs defaults for each action type. foreach (var defaultsForAction in defaults.GroupBy(k => k.Action)) { // avoid performing redundant queries when the database is empty and needs to be re-filled. - int existingCount = existingBindings.Count(k => k.RulesetID == rulesetId && k.Variant == variant && k.ActionInt == (int)defaultsForAction.Key); + int existingCount = existingBindings.Count(k => k.RulesetName == rulesetName && k.Variant == variant && k.ActionInt == (int)defaultsForAction.Key); if (defaultsForAction.Count() <= existingCount) continue; @@ -92,7 +92,7 @@ namespace osu.Game.Input { KeyCombinationString = k.KeyCombination.ToString(), ActionInt = (int)k.Action, - RulesetID = rulesetId, + RulesetName = rulesetName, Variant = variant })); } diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index 96a685a9c5..e0a1a82326 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -524,7 +524,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input public void UpdateKeyCombination(KeyCombination newCombination) { - if (KeyBinding.RulesetID != null && !RealmKeyBindingStore.CheckValidForGameplay(newCombination)) + if (KeyBinding.RulesetName != null && !RealmKeyBindingStore.CheckValidForGameplay(newCombination)) return; KeyBinding.KeyCombination = newCombination; diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs index be0830a7c2..115a7bdc79 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs @@ -32,12 +32,12 @@ namespace osu.Game.Overlays.Settings.Sections.Input [BackgroundDependencyLoader] private void load(RealmContextFactory realmFactory) { - int? rulesetId = Ruleset?.ID; + string rulesetName = Ruleset?.ShortName; List bindings; using (var realm = realmFactory.CreateContext()) - bindings = realm.All().Where(b => b.RulesetID == rulesetId && b.Variant == variant).Detach(); + 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/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index b2252a5575..75bebfa763 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -207,7 +207,7 @@ namespace osu.Game.Overlays.Toolbar { if (Hotkey == null) return; - var realmKeyBinding = realmFactory.Context.All().FirstOrDefault(rkb => rkb.RulesetID == null && rkb.ActionInt == (int)Hotkey.Value); + var realmKeyBinding = realmFactory.Context.All().FirstOrDefault(rkb => rkb.RulesetName == null && rkb.ActionInt == (int)Hotkey.Value); if (realmKeyBinding != null) { From 6c36770eb388fe80f079b6001e7f4899bc1f20c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Nov 2021 21:41:09 +0900 Subject: [PATCH 0172/4398] Add back allowance for tests scenes using empty `ShortName` --- osu.Game/Rulesets/Ruleset.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index a252bd4786..31ebcfd600 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -49,6 +49,10 @@ namespace osu.Game.Rulesets { get { + // Is the case for many test usages. + if (string.IsNullOrEmpty(ShortName)) + return CreateAllMods(); + if (!mod_reference_cache.TryGetValue(ShortName, out var mods)) mod_reference_cache[ShortName] = mods = CreateAllMods().Cast().ToArray(); From 2350806b4cc8fbe23ee21288e5c75d16407133a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Nov 2021 20:26:45 +0100 Subject: [PATCH 0173/4398] Add failing test case for number box stack overflow scenario --- .../Settings/TestSceneSettingsNumberBox.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 osu.Game.Tests/Visual/Settings/TestSceneSettingsNumberBox.cs diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsNumberBox.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsNumberBox.cs new file mode 100644 index 0000000000..ffa1200f32 --- /dev/null +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsNumberBox.cs @@ -0,0 +1,25 @@ +// 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.Testing; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Tests.Visual.Settings +{ + public class TestSceneSettingsNumberBox : OsuTestScene + { + [Test] + public void TestLargeInteger() + { + SettingsNumberBox numberBox = null; + + AddStep("create number box", () => Child = numberBox = new SettingsNumberBox()); + + AddStep("set value to 1,000,000,000", () => numberBox.Current.Value = 1_000_000_000); + AddAssert("text box text is correct", () => numberBox.ChildrenOfType().Single().Current.Value == "1000000000"); + } + } +} From dced6a2e682a9902f43e1901e2ae1912bb637016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Nov 2021 20:39:44 +0100 Subject: [PATCH 0174/4398] Add extended test coverage for desired input handling --- .../Settings/TestSceneSettingsNumberBox.cs | 65 +++++++++++++++++-- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsNumberBox.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsNumberBox.cs index ffa1200f32..c063e5526a 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsNumberBox.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsNumberBox.cs @@ -11,15 +11,68 @@ namespace osu.Game.Tests.Visual.Settings { public class TestSceneSettingsNumberBox : OsuTestScene { + private SettingsNumberBox numberBox; + private OsuTextBox textBox; + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create number box", () => Child = numberBox = new SettingsNumberBox()); + AddStep("get inner text box", () => textBox = numberBox.ChildrenOfType().Single()); + } + [Test] public void TestLargeInteger() { - SettingsNumberBox numberBox = null; - - AddStep("create number box", () => Child = numberBox = new SettingsNumberBox()); - - AddStep("set value to 1,000,000,000", () => numberBox.Current.Value = 1_000_000_000); - AddAssert("text box text is correct", () => numberBox.ChildrenOfType().Single().Current.Value == "1000000000"); + AddStep("set current to 1,000,000,000", () => numberBox.Current.Value = 1_000_000_000); + AddAssert("text box text is correct", () => textBox.Text == "1000000000"); } + + [Test] + public void TestUserInput() + { + inputText("42"); + currentValueIs(42); + currentTextIs("42"); + + inputText(string.Empty); + currentValueIs(null); + currentTextIs(string.Empty); + + inputText("555"); + currentValueIs(555); + currentTextIs("555"); + + inputText("-4444"); + // attempting to input the minus will raise an input error, the rest will pass through fine. + currentValueIs(4444); + currentTextIs("4444"); + + // checking the upper bound. + inputText(int.MaxValue.ToString()); + currentValueIs(int.MaxValue); + currentTextIs(int.MaxValue.ToString()); + + inputText((long)int.MaxValue + 1.ToString()); + currentValueIs(int.MaxValue); + currentTextIs(int.MaxValue.ToString()); + + inputText("0"); + currentValueIs(0); + currentTextIs("0"); + + // checking that leading zeroes are stripped. + inputText("00"); + currentValueIs(0); + currentTextIs("0"); + + inputText("01"); + currentValueIs(1); + currentTextIs("1"); + } + + private void inputText(string text) => AddStep($"set textbox text to {text}", () => textBox.Text = text); + private void currentValueIs(int? value) => AddAssert($"current value is {value?.ToString() ?? "null"}", () => numberBox.Current.Value == value); + private void currentTextIs(string value) => AddAssert($"current text is {value}", () => textBox.Text == value); } } From 4a9f080f3c0c227ff17ab5b4dc1744d4c433e354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Nov 2021 20:41:27 +0100 Subject: [PATCH 0175/4398] Accept full range of `int` in `SettingsNumberBox` This fixes stack overflow exceptions that would arise when a `Current.Value` of 1 billion or more was set on a `SettingsNumberBox`. The stack overflow was caused by the "maximum 9 digits" spec. If a value technically within `int` bounds, but larger than 1 billion (in the range [1,000,000,000; 2,147,483,647], to be more precise), a feedback loop between the setting control's `Current` and its inner text box's `Current` would occur, wherein the last digit would be trimmed and then re-appended again forevermore. To resolve, remove the offending spec and rely on `int.TryParse` entirely to be able to discern overflow range. Additionally, UX of the text box is slightly changed to notify when the `int` range is exceeded with a red flash. This behaviour would not have been possible to implement without recent framework-side fixes to text box (removal of text set scheduling). --- osu.Game/Overlays/Settings/SettingsNumberBox.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsNumberBox.cs b/osu.Game/Overlays/Settings/SettingsNumberBox.cs index 545f1050b2..cbe9f7fc64 100644 --- a/osu.Game/Overlays/Settings/SettingsNumberBox.cs +++ b/osu.Game/Overlays/Settings/SettingsNumberBox.cs @@ -35,7 +35,6 @@ namespace osu.Game.Overlays.Settings { numberBox = new OutlinedNumberBox { - LengthLimit = 9, // limited to less than a value that could overflow int32 backing. Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, CommitOnFocusLost = true @@ -44,12 +43,19 @@ namespace osu.Game.Overlays.Settings numberBox.Current.BindValueChanged(e => { - int? value = null; + if (string.IsNullOrEmpty(e.NewValue)) + { + Current.Value = null; + return; + } if (int.TryParse(e.NewValue, out int intVal)) - value = intVal; + Current.Value = intVal; + else + numberBox.NotifyInputError(); - current.Value = value; + // trigger Current again to either restore the previous text box value, or to reformat the new value via .ToString(). + Current.TriggerChange(); }); Current.BindValueChanged(e => @@ -62,6 +68,8 @@ namespace osu.Game.Overlays.Settings private class OutlinedNumberBox : OutlinedTextBox { protected override bool CanAddCharacter(char character) => char.IsNumber(character); + + public new void NotifyInputError() => base.NotifyInputError(); } } } From 1f321e29107559f3e099ea9acf9f79988be94dd0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Nov 2021 11:48:56 +0900 Subject: [PATCH 0176/4398] Run EF migrations earlier to ensure it is complete before usage --- osu.Game/OsuGameBase.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index d8050353c5..a2dd417491 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -187,6 +187,9 @@ namespace osu.Game Resources.AddStore(new DllResourceStore(OsuResources.ResourceAssembly)); dependencies.Cache(contextFactory = new DatabaseContextFactory(Storage)); + + runMigrations(); + dependencies.Cache(RulesetStore = new RulesetStore(contextFactory, Storage)); dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client", RulesetStore)); @@ -204,8 +207,6 @@ namespace osu.Game Audio.Samples.PlaybackConcurrency = SAMPLE_CONCURRENCY; - runMigrations(); - dependencies.Cache(SkinManager = new SkinManager(Storage, contextFactory, Host, Resources, Audio)); dependencies.CacheAs(SkinManager); From feb983d5bdb876ef9f0ac61a7985ab38f2b1b4ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Nov 2021 13:09:41 +0900 Subject: [PATCH 0177/4398] Rename `Debug` namespace to avoid collisions --- osu.Game/Overlays/Settings/Sections/DebugSection.cs | 2 +- .../Sections/{Debug => DebugSettings}/GeneralSettings.cs | 2 +- .../Sections/{Debug => DebugSettings}/MemorySettings.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename osu.Game/Overlays/Settings/Sections/{Debug => DebugSettings}/GeneralSettings.cs (96%) rename osu.Game/Overlays/Settings/Sections/{Debug => DebugSettings}/MemorySettings.cs (93%) diff --git a/osu.Game/Overlays/Settings/Sections/DebugSection.cs b/osu.Game/Overlays/Settings/Sections/DebugSection.cs index aa85ec920c..2e23d8a22a 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSection.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSection.cs @@ -5,7 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Localisation; -using osu.Game.Overlays.Settings.Sections.Debug; +using osu.Game.Overlays.Settings.Sections.DebugSettings; namespace osu.Game.Overlays.Settings.Sections { diff --git a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs similarity index 96% rename from osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs rename to osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs index 25e20911b8..60540a089e 100644 --- a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs @@ -9,7 +9,7 @@ using osu.Framework.Screens; using osu.Game.Localisation; using osu.Game.Screens.Import; -namespace osu.Game.Overlays.Settings.Sections.Debug +namespace osu.Game.Overlays.Settings.Sections.DebugSettings { public class GeneralSettings : SettingsSubsection { diff --git a/osu.Game/Overlays/Settings/Sections/Debug/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs similarity index 93% rename from osu.Game/Overlays/Settings/Sections/Debug/MemorySettings.cs rename to osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index 07fb0aca5a..6f48768dcd 100644 --- a/osu.Game/Overlays/Settings/Sections/Debug/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -8,7 +8,7 @@ using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Game.Localisation; -namespace osu.Game.Overlays.Settings.Sections.Debug +namespace osu.Game.Overlays.Settings.Sections.DebugSettings { public class MemorySettings : SettingsSubsection { From 10bd7176e015f63e4bf2dfa829db66ce6d4a71f4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 23 Nov 2021 14:06:06 +0900 Subject: [PATCH 0178/4398] Fix potential test failure in TestSceneMultiplayerReadyButton Couldn't exactly reproduce https://github.com/ppy/osu/runs/4294316800, but I found a similar issue via: ```diff diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 84b63a5733..29cac9b061 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; @@ -81,6 +82,8 @@ private void load(GameHost host, AudioManager audio) await Client.ToggleReady(); + Thread.Sleep(1000); + readyClickOperation.Dispose(); }); } ``` --- .../Multiplayer/TestSceneMultiplayerReadyButton.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index e9612bf55c..84b63a5733 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Framework.Utils; @@ -198,11 +199,15 @@ namespace osu.Game.Tests.Visual.Multiplayer }, users); } - private void addClickButtonStep() => AddStep("click button", () => + private void addClickButtonStep() { - InputManager.MoveMouseTo(button); - InputManager.Click(MouseButton.Left); - }); + AddUntilStep("wait for button to be ready", () => button.ChildrenOfType protected virtual bool UseOnlineAPI => false; + /// + /// A database context factory to be used by test runs. Can be isolated and reset by setting to true. + /// + /// + /// In interactive runs (ie. VisualTests) this will use the user's database if is not set to true. + /// protected DatabaseContextFactory ContextFactory => contextFactory.Value; private Lazy contextFactory; + /// + /// Whether a fresh storage should be initialised per test (method) run. + /// + /// + /// By default (ie. if not set to true): + /// - in interactive runs, the user's storage will be used + /// - in headless runs, a shared temporary storage will be used per test class. + /// protected virtual bool UseFreshStoragePerRun => false; /// /// A storage to be used by test runs. Can be isolated by setting to true. /// + /// + /// In interactive runs (ie. VisualTests) this will use the user's storage if is not set to true. + /// protected Storage LocalStorage => localStorage.Value; private Lazy localStorage; From 4624977b7709876eb92a11552982a4877abafdce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Nov 2021 14:38:37 +0900 Subject: [PATCH 0183/4398] Fix tournament tests potentially using data left over from previous runs --- .../NonVisual/CustomTourneyDirectoryTest.cs | 36 ++++++++++++++----- .../NonVisual/IPCLocationTest.cs | 13 +++++-- .../NonVisual/TournamentHostTest.cs | 2 +- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index fcc9f44f0c..94472a8bc7 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tournament.Tests.NonVisual { using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestCustomDirectory))) // don't use clean run as we are writing a config file. { - string osuDesktopStorage = basePath(nameof(TestCustomDirectory)); + string osuDesktopStorage = PrepareBasePath(nameof(TestCustomDirectory)); const string custom_tournament = "custom"; // need access before the game has constructed its own storage yet. @@ -60,6 +60,15 @@ namespace osu.Game.Tournament.Tests.NonVisual finally { host.Exit(); + + try + { + if (Directory.Exists(osuDesktopStorage)) + Directory.Delete(osuDesktopStorage, true); + } + catch + { + } } } } @@ -69,7 +78,7 @@ namespace osu.Game.Tournament.Tests.NonVisual { using (HeadlessGameHost host = new HeadlessGameHost(nameof(TestMigration))) // don't use clean run as we are writing test files for migration. { - string osuRoot = basePath(nameof(TestMigration)); + string osuRoot = PrepareBasePath(nameof(TestMigration)); string configFile = Path.Combine(osuRoot, "tournament.ini"); if (File.Exists(configFile)) @@ -136,18 +145,29 @@ namespace osu.Game.Tournament.Tests.NonVisual } finally { + host.Exit(); + try { - host.Storage.Delete("tournament.ini"); - host.Storage.DeleteDirectory("tournaments"); + if (Directory.Exists(osuRoot)) + Directory.Delete(osuRoot, true); + } + catch + { } - catch { } - - host.Exit(); } } } - private string basePath(string testInstance) => Path.Combine(RuntimeInfo.StartupDirectory, "headless", testInstance); + public static string PrepareBasePath(string testInstance) + { + string basePath = Path.Combine(RuntimeInfo.StartupDirectory, "headless", testInstance); + + // manually clean before starting in case there are left-over files at the test site. + if (Directory.Exists(basePath)) + Directory.Delete(basePath, true); + + return basePath; + } } } diff --git a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs index eaa009c180..22d9a4ff9d 100644 --- a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tournament.Tests.NonVisual // don't use clean run because files are being written before osu! launches. using (HeadlessGameHost host = new HeadlessGameHost(nameof(CheckIPCLocation))) { - string basePath = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(CheckIPCLocation)); + string basePath = CustomTourneyDirectoryTest.PrepareBasePath(nameof(CheckIPCLocation)); // Set up a fake IPC client for the IPC Storage to switch to. string testStableInstallDirectory = Path.Combine(basePath, "stable-ce"); @@ -42,9 +42,16 @@ namespace osu.Game.Tournament.Tests.NonVisual } finally { - host.Storage.DeleteDirectory(testStableInstallDirectory); - host.Storage.DeleteDirectory("tournaments"); host.Exit(); + + try + { + if (Directory.Exists(basePath)) + Directory.Delete(basePath, true); + } + catch + { + } } } } diff --git a/osu.Game.Tournament.Tests/NonVisual/TournamentHostTest.cs b/osu.Game.Tournament.Tests/NonVisual/TournamentHostTest.cs index 319a768e65..bf99f69b2a 100644 --- a/osu.Game.Tournament.Tests/NonVisual/TournamentHostTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/TournamentHostTest.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tournament.Tests.NonVisual return tournament; } - public static void WaitForOrAssert(Func result, string failureMessage, int timeout = 90000) + public static void WaitForOrAssert(Func result, string failureMessage, int timeout = 30000) { Task task = Task.Run(() => { From b596a0204c5d4a48965812dbba173d3fa4729577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Nov 2021 06:43:17 +0100 Subject: [PATCH 0184/4398] Rewrite overflow test step for legibility --- .../Visual/Settings/TestSceneSettingsNumberBox.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsNumberBox.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsNumberBox.cs index c063e5526a..334a814688 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsNumberBox.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsNumberBox.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Settings currentValueIs(int.MaxValue); currentTextIs(int.MaxValue.ToString()); - inputText((long)int.MaxValue + 1.ToString()); + inputText(smallestOverflowValue.ToString()); currentValueIs(int.MaxValue); currentTextIs(int.MaxValue.ToString()); @@ -74,5 +74,10 @@ namespace osu.Game.Tests.Visual.Settings private void inputText(string text) => AddStep($"set textbox text to {text}", () => textBox.Text = text); private void currentValueIs(int? value) => AddAssert($"current value is {value?.ToString() ?? "null"}", () => numberBox.Current.Value == value); private void currentTextIs(string value) => AddAssert($"current text is {value}", () => textBox.Text == value); + + /// + /// The smallest number that overflows . + /// + private static long smallestOverflowValue => 1L + int.MaxValue; } } From 6fb27577397056e76ac64be9b974b312a6be8ef3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Nov 2021 14:58:18 +0900 Subject: [PATCH 0185/4398] Remove usage of `Nuget.Packaging` extension methods for `IList.AddRange` --- osu.Game/Extensions/CollectionExtensions.cs | 22 ++++++++++++++++++++ osu.Game/Stores/BeatmapImporter.cs | 1 - osu.Game/Stores/RealmArchiveModelImporter.cs | 1 - 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Extensions/CollectionExtensions.cs diff --git a/osu.Game/Extensions/CollectionExtensions.cs b/osu.Game/Extensions/CollectionExtensions.cs new file mode 100644 index 0000000000..473dc4b8f4 --- /dev/null +++ b/osu.Game/Extensions/CollectionExtensions.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.Collections.Generic; + +namespace osu.Game.Extensions +{ + public static class CollectionExtensions + { + public static void AddRange(this ICollection collection, IEnumerable items) + { + // List has a potentially more optimal path to adding a range. + if (collection is List list) + list.AddRange(items); + else + { + foreach (T obj in items) + collection.Add(obj); + } + } + } +} diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index dad2b29dd0..32f0cd3d7a 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -7,7 +7,6 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using NuGet.Packaging; using osu.Framework.Audio.Track; using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; diff --git a/osu.Game/Stores/RealmArchiveModelImporter.cs b/osu.Game/Stores/RealmArchiveModelImporter.cs index 5b8c73b218..6370d4ebe4 100644 --- a/osu.Game/Stores/RealmArchiveModelImporter.cs +++ b/osu.Game/Stores/RealmArchiveModelImporter.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Humanizer; -using NuGet.Packaging; using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Logging; From a8c4fd694fcccd257f35ec07dd83d977c70f5ce3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Nov 2021 15:04:44 +0900 Subject: [PATCH 0186/4398] Add `BannedSymbols` entry for `NuGet.Packaging` --- CodeAnalysis/BannedSymbols.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index ea3e25142c..b72803482d 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -8,4 +8,5 @@ M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText. M:osu.Framework.Bindables.IBindableList`1.GetBoundCopy();Fails on iOS. Use manual ctor + BindTo instead. (see https://github.com/mono/mono/issues/19900) T:Microsoft.EntityFrameworkCore.Internal.EnumerableExtensions;Don't use internal extension methods. T:Microsoft.EntityFrameworkCore.Internal.TypeExtensions;Don't use internal extension methods. -M:System.Enum.HasFlag(System.Enum);Use osu.Framework.Extensions.EnumExtensions.HasFlagFast() instead. \ No newline at end of file +T:NuGet.Packaging.CollectionExtensions;Don't use internal extension methods. +M:System.Enum.HasFlag(System.Enum);Use osu.Framework.Extensions.EnumExtensions.HasFlagFast() instead. From 7906ae2b1dab8fd45b6b2fa0fb31a37c8fbf6756 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 23 Nov 2021 15:07:17 +0900 Subject: [PATCH 0187/4398] Update room immediately on join --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index bd32687c5e..6b8bb7ecdf 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -160,11 +160,11 @@ namespace osu.Game.Online.Multiplayer foreach (var user in joinedRoom.Users) updateUserPlayingState(user.UserID, user.State); + updateLocalRoomSettings(joinedRoom.Settings); + OnRoomJoined(); }, cancellationSource.Token).ConfigureAwait(false); - // Update room settings. - await updateLocalRoomSettings(joinedRoom.Settings, cancellationSource.Token).ConfigureAwait(false); }, cancellationSource.Token).ConfigureAwait(false); } @@ -447,8 +447,7 @@ namespace osu.Game.Online.Multiplayer Task IMultiplayerClient.SettingsChanged(MultiplayerRoomSettings newSettings) { - // Do not return this task, as it will cause tests to deadlock. - updateLocalRoomSettings(newSettings); + Scheduler.Add(() => updateLocalRoomSettings(newSettings)); return Task.CompletedTask; } @@ -685,8 +684,7 @@ namespace osu.Game.Online.Multiplayer /// This updates both the joined and the respective API . /// /// The new to update from. - /// The to cancel the update. - private Task updateLocalRoomSettings(MultiplayerRoomSettings settings, CancellationToken cancellationToken = default) => scheduleAsync(() => + private void updateLocalRoomSettings(MultiplayerRoomSettings settings) { if (Room == null) return; @@ -701,7 +699,7 @@ namespace osu.Game.Online.Multiplayer RoomUpdated?.Invoke(); CurrentMatchPlayingItem.Value = APIRoom.Playlist.SingleOrDefault(p => p.ID == settings.PlaylistItemId); - }, cancellationToken); + } private async Task createPlaylistItem(MultiplayerPlaylistItem item) { From 69a9fc97328ef2bc70dddd30b76cece5b1b608c4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 23 Nov 2021 16:04:11 +0900 Subject: [PATCH 0188/4398] Fix a few more multiplayer test timeouts These can be tested by adding a `Task.Delay(3000);` at the end of the `MultiplayerClient.JoinRoom` task. The reason is typically that `Client.Room` becomes not-null but the join task still hasn't completed yet, so e.g. the ready button is still disabled. --- .../StatefulMultiplayerClientTest.cs | 2 +- .../Visual/Multiplayer/QueueModeTestScene.cs | 2 +- .../Multiplayer/TestSceneMultiplayer.cs | 10 +++---- .../TestSceneMultiplayerMatchSubScreen.cs | 4 +-- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 2 +- .../Visual/TestMultiplayerScreenStack.cs | 2 ++ .../Screens/OnlinePlay/OnlinePlayScreen.cs | 4 +-- .../Multiplayer/MultiplayerTestScene.cs | 4 ++- .../Multiplayer/TestMultiplayerRoomManager.cs | 27 +++++++++++++++++++ 9 files changed, 44 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index abe5664737..840ff20a83 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -72,7 +72,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer RoomManager.CreateRoom(newRoom); }); - AddUntilStep("wait for room join", () => Client.Room != null); + AddUntilStep("wait for room join", () => RoomJoined); checkPlayingUserCount(1); } diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index e94e91dfe3..8e731e34ea 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.Click(MouseButton.Left); }); - AddUntilStep("wait for join", () => Client.Room != null); + AddUntilStep("wait for join", () => RoomManager.RoomJoined); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index b828379848..c4833b5226 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -214,7 +214,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.Room != null); + AddUntilStep("wait for join", () => roomManager.RoomJoined); } [Test] @@ -293,7 +293,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.Room != null); + AddUntilStep("wait for join", () => roomManager.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); @@ -351,7 +351,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.Room != null); + AddUntilStep("wait for join", () => roomManager.RoomJoined); } [Test] @@ -618,7 +618,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.Room != null); + AddUntilStep("wait for join", () => roomManager.RoomJoined); AddAssert("local room has correct settings", () => { @@ -644,7 +644,7 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.Click(MouseButton.Left); }); - AddUntilStep("wait for join", () => client.Room != null); + AddUntilStep("wait for join", () => roomManager.RoomJoined); } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index f544f5fdf3..bd230864df 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -78,7 +78,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); }); - AddUntilStep("wait for join", () => Client.Room != null); + AddUntilStep("wait for join", () => RoomJoined); } [Test] @@ -116,7 +116,7 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.Click(MouseButton.Left); }); - AddUntilStep("wait for room join", () => Client.Room != null); + AddUntilStep("wait for room join", () => RoomJoined); AddStep("join other user (ready)", () => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 99ff307235..c70906927e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -158,7 +158,7 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.Click(MouseButton.Left); }); - AddUntilStep("wait for join", () => client.Room != null); + AddUntilStep("wait for join", () => multiplayerScreenStack.RoomManager.RoomJoined); } } } diff --git a/osu.Game.Tests/Visual/TestMultiplayerScreenStack.cs b/osu.Game.Tests/Visual/TestMultiplayerScreenStack.cs index 7f1171db1f..43cb857c9a 100644 --- a/osu.Game.Tests/Visual/TestMultiplayerScreenStack.cs +++ b/osu.Game.Tests/Visual/TestMultiplayerScreenStack.cs @@ -7,6 +7,7 @@ using osu.Framework.Screens; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; using osu.Game.Screens; +using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Tests.Visual.Multiplayer; using osu.Game.Tests.Visual.OnlinePlay; @@ -76,6 +77,7 @@ namespace osu.Game.Tests.Visual { public new TestMultiplayerRoomManager RoomManager { get; private set; } public TestRoomRequestsHandler RequestsHandler { get; private set; } + public new OngoingOperationTracker OngoingOperationTracker => base.OngoingOperationTracker; protected override RoomManager CreateRoomManager() => RoomManager = new TestMultiplayerRoomManager(RequestsHandler = new TestRoomRequestsHandler()); } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index a18e4b45cf..6df79df535 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.OnlinePlay protected RoomManager RoomManager { get; private set; } [Cached] - private readonly OngoingOperationTracker ongoingOperationTracker = new OngoingOperationTracker(); + protected readonly OngoingOperationTracker OngoingOperationTracker = new OngoingOperationTracker(); [Resolved(CanBeNull = true)] private MusicController music { get; set; } @@ -75,7 +75,7 @@ namespace osu.Game.Screens.OnlinePlay screenStack = new OnlinePlaySubScreenStack { RelativeSizeAxes = Axes.Both }, new Header(ScreenTitle, screenStack), RoomManager, - ongoingOperationTracker + OngoingOperationTracker } }; } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index c628541825..5656704abf 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -24,6 +24,8 @@ namespace osu.Game.Tests.Visual.Multiplayer protected new MultiplayerTestSceneDependencies OnlinePlayDependencies => (MultiplayerTestSceneDependencies)base.OnlinePlayDependencies; + public bool RoomJoined => RoomManager.RoomJoined; + private readonly bool joinRoom; protected MultiplayerTestScene(bool joinRoom = true) @@ -61,7 +63,7 @@ namespace osu.Game.Tests.Visual.Multiplayer if (joinRoom) { AddStep("join room", () => RoomManager.CreateRoom(SelectedRoom.Value)); - AddUntilStep("wait for room join", () => Client.Room != null); + AddUntilStep("wait for room join", () => RoomJoined); } } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs index 4129d190be..f8419b4164 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.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 osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; @@ -15,6 +16,8 @@ namespace osu.Game.Tests.Visual.Multiplayer /// public class TestMultiplayerRoomManager : MultiplayerRoomManager { + public bool RoomJoined { get; private set; } + private readonly TestRoomRequestsHandler requestsHandler; public TestMultiplayerRoomManager(TestRoomRequestsHandler requestsHandler) @@ -24,6 +27,30 @@ namespace osu.Game.Tests.Visual.Multiplayer public IReadOnlyList ServerSideRooms => requestsHandler.ServerSideRooms; + public override void CreateRoom(Room room, Action onSuccess = null, Action onError = null) + { + base.CreateRoom(room, r => + { + onSuccess?.Invoke(r); + RoomJoined = true; + }, onError); + } + + public override void JoinRoom(Room room, string password = null, Action onSuccess = null, Action onError = null) + { + base.JoinRoom(room, password, r => + { + onSuccess?.Invoke(r); + RoomJoined = true; + }, onError); + } + + public override void PartRoom() + { + base.PartRoom(); + RoomJoined = false; + } + /// /// Adds a room to a local "server-side" list that's returned when a is fired. /// From 1f136696362aab0824fb85e6e90693c85c5820ef Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 23 Nov 2021 16:13:53 +0900 Subject: [PATCH 0189/4398] Don't poll while in room Fixes timeout in `TestJoinRoomWithoutPassword`, because the 'server' returns out-of-date data while the `MatchSubScreen` has possible not been entered yet (and thus hasn't disabled polling itself yet). Can be tested by adding a `Task.Delay(3000);` at the end of the `MultiplayerClient.JoinRoom()` task. --- .../OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index cf1066df10..5cdec52bc2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs @@ -93,6 +93,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (!isConnected.Value) return Task.CompletedTask; + if (client.Room != null) + return Task.CompletedTask; + return base.Poll(); } } From 6363833fb3c1071bcea9a8d021270bf0cc64056b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 23 Nov 2021 16:16:38 +0900 Subject: [PATCH 0190/4398] Revert unnecessary changes --- osu.Game.Tests/Visual/TestMultiplayerScreenStack.cs | 2 -- osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/TestMultiplayerScreenStack.cs b/osu.Game.Tests/Visual/TestMultiplayerScreenStack.cs index 43cb857c9a..7f1171db1f 100644 --- a/osu.Game.Tests/Visual/TestMultiplayerScreenStack.cs +++ b/osu.Game.Tests/Visual/TestMultiplayerScreenStack.cs @@ -7,7 +7,6 @@ using osu.Framework.Screens; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; using osu.Game.Screens; -using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Tests.Visual.Multiplayer; using osu.Game.Tests.Visual.OnlinePlay; @@ -77,7 +76,6 @@ namespace osu.Game.Tests.Visual { public new TestMultiplayerRoomManager RoomManager { get; private set; } public TestRoomRequestsHandler RequestsHandler { get; private set; } - public new OngoingOperationTracker OngoingOperationTracker => base.OngoingOperationTracker; protected override RoomManager CreateRoomManager() => RoomManager = new TestMultiplayerRoomManager(RequestsHandler = new TestRoomRequestsHandler()); } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index 6df79df535..a18e4b45cf 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.OnlinePlay protected RoomManager RoomManager { get; private set; } [Cached] - protected readonly OngoingOperationTracker OngoingOperationTracker = new OngoingOperationTracker(); + private readonly OngoingOperationTracker ongoingOperationTracker = new OngoingOperationTracker(); [Resolved(CanBeNull = true)] private MusicController music { get; set; } @@ -75,7 +75,7 @@ namespace osu.Game.Screens.OnlinePlay screenStack = new OnlinePlaySubScreenStack { RelativeSizeAxes = Axes.Both }, new Header(ScreenTitle, screenStack), RoomManager, - OngoingOperationTracker + ongoingOperationTracker } }; } From 0cf5a738dc9a0851e74d546ad155001f0e8ca646 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 23 Nov 2021 16:32:44 +0900 Subject: [PATCH 0191/4398] Remove unused using --- osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs index 22d9a4ff9d..db89855db7 100644 --- a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs @@ -3,7 +3,6 @@ using System.IO; using NUnit.Framework; -using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; using osu.Game.Tournament.IO; From 9157b91e5f2aebb4fcbee2052227a27c781041fa Mon Sep 17 00:00:00 2001 From: GoldenMine0502 Date: Tue, 23 Nov 2021 16:41:20 +0900 Subject: [PATCH 0192/4398] fix adding wrong values --- 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 24881d9c47..4f87767fa7 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (strainTime < min_speed_bonus) speedBonus = 1 + 0.75 * Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2); - double distance = Math.Min(single_spacing_threshold, osuCurrObj.TravelDistance + osuCurrObj.JumpDistance); + double distance = Math.Min(single_spacing_threshold, osuCurrObj.TravelDistance + osuCurrObj.MovementDistance); return (speedBonus + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / strainTime; } From a521d1b83584c40f8ed201716c0b83e3b4ffa8b1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 23 Nov 2021 17:09:38 +0900 Subject: [PATCH 0193/4398] Wait for ready button to be enabled first --- .../Visual/Multiplayer/QueueModeTestScene.cs | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 8e731e34ea..357db16e2c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -5,6 +5,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; @@ -104,23 +105,24 @@ namespace osu.Game.Tests.Visual.Multiplayer protected void RunGameplay() { AddUntilStep("wait for idle", () => Client.LocalUser?.State == MultiplayerUserState.Idle); - - AddStep("click ready button", () => - { - InputManager.MoveMouseTo(this.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); + clickReadyButton(); AddUntilStep("wait for ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready); - - AddStep("click ready button", () => - { - InputManager.MoveMouseTo(this.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); + clickReadyButton(); AddUntilStep("wait for player", () => multiplayerScreenStack.CurrentScreen is Player player && player.IsLoaded); AddStep("exit player", () => multiplayerScreenStack.MultiplayerScreen.MakeCurrent()); } + + private void clickReadyButton() + { + AddUntilStep("wait for ready button to be enabled", () => this.ChildrenOfType().Single().ChildrenOfType