From 83cb70db17e10ee9c42ba5c20e5547efff6a9118 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Mon, 19 Aug 2019 22:54:07 +0200 Subject: [PATCH 001/256] 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 002/256] 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 003/256] 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 004/256] 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 005/256] 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 006/256] 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 327822de5bb5e15098c53fce05ae0eddad495e58 Mon Sep 17 00:00:00 2001 From: pikokr Date: Mon, 27 Dec 2021 19:41:36 +0900 Subject: [PATCH 007/256] Add touchscreen support for osu!mania ruleset --- osu.Game.Rulesets.Mania/UI/Column.cs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 9d060944cd..df39b5397b 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -136,6 +136,33 @@ namespace osu.Game.Rulesets.Mania.UI { } + // https://github.com/ppy/osu-framework/blob/49c954321c3686628b2c223670363438f88a0341/osu.Framework/Graphics/Drawable.cs#L1513-L1524 + private T findClosestParent() where T : class, IDrawable + { + Drawable cursor = this; + + while ((cursor = cursor.Parent) != null) + { + if (cursor is T match) + return match; + } + + return default; + } + + private ManiaInputManager.RulesetKeyBindingContainer keyBindingManager => findClosestParent(); + + protected override bool OnTouchDown(TouchDownEvent e) + { + keyBindingManager.TriggerPressed(Action.Value); + return base.OnTouchDown(e); + } + + protected override void OnTouchUp(TouchUpEvent e) + { + keyBindingManager.TriggerReleased(Action.Value); + } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) // This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border => DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos)); From 58994b790c8e8a3c85fa5d82edc2835b648b8843 Mon Sep 17 00:00:00 2001 From: pikokr Date: Mon, 27 Dec 2021 21:20:52 +0900 Subject: [PATCH 008/256] Get key binding container once instead of getting on every touch --- osu.Game.Rulesets.Mania/ManiaInputManager.cs | 5 +++++ osu.Game.Rulesets.Mania/UI/Column.cs | 21 ++++++-------------- osu.Game/Rulesets/UI/DrawableRuleset.cs | 1 + 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaInputManager.cs b/osu.Game.Rulesets.Mania/ManiaInputManager.cs index 186fc4b15d..bf94735077 100644 --- a/osu.Game.Rulesets.Mania/ManiaInputManager.cs +++ b/osu.Game.Rulesets.Mania/ManiaInputManager.cs @@ -13,6 +13,11 @@ namespace osu.Game.Rulesets.Mania : base(ruleset, variant, SimultaneousBindingMode.Unique) { } + + public RulesetKeyBindingContainer GetKeyBindingContainer() + { + return (RulesetKeyBindingContainer)KeyBindingContainer; + } } public enum ManiaAction diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index df39b5397b..6be36619c7 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -136,31 +136,22 @@ namespace osu.Game.Rulesets.Mania.UI { } - // https://github.com/ppy/osu-framework/blob/49c954321c3686628b2c223670363438f88a0341/osu.Framework/Graphics/Drawable.cs#L1513-L1524 - private T findClosestParent() where T : class, IDrawable + private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } + + private ManiaInputManager.RulesetKeyBindingContainer getKeyBindingManager() { - Drawable cursor = this; - - while ((cursor = cursor.Parent) != null) - { - if (cursor is T match) - return match; - } - - return default; + return keyBindingContainer ??= ((ManiaInputManager)GetContainingInputManager()).GetKeyBindingContainer(); } - private ManiaInputManager.RulesetKeyBindingContainer keyBindingManager => findClosestParent(); - protected override bool OnTouchDown(TouchDownEvent e) { - keyBindingManager.TriggerPressed(Action.Value); + getKeyBindingManager().TriggerPressed(Action.Value); return base.OnTouchDown(e); } protected override void OnTouchUp(TouchUpEvent e) { - keyBindingManager.TriggerReleased(Action.Value); + getKeyBindingManager().TriggerReleased(Action.Value); } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 29559f5036..30012784a3 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -50,6 +50,7 @@ namespace osu.Game.Rulesets.UI /// /// The key conversion input manager for this DrawableRuleset. /// + [Cached] public PassThroughInputManager KeyBindingInputManager; public override double GameplayStartTime => Objects.FirstOrDefault()?.StartTime - 2000 ?? 0; From d62930500298d23419b2f37b0376881f0e6bb573 Mon Sep 17 00:00:00 2001 From: pikokr Date: Tue, 28 Dec 2021 21:47:58 +0900 Subject: [PATCH 009/256] Remove `Cached` attribute from `DrawableRuleset.KeyBindingInputManager` --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 30012784a3..29559f5036 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -50,7 +50,6 @@ namespace osu.Game.Rulesets.UI /// /// The key conversion input manager for this DrawableRuleset. /// - [Cached] public PassThroughInputManager KeyBindingInputManager; public override double GameplayStartTime => Objects.FirstOrDefault()?.StartTime - 2000 ?? 0; From 59b4aea5f9dc22a3cf543bddae589e462a8be383 Mon Sep 17 00:00:00 2001 From: pikokr Date: Tue, 28 Dec 2021 21:52:46 +0900 Subject: [PATCH 010/256] Make method and property name to match class name --- osu.Game.Rulesets.Mania/UI/Column.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 6be36619c7..36473aa900 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -136,22 +136,22 @@ namespace osu.Game.Rulesets.Mania.UI { } - private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } + private ManiaInputManager.RulesetKeyBindingContainer rulesetKeyBindingContainer { get; set; } - private ManiaInputManager.RulesetKeyBindingContainer getKeyBindingManager() + private ManiaInputManager.RulesetKeyBindingContainer getRulesetKeyBindingContainer() { - return keyBindingContainer ??= ((ManiaInputManager)GetContainingInputManager()).GetKeyBindingContainer(); + return rulesetKeyBindingContainer ??= ((ManiaInputManager)GetContainingInputManager()).GetKeyBindingContainer(); } protected override bool OnTouchDown(TouchDownEvent e) { - getKeyBindingManager().TriggerPressed(Action.Value); + getRulesetKeyBindingContainer().TriggerPressed(Action.Value); return base.OnTouchDown(e); } protected override void OnTouchUp(TouchUpEvent e) { - getKeyBindingManager().TriggerReleased(Action.Value); + getRulesetKeyBindingContainer().TriggerReleased(Action.Value); } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) From 62d6bb8c2e9c8a6e10a318599a7ad04974d55e76 Mon Sep 17 00:00:00 2001 From: pikokr Date: Tue, 28 Dec 2021 22:35:45 +0900 Subject: [PATCH 011/256] Trigger touch on click key area --- osu.Game.Rulesets.Mania/UI/Column.cs | 18 ------------- .../UI/Components/DefaultKeyArea.cs | 25 +++++++++++++++++-- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 36473aa900..9d060944cd 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -136,24 +136,6 @@ namespace osu.Game.Rulesets.Mania.UI { } - private ManiaInputManager.RulesetKeyBindingContainer rulesetKeyBindingContainer { get; set; } - - private ManiaInputManager.RulesetKeyBindingContainer getRulesetKeyBindingContainer() - { - return rulesetKeyBindingContainer ??= ((ManiaInputManager)GetContainingInputManager()).GetKeyBindingContainer(); - } - - protected override bool OnTouchDown(TouchDownEvent e) - { - getRulesetKeyBindingContainer().TriggerPressed(Action.Value); - return base.OnTouchDown(e); - } - - protected override void OnTouchUp(TouchUpEvent e) - { - getRulesetKeyBindingContainer().TriggerReleased(Action.Value); - } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) // This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border => DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos)); diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs index 267ed1f5f4..4e0bb71514 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs @@ -36,10 +36,31 @@ namespace osu.Game.Rulesets.Mania.UI.Components RelativeSizeAxes = Axes.Both; } + public class ChildContainer : Container + { + private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } + + private ManiaInputManager.RulesetKeyBindingContainer getKeyBindingContainer() + { + return keyBindingContainer ??= ((ManiaInputManager)GetContainingInputManager()).GetKeyBindingContainer(); + } + + protected override bool OnTouchDown(TouchDownEvent e) + { + getKeyBindingContainer().TriggerPressed(((DefaultKeyArea)Parent).column.Action.Value); + return base.OnTouchDown(e); + } + + protected override void OnTouchUp(TouchUpEvent e) + { + getKeyBindingContainer().TriggerReleased(((DefaultKeyArea)Parent).column.Action.Value); + } + } + [BackgroundDependencyLoader] private void load(IScrollingInfo scrollingInfo) { - InternalChild = directionContainer = new Container + InternalChild = directionContainer = new ChildContainer { RelativeSizeAxes = Axes.X, Height = Stage.HIT_TARGET_POSITION, @@ -69,7 +90,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components AlwaysPresent = true } } - } + }, } }; From dec1f317494c73bbb4313d7be2f1cadb5fde488c Mon Sep 17 00:00:00 2001 From: pikokr Date: Tue, 28 Dec 2021 22:43:07 +0900 Subject: [PATCH 012/256] Make `KeyBindingContainer` public --- osu.Game.Rulesets.Mania/ManiaInputManager.cs | 5 ----- osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs | 2 +- osu.Game/Rulesets/UI/RulesetInputManager.cs | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaInputManager.cs b/osu.Game.Rulesets.Mania/ManiaInputManager.cs index bf94735077..186fc4b15d 100644 --- a/osu.Game.Rulesets.Mania/ManiaInputManager.cs +++ b/osu.Game.Rulesets.Mania/ManiaInputManager.cs @@ -13,11 +13,6 @@ namespace osu.Game.Rulesets.Mania : base(ruleset, variant, SimultaneousBindingMode.Unique) { } - - public RulesetKeyBindingContainer GetKeyBindingContainer() - { - return (RulesetKeyBindingContainer)KeyBindingContainer; - } } public enum ManiaAction diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs index 4e0bb71514..b3a2a97bf7 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components private ManiaInputManager.RulesetKeyBindingContainer getKeyBindingContainer() { - return keyBindingContainer ??= ((ManiaInputManager)GetContainingInputManager()).GetKeyBindingContainer(); + return keyBindingContainer ??= (ManiaInputManager.RulesetKeyBindingContainer)((ManiaInputManager)GetContainingInputManager()).KeyBindingContainer; } protected override bool OnTouchDown(TouchDownEvent e) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 6564ff9e23..6ef99f8aae 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.UI protected override InputState CreateInitialState() => new RulesetInputManagerInputState(base.CreateInitialState()); - protected readonly KeyBindingContainer KeyBindingContainer; + public readonly KeyBindingContainer KeyBindingContainer; protected override Container Content => content; From 4cb8272d14369fb7a129d5ec3997b92a14e14867 Mon Sep 17 00:00:00 2001 From: pikokr Date: Thu, 30 Dec 2021 17:37:14 +0900 Subject: [PATCH 013/256] Column Touch area & highlighting on start --- osu.Game.Rulesets.Mania/UI/Column.cs | 60 ++++++++++++++++++- .../UI/Components/DefaultKeyArea.cs | 23 +------ 2 files changed, 60 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 9d060944cd..1e07c84d9f 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -44,6 +44,63 @@ namespace osu.Game.Rulesets.Mania.UI private readonly GameplaySampleTriggerSource sampleTriggerSource; + public class ColumnTouchInputArea : Container + { + private Column column => (Column)Parent; + + private Container hintContainer; + + public ColumnTouchInputArea() + { + RelativeSizeAxes = Axes.X; + Anchor = Anchor.BottomCentre; + Origin = Anchor.BottomCentre; + Height = 100; + InternalChild = hintContainer = new Container + { + RelativeSizeAxes = Axes.Both, + BorderColour = Color4.Red, + BorderThickness = 5, + Masking = true, + }; + } + + protected override void LoadComplete() + { + hintContainer.Delay(1000).FadeOutFromOne(500, Easing.OutSine); + } + + private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } + + private ManiaInputManager.RulesetKeyBindingContainer getKeyBindingContainer() + { + return keyBindingContainer ??= (ManiaInputManager.RulesetKeyBindingContainer)((ManiaInputManager)GetContainingInputManager()).KeyBindingContainer; + } + + protected override bool OnTouchDown(TouchDownEvent e) + { + getKeyBindingContainer().TriggerPressed(column.Action.Value); + return base.OnTouchDown(e); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + getKeyBindingContainer().TriggerPressed(column.Action.Value); + + return base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseUpEvent e) + { + getKeyBindingContainer().TriggerReleased(column.Action.Value); + } + + protected override void OnTouchUp(TouchUpEvent e) + { + getKeyBindingContainer().TriggerReleased(column.Action.Value); + } + } + public Column(int index) { Index = index; @@ -68,7 +125,8 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Both }, background, - TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both } + TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }, + new ColumnTouchInputArea() }; hitPolicy = new OrderedHitPolicy(HitObjectContainer); diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs index b3a2a97bf7..15018b464f 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs @@ -36,31 +36,10 @@ namespace osu.Game.Rulesets.Mania.UI.Components RelativeSizeAxes = Axes.Both; } - public class ChildContainer : Container - { - private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } - - private ManiaInputManager.RulesetKeyBindingContainer getKeyBindingContainer() - { - return keyBindingContainer ??= (ManiaInputManager.RulesetKeyBindingContainer)((ManiaInputManager)GetContainingInputManager()).KeyBindingContainer; - } - - protected override bool OnTouchDown(TouchDownEvent e) - { - getKeyBindingContainer().TriggerPressed(((DefaultKeyArea)Parent).column.Action.Value); - return base.OnTouchDown(e); - } - - protected override void OnTouchUp(TouchUpEvent e) - { - getKeyBindingContainer().TriggerReleased(((DefaultKeyArea)Parent).column.Action.Value); - } - } - [BackgroundDependencyLoader] private void load(IScrollingInfo scrollingInfo) { - InternalChild = directionContainer = new ChildContainer + InternalChild = directionContainer = new Container { RelativeSizeAxes = Axes.X, Height = Stage.HIT_TARGET_POSITION, From 612f69782b14e6d0397c9fd248b9f77ffd48e01e Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Tue, 4 Jan 2022 14:29:44 +0100 Subject: [PATCH 014/256] use Playfield.HitObjectContainer.AliveObjects --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 93 ++++++++++--------- 1 file changed, 49 insertions(+), 44 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index f70ad2ac7c..1849b48073 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -7,54 +7,55 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Osu.UI; using osuTK; +using System.Linq; namespace osu.Game.Rulesets.Osu.Mods { - internal class OsuModAimAssist : Mod, IApplicableToDrawableHitObjects, IUpdatableByPlayfield + internal class OsuModAimAssist : Mod, IUpdatableByPlayfield { public override string Name => "Aim Assist"; public override string Acronym => "AA"; - public override IconUsage Icon => FontAwesome.Solid.MousePointer; + public override IconUsage? Icon => FontAwesome.Solid.MousePointer; public override ModType Type => ModType.Fun; public override string Description => "No need to chase the circle, the circle chases you"; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; - private readonly List movingObjects = new List(); + public const float SPIN_RADIUS = 50; // same as OsuAutoGeneratorBase.SPIN_RADIUS + private DrawableSpinner activeSpinner; - private double spinnerAngle; // in radians + private float spinnerAngle; // in radians public void Update(Playfield playfield) { var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; - var currentTime = playfield.Clock.CurrentTime; + double currentTime = playfield.Clock.CurrentTime; - // Avoid relocating judgment displays and hide follow points + // Judgment displays would all be cramped onto the cursor playfield.DisplayJudgements.Value = false; - (playfield as OsuPlayfield)?.ConnectionLayer.Hide(); + + // FIXME: Hide follow points + //(playfield as OsuPlayfield)?.ConnectionLayer.Hide(); // If object too old, remove from movingObjects list, otherwise move to new destination - movingObjects.RemoveAll(d => + foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { - var h = d.HitObject; - var endTime = (h as IHasEndTime)?.EndTime ?? h.StartTime; + var h = drawable.HitObject; + double endTime = h.GetEndTime(); - // Object no longer required to be moved -> remove from list - if (currentTime > endTime) - return true; - - switch (d) + switch (drawable) { case DrawableHitCircle circle: // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast circle.MoveTo(cursorPos, Math.Max(0, endTime - currentTime - 10)); - return false; + + break; case DrawableSlider slider: @@ -66,11 +67,12 @@ namespace osu.Game.Rulesets.Osu.Mods // Move slider so that sliderball stays on the cursor else { - slider.HeadCircle.Hide(); // temporary solution to supress HeadCircle's explosion, flash, ... at wrong location + // FIXME: Hide flashes + //slider.HeadCircle.Hide(); slider.MoveTo(cursorPos - slider.Ball.DrawPosition); } - return false; + break; case DrawableSpinner spinner: @@ -78,23 +80,32 @@ namespace osu.Game.Rulesets.Osu.Mods if (currentTime < h.StartTime) { spinner.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); - return false; } else { - spinnerAngle = 0; - activeSpinner = spinner; - return true; + // TODO: + // - get current angle to cursor + // - move clockwise(?) + // - call spinner.RotationTracker.AddRotation + + // TODO: Remove + //spinnerAngle = 0; + //activeSpinner = spinner; } - default: - return true; - } - }); + break; + default: + continue; + } + } + + // Move active spinner around the cursor if (activeSpinner != null) { - if (currentTime > (activeSpinner.HitObject as IHasEndTime)?.EndTime) + double spinnerEndTime = activeSpinner.HitObject.GetEndTime(); + + if (currentTime > spinnerEndTime) { activeSpinner = null; spinnerAngle = 0; @@ -102,29 +113,23 @@ namespace osu.Game.Rulesets.Osu.Mods else { const float additional_degrees = 4; - const int dist_from_cursor = 30; - spinnerAngle += additional_degrees * Math.PI / 180; + float added_degrees = additional_degrees * (float)Math.PI / 180; + spinnerAngle += added_degrees; + + //int spinsRequired = activeSpinner.HitObject.SpinsRequired; + //float spunDegrees = activeSpinner.Result.RateAdjustedRotation; + //double timeLeft = spinnerEndTime - currentTime; // Visual progress - activeSpinner.MoveTo(new Vector2((float)(dist_from_cursor * Math.Cos(spinnerAngle) + cursorPos.X), (float)(dist_from_cursor * Math.Sin(spinnerAngle) + cursorPos.Y))); + activeSpinner.MoveTo(new Vector2((float)(SPIN_RADIUS * Math.Cos(spinnerAngle) + cursorPos.X), (float)(SPIN_RADIUS * Math.Sin(spinnerAngle) + cursorPos.Y))); // Logical progress - activeSpinner.Disc.RotationAbsolute += additional_degrees; + activeSpinner.RotationTracker.AddRotation(added_degrees); + Console.WriteLine($"added_degrees={added_degrees}"); + //activeSpinner.Disc.RotationAbsolute += additional_degrees; } } } - - public void ApplyToDrawableHitObjects(IEnumerable drawables) - { - foreach (var drawable in drawables) - drawable.ApplyCustomUpdateState += drawableOnApplyCustomUpdateState; - } - - private void drawableOnApplyCustomUpdateState(DrawableHitObject drawable, ArmedState state) - { - if (drawable is DrawableOsuHitObject hitobject) - movingObjects.Add(hitobject); - } } /* From 27a8bfa4968672c44c64e7beb1e842894755ecf5 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Tue, 4 Jan 2022 22:17:50 +0100 Subject: [PATCH 015/256] handle spinners and follow points --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 80 +++++-------------- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 9 ++- 2 files changed, 26 insertions(+), 63 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 1849b48073..306e7a8b24 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -2,11 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.UI; @@ -26,23 +24,19 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; - public const float SPIN_RADIUS = 50; // same as OsuAutoGeneratorBase.SPIN_RADIUS - - private DrawableSpinner activeSpinner; - private float spinnerAngle; // in radians + private const float spin_radius = 30; + private Vector2? prevCursorPos; public void Update(Playfield playfield) { var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; double currentTime = playfield.Clock.CurrentTime; - // Judgment displays would all be cramped onto the cursor + // Hide judgment displays and follow points playfield.DisplayJudgements.Value = false; + (playfield as OsuPlayfield)?.FollowPoints.Clear(); - // FIXME: Hide follow points - //(playfield as OsuPlayfield)?.ConnectionLayer.Hide(); - - // If object too old, remove from movingObjects list, otherwise move to new destination + // Move all currently alive object to new destination foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { var h = drawable.HitObject; @@ -79,64 +73,32 @@ namespace osu.Game.Rulesets.Osu.Mods // Move spinner to cursor if (currentTime < h.StartTime) { - spinner.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + spinner.MoveTo(cursorPos + new Vector2(0, -spin_radius), Math.Max(0, h.StartTime - currentTime - 10)); } else { - // TODO: - // - get current angle to cursor - // - move clockwise(?) - // - call spinner.RotationTracker.AddRotation + // Move spinner visually + Vector2 delta = spin_radius * (spinner.Position - prevCursorPos ?? cursorPos).Normalized(); + const float angle = 3 * MathF.PI / 180; // radians per update, arbitrary value - // TODO: Remove - //spinnerAngle = 0; - //activeSpinner = spinner; + // Rotation matrix + var targetPos = new Vector2( + delta.X * MathF.Cos(angle) - delta.Y * MathF.Sin(angle) + cursorPos.X, + delta.X * MathF.Sin(angle) + delta.Y * MathF.Cos(angle) + cursorPos.Y + ); + + spinner.MoveTo(targetPos); + + // Logically finish spinner immediatly, no need for the user to click. + // Temporary workaround until spinner rotations are easier to handle, similar as Autopilot mod. + spinner.Result.RateAdjustedRotation = spinner.HitObject.SpinsRequired * 360; } break; - - default: - continue; } } - // Move active spinner around the cursor - if (activeSpinner != null) - { - double spinnerEndTime = activeSpinner.HitObject.GetEndTime(); - - if (currentTime > spinnerEndTime) - { - activeSpinner = null; - spinnerAngle = 0; - } - else - { - const float additional_degrees = 4; - float added_degrees = additional_degrees * (float)Math.PI / 180; - spinnerAngle += added_degrees; - - //int spinsRequired = activeSpinner.HitObject.SpinsRequired; - //float spunDegrees = activeSpinner.Result.RateAdjustedRotation; - //double timeLeft = spinnerEndTime - currentTime; - - // Visual progress - activeSpinner.MoveTo(new Vector2((float)(SPIN_RADIUS * Math.Cos(spinnerAngle) + cursorPos.X), (float)(SPIN_RADIUS * Math.Sin(spinnerAngle) + cursorPos.Y))); - - // Logical progress - activeSpinner.RotationTracker.AddRotation(added_degrees); - Console.WriteLine($"added_degrees={added_degrees}"); - //activeSpinner.Disc.RotationAbsolute += additional_degrees; - } - } + prevCursorPos = cursorPos; } } - - /* - * TODOs - * - fix sliders reappearing at original position after their EndTime (see https://puu.sh/E7zT4/111cf9cdc8.gif) - * - find nicer way to handle slider headcircle explosion, flash, ... - * - add Aim Assist as incompatible mod for Autoplay (?) - * - */ } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 2233a547b9..bc1e80cd12 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -31,7 +31,8 @@ namespace osu.Game.Rulesets.Osu.UI private readonly ProxyContainer approachCircles; private readonly ProxyContainer spinnerProxies; private readonly JudgementContainer judgementLayer; - private readonly FollowPointRenderer followPoints; + + public FollowPointRenderer FollowPoints { get; } public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); @@ -50,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.UI { playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both }, spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both }, - followPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both }, + FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both }, judgementLayer = new JudgementContainer { RelativeSizeAxes = Axes.Both }, HitObjectContainer, judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both }, @@ -131,13 +132,13 @@ namespace osu.Game.Rulesets.Osu.UI protected override void OnHitObjectAdded(HitObject hitObject) { base.OnHitObjectAdded(hitObject); - followPoints.AddFollowPoints((OsuHitObject)hitObject); + FollowPoints.AddFollowPoints((OsuHitObject)hitObject); } protected override void OnHitObjectRemoved(HitObject hitObject) { base.OnHitObjectRemoved(hitObject); - followPoints.RemoveFollowPoints((OsuHitObject)hitObject); + FollowPoints.RemoveFollowPoints((OsuHitObject)hitObject); } private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) From 5a62760fe4f301d7f6a736f35feee14c847f5c96 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Wed, 5 Jan 2022 13:05:22 +0100 Subject: [PATCH 016/256] hold spinners & minor adjustments --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 306e7a8b24..cde86c8868 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -5,16 +5,16 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Osu.UI; using osuTK; using System.Linq; +using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Mods { - internal class OsuModAimAssist : Mod, IUpdatableByPlayfield + internal class OsuModAimAssist : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset { public override string Name => "Aim Assist"; public override string Acronym => "AA"; @@ -25,7 +25,15 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; private const float spin_radius = 30; + private Vector2? prevCursorPos; + private OsuInputManager inputManager; + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + // Grab the input manager for future use + inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; + } public void Update(Playfield playfield) { @@ -34,20 +42,20 @@ namespace osu.Game.Rulesets.Osu.Mods // Hide judgment displays and follow points playfield.DisplayJudgements.Value = false; - (playfield as OsuPlayfield)?.FollowPoints.Clear(); + (playfield as OsuPlayfield)?.FollowPoints.Hide(); // Move all currently alive object to new destination foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { var h = drawable.HitObject; - double endTime = h.GetEndTime(); switch (drawable) { case DrawableHitCircle circle: // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast - circle.MoveTo(cursorPos, Math.Max(0, endTime - currentTime - 10)); + circle.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + // FIXME: some circles cause flash at original(?) position when clicked too early break; @@ -61,16 +69,16 @@ namespace osu.Game.Rulesets.Osu.Mods // Move slider so that sliderball stays on the cursor else { - // FIXME: Hide flashes - //slider.HeadCircle.Hide(); + slider.HeadCircle.Hide(); // hide flash, triangles, ... so they don't move with slider slider.MoveTo(cursorPos - slider.Ball.DrawPosition); + // FIXME: some sliders re-appearing at their original position for a single frame when they're done } break; case DrawableSpinner spinner: - // Move spinner to cursor + // Move spinner _next_ to cursor if (currentTime < h.StartTime) { spinner.MoveTo(cursorPos + new Vector2(0, -spin_radius), Math.Max(0, h.StartTime - currentTime - 10)); @@ -89,9 +97,12 @@ namespace osu.Game.Rulesets.Osu.Mods spinner.MoveTo(targetPos); - // Logically finish spinner immediatly, no need for the user to click. - // Temporary workaround until spinner rotations are easier to handle, similar as Autopilot mod. - spinner.Result.RateAdjustedRotation = spinner.HitObject.SpinsRequired * 360; + // Move spinner logically + if (inputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false) + { + // Arbitrary value, might lead to some inconsistencies depending on clock rate, replay, ... + spinner.RotationTracker.AddRotation(2 * MathF.PI); + } } break; From 04d060aba3f95cb75899d642a964c168679524a4 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Thu, 6 Jan 2022 10:38:30 +0100 Subject: [PATCH 017/256] update general playfield only once --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index cde86c8868..f983ece71b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -33,6 +33,10 @@ namespace osu.Game.Rulesets.Osu.Mods { // Grab the input manager for future use inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; + + // Hide judgment displays and follow points + drawableRuleset.Playfield.DisplayJudgements.Value = false; + (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); } public void Update(Playfield playfield) @@ -40,10 +44,6 @@ namespace osu.Game.Rulesets.Osu.Mods var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; double currentTime = playfield.Clock.CurrentTime; - // Hide judgment displays and follow points - playfield.DisplayJudgements.Value = false; - (playfield as OsuPlayfield)?.FollowPoints.Hide(); - // Move all currently alive object to new destination foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { From b9d2a10530695421bf5eee2ddad792718624493d Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Thu, 6 Jan 2022 14:47:58 +0100 Subject: [PATCH 018/256] adjustable assist strength + dont update spinner & running slider --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 82 ++++--------------- 1 file changed, 16 insertions(+), 66 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index f983ece71b..c7cabd7ab4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -11,6 +11,8 @@ using osu.Game.Rulesets.Osu.UI; using osuTK; using System.Linq; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Configuration; +using osu.Framework.Bindables; namespace osu.Game.Rulesets.Osu.Mods { @@ -24,16 +26,16 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; - private const float spin_radius = 30; - - private Vector2? prevCursorPos; - private OsuInputManager inputManager; + [SettingSource("Assist strength", "Change the distance notes should travel towards you.", 0)] + public BindableFloat AssistStrength { get; } = new BindableFloat(0.3f) + { + Precision = 0.05f, + MinValue = 0.0f, + MaxValue = 1.0f, + }; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - // Grab the input manager for future use - inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; - // Hide judgment displays and follow points drawableRuleset.Playfield.DisplayJudgements.Value = false; (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); @@ -41,75 +43,23 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { - var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; + Vector2 cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; double currentTime = playfield.Clock.CurrentTime; - // Move all currently alive object to new destination foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { var h = drawable.HitObject; - switch (drawable) + if (currentTime < h.StartTime && (drawable is DrawableHitCircle || drawable is DrawableSlider)) { - case DrawableHitCircle circle: + double timeMoving = currentTime - (h.StartTime - h.TimePreempt); + float percentDoneMoving = (float)(timeMoving / h.TimePreempt); + float percentDistLeft = Math.Clamp(AssistStrength.Value - percentDoneMoving + 0.1f, 0, 1); - // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast - circle.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); - // FIXME: some circles cause flash at original(?) position when clicked too early - - break; - - case DrawableSlider slider: - - // Move slider to cursor - if (currentTime < h.StartTime) - { - slider.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); - } - // Move slider so that sliderball stays on the cursor - else - { - slider.HeadCircle.Hide(); // hide flash, triangles, ... so they don't move with slider - slider.MoveTo(cursorPos - slider.Ball.DrawPosition); - // FIXME: some sliders re-appearing at their original position for a single frame when they're done - } - - break; - - case DrawableSpinner spinner: - - // Move spinner _next_ to cursor - if (currentTime < h.StartTime) - { - spinner.MoveTo(cursorPos + new Vector2(0, -spin_radius), Math.Max(0, h.StartTime - currentTime - 10)); - } - else - { - // Move spinner visually - Vector2 delta = spin_radius * (spinner.Position - prevCursorPos ?? cursorPos).Normalized(); - const float angle = 3 * MathF.PI / 180; // radians per update, arbitrary value - - // Rotation matrix - var targetPos = new Vector2( - delta.X * MathF.Cos(angle) - delta.Y * MathF.Sin(angle) + cursorPos.X, - delta.X * MathF.Sin(angle) + delta.Y * MathF.Cos(angle) + cursorPos.Y - ); - - spinner.MoveTo(targetPos); - - // Move spinner logically - if (inputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false) - { - // Arbitrary value, might lead to some inconsistencies depending on clock rate, replay, ... - spinner.RotationTracker.AddRotation(2 * MathF.PI); - } - } - - break; + Vector2 targetPos = drawable.Position + percentDistLeft * (cursorPos - drawable.Position); + drawable.MoveTo(targetPos, h.StartTime - currentTime); } } - - prevCursorPos = cursorPos; } } } From 197ada1a8cf43c7fcb9db2fffa072e310be22b9e Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Thu, 6 Jan 2022 16:04:38 +0100 Subject: [PATCH 019/256] naive 10hz update --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index c7cabd7ab4..89a385dbdc 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -34,6 +34,8 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 1.0f, }; + private DateTime? lastUpdate; + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { // Hide judgment displays and follow points @@ -43,6 +45,9 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { + if (DateTime.Now - (lastUpdate ?? DateTime.MinValue) < TimeSpan.FromMilliseconds(100)) + return; + Vector2 cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; double currentTime = playfield.Clock.CurrentTime; @@ -60,6 +65,8 @@ namespace osu.Game.Rulesets.Osu.Mods drawable.MoveTo(targetPos, h.StartTime - currentTime); } } + + lastUpdate = DateTime.Now; } } } From b3230868cc88ca7ada83ea6c4c8f0542c333c569 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Thu, 6 Jan 2022 16:31:30 +0100 Subject: [PATCH 020/256] use playfield clock --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 89a385dbdc..a383b533fd 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 1.0f, }; - private DateTime? lastUpdate; + private double? lastUpdate; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { @@ -45,11 +45,12 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { - if (DateTime.Now - (lastUpdate ?? DateTime.MinValue) < TimeSpan.FromMilliseconds(100)) + double currentTime = playfield.Clock.CurrentTime; + + if (currentTime - (lastUpdate ?? double.MinValue) < 100) return; Vector2 cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; - double currentTime = playfield.Clock.CurrentTime; foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { @@ -66,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Mods } } - lastUpdate = DateTime.Now; + lastUpdate = currentTime; } } } From b0d61a18b05424bdc5b39b248a36c5d139ad241c Mon Sep 17 00:00:00 2001 From: pikokr Date: Fri, 7 Jan 2022 15:57:30 +0900 Subject: [PATCH 021/256] Load keyBindingContainer once on LoadComplete() & make touch area height to const --- osu.Game.Rulesets.Mania/UI/Column.cs | 43 ++++++---------------------- 1 file changed, 9 insertions(+), 34 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 1e07c84d9f..cd5f3d2170 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -28,6 +28,7 @@ namespace osu.Game.Rulesets.Mania.UI { public const float COLUMN_WIDTH = 80; public const float SPECIAL_COLUMN_WIDTH = 70; + public const float TOUCH_AREA_HEIGHT = 100; /// /// The index of this column as part of the whole playfield. @@ -44,60 +45,34 @@ namespace osu.Game.Rulesets.Mania.UI private readonly GameplaySampleTriggerSource sampleTriggerSource; - public class ColumnTouchInputArea : Container + public class ColumnTouchInputArea : Drawable { - private Column column => (Column)Parent; - - private Container hintContainer; - public ColumnTouchInputArea() { RelativeSizeAxes = Axes.X; Anchor = Anchor.BottomCentre; Origin = Anchor.BottomCentre; - Height = 100; - InternalChild = hintContainer = new Container - { - RelativeSizeAxes = Axes.Both, - BorderColour = Color4.Red, - BorderThickness = 5, - Masking = true, - }; + Height = TOUCH_AREA_HEIGHT; } + private Column column => (Column)Parent; + protected override void LoadComplete() { - hintContainer.Delay(1000).FadeOutFromOne(500, Easing.OutSine); + keyBindingContainer = (ManiaInputManager.RulesetKeyBindingContainer)((ManiaInputManager)GetContainingInputManager()).KeyBindingContainer; } private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } - private ManiaInputManager.RulesetKeyBindingContainer getKeyBindingContainer() - { - return keyBindingContainer ??= (ManiaInputManager.RulesetKeyBindingContainer)((ManiaInputManager)GetContainingInputManager()).KeyBindingContainer; - } - protected override bool OnTouchDown(TouchDownEvent e) { - getKeyBindingContainer().TriggerPressed(column.Action.Value); - return base.OnTouchDown(e); - } - - protected override bool OnMouseDown(MouseDownEvent e) - { - getKeyBindingContainer().TriggerPressed(column.Action.Value); - - return base.OnMouseDown(e); - } - - protected override void OnMouseUp(MouseUpEvent e) - { - getKeyBindingContainer().TriggerReleased(column.Action.Value); + keyBindingContainer.TriggerPressed(column.Action.Value); + return false; } protected override void OnTouchUp(TouchUpEvent e) { - getKeyBindingContainer().TriggerReleased(column.Action.Value); + keyBindingContainer.TriggerReleased(column.Action.Value); } } From 1bb1366c9f1c7e3d6d0b43f0bdf7d7f0ce3fe551 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 16:26:06 +0900 Subject: [PATCH 022/256] Fix notification reset events potentially arriving out of order if a block operation times out --- osu.Game/Database/RealmAccess.cs | 40 +++++++++++++++----------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index e6e3c9ee41..bf88313663 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -334,25 +334,6 @@ namespace osu.Game.Database } } - /// - /// Unregister all subscriptions when the realm instance is to be recycled. - /// Subscriptions will still remain and will be re-subscribed when the realm instance returns. - /// - private void unregisterAllSubscriptions() - { - lock (realmLock) - { - foreach (var action in notificationsResetMap.Values) - action(); - - foreach (var action in customSubscriptionsResetMap) - { - action.Value?.Dispose(); - customSubscriptionsResetMap[action.Key] = null; - } - } - } - private Realm getRealmInstance() { if (isDisposed) @@ -605,9 +586,16 @@ namespace osu.Game.Database throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); syncContext = SynchronizationContext.Current; - } - unregisterAllSubscriptions(); + // Before disposing the update context, clean up all subscriptions. + // Note that in the case of realm notification subscriptions, this is not really required (they will be cleaned up by disposal). + // In the case of custom subscriptions, we want them to fire before the update realm is disposed in case they do any follow-up work. + foreach (var action in customSubscriptionsResetMap) + { + action.Value?.Dispose(); + customSubscriptionsResetMap[action.Key] = null; + } + } Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); @@ -615,6 +603,16 @@ namespace osu.Game.Database updateRealm = null; } + // In order to ensure events arrive in the correct order, these *must* be fired post disposal of the update realm, + // and must be posted to the synchronization context. + // This is because realm may fire event callbacks between the `unregisterAllSubscriptions` and `updateRealm.Dispose` + // calls above. + syncContext?.Post(_ => + { + foreach (var action in notificationsResetMap.Values) + action(); + }, null); + const int sleep_length = 200; int timeout = 5000; From 958cfde60834a0e5a9342c3dfc7f59893e52942d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 23:45:06 +0900 Subject: [PATCH 023/256] Stop detaching and exposing beatmaps from `MusicController` --- osu.Game/Overlays/MusicController.cs | 14 ++++++-------- osu.Game/Overlays/NowPlayingOverlay.cs | 1 - 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 9dc55d24dd..335abb53dc 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -30,8 +30,6 @@ namespace osu.Game.Overlays [Resolved] private BeatmapManager beatmaps { get; set; } - public IBindableList BeatmapSets => beatmapSets; - /// /// Point in time after which the current track will be restarted on triggering a "previous track" action. /// @@ -88,12 +86,12 @@ namespace osu.Game.Overlays { beatmapSets.Clear(); foreach (var s in sender) - beatmapSets.Add(s.Detach()); + beatmapSets.Add(s); return; } foreach (int i in changes.InsertedIndices) - beatmapSets.Insert(i, sender[i].Detach()); + beatmapSets.Insert(i, sender[i]); foreach (int i in changes.DeletedIndices.OrderByDescending(i => i)) beatmapSets.RemoveAt(i); @@ -240,7 +238,7 @@ namespace osu.Game.Overlays queuedDirection = TrackChangeDirection.Prev; - var playable = BeatmapSets.TakeWhile(i => !i.Equals(current.BeatmapSetInfo)).LastOrDefault() ?? BeatmapSets.LastOrDefault(); + var playable = beatmapSets.TakeWhile(i => !i.Equals(current.BeatmapSetInfo)).LastOrDefault() ?? beatmapSets.LastOrDefault(); if (playable != null) { @@ -271,7 +269,7 @@ namespace osu.Game.Overlays queuedDirection = TrackChangeDirection.Next; - var playableSet = BeatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).ElementAtOrDefault(1) ?? BeatmapSets.FirstOrDefault(); + var playableSet = beatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).ElementAtOrDefault(1) ?? beatmapSets.FirstOrDefault(); var playableBeatmap = playableSet?.Beatmaps.FirstOrDefault(); if (playableBeatmap != null) @@ -322,8 +320,8 @@ namespace osu.Game.Overlays else { // figure out the best direction based on order in playlist. - int last = BeatmapSets.TakeWhile(b => !b.Equals(current.BeatmapSetInfo)).Count(); - int next = newWorking == null ? -1 : BeatmapSets.TakeWhile(b => !b.Equals(newWorking.BeatmapSetInfo)).Count(); + int last = beatmapSets.TakeWhile(b => !b.Equals(current.BeatmapSetInfo)).Count(); + int next = newWorking == null ? -1 : beatmapSets.TakeWhile(b => !b.Equals(newWorking.BeatmapSetInfo)).Count(); direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next; } diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 4dd23c0008..4617a91885 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -197,7 +197,6 @@ namespace osu.Game.Overlays { dragContainer.Add(playlist); - playlist.BeatmapSets.BindTo(musicController.BeatmapSets); playlist.State.BindValueChanged(s => playlistButton.FadeColour(s.NewValue == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint), true); togglePlaylist(); From 8a4f3a7ce093b02dbbfa655146a30308214330d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 01:18:21 +0900 Subject: [PATCH 024/256] Reimplement subscription logic in `PlaylistOverlay` directly --- osu.Game/Overlays/Music/PlaylistOverlay.cs | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 78b2d58dae..245886743a 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -10,9 +11,11 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics; using osuTK; using osuTK.Graphics; +using Realms; namespace osu.Game.Overlays.Music { @@ -30,6 +33,15 @@ namespace osu.Game.Overlays.Music [Resolved] private BeatmapManager beatmaps { get; set; } + [Resolved] + private RealmContextFactory realmFactory { get; set; } + + private IDisposable beatmapSubscription; + + private IQueryable availableBeatmaps => realmFactory.Context + .All() + .Where(s => !s.DeletePending); + private FilterControl filter; private Playlist list; @@ -91,10 +103,31 @@ namespace osu.Game.Overlays.Music { base.LoadComplete(); + // tests might bind externally, in which case we don't want to involve realm. + if (beatmapSets.Count == 0) + beatmapSubscription = realmFactory.RegisterForNotifications(realm => availableBeatmaps, beatmapsChanged); + list.Items.BindTo(beatmapSets); beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo, true); } + private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) + { + if (changes == null) + { + beatmapSets.Clear(); + // must use AddRange to avoid RearrangeableList sort overhead per add op. + beatmapSets.AddRange(sender); + return; + } + + foreach (int i in changes.InsertedIndices) + beatmapSets.Insert(i, sender[i]); + + foreach (int i in changes.DeletedIndices.OrderByDescending(i => i)) + beatmapSets.RemoveAt(i); + } + protected override void PopIn() { filter.Search.HoldFocus = true; @@ -123,5 +156,11 @@ namespace osu.Game.Overlays.Music beatmap.Value = beatmaps.GetWorkingBeatmap(set.Beatmaps.First()); beatmap.Value.Track.Restart(); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + beatmapSubscription?.Dispose(); + } } } From ace2bd2208eead324a2ad8753b4c33bb6e1d02ea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 01:18:29 +0900 Subject: [PATCH 025/256] Apply some initial optimisations to `PlaylistItem` --- osu.Game/Overlays/Music/PlaylistItem.cs | 44 +++++++++++-------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 04c12b8cd7..b3b039148b 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -32,8 +32,6 @@ namespace osu.Game.Overlays.Music : base(item) { Padding = new MarginPadding { Left = 5 }; - - FilterTerms = item.Metadata.GetSearchableTerms(); } [BackgroundDependencyLoader] @@ -46,6 +44,25 @@ namespace osu.Game.Overlays.Music { base.LoadComplete(); + var metadata = Model.Metadata; + + var title = new RomanisableString(metadata.TitleUnicode, metadata.Title); + var artist = new RomanisableString(metadata.ArtistUnicode, metadata.Artist); + + titlePart = text.AddText(title, sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)); + + updateSelectionState(true); + titlePart.DrawablePartsRecreated += _ => updateSelectionState(true); + + text.AddText(@" "); // to separate the title from the artist. + + text.AddText(artist, sprite => + { + sprite.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); + sprite.Colour = colours.Gray9; + sprite.Padding = new MarginPadding { Top = 1 }; + }); + SelectedSet.BindValueChanged(set => { if (set.OldValue?.Equals(Model) != true && set.NewValue?.Equals(Model) != true) @@ -67,27 +84,6 @@ namespace osu.Game.Overlays.Music AutoSizeAxes = Axes.Y, }; - protected override void LoadAsyncComplete() - { - base.LoadAsyncComplete(); - - var title = new RomanisableString(Model.Metadata.TitleUnicode, Model.Metadata.Title); - var artist = new RomanisableString(Model.Metadata.ArtistUnicode, Model.Metadata.Artist); - - titlePart = text.AddText(title, sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)); - updateSelectionState(true); - titlePart.DrawablePartsRecreated += _ => updateSelectionState(true); - - text.AddText(@" "); // to separate the title from the artist. - - text.AddText(artist, sprite => - { - sprite.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); - sprite.Colour = colours.Gray9; - sprite.Padding = new MarginPadding { Top = 1 }; - }); - } - protected override bool OnClick(ClickEvent e) { RequestSelection?.Invoke(Model); @@ -109,7 +105,7 @@ namespace osu.Game.Overlays.Music } } - public IEnumerable FilterTerms { get; } + public IEnumerable FilterTerms => Model.Metadata.GetSearchableTerms(); private bool matchingFilter = true; From 2a786f9ec0302f3ba39561ae5a5d56a0eafa7fe5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 01:49:07 +0900 Subject: [PATCH 026/256] Load text only after it comes on screen (and tidy up selection handling logic) --- osu.Game/Overlays/Music/PlaylistItem.cs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index b3b039148b..b31a03a64d 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -50,12 +50,9 @@ namespace osu.Game.Overlays.Music var artist = new RomanisableString(metadata.ArtistUnicode, metadata.Artist); titlePart = text.AddText(title, sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)); - - updateSelectionState(true); titlePart.DrawablePartsRecreated += _ => updateSelectionState(true); text.AddText(@" "); // to separate the title from the artist. - text.AddText(artist, sprite => { sprite.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); @@ -65,24 +62,31 @@ namespace osu.Game.Overlays.Music SelectedSet.BindValueChanged(set => { - if (set.OldValue?.Equals(Model) != true && set.NewValue?.Equals(Model) != true) + bool newSelected = set.NewValue?.Equals(Model) == true; + + if (newSelected == selected) return; + selected = newSelected; updateSelectionState(false); - }, true); + }); + + updateSelectionState(true); } + private bool selected; + private void updateSelectionState(bool instant) { foreach (Drawable s in titlePart.Drawables) - s.FadeColour(SelectedSet.Value?.Equals(Model) == true ? colours.Yellow : Color4.White, instant ? 0 : FADE_DURATION); + s.FadeColour(selected ? colours.Yellow : Color4.White, instant ? 0 : FADE_DURATION); } - protected override Drawable CreateContent() => text = new OsuTextFlowContainer + protected override Drawable CreateContent() => new DelayedLoadWrapper(text = new OsuTextFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - }; + }); protected override bool OnClick(ClickEvent e) { From 83b0e4572a057c520723c3319d7227c1ccf8951f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 02:39:01 +0900 Subject: [PATCH 027/256] Fix test failures --- osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs index 62f3b63780..7131c19471 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs @@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("drag to 5th", () => { var item = this.ChildrenOfType().ElementAt(4); - InputManager.MoveMouseTo(item.ScreenSpaceDrawQuad.Centre); + InputManager.MoveMouseTo(item.ScreenSpaceDrawQuad.BottomLeft); }); AddAssert("song 1 is 5th", () => beatmapSets[4].Equals(first)); From 1a776a95877907b7d9304ffa6ac4a529558f1b79 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 04:27:07 +0900 Subject: [PATCH 028/256] Completely remove subscription from `MusicController` --- osu.Game/Overlays/Music/PlaylistOverlay.cs | 10 ++-- osu.Game/Overlays/MusicController.cs | 68 ++++------------------ 2 files changed, 16 insertions(+), 62 deletions(-) diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 245886743a..6adcd2a6d6 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -34,13 +34,13 @@ namespace osu.Game.Overlays.Music private BeatmapManager beatmaps { get; set; } [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } private IDisposable beatmapSubscription; - private IQueryable availableBeatmaps => realmFactory.Context - .All() - .Where(s => !s.DeletePending); + private IQueryable availableBeatmaps => realm.Realm + .All() + .Where(s => !s.DeletePending); private FilterControl filter; private Playlist list; @@ -105,7 +105,7 @@ namespace osu.Game.Overlays.Music // tests might bind externally, in which case we don't want to involve realm. if (beatmapSets.Count == 0) - beatmapSubscription = realmFactory.RegisterForNotifications(realm => availableBeatmaps, beatmapsChanged); + beatmapSubscription = realm.RegisterForNotifications(realm => availableBeatmaps, beatmapsChanged); list.Items.BindTo(beatmapSets); beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo, true); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 335abb53dc..5fc0da8891 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -16,7 +16,6 @@ using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Rulesets.Mods; -using Realms; namespace osu.Game.Overlays { @@ -25,8 +24,6 @@ namespace osu.Game.Overlays /// public class MusicController : CompositeDrawable { - private IDisposable beatmapSubscription; - [Resolved] private BeatmapManager beatmaps { get; set; } @@ -35,8 +32,6 @@ namespace osu.Game.Overlays /// private const double restart_cutoff_point = 5000; - private readonly BindableList beatmapSets = new BindableList(); - /// /// Whether the user has requested the track to be paused. Use to determine whether the track is still playing. /// @@ -69,50 +64,11 @@ namespace osu.Game.Overlays mods.BindValueChanged(_ => ResetTrackAdjustments(), true); } - private IQueryable queryRealmBeatmapSets() => - realm.Realm - .All() - .Where(s => !s.DeletePending); - - protected override void LoadComplete() - { - base.LoadComplete(); - beatmapSubscription = realm.RegisterForNotifications(r => queryRealmBeatmapSets(), beatmapsChanged); - } - - private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) - { - if (changes == null) - { - beatmapSets.Clear(); - foreach (var s in sender) - beatmapSets.Add(s); - return; - } - - foreach (int i in changes.InsertedIndices) - beatmapSets.Insert(i, sender[i]); - - foreach (int i in changes.DeletedIndices.OrderByDescending(i => i)) - beatmapSets.RemoveAt(i); - } - /// /// Forcefully reload the current 's track from disk. /// public void ReloadCurrentTrack() => changeTrack(); - /// - /// Change the position of a in the current playlist. - /// - /// The beatmap to move. - /// The new position. - public void ChangeBeatmapSetPosition(BeatmapSetInfo beatmapSetInfo, int index) - { - beatmapSets.Remove(beatmapSetInfo); - beatmapSets.Insert(index, beatmapSetInfo); - } - /// /// Returns whether the beatmap track is playing. /// @@ -238,11 +194,12 @@ namespace osu.Game.Overlays queuedDirection = TrackChangeDirection.Prev; - var playable = beatmapSets.TakeWhile(i => !i.Equals(current.BeatmapSetInfo)).LastOrDefault() ?? beatmapSets.LastOrDefault(); + var playableSet = getBeatmapSets().AsEnumerable().TakeWhile(i => !i.Equals(current.BeatmapSetInfo)).LastOrDefault() + ?? getBeatmapSets().LastOrDefault(); - if (playable != null) + if (playableSet != null) { - changeBeatmap(beatmaps.GetWorkingBeatmap(playable.Beatmaps.First())); + changeBeatmap(beatmaps.GetWorkingBeatmap(playableSet.Beatmaps.First())); restartTrack(); return PreviousTrackResult.Previous; } @@ -269,7 +226,9 @@ namespace osu.Game.Overlays queuedDirection = TrackChangeDirection.Next; - var playableSet = beatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).ElementAtOrDefault(1) ?? beatmapSets.FirstOrDefault(); + var playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current.BeatmapSetInfo)).ElementAtOrDefault(1) + ?? getBeatmapSets().FirstOrDefault(); + var playableBeatmap = playableSet?.Beatmaps.FirstOrDefault(); if (playableBeatmap != null) @@ -293,6 +252,8 @@ namespace osu.Game.Overlays private TrackChangeDirection? queuedDirection; + private IQueryable getBeatmapSets() => realm.Realm.All().Where(s => !s.DeletePending); + private void beatmapChanged(ValueChangedEvent beatmap) => changeBeatmap(beatmap.NewValue); private void changeBeatmap(WorkingBeatmap newWorking) @@ -320,8 +281,8 @@ namespace osu.Game.Overlays else { // figure out the best direction based on order in playlist. - int last = beatmapSets.TakeWhile(b => !b.Equals(current.BeatmapSetInfo)).Count(); - int next = newWorking == null ? -1 : beatmapSets.TakeWhile(b => !b.Equals(newWorking.BeatmapSetInfo)).Count(); + int last = getBeatmapSets().AsEnumerable().TakeWhile(b => !b.Equals(current.BeatmapSetInfo)).Count(); + int next = newWorking == null ? -1 : getBeatmapSets().AsEnumerable().TakeWhile(b => !b.Equals(newWorking.BeatmapSetInfo)).Count(); direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next; } @@ -435,13 +396,6 @@ namespace osu.Game.Overlays mod.ApplyToTrack(CurrentTrack); } } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - beatmapSubscription?.Dispose(); - } } public enum TrackChangeDirection From ffd7877a1e8b6b822fd5f5922111b3d1ae7c027b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 17:41:22 +0900 Subject: [PATCH 029/256] Remove synchronization context hacks in realm tests --- osu.Game.Tests/Database/GeneralUsageTests.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index dc0d42595b..8262ef18d4 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -87,11 +87,6 @@ namespace osu.Game.Tests.Database hasThreadedUsage.Wait(); - // Usually the host would run the synchronization context work per frame. - // For the sake of keeping this test simple (there's only one update invocation), - // let's replace it so we can ensure work is run immediately. - SynchronizationContext.SetSynchronizationContext(new ImmediateExecuteSynchronizationContext()); - Assert.Throws(() => { using (realm.BlockAllOperations()) @@ -107,10 +102,5 @@ namespace osu.Game.Tests.Database } }); } - - private class ImmediateExecuteSynchronizationContext : SynchronizationContext - { - public override void Post(SendOrPostCallback d, object? state) => d(state); - } } } From 4a9f4eecba45e66428de23094f1d1387199bb450 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 20:49:52 +0900 Subject: [PATCH 030/256] Use blocking calls to `SynchronizationContext` to guarantee order of execution --- osu.Game/Database/RealmAccess.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index bf88313663..ffaae9b4ae 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -607,7 +607,7 @@ namespace osu.Game.Database // and must be posted to the synchronization context. // This is because realm may fire event callbacks between the `unregisterAllSubscriptions` and `updateRealm.Dispose` // calls above. - syncContext?.Post(_ => + syncContext?.Send(_ => { foreach (var action in notificationsResetMap.Values) action(); @@ -647,8 +647,14 @@ namespace osu.Game.Database { Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); realmRetrievalLock.Release(); + // Post back to the update thread to revive any subscriptions. - syncContext?.Post(_ => ensureUpdateRealm(), null); + // In the case we are on the update thread, let's also require this to run synchronously. + // This requirement is mostly due to test coverage, but shouldn't cause any harm. + if (ThreadSafety.IsUpdateThread) + syncContext?.Send(_ => ensureUpdateRealm(), null); + else + syncContext?.Post(_ => ensureUpdateRealm(), null); } } From 64914c45a4d7528713b8d06d3e7d54c2335db2cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 11:53:50 +0900 Subject: [PATCH 031/256] Remove unnecessary realm query helper method --- osu.Game/Overlays/Music/PlaylistOverlay.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 6adcd2a6d6..ffddf168ed 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -38,10 +38,6 @@ namespace osu.Game.Overlays.Music private IDisposable beatmapSubscription; - private IQueryable availableBeatmaps => realm.Realm - .All() - .Where(s => !s.DeletePending); - private FilterControl filter; private Playlist list; @@ -105,7 +101,7 @@ namespace osu.Game.Overlays.Music // tests might bind externally, in which case we don't want to involve realm. if (beatmapSets.Count == 0) - beatmapSubscription = realm.RegisterForNotifications(realm => availableBeatmaps, beatmapsChanged); + beatmapSubscription = realm.RegisterForNotifications(r => r.All().Where(s => !s.DeletePending), beatmapsChanged); list.Items.BindTo(beatmapSets); beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo, true); From dda513dd08f4aad11b847fef0da0ef4e669f35b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 12:19:05 +0900 Subject: [PATCH 032/256] Change `PlaylistOverlay` to use `ILive` --- .../UserInterface/TestScenePlaylistOverlay.cs | 9 +-- osu.Game/Collections/BeatmapCollection.cs | 2 +- .../Collections/CollectionFilterDropdown.cs | 4 +- osu.Game/Overlays/Music/Playlist.cs | 17 ++--- osu.Game/Overlays/Music/PlaylistItem.cs | 66 ++++++++++--------- osu.Game/Overlays/Music/PlaylistOverlay.cs | 42 +++++++----- 6 files changed, 76 insertions(+), 64 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs index 7131c19471..1a3e38ddd7 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.Overlays.Music; using osu.Game.Tests.Resources; @@ -18,11 +19,11 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestScenePlaylistOverlay : OsuManualInputManagerTestScene { - private readonly BindableList beatmapSets = new BindableList(); + private readonly BindableList> beatmapSets = new BindableList>(); private PlaylistOverlay playlistOverlay; - private BeatmapSetInfo first; + private ILive first; [SetUp] public void Setup() => Schedule(() => @@ -45,7 +46,7 @@ namespace osu.Game.Tests.Visual.UserInterface for (int i = 0; i < 100; i++) { - beatmapSets.Add(TestResources.CreateTestBeatmapSetInfo()); + beatmapSets.Add(TestResources.CreateTestBeatmapSetInfo().ToLiveUnmanaged()); } first = beatmapSets.First(); @@ -60,7 +61,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("hold 1st item handle", () => { - var handle = this.ChildrenOfType.PlaylistItemHandle>().First(); + var handle = this.ChildrenOfType>.PlaylistItemHandle>().First(); InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre); InputManager.PressButton(MouseButton.Left); }); diff --git a/osu.Game/Collections/BeatmapCollection.cs b/osu.Game/Collections/BeatmapCollection.cs index 1a739f824f..7e4b15ecf9 100644 --- a/osu.Game/Collections/BeatmapCollection.cs +++ b/osu.Game/Collections/BeatmapCollection.cs @@ -25,7 +25,7 @@ namespace osu.Game.Collections /// /// The beatmaps contained by the collection. /// - public readonly BindableList Beatmaps = new BindableList(); + public readonly BindableList Beatmaps = new BindableList(); /// /// The date when this collection was last modified. diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index 77bda00107..c46ba8e06e 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -38,7 +38,7 @@ namespace osu.Game.Collections } private readonly IBindableList collections = new BindableList(); - private readonly IBindableList beatmaps = new BindableList(); + private readonly IBindableList beatmaps = new BindableList(); private readonly BindableList filters = new BindableList(); [Resolved(CanBeNull = true)] @@ -196,7 +196,7 @@ namespace osu.Game.Collections private IBindable beatmap { get; set; } [CanBeNull] - private readonly BindableList collectionBeatmaps; + private readonly BindableList collectionBeatmaps; [NotNull] private readonly Bindable collectionName; diff --git a/osu.Game/Overlays/Music/Playlist.cs b/osu.Game/Overlays/Music/Playlist.cs index 0b15a3a1bc..c86146ff25 100644 --- a/osu.Game/Overlays/Music/Playlist.cs +++ b/osu.Game/Overlays/Music/Playlist.cs @@ -7,16 +7,17 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics.Containers; using osuTK; namespace osu.Game.Overlays.Music { - public class Playlist : OsuRearrangeableListContainer + public class Playlist : OsuRearrangeableListContainer> { - public Action RequestSelection; + public Action> RequestSelection; - public readonly Bindable SelectedSet = new Bindable(); + public readonly Bindable> SelectedSet = new Bindable>(); public new MarginPadding Padding { @@ -26,23 +27,23 @@ namespace osu.Game.Overlays.Music public void Filter(FilterCriteria criteria) { - var items = (SearchContainer>)ListContainer; + var items = (SearchContainer>>)ListContainer; foreach (var item in items.OfType()) - item.InSelectedCollection = criteria.Collection?.Beatmaps.Any(b => item.Model.Equals(b.BeatmapSet)) ?? true; + item.InSelectedCollection = criteria.Collection?.Beatmaps.Any(b => item.Model.ID == b.BeatmapSet?.ID) ?? true; items.SearchTerm = criteria.SearchText; } - public BeatmapSetInfo FirstVisibleSet => Items.FirstOrDefault(i => ((PlaylistItem)ItemMap[i]).MatchingFilter); + public ILive FirstVisibleSet => Items.FirstOrDefault(i => ((PlaylistItem)ItemMap[i]).MatchingFilter); - protected override OsuRearrangeableListItem CreateOsuDrawable(BeatmapSetInfo item) => new PlaylistItem(item) + protected override OsuRearrangeableListItem> CreateOsuDrawable(ILive item) => new PlaylistItem(item) { SelectedSet = { BindTarget = SelectedSet }, RequestSelection = set => RequestSelection?.Invoke(set) }; - protected override FillFlowContainer> CreateListFillFlowContainer() => new SearchContainer> + protected override FillFlowContainer>> CreateListFillFlowContainer() => new SearchContainer>> { Spacing = new Vector2(0, 3), LayoutDuration = 200, diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index b31a03a64d..3f82580bfb 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -10,17 +10,18 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK.Graphics; namespace osu.Game.Overlays.Music { - public class PlaylistItem : OsuRearrangeableListItem, IFilterable + public class PlaylistItem : OsuRearrangeableListItem>, IFilterable { - public readonly Bindable SelectedSet = new Bindable(); + public readonly Bindable> SelectedSet = new Bindable>(); - public Action RequestSelection; + public Action> RequestSelection; private TextFlowContainer text; private ITextPart titlePart; @@ -28,7 +29,7 @@ namespace osu.Game.Overlays.Music [Resolved] private OsuColour colours { get; set; } - public PlaylistItem(BeatmapSetInfo item) + public PlaylistItem(ILive item) : base(item) { Padding = new MarginPadding { Left = 5 }; @@ -44,34 +45,37 @@ namespace osu.Game.Overlays.Music { base.LoadComplete(); - var metadata = Model.Metadata; - - var title = new RomanisableString(metadata.TitleUnicode, metadata.Title); - var artist = new RomanisableString(metadata.ArtistUnicode, metadata.Artist); - - titlePart = text.AddText(title, sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)); - titlePart.DrawablePartsRecreated += _ => updateSelectionState(true); - - text.AddText(@" "); // to separate the title from the artist. - text.AddText(artist, sprite => + Model.PerformRead(m => { - sprite.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); - sprite.Colour = colours.Gray9; - sprite.Padding = new MarginPadding { Top = 1 }; + var metadata = m.Metadata; + + var title = new RomanisableString(metadata.TitleUnicode, metadata.Title); + var artist = new RomanisableString(metadata.ArtistUnicode, metadata.Artist); + + titlePart = text.AddText(title, sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)); + titlePart.DrawablePartsRecreated += _ => updateSelectionState(true); + + text.AddText(@" "); // to separate the title from the artist. + text.AddText(artist, sprite => + { + sprite.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); + sprite.Colour = colours.Gray9; + sprite.Padding = new MarginPadding { Top = 1 }; + }); + + SelectedSet.BindValueChanged(set => + { + bool newSelected = set.NewValue?.Equals(Model) == true; + + if (newSelected == selected) + return; + + selected = newSelected; + updateSelectionState(false); + }); + + updateSelectionState(true); }); - - SelectedSet.BindValueChanged(set => - { - bool newSelected = set.NewValue?.Equals(Model) == true; - - if (newSelected == selected) - return; - - selected = newSelected; - updateSelectionState(false); - }); - - updateSelectionState(true); } private bool selected; @@ -109,7 +113,7 @@ namespace osu.Game.Overlays.Music } } - public IEnumerable FilterTerms => Model.Metadata.GetSearchableTerms(); + public IEnumerable FilterTerms => Model.PerformRead(m => m.Metadata.GetSearchableTerms()); private bool matchingFilter = true; diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index ffddf168ed..4b10bb779e 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -24,9 +24,9 @@ namespace osu.Game.Overlays.Music private const float transition_duration = 600; private const float playlist_height = 510; - public IBindableList BeatmapSets => beatmapSets; + public IBindableList> BeatmapSets => beatmapSets; - private readonly BindableList beatmapSets = new BindableList(); + private readonly BindableList> beatmapSets = new BindableList>(); private readonly Bindable beatmap = new Bindable(); @@ -85,13 +85,16 @@ namespace osu.Game.Overlays.Music filter.Search.OnCommit += (sender, newText) => { - BeatmapInfo toSelect = list.FirstVisibleSet?.Beatmaps.FirstOrDefault(); - - if (toSelect != null) + list.FirstVisibleSet.PerformRead(set => { - beatmap.Value = beatmaps.GetWorkingBeatmap(toSelect); - beatmap.Value.Track.Restart(); - } + BeatmapInfo toSelect = set.Beatmaps.FirstOrDefault(); + + if (toSelect != null) + { + beatmap.Value = beatmaps.GetWorkingBeatmap(toSelect); + beatmap.Value.Track.Restart(); + } + }); }; } @@ -104,7 +107,7 @@ namespace osu.Game.Overlays.Music beatmapSubscription = realm.RegisterForNotifications(r => r.All().Where(s => !s.DeletePending), beatmapsChanged); list.Items.BindTo(beatmapSets); - beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo, true); + beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo.ToLive(realm), true); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) @@ -113,12 +116,12 @@ namespace osu.Game.Overlays.Music { beatmapSets.Clear(); // must use AddRange to avoid RearrangeableList sort overhead per add op. - beatmapSets.AddRange(sender); + beatmapSets.AddRange(sender.ToLive(realm)); return; } foreach (int i in changes.InsertedIndices) - beatmapSets.Insert(i, sender[i]); + beatmapSets.Insert(i, sender[i].ToLive(realm)); foreach (int i in changes.DeletedIndices.OrderByDescending(i => i)) beatmapSets.RemoveAt(i); @@ -141,16 +144,19 @@ namespace osu.Game.Overlays.Music this.FadeOut(transition_duration); } - private void itemSelected(BeatmapSetInfo set) + private void itemSelected(ILive beatmapSet) { - if (set.Equals((beatmap.Value?.BeatmapSetInfo))) + beatmapSet.PerformRead(set => { - beatmap.Value?.Track.Seek(0); - return; - } + if (set.Equals((beatmap.Value?.BeatmapSetInfo))) + { + beatmap.Value?.Track.Seek(0); + return; + } - beatmap.Value = beatmaps.GetWorkingBeatmap(set.Beatmaps.First()); - beatmap.Value.Track.Restart(); + beatmap.Value = beatmaps.GetWorkingBeatmap(set.Beatmaps.First()); + beatmap.Value.Track.Restart(); + }); } protected override void Dispose(bool isDisposing) From 3491b77c8cc95ce752a25434e00f130227dad182 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 14:25:55 +0900 Subject: [PATCH 033/256] Fix `ScoreInfo.RealmUser` not getting deep cloned correctly I'm still not at all happy with the play-to-results flow (with multiple clones), but this will have to do for now. --- osu.Game.Tests/NonVisual/ScoreInfoTest.cs | 4 ++++ osu.Game/Scoring/ScoreInfo.cs | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/osu.Game.Tests/NonVisual/ScoreInfoTest.cs b/osu.Game.Tests/NonVisual/ScoreInfoTest.cs index e0acc6d8db..836c978ba2 100644 --- a/osu.Game.Tests/NonVisual/ScoreInfoTest.cs +++ b/osu.Game.Tests/NonVisual/ScoreInfoTest.cs @@ -26,12 +26,16 @@ namespace osu.Game.Tests.NonVisual score.Statistics[HitResult.Good]++; score.Rank = ScoreRank.X; + score.RealmUser.Username = "test"; Assert.That(scoreCopy.Statistics[HitResult.Good], Is.EqualTo(10)); Assert.That(score.Statistics[HitResult.Good], Is.EqualTo(11)); Assert.That(scoreCopy.Rank, Is.EqualTo(ScoreRank.B)); Assert.That(score.Rank, Is.EqualTo(ScoreRank.X)); + + Assert.That(scoreCopy.RealmUser.Username, Is.EqualTo("test")); + Assert.That(score.Rank, Is.Empty); } [Test] diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index a28e16450f..4de1d580dc 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -133,6 +133,11 @@ namespace osu.Game.Scoring var clone = (ScoreInfo)this.Detach().MemberwiseClone(); clone.Statistics = new Dictionary(clone.Statistics); + clone.RealmUser = new RealmUser + { + OnlineID = RealmUser.OnlineID, + Username = RealmUser.Username, + }; return clone; } From 9532454e2a0d691652f40b7e15c3bd3503d9c9b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 15:12:01 +0900 Subject: [PATCH 034/256] Remove `ILive` remnants --- .../UserInterface/TestScenePlaylistOverlay.cs | 6 +++--- osu.Game/Overlays/Music/Playlist.cs | 14 +++++++------- osu.Game/Overlays/Music/PlaylistItem.cs | 8 ++++---- osu.Game/Overlays/Music/PlaylistOverlay.cs | 6 +++--- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs index 1a3e38ddd7..09e5bc849e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs @@ -19,11 +19,11 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestScenePlaylistOverlay : OsuManualInputManagerTestScene { - private readonly BindableList> beatmapSets = new BindableList>(); + private readonly BindableList> beatmapSets = new BindableList>(); private PlaylistOverlay playlistOverlay; - private ILive first; + private Live first; [SetUp] public void Setup() => Schedule(() => @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("hold 1st item handle", () => { - var handle = this.ChildrenOfType>.PlaylistItemHandle>().First(); + var handle = this.ChildrenOfType>.PlaylistItemHandle>().First(); InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre); InputManager.PressButton(MouseButton.Left); }); diff --git a/osu.Game/Overlays/Music/Playlist.cs b/osu.Game/Overlays/Music/Playlist.cs index c86146ff25..24d867141c 100644 --- a/osu.Game/Overlays/Music/Playlist.cs +++ b/osu.Game/Overlays/Music/Playlist.cs @@ -13,11 +13,11 @@ using osuTK; namespace osu.Game.Overlays.Music { - public class Playlist : OsuRearrangeableListContainer> + public class Playlist : OsuRearrangeableListContainer> { - public Action> RequestSelection; + public Action> RequestSelection; - public readonly Bindable> SelectedSet = new Bindable>(); + public readonly Bindable> SelectedSet = new Bindable>(); public new MarginPadding Padding { @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Music public void Filter(FilterCriteria criteria) { - var items = (SearchContainer>>)ListContainer; + var items = (SearchContainer>>)ListContainer; foreach (var item in items.OfType()) item.InSelectedCollection = criteria.Collection?.Beatmaps.Any(b => item.Model.ID == b.BeatmapSet?.ID) ?? true; @@ -35,15 +35,15 @@ namespace osu.Game.Overlays.Music items.SearchTerm = criteria.SearchText; } - public ILive FirstVisibleSet => Items.FirstOrDefault(i => ((PlaylistItem)ItemMap[i]).MatchingFilter); + public Live FirstVisibleSet => Items.FirstOrDefault(i => ((PlaylistItem)ItemMap[i]).MatchingFilter); - protected override OsuRearrangeableListItem> CreateOsuDrawable(ILive item) => new PlaylistItem(item) + protected override OsuRearrangeableListItem> CreateOsuDrawable(Live item) => new PlaylistItem(item) { SelectedSet = { BindTarget = SelectedSet }, RequestSelection = set => RequestSelection?.Invoke(set) }; - protected override FillFlowContainer>> CreateListFillFlowContainer() => new SearchContainer>> + protected override FillFlowContainer>> CreateListFillFlowContainer() => new SearchContainer>> { Spacing = new Vector2(0, 3), LayoutDuration = 200, diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 3f82580bfb..f081cc0503 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -17,11 +17,11 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Music { - public class PlaylistItem : OsuRearrangeableListItem>, IFilterable + public class PlaylistItem : OsuRearrangeableListItem>, IFilterable { - public readonly Bindable> SelectedSet = new Bindable>(); + public readonly Bindable> SelectedSet = new Bindable>(); - public Action> RequestSelection; + public Action> RequestSelection; private TextFlowContainer text; private ITextPart titlePart; @@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Music [Resolved] private OsuColour colours { get; set; } - public PlaylistItem(ILive item) + public PlaylistItem(Live item) : base(item) { Padding = new MarginPadding { Left = 5 }; diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 4b10bb779e..7b705a905b 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -24,9 +24,9 @@ namespace osu.Game.Overlays.Music private const float transition_duration = 600; private const float playlist_height = 510; - public IBindableList> BeatmapSets => beatmapSets; + public IBindableList> BeatmapSets => beatmapSets; - private readonly BindableList> beatmapSets = new BindableList>(); + private readonly BindableList> beatmapSets = new BindableList>(); private readonly Bindable beatmap = new Bindable(); @@ -144,7 +144,7 @@ namespace osu.Game.Overlays.Music this.FadeOut(transition_duration); } - private void itemSelected(ILive beatmapSet) + private void itemSelected(Live beatmapSet) { beatmapSet.PerformRead(set => { From d0a2818847d85fe68e99a3cc5feb11ca4602e130 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 15:14:43 +0900 Subject: [PATCH 035/256] Fix incorrect testing --- osu.Game.Tests/NonVisual/ScoreInfoTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/NonVisual/ScoreInfoTest.cs b/osu.Game.Tests/NonVisual/ScoreInfoTest.cs index 836c978ba2..41b08a9e98 100644 --- a/osu.Game.Tests/NonVisual/ScoreInfoTest.cs +++ b/osu.Game.Tests/NonVisual/ScoreInfoTest.cs @@ -34,8 +34,8 @@ namespace osu.Game.Tests.NonVisual Assert.That(scoreCopy.Rank, Is.EqualTo(ScoreRank.B)); Assert.That(score.Rank, Is.EqualTo(ScoreRank.X)); - Assert.That(scoreCopy.RealmUser.Username, Is.EqualTo("test")); - Assert.That(score.Rank, Is.Empty); + Assert.That(scoreCopy.RealmUser.Username, Is.Empty); + Assert.That(score.RealmUser.Username, Is.EqualTo("test")); } [Test] From 267a7bd21fa709fc941d3620fe2c57c48e5bbe4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 15:14:49 +0900 Subject: [PATCH 036/256] Give `RealmUser.Username` a better default value --- osu.Game/Models/RealmUser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Models/RealmUser.cs b/osu.Game/Models/RealmUser.cs index ff35528827..5fccff597c 100644 --- a/osu.Game/Models/RealmUser.cs +++ b/osu.Game/Models/RealmUser.cs @@ -11,7 +11,7 @@ namespace osu.Game.Models { public int OnlineID { get; set; } = 1; - public string Username { get; set; } + public string Username { get; set; } = string.Empty; public bool IsBot => false; From 4fe3d83fc40a176edf7ba9a1e3785c0fb57d24f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 15:21:14 +0900 Subject: [PATCH 037/256] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 4e5b9fdbb1..f85a96f819 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index af5d8a5920..aa6fb93aa0 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 2bcdea61b3..fbb4688588 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - + From 5ea781faef9a4fe2de3be7a72ce84ad2306ecf23 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 15:24:53 +0900 Subject: [PATCH 038/256] `Send` unsubscribe actions to synchronization context for consistency and safety --- osu.Game/Database/RealmAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 3866138d40..3572446aef 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -300,7 +300,7 @@ namespace osu.Game.Database return new InvokeOnDisposal(() => { if (ThreadSafety.IsUpdateThread) - unsubscribe(); + syncContext.Send(_ => unsubscribe(), null); else syncContext.Post(_ => unsubscribe(), null); From 24bcba64183a7bf404eca503a3c31a24762bfdac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 15:57:05 +0900 Subject: [PATCH 039/256] Move final result set firing to before the update realm is disposed Without this, if any registered callback attempts to access `RealmAccess.Realm` when handling the empty set callback, it will deadlock the game. --- osu.Game/Database/RealmAccess.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 3572446aef..1a40176c1d 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -604,20 +604,18 @@ namespace osu.Game.Database Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); + // Force a flush of any pending callbacks in the synchronization context. + // We want to ensure that the empty set callbacks are the last thing to arrive. + syncContext?.Send(_ => + { + foreach (var action in notificationsResetMap.Values) + action(); + }, null); + updateRealm?.Dispose(); updateRealm = null; } - // In order to ensure events arrive in the correct order, these *must* be fired post disposal of the update realm, - // and must be posted to the synchronization context. - // This is because realm may fire event callbacks between the `unregisterAllSubscriptions` and `updateRealm.Dispose` - // calls above. - syncContext?.Send(_ => - { - foreach (var action in notificationsResetMap.Values) - action(); - }, null); - const int sleep_length = 200; int timeout = 5000; From 11f0f3c17de6986c7aa6f6a3915b239bf2b5543e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 16:21:24 +0900 Subject: [PATCH 040/256] Revert "Move final result set firing to before the update realm is disposed" This reverts commit 24bcba64183a7bf404eca503a3c31a24762bfdac. --- osu.Game/Database/RealmAccess.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 1a40176c1d..3572446aef 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -604,18 +604,20 @@ namespace osu.Game.Database Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); - // Force a flush of any pending callbacks in the synchronization context. - // We want to ensure that the empty set callbacks are the last thing to arrive. - syncContext?.Send(_ => - { - foreach (var action in notificationsResetMap.Values) - action(); - }, null); - updateRealm?.Dispose(); updateRealm = null; } + // In order to ensure events arrive in the correct order, these *must* be fired post disposal of the update realm, + // and must be posted to the synchronization context. + // This is because realm may fire event callbacks between the `unregisterAllSubscriptions` and `updateRealm.Dispose` + // calls above. + syncContext?.Send(_ => + { + foreach (var action in notificationsResetMap.Values) + action(); + }, null); + const int sleep_length = 200; int timeout = 5000; From 791ea0308f8026c127d26ee2118b083ffa72abf9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 17:09:28 +0900 Subject: [PATCH 041/256] Add flag to guard against deadlocks during blocking operations --- osu.Game/Database/RealmAccess.cs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 3572446aef..dc7c8bf668 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -89,10 +89,15 @@ namespace osu.Game.Database private Realm? updateRealm; + private bool isSendingNotificationResetEvents; + public Realm Realm => ensureUpdateRealm(); private Realm ensureUpdateRealm() { + if (isSendingNotificationResetEvents) + throw new InvalidOperationException("Cannot retrieve a realm context from a notification callback during a blocking operation."); + if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException(@$"Use {nameof(getRealmInstance)} when performing realm operations from a non-update thread"); @@ -614,8 +619,20 @@ namespace osu.Game.Database // calls above. syncContext?.Send(_ => { - foreach (var action in notificationsResetMap.Values) - action(); + // Flag ensures that we don't get in a deadlocked scenario due to a callback attempting to access `RealmAccess.Realm` or `RealmAccess.Run` + // and hitting `realmRetrievalLock` a second time. Generally such usages should not exist, and as such we throw when an attempt is made + // to use in this fashion. + isSendingNotificationResetEvents = true; + + try + { + foreach (var action in notificationsResetMap.Values) + action(); + } + finally + { + isSendingNotificationResetEvents = false; + } }, null); const int sleep_length = 200; From 885fb92aada99e4dfccb0904f544e16a093787ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 17:21:57 +0900 Subject: [PATCH 042/256] Move final empty result set sending to post-compact --- osu.Game/Database/RealmAccess.cs | 44 ++++++++++++++++---------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index dc7c8bf668..9bdbebfe89 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -613,28 +613,6 @@ namespace osu.Game.Database updateRealm = null; } - // In order to ensure events arrive in the correct order, these *must* be fired post disposal of the update realm, - // and must be posted to the synchronization context. - // This is because realm may fire event callbacks between the `unregisterAllSubscriptions` and `updateRealm.Dispose` - // calls above. - syncContext?.Send(_ => - { - // Flag ensures that we don't get in a deadlocked scenario due to a callback attempting to access `RealmAccess.Realm` or `RealmAccess.Run` - // and hitting `realmRetrievalLock` a second time. Generally such usages should not exist, and as such we throw when an attempt is made - // to use in this fashion. - isSendingNotificationResetEvents = true; - - try - { - foreach (var action in notificationsResetMap.Values) - action(); - } - finally - { - isSendingNotificationResetEvents = false; - } - }, null); - const int sleep_length = 200; int timeout = 5000; @@ -656,6 +634,28 @@ namespace osu.Game.Database // We still want to continue with the blocking operation, though. Logger.Log($"Realm compact failed with error {e}", LoggingTarget.Database); } + + // In order to ensure events arrive in the correct order, these *must* be fired post disposal of the update realm, + // and must be posted to the synchronization context. + // This is because realm may fire event callbacks between the `unregisterAllSubscriptions` and `updateRealm.Dispose` + // calls above. + syncContext?.Send(_ => + { + // Flag ensures that we don't get in a deadlocked scenario due to a callback attempting to access `RealmAccess.Realm` or `RealmAccess.Run` + // and hitting `realmRetrievalLock` a second time. Generally such usages should not exist, and as such we throw when an attempt is made + // to use in this fashion. + isSendingNotificationResetEvents = true; + + try + { + foreach (var action in notificationsResetMap.Values) + action(); + } + finally + { + isSendingNotificationResetEvents = false; + } + }, null); } catch { From d1a22562621a2768056d3604400108f0b20c5441 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 17:48:11 +0900 Subject: [PATCH 043/256] Refactor `SkinSection` to avoid unnecessary realm queries --- .../Overlays/Settings/Sections/SkinSection.cs | 83 ++++++++++--------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 0846c023c1..441a439596 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -18,6 +18,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Skinning; using osu.Game.Skinning.Editor; +using Realms; namespace osu.Game.Overlays.Settings.Sections { @@ -51,12 +52,6 @@ namespace osu.Game.Overlays.Settings.Sections private IDisposable realmSubscription; - private IQueryable queryRealmSkins() => - realm.Realm.All() - .Where(s => !s.DeletePending) - .OrderByDescending(s => s.Protected) // protected skins should be at the top. - .ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase); - [BackgroundDependencyLoader(permitNulls: true)] private void load(OsuConfigManager config, [CanBeNull] SkinEditorOverlay skinEditor) { @@ -83,37 +78,54 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown.Current = dropdownBindable; - realmSubscription = realm.RegisterForNotifications(r => queryRealmSkins(), (sender, changes, error) => - { - // The first fire of this is a bit redundant due to the call below, - // but this is safest in case the subscription is restored after a context recycle. - updateItems(); - }); - - updateItems(); + realmSubscription = realm.RegisterForNotifications(r => realm.Realm.All() + .Where(s => !s.DeletePending) + .OrderByDescending(s => s.Protected) // protected skins should be at the top. + .ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase), skinsChanged); configBindable.BindValueChanged(id => Scheduler.AddOnce(updateSelectedSkinFromConfig)); - updateSelectedSkinFromConfig(); - dropdownBindable.BindValueChanged(skin => + dropdownBindable.BindValueChanged(dropdownSelectionChanged); + } + + private void dropdownSelectionChanged(ValueChangedEvent> skin) + { + // Only handle cases where it's clear the user has intent to change skins. + if (skin.OldValue == null) return; + + if (skin.NewValue.Equals(random_skin_info)) { - if (skin.NewValue.Equals(random_skin_info)) + var skinBefore = skins.CurrentSkinInfo.Value; + + skins.SelectRandomSkin(); + + if (skinBefore == skins.CurrentSkinInfo.Value) { - var skinBefore = skins.CurrentSkinInfo.Value; - - skins.SelectRandomSkin(); - - if (skinBefore == skins.CurrentSkinInfo.Value) - { - // the random selection didn't change the skin, so we should manually update the dropdown to match. - dropdownBindable.Value = skins.CurrentSkinInfo.Value; - } - - return; + // the random selection didn't change the skin, so we should manually update the dropdown to match. + dropdownBindable.Value = skins.CurrentSkinInfo.Value; } - configBindable.Value = skin.NewValue.ID.ToString(); - }); + return; + } + + configBindable.Value = skin.NewValue.ID.ToString(); + } + + private void skinsChanged(IRealmCollection skins, ChangeSet changes, Exception error) + { + // This can only mean that realm is recycling, else we would see the protected skins. + // Because we are using `Live<>` in this class, we don't need to worry about this scenario too much. + if (!skins.Any()) + return; + + int protectedCount = skins.Count(s => s.Protected); + + skinItems = skins.ToLive(realm); + skinItems.Insert(protectedCount, random_skin_info); + + skinDropdown.Items = skinItems; + + updateSelectedSkinFromConfig(); } private void updateSelectedSkinFromConfig() @@ -126,17 +138,6 @@ namespace osu.Game.Overlays.Settings.Sections dropdownBindable.Value = skin ?? skinDropdown.Items.First(); } - private void updateItems() - { - int protectedCount = queryRealmSkins().Count(s => s.Protected); - - skinItems = queryRealmSkins().ToLive(realm); - - skinItems.Insert(protectedCount, random_skin_info); - - skinDropdown.Items = skinItems; - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From f2d48d088d81773f1d93f298b8e7d142565b7e80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 17:57:03 +0900 Subject: [PATCH 044/256] Fix realm migration failures with presence of databased EF rulesets that don't exist on disk --- osu.Game/Rulesets/EFRulesetInfo.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/EFRulesetInfo.cs b/osu.Game/Rulesets/EFRulesetInfo.cs index 473b7c657e..9559cfa00c 100644 --- a/osu.Game/Rulesets/EFRulesetInfo.cs +++ b/osu.Game/Rulesets/EFRulesetInfo.cs @@ -28,21 +28,15 @@ namespace osu.Game.Rulesets public Ruleset CreateInstance() { if (!Available) - throw new RulesetLoadException(@"Ruleset not available"); + return null; var type = Type.GetType(InstantiationInfo); if (type == null) - throw new RulesetLoadException(@"Type lookup failure"); + return null; var ruleset = Activator.CreateInstance(type) as Ruleset; - if (ruleset == null) - throw new RulesetLoadException(@"Instantiation failure"); - - // overwrite the pre-populated RulesetInfo with a potentially database attached copy. - // ruleset.RulesetInfo = this; - return ruleset; } From 3aa681005bd5bf676b0cdf912aa0567a8120f077 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 18:04:53 +0900 Subject: [PATCH 045/256] Skip importing scores which have no matching realm ruleset There's no real way to recover these unless we want to start importing rulesets into realm. And that seems counter productive. This can only happen if users don't have the dll present any more, and it was removed far before realm was tracking rulesets (else it would have an `Available=0` entry in realm to match). --- osu.Game/Database/EFToRealmMigrator.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index adf91e4a41..33f7c25b28 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -286,6 +286,7 @@ namespace osu.Game.Database var transaction = r.BeginWrite(); int written = 0; + int missing = 0; try { @@ -300,6 +301,13 @@ namespace osu.Game.Database var beatmap = r.All().First(b => b.Hash == score.BeatmapInfo.Hash); var ruleset = r.Find(score.Ruleset.ShortName); + + if (ruleset == null) + { + log($"Skipping {++missing} scores with missing ruleset"); + continue; + } + var user = new RealmUser { OnlineID = score.User.OnlineID, From 45636ce04b224ca516b798c253f2289c357aaa5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 18:25:27 +0900 Subject: [PATCH 046/256] Remove collection `ToLive` helper method to avoid confusion --- osu.Game/Database/RealmObjectExtensions.cs | 6 ------ .../Overlays/Settings/Sections/SkinSection.cs | 18 +++++++++++------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index dba8633f53..7a0ca2c85a 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -216,12 +216,6 @@ namespace osu.Game.Database return new RealmLiveUnmanaged(realmObject); } - public static List> ToLive(this IEnumerable realmList, RealmAccess realm) - where T : RealmObject, IHasGuidPrimaryKey - { - return realmList.Select(l => new RealmLive(l, realm)).Cast>().ToList(); - } - public static Live ToLive(this T realmObject, RealmAccess realm) where T : RealmObject, IHasGuidPrimaryKey { diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 441a439596..1dfe49945f 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Settings.Sections Name = "", }.ToLiveUnmanaged(); - private List> skinItems; + private readonly List> dropdownItems = new List>(); [Resolved] private SkinManager skins { get; set; } @@ -111,19 +111,23 @@ namespace osu.Game.Overlays.Settings.Sections configBindable.Value = skin.NewValue.ID.ToString(); } - private void skinsChanged(IRealmCollection skins, ChangeSet changes, Exception error) + private void skinsChanged(IRealmCollection sender, ChangeSet changes, Exception error) { // This can only mean that realm is recycling, else we would see the protected skins. // Because we are using `Live<>` in this class, we don't need to worry about this scenario too much. - if (!skins.Any()) + if (!sender.Any()) return; - int protectedCount = skins.Count(s => s.Protected); + int protectedCount = sender.Count(s => s.Protected); - skinItems = skins.ToLive(realm); - skinItems.Insert(protectedCount, random_skin_info); + // For simplicity repopulate the full list. + // In the future we should change this to properly handle ChangeSet events. + dropdownItems.Clear(); + foreach (var skin in sender) + dropdownItems.Add(skin.ToLive(realm)); + dropdownItems.Insert(protectedCount, random_skin_info); - skinDropdown.Items = skinItems; + skinDropdown.Items = dropdownItems; updateSelectedSkinFromConfig(); } From 473c4d00cabb5079b8c1630dd46659889cb25665 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 18:38:44 +0900 Subject: [PATCH 047/256] Fix grouped difficulty icons using incorrect lookup for ruleset grouping --- osu.Game/Screens/Select/Carousel/SetPanelContent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index 619806f96e..82523c9d9d 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -87,7 +87,7 @@ namespace osu.Game.Screens.Select.Carousel var beatmaps = carouselSet.Beatmaps.ToList(); return beatmaps.Count > maximum_difficulty_icons - ? (IEnumerable)beatmaps.GroupBy(b => b.BeatmapInfo.RulesetID) + ? (IEnumerable)beatmaps.GroupBy(b => b.BeatmapInfo.Ruleset.ShortName) .Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Last().BeatmapInfo.Ruleset)) : beatmaps.Select(b => new FilterableDifficultyIcon(b)); } From 57e624d8e7519427fbe05a54da4fe7bed5f7cf67 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 18:46:32 +0900 Subject: [PATCH 048/256] Fix custom rulesets being displayed before official ones --- osu.Game/Rulesets/RulesetStore.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 9af9ace7ad..0f518be639 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -163,7 +163,10 @@ namespace osu.Game.Rulesets } } - availableRulesets.AddRange(detachedRulesets); + // add known official rulesets first.. + availableRulesets.AddRange(detachedRulesets.Where(r => r.OnlineID >= 0).OrderBy(r => r.OnlineID)); + // .. then add any customs + availableRulesets.AddRange(detachedRulesets.Where(r => r.OnlineID < 0).OrderBy(r => r.ShortName)); }); } From abe2cccaaecd1a01b5f3855705b4195958ef11da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 19:03:26 +0900 Subject: [PATCH 049/256] Fix completely invalid method of testing realm migration --- osu.Game.Tests/Database/RealmLiveTests.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 3f81b36378..4bc1f5078a 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -47,16 +47,14 @@ namespace osu.Game.Tests.Database liveBeatmap = beatmap.ToLive(realm); }); - using (realm.BlockAllOperations()) - { - // recycle realm before migrating - } - using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target")) { migratedStorage.DeleteDirectory(string.Empty); - storage.Migrate(migratedStorage); + using (realm.BlockAllOperations()) + { + storage.Migrate(migratedStorage); + } Assert.IsFalse(liveBeatmap?.PerformRead(l => l.Hidden)); } From b3f2392358ac141b17f28328932d299384e3bec9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 26 Jan 2022 19:04:18 +0900 Subject: [PATCH 050/256] Resolve compilation error due to removed method --- osu.Game/Overlays/Music/PlaylistOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 7b705a905b..59ade0918d 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -116,7 +116,7 @@ namespace osu.Game.Overlays.Music { beatmapSets.Clear(); // must use AddRange to avoid RearrangeableList sort overhead per add op. - beatmapSets.AddRange(sender.ToLive(realm)); + beatmapSets.AddRange(sender.Select(b => b.ToLive(realm))); return; } From 378173cc66e202e97a342b51c6f0f4a6327cf278 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jan 2022 22:46:29 +0900 Subject: [PATCH 051/256] Fix some score imports failing due to null string attempted to be parsed as json --- osu.Game/Scoring/EFScoreInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/EFScoreInfo.cs b/osu.Game/Scoring/EFScoreInfo.cs index 1dd4e3b6b3..4161336cfc 100644 --- a/osu.Game/Scoring/EFScoreInfo.cs +++ b/osu.Game/Scoring/EFScoreInfo.cs @@ -105,7 +105,7 @@ namespace osu.Game.Scoring public string ModsJson { get => JsonConvert.SerializeObject(APIMods); - set => APIMods = JsonConvert.DeserializeObject(value); + set => APIMods = !string.IsNullOrEmpty(value) ? JsonConvert.DeserializeObject(value) : Array.Empty(); } [NotMapped] From 7f34085baa52a837edef6e1e1ed431f186d08224 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 26 Jan 2022 17:04:28 +0300 Subject: [PATCH 052/256] Mark `EditorSavingTestScene` as abstract --- osu.Game/Tests/Visual/EditorSavingTestScene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/EditorSavingTestScene.cs b/osu.Game/Tests/Visual/EditorSavingTestScene.cs index 72b5d076a5..cc39ead1de 100644 --- a/osu.Game/Tests/Visual/EditorSavingTestScene.cs +++ b/osu.Game/Tests/Visual/EditorSavingTestScene.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual /// /// Tests the general expected flow of creating a new beatmap, saving it, then loading it back from song select. /// - public class EditorSavingTestScene : OsuGameTestScene + public abstract class EditorSavingTestScene : OsuGameTestScene { protected Editor Editor => Game.ChildrenOfType().FirstOrDefault(); From 873d3676153521b732167bfeb052445931984846 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Wed, 26 Jan 2022 15:51:39 +0100 Subject: [PATCH 053/256] Fix custom rulesets not being able to convert maps --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 6b198ab505..7fd312371b 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Select.Carousel bool match = criteria.Ruleset == null || BeatmapInfo.RulesetID == criteria.Ruleset.OnlineID || - (BeatmapInfo.RulesetID == 0 && criteria.Ruleset.OnlineID > 0 && criteria.AllowConvertedBeatmaps); + (BeatmapInfo.RulesetID == 0 && criteria.Ruleset.OnlineID != 0 && criteria.AllowConvertedBeatmaps); if (BeatmapInfo.BeatmapSet?.Equals(criteria.SelectedBeatmapSet) == true) { From e712fab2994557019a825c80d8658197fca51b21 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Wed, 26 Jan 2022 16:14:07 +0100 Subject: [PATCH 054/256] Add test for custom ruleset conversion filtering --- .../NonVisual/Filtering/FilterMatchingTest.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 74904f4585..2e7a4a65e5 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -78,6 +78,20 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.IsFalse(carouselItem.Filtered.Value); } + [Test] + public void TestCriteriaMatchingConvertedBeatmapsForCustomRulesets() + { + var exampleBeatmapInfo = getExampleBeatmap(); + var criteria = new FilterCriteria + { + Ruleset = new RulesetInfo { OnlineID = -25 }, + AllowConvertedBeatmaps = true + }; + var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); + carouselItem.Filter(criteria); + Assert.IsFalse(carouselItem.Filtered.Value); + } + [Test] [TestCase(true)] [TestCase(false)] From 6ec9c5c21a7891c0d1d7e8f9e88b44f482885da1 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Wed, 26 Jan 2022 16:23:00 +0100 Subject: [PATCH 055/256] Use default custom ruleset ID --- osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 2e7a4a65e5..363753cb33 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -84,7 +84,7 @@ namespace osu.Game.Tests.NonVisual.Filtering var exampleBeatmapInfo = getExampleBeatmap(); var criteria = new FilterCriteria { - Ruleset = new RulesetInfo { OnlineID = -25 }, + Ruleset = new RulesetInfo { OnlineID = -1 }, AllowConvertedBeatmaps = true }; var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); From f21e3d0d866e637870d9a9693b693b108c114611 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 00:34:51 +0900 Subject: [PATCH 056/256] Block collection loading until realm migration has completed --- osu.Game/Collections/CollectionManager.cs | 5 +++++ osu.Game/Database/DatabaseContextFactory.cs | 6 ++++++ osu.Game/Database/EFToRealmMigrator.cs | 1 + 3 files changed, 12 insertions(+) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index c4f991094c..5845e0d4d1 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -50,9 +50,14 @@ namespace osu.Game.Collections this.storage = storage; } + [Resolved(canBeNull: true)] + private DatabaseContextFactory efContextFactory { get; set; } = null!; + [BackgroundDependencyLoader] private void load() { + efContextFactory?.WaitForMigrationCompletion(); + Collections.CollectionChanged += collectionsChanged; if (storage.Exists(database_backup_name)) diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index c84edbfb81..123ed8019c 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -184,5 +184,11 @@ namespace osu.Game.Database } public static string CreateDatabaseConnectionString(string filename, Storage storage) => string.Concat("Data Source=", storage.GetFullPath($@"{filename}", true)); + + private readonly ManualResetEventSlim migrationComplete = new ManualResetEventSlim(); + + public void SetMigrationCompletion() => migrationComplete.Set(); + + public void WaitForMigrationCompletion() => migrationComplete.Wait(); } } diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 33f7c25b28..96baad35ec 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -128,6 +128,7 @@ namespace osu.Game.Database }, TaskCreationOptions.LongRunning).ContinueWith(t => { migrationCompleted.SetResult(true); + efContextFactory.SetMigrationCompletion(); }); } From 0c2ed2f9a77c863865844f20bc0796ef1d6e14e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 01:25:55 +0900 Subject: [PATCH 057/256] Add failing test coverage of incorrect filter ruleset matching --- osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 363753cb33..33204d33a7 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -16,7 +16,11 @@ namespace osu.Game.Tests.NonVisual.Filtering { private BeatmapInfo getExampleBeatmap() => new BeatmapInfo { - Ruleset = new RulesetInfo { OnlineID = 0 }, + Ruleset = new RulesetInfo + { + ShortName = "osu", + OnlineID = 0 + }, StarRating = 4.0d, Difficulty = new BeatmapDifficulty { @@ -57,7 +61,7 @@ namespace osu.Game.Tests.NonVisual.Filtering var exampleBeatmapInfo = getExampleBeatmap(); var criteria = new FilterCriteria { - Ruleset = new RulesetInfo { OnlineID = 6 } + Ruleset = new RulesetInfo { ShortName = "catch" } }; var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); carouselItem.Filter(criteria); From f70e10e8a4607dd51254a9915c78880b35a4f923 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 01:25:40 +0900 Subject: [PATCH 058/256] Fix ruleset filter matching using `OnlineID` instead of `ShortName` --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 7fd312371b..e6dee0233f 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Select.Carousel bool match = criteria.Ruleset == null || - BeatmapInfo.RulesetID == criteria.Ruleset.OnlineID || + BeatmapInfo.Ruleset.ShortName == criteria.Ruleset.ShortName || (BeatmapInfo.RulesetID == 0 && criteria.Ruleset.OnlineID != 0 && criteria.AllowConvertedBeatmaps); if (BeatmapInfo.BeatmapSet?.Equals(criteria.SelectedBeatmapSet) == true) From 4382adad8240fee9c85f1b5cb21efea70c011184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 25 Jan 2022 23:20:33 +0100 Subject: [PATCH 059/256] Add test coverage for editor changes not resetting after exit without save --- .../Visual/Editing/TestSceneEditorSaving.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index 58daab1ce2..adaa24d542 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -4,11 +4,13 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Screens; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osu.Game.Screens.Select; using osuTK.Input; namespace osu.Game.Tests.Visual.Editing @@ -116,5 +118,24 @@ namespace osu.Game.Tests.Visual.Editing EditorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT && EditorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT); } + + [Test] + public void TestExitWithoutSaveFromExistingBeatmap() + { + const string tags_to_save = "these tags will be saved"; + const string tags_to_discard = "these tags should be discarded"; + + AddStep("Set tags", () => EditorBeatmap.BeatmapInfo.Metadata.Tags = tags_to_save); + SaveEditor(); + AddAssert("Tags saved correctly", () => EditorBeatmap.BeatmapInfo.Metadata.Tags == tags_to_save); + + ReloadEditorToSameBeatmap(); + AddAssert("Tags saved correctly", () => EditorBeatmap.BeatmapInfo.Metadata.Tags == tags_to_save); + AddStep("Set tags again", () => EditorBeatmap.BeatmapInfo.Metadata.Tags = tags_to_discard); + + AddStep("Exit editor", () => Editor.Exit()); + AddUntilStep("Wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); + AddAssert("Tags reverted correctly", () => Game.Beatmap.Value.BeatmapInfo.Metadata.Tags == tags_to_save); + } } } From d760283665b6ecadcdee4989ded6e7684387dd41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 25 Jan 2022 23:31:42 +0100 Subject: [PATCH 060/256] Ensure edited beatmap is restored to a baseline state on exit --- osu.Game/Screens/Edit/Editor.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b42f629aad..e26453f99a 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -574,7 +574,9 @@ namespace osu.Game.Screens.Edit // To update the game-wide beatmap with any changes, perform a re-fetch on exit/suspend. // This is required as the editor makes its local changes via EditorBeatmap // (which are not propagated outwards to a potentially cached WorkingBeatmap). - var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(Beatmap.Value.BeatmapInfo); + ((IWorkingBeatmapCache)beatmapManager).Invalidate(Beatmap.Value.BeatmapInfo); + var refetchedBeatmapInfo = beatmapManager.QueryBeatmap(b => b.ID == Beatmap.Value.BeatmapInfo.ID); + var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(refetchedBeatmapInfo); if (!(refetchedBeatmap is DummyWorkingBeatmap)) { From e0616476e2a0a772c5998b0194d8a0dab0ad0eb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 26 Jan 2022 20:52:14 +0100 Subject: [PATCH 061/256] Fix test gameplay tests failing due to beatmap refetch on suspend --- .../Visual/Editing/TestSceneEditorTestGameplay.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index 79afc8cf27..e2406a8d27 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -70,7 +70,11 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); AddUntilStep("background has correct params", () => { - var background = this.ChildrenOfType().Single(); + // the test gameplay player's beatmap may be the "same" beatmap as the one being edited, *but* the `BeatmapInfo` references may differ + // due to the beatmap refetch logic ran on editor suspend. + // this test cares about checking the background belonging to the editor specifically, so check that using reference equality + // (as `.Equals()` cannot discern between the two, as they technically share the same database GUID). + var background = this.ChildrenOfType().Single(b => ReferenceEquals(b.Beatmap.BeatmapInfo, EditorBeatmap.BeatmapInfo)); return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0; }); AddAssert("no mods selected", () => SelectedMods.Value.Count == 0); @@ -99,7 +103,11 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); AddUntilStep("background has correct params", () => { - var background = this.ChildrenOfType().Single(); + // the test gameplay player's beatmap may be the "same" beatmap as the one being edited, *but* the `BeatmapInfo` references may differ + // due to the beatmap refetch logic ran on editor suspend. + // this test cares about checking the background belonging to the editor specifically, so check that using reference equality + // (as `.Equals()` cannot discern between the two, as they technically share the same database GUID). + var background = this.ChildrenOfType().Single(b => ReferenceEquals(b.Beatmap.BeatmapInfo, EditorBeatmap.BeatmapInfo)); return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0; }); From 587c0f965c877ee05db77ad87762ee9c810f4366 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 14:32:20 +0900 Subject: [PATCH 062/256] Add more attempts to delete EF database Just noticed in passing. Probably best we do this since it was known to fail on windows in some rare cases. --- osu.Game/Database/DatabaseContextFactory.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index 123ed8019c..45557aa5ec 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.IO; using System.Linq; using System.Threading; @@ -163,7 +164,24 @@ namespace osu.Game.Database try { - storage.Delete(DATABASE_NAME); + int attempts = 10; + + // Retry logic taken from MigratableStorage.AttemptOperation. + while (true) + { + try + { + storage.Delete(DATABASE_NAME); + return; + } + catch (Exception) + { + if (attempts-- == 0) + throw; + } + + Thread.Sleep(250); + } } catch { From deb5d75b5f21078d0877fd1ce5b75468fc896e3d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 14:33:44 +0900 Subject: [PATCH 063/256] Change migration process to always delete old EF database It is already backed up, so this is probably fine. --- osu.Game/Database/EFToRealmMigrator.cs | 44 +++++++++++++++++++++----- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 96baad35ec..072d1c95f1 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -15,6 +15,8 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Models; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Skinning; @@ -40,6 +42,12 @@ namespace osu.Game.Database [Resolved] private OsuConfigManager config { get; set; } = null!; + [Resolved] + private NotificationOverlay notificationOverlay { get; set; } = null!; + + [Resolved] + private OsuGame game { get; set; } = null!; + private readonly OsuSpriteText currentOperationText; public EFToRealmMigrator() @@ -96,7 +104,11 @@ namespace osu.Game.Database protected override void LoadComplete() { base.LoadComplete(); + beginMigration(); + } + private void beginMigration() + { Task.Factory.StartNew(() => { using (var ef = efContextFactory.Get()) @@ -117,21 +129,37 @@ namespace osu.Game.Database migrateBeatmaps(ef); migrateScores(ef); } - - // Delete the database permanently. - // Will cause future startups to not attempt migration. - log("Migration successful, deleting EF database"); - efContextFactory.ResetDatabase(); - - if (DebugUtils.IsDebugBuild) - Logger.Log("Your development database has been fully migrated to realm. If you switch back to a pre-realm branch and need your previous database, rename the backup file back to \"client.db\".\n\nNote that doing this can potentially leave your file store in a bad state.", level: LogLevel.Important); }, TaskCreationOptions.LongRunning).ContinueWith(t => { + if (t.Exception == null) + { + log("Migration successful!"); + + if (DebugUtils.IsDebugBuild) + Logger.Log("Your development database has been fully migrated to realm. If you switch back to a pre-realm branch and need your previous database, rename the backup file back to \"client.db\".\n\nNote that doing this can potentially leave your file store in a bad state.", level: LogLevel.Important); + } + else + { + log("Migration failed!"); + Logger.Log(t.Exception.ToString(), LoggingTarget.Database); + } + + // Regardless of success, since the game is going to continue with startup let's move the ef database out of the way. + // If we were to not do this, the migration would run another time the next time the user starts the game. + deletePreRealmData(); + migrationCompleted.SetResult(true); efContextFactory.SetMigrationCompletion(); }); } + private void deletePreRealmData() + { + // Delete the database permanently. + // Will cause future startups to not attempt migration. + efContextFactory.ResetDatabase(); + } + private void log(string message) { Logger.Log(message, LoggingTarget.Database); From b745252962b232e3bc3173491ab37495f302a56d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 14:34:18 +0900 Subject: [PATCH 064/256] Show notification when migration fails to give users a recovery path --- osu.Game/Database/EFToRealmMigrator.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 072d1c95f1..d110ac2f9f 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -142,6 +142,16 @@ namespace osu.Game.Database { log("Migration failed!"); Logger.Log(t.Exception.ToString(), LoggingTarget.Database); + + notificationOverlay.Post(new SimpleErrorNotification + { + Text = "IMPORTANT: During data migration, some of your data could not be successfully migrated. It has been backed up in your osu! folder.\n\nFor further assistance, please open a discussion on github and attach your backup files (click to get started).", + Activated = () => + { + game.OpenUrlExternally(@"https://github.com/ppy/osu/discussions/new?title=Realm%20migration%20issue&body=Please%20attach%20your%20database%20backups%20by%20zipping%20them%20and%20dragging%20in%20here!&category=q-a"); + return true; + } + }); } // Regardless of success, since the game is going to continue with startup let's move the ef database out of the way. From 08948f60f3108ccfaf098be3ebe99a69ffe61c93 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 14:39:07 +0900 Subject: [PATCH 065/256] Move backups to "backups" subfolder to make them easier to find --- osu.Game/OsuGameBase.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 09ca4e450d..8363c41437 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -202,16 +202,18 @@ namespace osu.Game // See https://github.com/ppy/osu/pull/16547 for more discussion. if (EFContextFactory != null) { + const string backup_folder = "backups"; + string migration = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; - EFContextFactory.CreateBackup($"client.{migration}.db"); - realm.CreateBackup($"client.{migration}.realm"); + EFContextFactory.CreateBackup(Path.Combine(backup_folder, $"client.{migration}.db")); + realm.CreateBackup(Path.Combine(backup_folder, $"client.{migration}.realm")); using (var source = Storage.GetStream("collection.db")) { if (source != null) { - using (var destination = Storage.GetStream($"collection.{migration}.db", FileAccess.Write, FileMode.CreateNew)) + using (var destination = Storage.GetStream(Path.Combine(backup_folder, $"collection.{migration}.db"), FileAccess.Write, FileMode.CreateNew)) source.CopyTo(destination); } } From 31abb372e5c2ac123decc43f88aa8d68b5d3c2ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 14:52:43 +0900 Subject: [PATCH 066/256] Automatically zip and show the backup archive to the user --- osu.Game/Database/EFToRealmMigrator.cs | 29 ++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index d110ac2f9f..c41461cc68 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; @@ -9,6 +10,7 @@ using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; +using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; @@ -22,6 +24,10 @@ using osu.Game.Scoring; using osu.Game.Skinning; using osuTK; using Realms; +using SharpCompress.Archives; +using SharpCompress.Archives.Zip; +using SharpCompress.Common; +using SharpCompress.Writers.Zip; #nullable enable @@ -48,6 +54,9 @@ namespace osu.Game.Database [Resolved] private OsuGame game { get; set; } = null!; + [Resolved] + private Storage storage { get; set; } = null!; + private readonly OsuSpriteText currentOperationText; public EFToRealmMigrator() @@ -145,10 +154,26 @@ namespace osu.Game.Database notificationOverlay.Post(new SimpleErrorNotification { - Text = "IMPORTANT: During data migration, some of your data could not be successfully migrated. It has been backed up in your osu! folder.\n\nFor further assistance, please open a discussion on github and attach your backup files (click to get started).", + Text = "IMPORTANT: During data migration, some of your data could not be successfully migrated. The previous version has been backed up.\n\nFor further assistance, please open a discussion on github and attach your backup files (click to get started).", Activated = () => { - game.OpenUrlExternally(@"https://github.com/ppy/osu/discussions/new?title=Realm%20migration%20issue&body=Please%20attach%20your%20database%20backups%20by%20zipping%20them%20and%20dragging%20in%20here!&category=q-a"); + game.OpenUrlExternally(@"https://github.com/ppy/osu/discussions/new?title=Realm%20migration%20issue&body=Please%20drag%20the%20""attach_me.zip""%20file%20here!&category=q-a"); + + const string attachment_filename = "attach_me.zip"; + const string backup_folder = "backups"; + + var backupStorage = storage.GetStorageForDirectory(backup_folder); + + backupStorage.Delete(attachment_filename); + + using (var zip = ZipArchive.Create()) + { + zip.AddAllFromDirectory(backupStorage.GetFullPath(string.Empty)); + zip.SaveTo(Path.Combine(backupStorage.GetFullPath(string.Empty), attachment_filename), new ZipWriterOptions(CompressionType.Deflate)); + } + + backupStorage.PresentFileExternally(attachment_filename); + return true; } }); From 465e7d29feb9529dbde477bcf384bf8ef94b2949 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 14:53:11 +0900 Subject: [PATCH 067/256] Avoid showing the external link warning --- osu.Game/Database/EFToRealmMigrator.cs | 2 +- osu.Game/Online/Chat/ExternalLinkOpener.cs | 4 ++-- osu.Game/OsuGame.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index c41461cc68..05bc86a7c0 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -157,7 +157,7 @@ namespace osu.Game.Database Text = "IMPORTANT: During data migration, some of your data could not be successfully migrated. The previous version has been backed up.\n\nFor further assistance, please open a discussion on github and attach your backup files (click to get started).", Activated = () => { - game.OpenUrlExternally(@"https://github.com/ppy/osu/discussions/new?title=Realm%20migration%20issue&body=Please%20drag%20the%20""attach_me.zip""%20file%20here!&category=q-a"); + game.OpenUrlExternally(@"https://github.com/ppy/osu/discussions/new?title=Realm%20migration%20issue&body=Please%20drag%20the%20""attach_me.zip""%20file%20here!&category=q-a", true); const string attachment_filename = "attach_me.zip"; const string backup_folder = "backups"; diff --git a/osu.Game/Online/Chat/ExternalLinkOpener.cs b/osu.Game/Online/Chat/ExternalLinkOpener.cs index 8407e2ca6a..328b43c4e8 100644 --- a/osu.Game/Online/Chat/ExternalLinkOpener.cs +++ b/osu.Game/Online/Chat/ExternalLinkOpener.cs @@ -27,9 +27,9 @@ namespace osu.Game.Online.Chat externalLinkWarning = config.GetBindable(OsuSetting.ExternalLinkWarning); } - public void OpenUrlExternally(string url) + public void OpenUrlExternally(string url, bool bypassWarning = false) { - if (externalLinkWarning.Value) + if (!bypassWarning && externalLinkWarning.Value) dialogOverlay.Push(new ExternalLinkDialog(url, () => host.OpenUrlExternally(url))); else host.OpenUrlExternally(url); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c2e1b25d94..5b58dec0c3 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -357,12 +357,12 @@ namespace osu.Game } }); - public void OpenUrlExternally(string url) => waitForReady(() => externalLinkOpener, _ => + public void OpenUrlExternally(string url, bool bypassExternalUrlWarning = false) => waitForReady(() => externalLinkOpener, _ => { if (url.StartsWith('/')) url = $"{API.APIEndpointUrl}{url}"; - externalLinkOpener.OpenUrlExternally(url); + externalLinkOpener.OpenUrlExternally(url, bypassExternalUrlWarning); }); /// From fb081384e13ca4a09bee571636021ea0ce894dc2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 14:55:52 +0900 Subject: [PATCH 068/256] Add safety against zip creation potentially failing (probably can't but still) --- osu.Game/Database/EFToRealmMigrator.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 05bc86a7c0..723b3eeada 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -166,11 +166,15 @@ namespace osu.Game.Database backupStorage.Delete(attachment_filename); - using (var zip = ZipArchive.Create()) + try { - zip.AddAllFromDirectory(backupStorage.GetFullPath(string.Empty)); - zip.SaveTo(Path.Combine(backupStorage.GetFullPath(string.Empty), attachment_filename), new ZipWriterOptions(CompressionType.Deflate)); + using (var zip = ZipArchive.Create()) + { + zip.AddAllFromDirectory(backupStorage.GetFullPath(string.Empty)); + zip.SaveTo(Path.Combine(backupStorage.GetFullPath(string.Empty), attachment_filename), new ZipWriterOptions(CompressionType.Deflate)); + } } + catch { } backupStorage.PresentFileExternally(attachment_filename); From 67ccb879926f309f3b2d3dbd3bc31a2a8760b05e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 14:56:04 +0900 Subject: [PATCH 069/256] Add exception message to discussion template url --- osu.Game/Database/EFToRealmMigrator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 723b3eeada..639bf10e19 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -157,7 +157,7 @@ namespace osu.Game.Database Text = "IMPORTANT: During data migration, some of your data could not be successfully migrated. The previous version has been backed up.\n\nFor further assistance, please open a discussion on github and attach your backup files (click to get started).", Activated = () => { - game.OpenUrlExternally(@"https://github.com/ppy/osu/discussions/new?title=Realm%20migration%20issue&body=Please%20drag%20the%20""attach_me.zip""%20file%20here!&category=q-a", true); + game.OpenUrlExternally($@"https://github.com/ppy/osu/discussions/new?title=Realm%20migration%20issue ({t.Exception.Message})&body=Please%20drag%20the%20""attach_me.zip""%20file%20here!&category=q-a", true); const string attachment_filename = "attach_me.zip"; const string backup_folder = "backups"; From f30d63107a73c1a4f059366a12483f086e26c22f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 15:19:27 +0900 Subject: [PATCH 070/256] Add `SortID` to `RulesetInfo` to allow stable ordering of rulesets for display --- .../NonVisual/RulesetInfoOrderingTest.cs | 38 +++++++++++++++++++ osu.Game/Rulesets/RulesetInfo.cs | 5 +++ osu.Game/Rulesets/RulesetStore.cs | 5 +-- .../Select/Carousel/CarouselBeatmap.cs | 2 +- .../Select/Carousel/CarouselBeatmapSet.cs | 2 +- 5 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs diff --git a/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs b/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs new file mode 100644 index 0000000000..e7c04d27c1 --- /dev/null +++ b/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs @@ -0,0 +1,38 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Osu; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class RulesetInfoOrderingTest + { + [Test] + public void TestOrdering() + { + var rulesets = new[] + { + new RulesetInfo("custom2", "Custom Ruleset 2", string.Empty, -1), + new OsuRuleset().RulesetInfo, + new RulesetInfo("custom3", "Custom Ruleset 3", string.Empty, -1), + new RulesetInfo("custom2", "Custom Ruleset 2", string.Empty, -1), + new CatchRuleset().RulesetInfo, + new RulesetInfo("custom3", "Custom Ruleset 3", string.Empty, -1), + }; + + var orderedRulesets = rulesets.OrderBy(r => r.SortID); + + // Ensure all customs are after official. + Assert.That(orderedRulesets.Select(r => r.OnlineID), Is.EqualTo(new[] { 0, 2, -1, -1, -1, -1 })); + + // Ensure customs are grouped next to each other (ie. stably sorted). + Assert.That(orderedRulesets.SkipWhile(r => r.ShortName != "custom2").Skip(1).First().ShortName, Is.EqualTo("custom2")); + Assert.That(orderedRulesets.SkipWhile(r => r.ShortName != "custom3").Skip(1).First().ShortName, Is.EqualTo("custom3")); + } + } +} diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 2e2ec5c024..d721926e95 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -24,6 +24,11 @@ namespace osu.Game.Rulesets public string InstantiationInfo { get; set; } = string.Empty; + /// + /// A best effort sort ID which provides stable ordering and puts online rulesets before non-online rulesets. + /// + public int SortID => OnlineID >= 0 ? OnlineID : Math.Abs(ShortName.GetHashCode()); + public RulesetInfo(string shortName, string name, string instantiationInfo, int onlineID) { ShortName = shortName; diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 0f518be639..a9fd8f430e 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -163,10 +163,7 @@ namespace osu.Game.Rulesets } } - // add known official rulesets first.. - availableRulesets.AddRange(detachedRulesets.Where(r => r.OnlineID >= 0).OrderBy(r => r.OnlineID)); - // .. then add any customs - availableRulesets.AddRange(detachedRulesets.Where(r => r.OnlineID < 0).OrderBy(r => r.ShortName)); + availableRulesets.AddRange(detachedRulesets.OrderBy(r => r.SortID)); }); } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index e6dee0233f..9d49fdaf6e 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -89,7 +89,7 @@ namespace osu.Game.Screens.Select.Carousel { default: case SortMode.Difficulty: - int ruleset = BeatmapInfo.RulesetID.CompareTo(otherBeatmap.BeatmapInfo.RulesetID); + int ruleset = BeatmapInfo.Ruleset.SortID.CompareTo(otherBeatmap.BeatmapInfo.Ruleset.SortID); if (ruleset != 0) return ruleset; return BeatmapInfo.StarRating.CompareTo(otherBeatmap.BeatmapInfo.StarRating); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index b2b3b5411c..274be7b73a 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Select.Carousel beatmapSet.Beatmaps .Where(b => !b.Hidden) - .OrderBy(b => b.RulesetID) + .OrderBy(b => b.Ruleset.SortID) .ThenBy(b => b.StarRating) .Select(b => new CarouselBeatmap(b)) .ForEach(AddChild); From e67b1fe0ec9c50b7ddb429edb2ee13d534e881ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 15:25:56 +0900 Subject: [PATCH 071/256] Make `Ruleset.RulesetInfo` get only --- osu.Game/Rulesets/Ruleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index d279f6d6ee..616540b59c 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets [ExcludeFromDynamicCompile] public abstract class Ruleset { - public RulesetInfo RulesetInfo { get; internal set; } + public RulesetInfo RulesetInfo { get; } private static readonly ConcurrentDictionary mod_reference_cache = new ConcurrentDictionary(); From 714177cce13a86d3665d1685831da3a1e30c7382 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 15:23:16 +0900 Subject: [PATCH 072/256] Remove pointless constructor in `RulesetInfo` --- osu.Game.Tests/Database/RealmTest.cs | 2 +- osu.Game/Rulesets/RulesetInfo.cs | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/osu.Game.Tests/Database/RealmTest.cs b/osu.Game.Tests/Database/RealmTest.cs index c2339dd9ad..838759c991 100644 --- a/osu.Game.Tests/Database/RealmTest.cs +++ b/osu.Game.Tests/Database/RealmTest.cs @@ -114,7 +114,7 @@ namespace osu.Game.Tests.Database } protected static RulesetInfo CreateRuleset() => - new RulesetInfo(0, "osu!", "osu", true); + new RulesetInfo("osu", "osu!", string.Empty, 0) { Available = true }; private class RealmTestGame : Framework.Game { diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 2e2ec5c024..5171f0d63c 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -37,14 +37,6 @@ namespace osu.Game.Rulesets { } - public RulesetInfo(int? onlineID, string name, string shortName, bool available) - { - OnlineID = onlineID ?? -1; - Name = name; - ShortName = shortName; - Available = available; - } - public bool Available { get; set; } public bool Equals(RulesetInfo? other) From 5288eedd311feabd82cebf1ddf19336557197016 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 15:19:48 +0900 Subject: [PATCH 073/256] Update all usages of `RulesetID` and `Ruleset.ID` to use `Ruleset.OnlineID` --- .../ManiaFilterCriteria.cs | 2 +- .../Beatmaps/TaikoBeatmapConverter.cs | 2 +- .../Formats/LegacyBeatmapDecoderTest.cs | 2 +- .../Formats/LegacyBeatmapEncoderTest.cs | 2 +- .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- .../Editing/TestSceneDifficultySwitching.cs | 4 +- .../Editing/TestSceneEditorTestGameplay.cs | 2 +- .../TestScenePlayerScoreSubmission.cs | 4 +- .../Visual/Gameplay/TestSceneSpectator.cs | 2 +- .../Visual/Multiplayer/QueueModeTestScene.cs | 4 +- .../TestSceneMultiSpectatorScreen.cs | 2 +- .../Multiplayer/TestSceneMultiplayer.cs | 48 +++++++++---------- .../TestSceneMultiplayerMatchSongSelect.cs | 4 +- .../TestSceneMultiplayerPlaylist.cs | 2 +- .../TestSceneMultiplayerQueueList.cs | 2 +- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 8 ++-- .../SongSelect/TestSceneBeatmapCarousel.cs | 2 +- .../SongSelect/TestScenePlaySongSelect.cs | 26 +++++----- .../Components/TournamentModIcon.cs | 2 +- osu.Game/Beatmaps/BeatmapInfo.cs | 1 - osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs | 2 +- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 8 ++-- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 18 ++++--- osu.Game/Rulesets/RulesetInfo.cs | 6 --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 4 +- .../Select/Carousel/CarouselBeatmap.cs | 2 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- osu.Game/Stores/BeatmapImporter.cs | 4 +- .../Tests/Beatmaps/BeatmapConversionTest.cs | 2 +- .../Tests/Beatmaps/HitObjectSampleTest.cs | 2 +- osu.Game/Tests/Visual/OsuTestScene.cs | 2 +- 33 files changed, 89 insertions(+), 90 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs index 0290230490..c8832dfdfb 100644 --- a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs +++ b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania public bool Matches(BeatmapInfo beatmapInfo) { - return !keys.HasFilter || (beatmapInfo.RulesetID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmapInfo))); + return !keys.HasFilter || (beatmapInfo.Ruleset.OnlineID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmapInfo))); } public bool TryParseCustomKeywordCriteria(string key, Operator op, string value) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 613874b7d6..b1d8575de4 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps Beatmap converted = base.ConvertBeatmap(original, cancellationToken); - if (original.BeatmapInfo.RulesetID == 3) + if (original.BeatmapInfo.Ruleset.OnlineID == 3) { // Post processing step to transform mania hit objects with the same start time into strong hits converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x => diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 0459753b28..1edd21b5a9 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -60,7 +60,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(0, beatmapInfo.AudioLeadIn); Assert.AreEqual(164471, metadata.PreviewTime); Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); - Assert.IsTrue(beatmapInfo.RulesetID == 0); + Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); Assert.IsFalse(beatmapInfo.LetterboxInBreaks); Assert.IsFalse(beatmapInfo.SpecialStyle); Assert.IsFalse(beatmapInfo.WidescreenStoryboard); diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index d12da1a22f..d19b3c71f1 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -195,7 +195,7 @@ namespace osu.Game.Tests.Beatmaps.Formats private IBeatmap convert(IBeatmap beatmap) { - switch (beatmap.BeatmapInfo.RulesetID) + switch (beatmap.BeatmapInfo.Ruleset.OnlineID) { case 0: beatmap.BeatmapInfo.Ruleset = new OsuRuleset().RulesetInfo; diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 06ed638e0a..2eb75259d9 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(0, beatmapInfo.AudioLeadIn); Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); Assert.AreEqual(false, beatmapInfo.SpecialStyle); - Assert.IsTrue(beatmapInfo.RulesetID == 0); + Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks); Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard); Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs index cf6488f721..81cb286058 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -82,8 +82,8 @@ namespace osu.Game.Tests.Visual.Editing AddStep("set target difficulty", () => { targetDifficulty = sameRuleset - ? importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.RulesetID == Beatmap.Value.BeatmapInfo.RulesetID) - : importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.RulesetID != Beatmap.Value.BeatmapInfo.RulesetID); + ? importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.Ruleset.ShortName == Beatmap.Value.BeatmapInfo.Ruleset.ShortName) + : importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.Ruleset.ShortName != Beatmap.Value.BeatmapInfo.Ruleset.ShortName); }); switchToDifficulty(() => targetDifficulty); confirmEditingBeatmap(() => targetDifficulty); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index 79afc8cf27..2b827bee74 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Editing } protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) - => beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0)); + => beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)); protected override void LoadEditor() { diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index a4a4f351ec..a9675a2ee2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -110,7 +110,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); AddAssert("ensure passing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == true); - AddAssert("submitted score has correct ruleset ID", () => Player.SubmittedScore?.ScoreInfo.RulesetID == new TaikoRuleset().RulesetInfo.ID); + AddAssert("submitted score has correct ruleset ID", () => Player.SubmittedScore?.ScoreInfo.Ruleset.ShortName == new TaikoRuleset().RulesetInfo.ShortName); } [Test] @@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); AddAssert("ensure passing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == true); - AddAssert("submitted score has correct ruleset ID", () => Player.SubmittedScore?.ScoreInfo.RulesetID == new ManiaRuleset().RulesetInfo.ID); + AddAssert("submitted score has correct ruleset ID", () => Player.SubmittedScore?.ScoreInfo.Ruleset.ShortName == new ManiaRuleset().RulesetInfo.ShortName); } [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 8b7e1a1d85..0afd2c76dd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("import beatmap", () => { importedBeatmap = BeatmapImportHelper.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely(); - importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.RulesetID == 0).OnlineID; + importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.Ruleset.OnlineID == 0).OnlineID; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 4cd19b53a4..c79395b343 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -60,8 +60,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); importedSet = beatmaps.GetAllUsableBeatmapSets().First(); - InitialBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); - OtherBeatmap = importedSet.Beatmaps.Last(b => b.RulesetID == 0); + InitialBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0); + OtherBeatmap = importedSet.Beatmaps.Last(b => b.Ruleset.OnlineID == 0); }); AddStep("load multiplayer", () => LoadScreen(multiplayerComponents = new TestMultiplayerComponents())); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 9d67742e4d..b84f7760e4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void load() { importedSet = BeatmapImportHelper.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely(); - importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); + importedBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0); importedBeatmapId = importedBeatmap.OnlineID; } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 3f151a0ae8..3563869d8b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -234,7 +234,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -256,7 +256,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -286,7 +286,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -317,7 +317,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -339,7 +339,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -372,7 +372,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -392,7 +392,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -414,7 +414,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -453,7 +453,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -492,7 +492,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -531,7 +531,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -565,7 +565,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -605,7 +605,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -625,7 +625,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, AllowedMods = { new OsuModHidden() } } @@ -665,7 +665,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -696,7 +696,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -714,7 +714,7 @@ namespace osu.Game.Tests.Visual.Multiplayer roomManager.ServerSideRooms[0].Playlist.Add(new PlaylistItem { ID = 2, - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, }); }); @@ -742,7 +742,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -778,7 +778,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -817,7 +817,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -828,7 +828,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 })); AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem { - BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo.OnlineID + BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo.OnlineID }))); AddUntilStep("item arrived in playlist", () => client.Room?.Playlist.Count == 2); @@ -848,7 +848,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -859,7 +859,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 })); AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem { - BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo.OnlineID + BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo.OnlineID }))); AddUntilStep("item arrived in playlist", () => client.Room?.Playlist.Count == 2); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 5465061891..457b53ae61 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -107,7 +107,7 @@ namespace osu.Game.Tests.Visual.Multiplayer BeatmapInfo selectedBeatmap = null; AddStep("select beatmap", - () => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.Where(beatmap => beatmap.RulesetID == new OsuRuleset().LegacyID).ElementAt(1))); + () => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.Where(beatmap => beatmap.Ruleset.OnlineID == new OsuRuleset().LegacyID).ElementAt(1))); AddUntilStep("wait for selection", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap)); AddStep("exit song select", () => songSelect.Exit()); @@ -139,7 +139,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change ruleset", () => Ruleset.Value = new TaikoRuleset().RulesetInfo); AddStep("select beatmap", - () => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.First(beatmap => beatmap.RulesetID == new TaikoRuleset().LegacyID))); + () => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == new TaikoRuleset().LegacyID))); AddUntilStep("wait for selection", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap)); AddStep("set mods", () => SelectedMods.Value = new[] { new TaikoModDoubleTime() }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index d970ab6c34..936798e6b4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); importedSet = beatmaps.GetAllUsableBeatmapSets().First(); - importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); + importedBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0); }); AddStep("change to all players mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index d83421ee3a..ddf794b437 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); importedSet = beatmaps.GetAllUsableBeatmapSets().First(); - importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); + importedBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0); }); AddStep("change to all players mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 73c67d26d9..781f0a1824 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } @@ -158,7 +158,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { new PlaylistItem { - Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, } } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 0298c3bea9..830631ebad 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -586,7 +586,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("filter to ruleset 0", () => carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) }, false)); AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testMixed.Beatmaps[1], false)); - AddAssert("unfiltered beatmap not selected", () => carousel.SelectedBeatmapInfo.RulesetID == 0); + AddAssert("unfiltered beatmap not selected", () => carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 0); AddStep("remove mixed set", () => { diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 458c6130c7..136173c8a7 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -325,10 +325,10 @@ namespace osu.Game.Tests.Visual.SongSelect changeRuleset(2); addRulesetImportStep(2); addRulesetImportStep(1); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.RulesetID == 2); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 2); changeRuleset(1); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.RulesetID == 1); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 1); changeRuleset(0); AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmapInfo == null); @@ -341,7 +341,7 @@ namespace osu.Game.Tests.Visual.SongSelect changeRuleset(2); addRulesetImportStep(2); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.RulesetID == 2); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 2); addRulesetImportStep(0); addRulesetImportStep(0); @@ -352,7 +352,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select beatmap/ruleset externally", () => { target = manager.GetAllUsableBeatmapSets() - .Last(b => b.Beatmaps.Any(bi => bi.RulesetID == 0)).Beatmaps.Last(); + .Last(b => b.Beatmaps.Any(bi => bi.Ruleset.OnlineID == 0)).Beatmaps.Last(); Ruleset.Value = rulesets.AvailableRulesets.First(r => r.OnlineID == 0); Beatmap.Value = manager.GetWorkingBeatmap(target); @@ -371,7 +371,7 @@ namespace osu.Game.Tests.Visual.SongSelect changeRuleset(2); addRulesetImportStep(2); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.RulesetID == 2); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 2); addRulesetImportStep(0); addRulesetImportStep(0); @@ -382,7 +382,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select beatmap/ruleset externally", () => { target = manager.GetAllUsableBeatmapSets() - .Last(b => b.Beatmaps.Any(bi => bi.RulesetID == 0)).Beatmaps.Last(); + .Last(b => b.Beatmaps.Any(bi => bi.Ruleset.OnlineID == 0)).Beatmaps.Last(); Beatmap.Value = manager.GetWorkingBeatmap(target); Ruleset.Value = rulesets.AvailableRulesets.First(r => r.OnlineID == 0); @@ -493,9 +493,9 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select beatmap externally", () => { target = manager.GetAllUsableBeatmapSets() - .First(b => b.Beatmaps.Any(bi => bi.RulesetID == targetRuleset)) + .First(b => b.Beatmaps.Any(bi => bi.Ruleset.OnlineID == targetRuleset)) .Beatmaps - .First(bi => bi.RulesetID == targetRuleset); + .First(bi => bi.Ruleset.OnlineID == targetRuleset); Beatmap.Value = manager.GetWorkingBeatmap(target); }); @@ -544,7 +544,7 @@ namespace osu.Game.Tests.Visual.SongSelect { target = manager .GetAllUsableBeatmapSets() - .First(b => b.Beatmaps.Any(bi => bi.RulesetID == 1)) + .First(b => b.Beatmaps.Any(bi => bi.Ruleset.OnlineID == 1)) .Beatmaps.First(); Beatmap.Value = manager.GetWorkingBeatmap(target); @@ -799,8 +799,8 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestChangeRulesetWhilePresentingScore() { - BeatmapInfo getPresentBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.RulesetID == 0); - BeatmapInfo getSwitchBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.RulesetID == 1); + BeatmapInfo getPresentBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.Ruleset.OnlineID == 0); + BeatmapInfo getSwitchBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.Ruleset.OnlineID == 1); changeRuleset(0); @@ -831,8 +831,8 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestChangeBeatmapWhilePresentingScore() { - BeatmapInfo getPresentBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.RulesetID == 0); - BeatmapInfo getSwitchBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.RulesetID == 1); + BeatmapInfo getPresentBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.Ruleset.OnlineID == 0); + BeatmapInfo getSwitchBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.Ruleset.OnlineID == 1); changeRuleset(0); diff --git a/osu.Game.Tournament/Components/TournamentModIcon.cs b/osu.Game.Tournament/Components/TournamentModIcon.cs index ed8a36c220..57a0390ac2 100644 --- a/osu.Game.Tournament/Components/TournamentModIcon.cs +++ b/osu.Game.Tournament/Components/TournamentModIcon.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tournament.Components return; } - var ruleset = rulesets.GetRuleset(ladderInfo.Ruleset.Value?.ID ?? 0); + var ruleset = rulesets.GetRuleset(ladderInfo.Ruleset.Value?.OnlineID ?? 0); var modIcon = ruleset?.CreateInstance().CreateModFromAcronym(modAcronym); if (modIcon == null) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 4e1a34ddbf..3e6e33f1d0 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -155,7 +155,6 @@ namespace osu.Game.Beatmaps [Ignored] public int RulesetID { - get => Ruleset.OnlineID; set { if (!string.IsNullOrEmpty(Ruleset.InstantiationInfo)) diff --git a/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs b/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs index dc8201a402..163da12b2e 100644 --- a/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/FlatFileWorkingBeatmap.cs @@ -30,7 +30,7 @@ namespace osu.Game.Beatmaps { this.beatmap = beatmap; - beatmap.BeatmapInfo.Ruleset = rulesetProvider(beatmap.BeatmapInfo.RulesetID).RulesetInfo; + beatmap.BeatmapInfo.Ruleset = rulesetProvider(beatmap.BeatmapInfo.Ruleset.OnlineID).RulesetInfo; if (beatmapId.HasValue) beatmap.BeatmapInfo.OnlineID = beatmapId.Value; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 8f3f05aa9f..35d1cefeca 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -141,9 +141,11 @@ namespace osu.Game.Beatmaps.Formats break; case @"Mode": - beatmap.BeatmapInfo.RulesetID = Parsing.ParseInt(pair.Value); + int rulesetID = Parsing.ParseInt(pair.Value); - switch (beatmap.BeatmapInfo.RulesetID) + beatmap.BeatmapInfo.RulesetID = rulesetID; + + switch (rulesetID) { case 0: parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion); @@ -397,7 +399,7 @@ namespace osu.Game.Beatmaps.Formats OmitFirstBarLine = omitFirstBarSignature, }; - bool isOsuRuleset = beatmap.BeatmapInfo.RulesetID == 0; + bool isOsuRuleset = beatmap.BeatmapInfo.Ruleset.OnlineID == 0; // scrolling rulesets use effect points rather than difficulty points for scroll speed adjustments. if (!isOsuRuleset) effectPoint.ScrollSpeed = speedMultiplier; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 9d848fd8a4..7ddbc2f768 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -35,6 +35,8 @@ namespace osu.Game.Beatmaps.Formats [CanBeNull] private readonly ISkin skin; + private readonly int onlineRulesetID; + /// /// Creates a new . /// @@ -45,7 +47,9 @@ namespace osu.Game.Beatmaps.Formats this.beatmap = beatmap; this.skin = skin; - if (beatmap.BeatmapInfo.RulesetID < 0 || beatmap.BeatmapInfo.RulesetID > 3) + onlineRulesetID = beatmap.BeatmapInfo.Ruleset.OnlineID; + + if (onlineRulesetID < 0 || onlineRulesetID > 3) throw new ArgumentException("Only beatmaps in the osu, taiko, catch, or mania rulesets can be encoded to the legacy beatmap format.", nameof(beatmap)); } @@ -88,7 +92,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.BeatmapInfo.Countdown}")); writer.WriteLine(FormattableString.Invariant($"SampleSet: {toLegacySampleBank((beatmap.HitObjects.FirstOrDefault()?.SampleControlPoint ?? SampleControlPoint.DEFAULT).SampleBank)}")); writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.BeatmapInfo.StackLeniency}")); - writer.WriteLine(FormattableString.Invariant($"Mode: {beatmap.BeatmapInfo.RulesetID}")); + writer.WriteLine(FormattableString.Invariant($"Mode: {onlineRulesetID}")); writer.WriteLine(FormattableString.Invariant($"LetterboxInBreaks: {(beatmap.BeatmapInfo.LetterboxInBreaks ? '1' : '0')}")); // if (beatmap.BeatmapInfo.UseSkinSprites) // writer.WriteLine(@"UseSkinSprites: 1"); @@ -102,7 +106,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(@"EpilepsyWarning: 1"); if (beatmap.BeatmapInfo.CountdownOffset > 0) writer.WriteLine(FormattableString.Invariant($@"CountdownOffset: {beatmap.BeatmapInfo.CountdownOffset}")); - if (beatmap.BeatmapInfo.RulesetID == 3) + if (onlineRulesetID == 3) writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.BeatmapInfo.SpecialStyle ? '1' : '0')}")); writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.BeatmapInfo.WidescreenStoryboard ? '1' : '0')}")); if (beatmap.BeatmapInfo.SamplesMatchPlaybackRate) @@ -147,7 +151,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"ApproachRate: {beatmap.Difficulty.ApproachRate}")); // Taiko adjusts the slider multiplier (see: LEGACY_TAIKO_VELOCITY_MULTIPLIER) - writer.WriteLine(beatmap.BeatmapInfo.RulesetID == 1 + writer.WriteLine(onlineRulesetID == 1 ? FormattableString.Invariant($"SliderMultiplier: {beatmap.Difficulty.SliderMultiplier / LEGACY_TAIKO_VELOCITY_MULTIPLIER}") : FormattableString.Invariant($"SliderMultiplier: {beatmap.Difficulty.SliderMultiplier}")); @@ -179,7 +183,7 @@ namespace osu.Game.Beatmaps.Formats SampleControlPoint lastRelevantSamplePoint = null; DifficultyControlPoint lastRelevantDifficultyPoint = null; - bool isOsuRuleset = beatmap.BeatmapInfo.RulesetID == 0; + bool isOsuRuleset = onlineRulesetID == 0; // iterate over hitobjects and pull out all required sample and difficulty changes extractDifficultyControlPoints(beatmap.HitObjects); @@ -318,7 +322,7 @@ namespace osu.Game.Beatmaps.Formats { Vector2 position = new Vector2(256, 192); - switch (beatmap.BeatmapInfo.RulesetID) + switch (onlineRulesetID) { case 0: case 2: @@ -372,7 +376,7 @@ namespace osu.Game.Beatmaps.Formats break; case IHasDuration _: - if (beatmap.BeatmapInfo.RulesetID == 3) + if (onlineRulesetID == 3) type |= LegacyHitObjectType.Hold; else type |= LegacyHitObjectType.Spinner; diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 2e2ec5c024..e2007c910e 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -98,11 +98,5 @@ namespace osu.Game.Rulesets return ruleset; } - - #region Compatibility properties - - public int ID => OnlineID; - - #endregion } } diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 2902ff7848..9460ec680c 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -34,7 +34,7 @@ namespace osu.Game.Scoring.Legacy this.score = score; this.beatmap = beatmap; - if (score.ScoreInfo.BeatmapInfo.RulesetID < 0 || score.ScoreInfo.BeatmapInfo.RulesetID > 3) + if (score.ScoreInfo.BeatmapInfo.Ruleset.OnlineID < 0 || score.ScoreInfo.BeatmapInfo.Ruleset.OnlineID > 3) throw new ArgumentException("Only scores in the osu, taiko, catch, or mania rulesets can be encoded to the legacy score format.", nameof(score)); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b42f629aad..55b01bd8ab 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -338,7 +338,7 @@ namespace osu.Game.Screens.Edit public EditorState GetState([CanBeNull] BeatmapInfo nextBeatmap = null) => new EditorState { Time = clock.CurrentTimeAccurate, - ClipboardContent = nextBeatmap == null || editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? Clipboard.Content.Value : string.Empty + ClipboardContent = nextBeatmap == null || editorBeatmap.BeatmapInfo.Ruleset.ShortName == nextBeatmap.Ruleset.ShortName ? Clipboard.Content.Value : string.Empty }; /// @@ -780,7 +780,7 @@ namespace osu.Game.Screens.Edit var difficultyItems = new List(); - foreach (var rulesetBeatmaps in beatmapSet.Beatmaps.GroupBy(b => b.RulesetID).OrderBy(group => group.Key)) + foreach (var rulesetBeatmaps in beatmapSet.Beatmaps.GroupBy(b => b.Ruleset.ShortName).OrderBy(group => group.Key)) { if (difficultyItems.Count > 0) difficultyItems.Add(new EditorMenuItemSpacer()); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index e6dee0233f..3962f7b26f 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Select.Carousel bool match = criteria.Ruleset == null || BeatmapInfo.Ruleset.ShortName == criteria.Ruleset.ShortName || - (BeatmapInfo.RulesetID == 0 && criteria.Ruleset.OnlineID != 0 && criteria.AllowConvertedBeatmaps); + (BeatmapInfo.Ruleset.OnlineID == 0 && criteria.Ruleset.OnlineID != 0 && criteria.AllowConvertedBeatmaps); if (BeatmapInfo.BeatmapSet?.Equals(criteria.SelectedBeatmapSet) == true) { diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index f25997650b..2070e53257 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -155,7 +155,7 @@ namespace osu.Game.Screens.Select.Leaderboards var scores = r.All() .AsEnumerable() // TODO: update to use a realm filter directly (or at least figure out the beatmap part to reduce scope). - .Where(s => !s.DeletePending && s.BeatmapInfo.ID == fetchBeatmapInfo.ID && s.Ruleset.OnlineID == ruleset.Value.ID); + .Where(s => !s.DeletePending && s.BeatmapInfo.ID == fetchBeatmapInfo.ID && s.Ruleset.ShortName == ruleset.Value.ShortName); if (filterMods && !mods.Value.Any()) { diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 837f30eb2b..10150fcd9f 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -502,7 +502,7 @@ namespace osu.Game.Screens.Select // clear pending task immediately to track any potential nested debounce operation. selectionChangedDebounce = null; - Logger.Log($"updating selection with beatmap:{beatmap?.ID.ToString() ?? "null"} ruleset:{ruleset?.ID.ToString() ?? "null"}"); + Logger.Log($"updating selection with beatmap:{beatmap?.ID.ToString() ?? "null"} ruleset:{ruleset?.ShortName ?? "null"}"); if (transferRulesetValue()) { diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index a6f20c8d4f..709dd67087 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -219,11 +219,11 @@ namespace osu.Game.Stores var decodedInfo = decoded.BeatmapInfo; var decodedDifficulty = decodedInfo.Difficulty; - var ruleset = realm.All().FirstOrDefault(r => r.OnlineID == decodedInfo.RulesetID); + var ruleset = realm.All().FirstOrDefault(r => r.OnlineID == decodedInfo.Ruleset.OnlineID); if (ruleset?.Available != true) { - Logger.Log($"Skipping import of {file.Filename} due to missing local ruleset {decodedInfo.RulesetID}.", LoggingTarget.Database); + Logger.Log($"Skipping import of {file.Filename} due to missing local ruleset {decodedInfo.Ruleset.OnlineID}.", LoggingTarget.Database); continue; } diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 897d4363f1..8d622955b7 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -175,7 +175,7 @@ namespace osu.Game.Tests.Beatmaps var beatmap = decoder.Decode(stream); var rulesetInstance = CreateRuleset(); - beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.RulesetID == rulesetInstance.RulesetInfo.ID ? rulesetInstance.RulesetInfo : new RulesetInfo(); + beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.Ruleset.OnlineID == rulesetInstance.RulesetInfo.OnlineID ? rulesetInstance.RulesetInfo : new RulesetInfo(); return beatmap; } diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index f7e154b5e7..2a3e51b4f5 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Beatmaps currentTestBeatmap = Decoder.GetDecoder(reader).Decode(reader); // populate ruleset for beatmap converters that require it to be present. - var ruleset = rulesetStore.GetRuleset(currentTestBeatmap.BeatmapInfo.RulesetID); + var ruleset = rulesetStore.GetRuleset(currentTestBeatmap.BeatmapInfo.Ruleset.OnlineID); Debug.Assert(ruleset != null); diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 42e96f80ca..ec02655544 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -282,7 +282,7 @@ namespace osu.Game.Tests.Visual Status = beatmap.Status, Checksum = beatmap.MD5Hash, AuthorID = beatmap.Metadata.Author.OnlineID, - RulesetID = beatmap.RulesetID, + RulesetID = beatmap.Ruleset.OnlineID, StarRating = beatmap.StarRating, DifficultyName = beatmap.DifficultyName, } From 5637fd64d641dcf147d7f9e2d17532bf351264ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 15:59:20 +0900 Subject: [PATCH 074/256] Perform ordering using `IComparable` instead --- .../NonVisual/RulesetInfoOrderingTest.cs | 2 +- osu.Game/Rulesets/EFRulesetInfo.cs | 2 ++ osu.Game/Rulesets/IRulesetInfo.cs | 2 +- osu.Game/Rulesets/RulesetInfo.cs | 19 ++++++++++++++----- osu.Game/Rulesets/RulesetStore.cs | 2 +- .../Select/Carousel/CarouselBeatmap.cs | 2 +- .../Select/Carousel/CarouselBeatmapSet.cs | 2 +- 7 files changed, 21 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs b/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs index e7c04d27c1..ae999d08d5 100644 --- a/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs +++ b/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.NonVisual new RulesetInfo("custom3", "Custom Ruleset 3", string.Empty, -1), }; - var orderedRulesets = rulesets.OrderBy(r => r.SortID); + var orderedRulesets = rulesets.OrderBy(r => r); // Ensure all customs are after official. Assert.That(orderedRulesets.Select(r => r.OnlineID), Is.EqualTo(new[] { 0, 2, -1, -1, -1, -1 })); diff --git a/osu.Game/Rulesets/EFRulesetInfo.cs b/osu.Game/Rulesets/EFRulesetInfo.cs index 9559cfa00c..b4a64fe932 100644 --- a/osu.Game/Rulesets/EFRulesetInfo.cs +++ b/osu.Game/Rulesets/EFRulesetInfo.cs @@ -42,6 +42,8 @@ namespace osu.Game.Rulesets public bool Equals(EFRulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo; + public int CompareTo(RulesetInfo other) => ID?.CompareTo(other.ID) ?? -1; + public override bool Equals(object obj) => obj is EFRulesetInfo rulesetInfo && Equals(rulesetInfo); public bool Equals(IRulesetInfo other) => other is RulesetInfo b && Equals(b); diff --git a/osu.Game/Rulesets/IRulesetInfo.cs b/osu.Game/Rulesets/IRulesetInfo.cs index 6599e0d59d..44731a2495 100644 --- a/osu.Game/Rulesets/IRulesetInfo.cs +++ b/osu.Game/Rulesets/IRulesetInfo.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets /// /// A representation of a ruleset's metadata. /// - public interface IRulesetInfo : IHasOnlineID, IEquatable + public interface IRulesetInfo : IHasOnlineID, IEquatable, IComparable { /// /// The user-exposed name of this ruleset. diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index d721926e95..896b9ee279 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -24,11 +24,6 @@ namespace osu.Game.Rulesets public string InstantiationInfo { get; set; } = string.Empty; - /// - /// A best effort sort ID which provides stable ordering and puts online rulesets before non-online rulesets. - /// - public int SortID => OnlineID >= 0 ? OnlineID : Math.Abs(ShortName.GetHashCode()); - public RulesetInfo(string shortName, string name, string instantiationInfo, int onlineID) { ShortName = shortName; @@ -62,6 +57,20 @@ namespace osu.Game.Rulesets public bool Equals(IRulesetInfo? other) => other is RulesetInfo b && Equals(b); + public int CompareTo(RulesetInfo other) + { + // Official rulesets are always given precedence for the time being. + if (OnlineID >= 0) + { + if (other.OnlineID >= 0) + return OnlineID.CompareTo(other.OnlineID); + + return -1; + } + + return string.Compare(ShortName, other.ShortName, StringComparison.Ordinal); + } + public override int GetHashCode() { // Importantly, ignore the underlying realm hash code, as it will usually not match. diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index a9fd8f430e..d017d54ed9 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -163,7 +163,7 @@ namespace osu.Game.Rulesets } } - availableRulesets.AddRange(detachedRulesets.OrderBy(r => r.SortID)); + availableRulesets.AddRange(detachedRulesets.OrderBy(r => r)); }); } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 9d49fdaf6e..0045e7ba4f 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -89,7 +89,7 @@ namespace osu.Game.Screens.Select.Carousel { default: case SortMode.Difficulty: - int ruleset = BeatmapInfo.Ruleset.SortID.CompareTo(otherBeatmap.BeatmapInfo.Ruleset.SortID); + int ruleset = BeatmapInfo.Ruleset.CompareTo(otherBeatmap.BeatmapInfo.Ruleset); if (ruleset != 0) return ruleset; return BeatmapInfo.StarRating.CompareTo(otherBeatmap.BeatmapInfo.StarRating); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 274be7b73a..fc4b6c27f3 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Select.Carousel beatmapSet.Beatmaps .Where(b => !b.Hidden) - .OrderBy(b => b.Ruleset.SortID) + .OrderBy(b => b.Ruleset) .ThenBy(b => b.StarRating) .Select(b => new CarouselBeatmap(b)) .ForEach(AddChild); From b87d1a61a8d2d2d48674ba3bed92568cabef7c25 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 16:39:36 +0900 Subject: [PATCH 075/256] Fix `ButtonSystem` null reference crash due to missing null check in delayed animations ```csharp [runtime] 2022-01-27 07:36:34 [error]: System.NullReferenceException: Object reference not set to an instance of an object. [runtime] 2022-01-27 07:36:34 [error]: at osu.Game.Screens.Menu.ButtonSystem.<>c__DisplayClass56_0.b__1() in /Users/dean/Projects/osu/osu.Game/Screens/Menu/ButtonSystem.cs:line 357 [runtime] 2022-01-27 07:36:34 [error]: at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal() [runtime] 2022-01-27 07:36:34 [error]: at osu.Framework.Threading.Scheduler.Update() [runtime] 2022-01-27 07:36:34 [error]: at osu.Framework.Graphics.Drawable.UpdateSubTree() ``` --- osu.Game/Screens/Menu/ButtonSystem.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 32731407fd..b03425fef4 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -49,6 +50,7 @@ namespace osu.Game.Screens.Menu public const float BUTTON_WIDTH = 140f; public const float WEDGE_WIDTH = 20; + [CanBeNull] private OsuLogo logo; /// @@ -328,9 +330,9 @@ namespace osu.Game.Screens.Menu game?.Toolbar.Hide(); - logo.ClearTransforms(targetMember: nameof(Position)); - logo.MoveTo(new Vector2(0.5f), 800, Easing.OutExpo); - logo.ScaleTo(1, 800, Easing.OutExpo); + logo?.ClearTransforms(targetMember: nameof(Position)); + logo?.MoveTo(new Vector2(0.5f), 800, Easing.OutExpo); + logo?.ScaleTo(1, 800, Easing.OutExpo); }, buttonArea.Alpha * 150); break; @@ -354,7 +356,7 @@ namespace osu.Game.Screens.Menu logoDelayedAction = Scheduler.AddDelayed(() => { if (impact) - logo.Impact(); + logo?.Impact(); game?.Toolbar.Show(); }, 200); From 0a45aa80cb011e658dc1d7c3504d2fe4ef12f079 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 16:16:49 +0900 Subject: [PATCH 076/256] Remove unnecessary double-schedule in `UpdateBeatmapSet` --- osu.Game/Screens/Select/BeatmapCarousel.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index dff2c598c3..94e520ba1c 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -311,13 +311,10 @@ namespace osu.Game.Screens.Select itemsCache.Invalidate(); - Schedule(() => - { - if (!Scroll.UserScrolling) - ScrollToSelected(true); + if (!Scroll.UserScrolling) + ScrollToSelected(true); - BeatmapSetsChanged?.Invoke(); - }); + BeatmapSetsChanged?.Invoke(); }); /// From 449e9bcf5c123e1e1c92534f8f3a710412a5028d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 16:17:38 +0900 Subject: [PATCH 077/256] Ensure beatmap carousel scroll position is maintained during deletion operations --- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 94e520ba1c..c27915c383 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -286,6 +286,9 @@ namespace osu.Game.Screens.Select root.RemoveChild(existingSet); itemsCache.Invalidate(); + + if (!Scroll.UserScrolling) + ScrollToSelected(true); }); public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => From f2cecad83b2e1df2b2e15c5cd49e04534cf55dca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 16:38:02 +0900 Subject: [PATCH 078/256] Add failing test coverage showing carousel deletions don't keep scroll position --- .../SongSelect/TestSceneBeatmapCarousel.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 0298c3bea9..2f2001c4f2 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets; @@ -40,6 +41,36 @@ namespace osu.Game.Tests.Visual.SongSelect this.rulesets = rulesets; } + [Test] + public void TestScrollPositionMaintainedOnAdd() + { + loadBeatmaps(count: 1, randomDifficulties: false); + + for (int i = 0; i < 10; i++) + { + AddRepeatStep("Add some sets", () => carousel.UpdateBeatmapSet(TestResources.CreateTestBeatmapSetInfo()), 4); + + checkSelectionIsCentered(); + } + } + + [Test] + public void TestScrollPositionMaintainedOnDelete() + { + loadBeatmaps(count: 50, randomDifficulties: false); + + for (int i = 0; i < 10; i++) + { + AddRepeatStep("Remove some sets", () => + carousel.RemoveBeatmapSet(carousel.Items.Select(item => item.Item) + .OfType() + .OrderBy(item => item.GetHashCode()) + .First(item => item.State.Value != CarouselItemState.Selected && item.Visible).BeatmapSet), 4); + + checkSelectionIsCentered(); + } + } + [Test] public void TestManyPanels() { @@ -813,6 +844,18 @@ namespace osu.Game.Tests.Visual.SongSelect carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count); } + private void checkSelectionIsCentered() + { + AddAssert("Selected panel is centered", () => + { + return Precision.AlmostEquals( + carousel.ScreenSpaceDrawQuad.Centre, + carousel.Items + .First(i => i.Item.State.Value == CarouselItemState.Selected) + .ScreenSpaceDrawQuad.Centre, 100); + }); + } + private void checkNoSelection() => AddAssert("Selection is null", () => currentSelection == null); private void nextRandom() => From 3ae5973ab7de887e1908430d3601fbc18301489e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 17:08:12 +0900 Subject: [PATCH 079/256] Fix compilation error due to commit split --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 2 +- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 3962f7b26f..1f346d19f0 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -89,7 +89,7 @@ namespace osu.Game.Screens.Select.Carousel { default: case SortMode.Difficulty: - int ruleset = BeatmapInfo.RulesetID.CompareTo(otherBeatmap.BeatmapInfo.RulesetID); + int ruleset = BeatmapInfo.Ruleset.OnlineID.CompareTo(otherBeatmap.BeatmapInfo.Ruleset.OnlineID); if (ruleset != 0) return ruleset; return BeatmapInfo.StarRating.CompareTo(otherBeatmap.BeatmapInfo.StarRating); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index b2b3b5411c..6156fea769 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Select.Carousel beatmapSet.Beatmaps .Where(b => !b.Hidden) - .OrderBy(b => b.RulesetID) + .OrderBy(b => b.Ruleset.OnlineID) .ThenBy(b => b.StarRating) .Select(b => new CarouselBeatmap(b)) .ForEach(AddChild); From 831fa44433059078f0c96d06cf0e69b8686e5d8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 19:35:04 +0900 Subject: [PATCH 080/256] Fix song select tests not waiting for beatmap imports to arrive After the change to realm, notification fires could take a frame or two. We aren't accounting for this. Fixes test failures like https://github.com/ppy/osu/runs/4963255990?check_suite_focus=true --- .../SongSelect/TestScenePlaySongSelect.cs | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 458c6130c7..ca0f2b8012 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -13,6 +13,7 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Extensions; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; @@ -73,6 +74,17 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("delete all beatmaps", () => manager?.Delete()); } + public override void TearDownSteps() + { + base.TearDownSteps(); + + AddStep("remove song select", () => + { + songSelect?.Expire(); + songSelect = null; + }); + } + [Test] public void TestSingleFilterOnEnter() { @@ -870,9 +882,16 @@ namespace osu.Game.Tests.Visual.SongSelect return set.ChildrenOfType().ToList().FindIndex(i => i == icon); } - private void addRulesetImportStep(int id) => AddStep($"import test map for ruleset {id}", () => importForRuleset(id)); + private void addRulesetImportStep(int id) + { + Live imported = null; + AddStep($"import test map for ruleset {id}", () => imported = importForRuleset(id)); + // This is specifically for cases where the add is happening post song select load. + // For cases where song select is null, the assertions are provided by the load checks. + AddUntilStep("wait for imported to arrive in carousel", () => songSelect == null || songSelect.Carousel.BeatmapSets.Any(s => s.ID == imported?.ID)); + } - private void importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray())); + private Live importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray())); private void checkMusicPlaying(bool playing) => AddUntilStep($"music {(playing ? "" : "not ")}playing", () => music.IsPlaying == playing); From 1bdf16494baa48fd15715ece296b198f6f60b64f Mon Sep 17 00:00:00 2001 From: Spooghetti420 Date: Thu, 27 Jan 2022 11:35:31 +0000 Subject: [PATCH 081/256] Add No Long Notes mod --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 3 +- .../Mods/ManiaModNoLongNotes.cs | 80 +++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 6fc7dc018b..3471f4ca66 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -243,7 +243,8 @@ namespace osu.Game.Rulesets.Mania new ManiaModDifficultyAdjust(), new ManiaModClassic(), new ManiaModInvert(), - new ManiaModConstantSpeed() + new ManiaModConstantSpeed(), + new ManiaModNoLongNotes() }; case ModType.Automation: diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs new file mode 100644 index 0000000000..7295a9eb6e --- /dev/null +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs @@ -0,0 +1,80 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mods; +using osu.Framework.Graphics.Sprites; +using System; +using System.Collections.Generic; +using osu.Game.Audio; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Framework.Utils; +using osu.Game.Overlays.Settings; +using osu.Framework.Bindables; +using osu.Game.Configuration; +using osu.Game.Graphics; + + + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModNoLongNotes : Mod, IApplicableAfterBeatmapConversion + { + + public override string Name => "No Long Notes"; + public override string Acronym => "NL"; + public override double ScoreMultiplier => 1; + public override string Description => @"Turns all held notes into tap notes. No coordination required."; + public override IconUsage? Icon => FontAwesome.Solid.DotCircle; + public override ModType Type => ModType.Conversion; + + [SettingSource("Add end notes", "Also add a note at the end of a held note")] + public BindableBool AddEndNotes { get; } = new BindableBool + { + Default = true, + Value = true + }; + + [SettingSource("Length threshold", "Only add an end note for held notes longer than this threshold (in milliseconds)")] + public BindableNumber Threshold { get; } = new BindableDouble + { + MinValue = 1.0, + MaxValue = 1990.0, + Default = 200.0, + Value = 200.0, + Precision = 1.0, + }; + public void ApplyToBeatmap(IBeatmap beatmap) + { + var maniaBeatmap = (ManiaBeatmap)beatmap; + + var newObjects = new List(); + beatmap.HitObjects.OfType().ForEach(h => + { + // Add a note for the beginning of the hold note + newObjects.Add(new Note + { + Column = h.Column, + StartTime = h.StartTime, + Samples = h.Samples + }); + + // Don't add an end note if the duration is below the threshold, or end notes are disabled + if (AddEndNotes.Value && h.Duration > Threshold.Value) + { + newObjects.Add(new Note + { + Column = h.Column, + StartTime = h.EndTime, + Samples = h.Samples + }); + } + }); + + maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType().Concat(newObjects).OrderBy(h => h.StartTime).ToList(); + } + } +} From 33b7bdcf8260cf0d9090d9edaa186729682b8b3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 21:49:33 +0900 Subject: [PATCH 082/256] Update pointless `CompareTo` implementation --- osu.Game/Rulesets/EFRulesetInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/EFRulesetInfo.cs b/osu.Game/Rulesets/EFRulesetInfo.cs index b4a64fe932..ba56adac49 100644 --- a/osu.Game/Rulesets/EFRulesetInfo.cs +++ b/osu.Game/Rulesets/EFRulesetInfo.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets public bool Equals(EFRulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo; - public int CompareTo(RulesetInfo other) => ID?.CompareTo(other.ID) ?? -1; + public int CompareTo(RulesetInfo other) => OnlineID.CompareTo(other.OnlineID); public override bool Equals(object obj) => obj is EFRulesetInfo rulesetInfo && Equals(rulesetInfo); From 2d1a8a9d492258f760ed9d1475135862d76f483f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 21:58:04 +0900 Subject: [PATCH 083/256] Use a more correct `CompareTo` implementation for ruleset ordering --- osu.Game/Rulesets/RulesetInfo.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index dd47a19f32..0a0941d1ff 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -51,14 +51,14 @@ namespace osu.Game.Rulesets public int CompareTo(RulesetInfo other) { + if (OnlineID >= 0 && other.OnlineID >= 0) + return OnlineID.CompareTo(other.OnlineID); + // Official rulesets are always given precedence for the time being. if (OnlineID >= 0) - { - if (other.OnlineID >= 0) - return OnlineID.CompareTo(other.OnlineID); - return -1; - } + if (other.OnlineID >= 0) + return 1; return string.Compare(ShortName, other.ShortName, StringComparison.Ordinal); } From fae4f8bd8e17f7a5df716b8a19ca731a229c714f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 27 Jan 2022 21:59:44 +0900 Subject: [PATCH 084/256] Move nulling of previous `songSelect` to `SetUpSteps` instead --- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index ca0f2b8012..0c452f857f 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -69,22 +69,13 @@ namespace osu.Game.Tests.Visual.SongSelect { Ruleset.Value = new OsuRuleset().RulesetInfo; Beatmap.SetDefault(); + + songSelect = null; }); AddStep("delete all beatmaps", () => manager?.Delete()); } - public override void TearDownSteps() - { - base.TearDownSteps(); - - AddStep("remove song select", () => - { - songSelect?.Expire(); - songSelect = null; - }); - } - [Test] public void TestSingleFilterOnEnter() { From 81461be49fede927581065f46f03331d69d93e8d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 00:14:18 +0900 Subject: [PATCH 085/256] Skip beatmap imports where ruleset is not present in realm Closes #16651. When a ruleset is not available, the `Find` call would return null. When a null is passed to the constructor, `BeatmapInfo` would create an "osu" ruleset, which tries to get stored to realm and fails on duplicate primary key. Probably need to add better safeties against this (or change that constructor...) but this will fix the migration process. Probably not serious enough to pull the build. This only affects rulesets like karaoke which have custom beatmaps. --- osu.Game/Database/EFToRealmMigrator.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 639bf10e19..9fb0130619 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -232,6 +232,7 @@ namespace osu.Game.Database var transaction = r.BeginWrite(); int written = 0; + int missing = 0; try { @@ -261,6 +262,12 @@ namespace osu.Game.Database var ruleset = r.Find(beatmap.RulesetInfo.ShortName); var metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata); + if (ruleset == null) + { + log($"Skipping {++missing} beatmaps with missing ruleset"); + continue; + } + var realmBeatmap = new BeatmapInfo(ruleset, new BeatmapDifficulty(beatmap.BaseDifficulty), metadata) { DifficultyName = beatmap.DifficultyName, From 942ea896f1dfab080c624e57b8c90b30844e56ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 01:20:32 +0900 Subject: [PATCH 086/256] Skip scores missing beatmaps during realm migration --- osu.Game/Database/EFToRealmMigrator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 9fb0130619..a53704df9d 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -374,12 +374,12 @@ namespace osu.Game.Database log($"Migrated {written}/{count} scores..."); } - var beatmap = r.All().First(b => b.Hash == score.BeatmapInfo.Hash); + var beatmap = r.All().FirstOrDefault(b => b.Hash == score.BeatmapInfo.Hash); var ruleset = r.Find(score.Ruleset.ShortName); - if (ruleset == null) + if (beatmap == null || ruleset == null) { - log($"Skipping {++missing} scores with missing ruleset"); + log($"Skipping {++missing} scores with missing ruleset or beatmap"); continue; } From c0b2f8bd01b62c2bbd0366c8d97eb883e95b9349 Mon Sep 17 00:00:00 2001 From: Spooghetti420 Date: Thu, 27 Jan 2022 16:21:38 +0000 Subject: [PATCH 087/256] Fix newline style in mod --- osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs index 7295a9eb6e..56522a4587 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs @@ -25,10 +25,15 @@ namespace osu.Game.Rulesets.Mania.Mods { public override string Name => "No Long Notes"; + public override string Acronym => "NL"; + public override double ScoreMultiplier => 1; + public override string Description => @"Turns all held notes into tap notes. No coordination required."; + public override IconUsage? Icon => FontAwesome.Solid.DotCircle; + public override ModType Type => ModType.Conversion; [SettingSource("Add end notes", "Also add a note at the end of a held note")] From 400633bd99a8cb86f647437d18f25ee4c702232b Mon Sep 17 00:00:00 2001 From: Spooghetti420 Date: Thu, 27 Jan 2022 16:23:09 +0000 Subject: [PATCH 088/256] Add another newline --- osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs index 56522a4587..d10003b783 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs @@ -52,6 +52,7 @@ namespace osu.Game.Rulesets.Mania.Mods Value = 200.0, Precision = 1.0, }; + public void ApplyToBeatmap(IBeatmap beatmap) { var maniaBeatmap = (ManiaBeatmap)beatmap; From 9deeaee40481e7702c4b0e143ec177e755928dd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 27 Jan 2022 16:57:38 +0100 Subject: [PATCH 089/256] Fix tournament client not loading Caused by a `LoadComponentsAsync()` call being fired from a worker thread, which will throw exceptions since the recent addition of safety checks around that method. --- osu.Game.Tournament/TournamentGame.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index 5d613894d4..7967f54b49 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -61,18 +61,15 @@ namespace osu.Game.Tournament loadingSpinner.Show(); - BracketLoadTask.ContinueWith(t => + BracketLoadTask.ContinueWith(t => Schedule(() => { if (t.IsFaulted) { - Schedule(() => - { - loadingSpinner.Hide(); - loadingSpinner.Expire(); + loadingSpinner.Hide(); + loadingSpinner.Expire(); - Logger.Error(t.Exception, "Couldn't load bracket with error"); - Add(new WarningBox($"Your {BRACKET_FILENAME} file could not be parsed. Please check runtime.log for more details.")); - }); + Logger.Error(t.Exception, "Couldn't load bracket with error"); + Add(new WarningBox($"Your {BRACKET_FILENAME} file could not be parsed. Please check runtime.log for more details.")); return; } @@ -143,7 +140,7 @@ namespace osu.Game.Tournament windowMode.Value = WindowMode.Windowed; }), true); }); - }); + })); } } } From 7dc3940dee206b6ebec790078c4eefa6ee78b464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 27 Jan 2022 21:41:09 +0100 Subject: [PATCH 090/256] Add test coverage for preserving legacy beatmap info defaults --- .../Formats/LegacyBeatmapDecoderTest.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 1edd21b5a9..3a6051ed43 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -794,5 +794,32 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(path.Distance, Is.EqualTo(1)); } } + + [Test] + public void TestLegacyDefaultsPreserved() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var memoryStream = new MemoryStream()) + using (var stream = new LineBufferedReader(memoryStream)) + { + var decoded = decoder.Decode(stream); + + Assert.Multiple(() => + { + Assert.That(decoded.BeatmapInfo.AudioLeadIn, Is.EqualTo(0)); + Assert.That(decoded.BeatmapInfo.StackLeniency, Is.EqualTo(0.7f)); + Assert.That(decoded.BeatmapInfo.SpecialStyle, Is.False); + Assert.That(decoded.BeatmapInfo.LetterboxInBreaks, Is.False); + Assert.That(decoded.BeatmapInfo.WidescreenStoryboard, Is.False); + Assert.That(decoded.BeatmapInfo.EpilepsyWarning, Is.False); + Assert.That(decoded.BeatmapInfo.SamplesMatchPlaybackRate, Is.False); + Assert.That(decoded.BeatmapInfo.Countdown, Is.EqualTo(CountdownType.Normal)); + Assert.That(decoded.BeatmapInfo.CountdownOffset, Is.EqualTo(0)); + Assert.That(decoded.BeatmapInfo.Metadata.PreviewTime, Is.EqualTo(-1)); + Assert.That(decoded.BeatmapInfo.Ruleset.OnlineID, Is.EqualTo(0)); + }); + } + } } } From 1b8136e3e0fa023a613dd0b66602b9b943db83d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 27 Jan 2022 21:41:30 +0100 Subject: [PATCH 091/256] Change some `BeatmapInfo` defaults in a backwards compatible manner --- osu.Game/Beatmaps/BeatmapInfo.cs | 4 ++-- osu.Game/Beatmaps/BeatmapManager.cs | 9 +-------- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 15 +++++++++++++++ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 3e6e33f1d0..e4bfd768b7 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -99,11 +99,11 @@ namespace osu.Game.Beatmaps public bool LetterboxInBreaks { get; set; } - public bool WidescreenStoryboard { get; set; } + public bool WidescreenStoryboard { get; set; } = true; public bool EpilepsyWarning { get; set; } - public bool SamplesMatchPlaybackRate { get; set; } + public bool SamplesMatchPlaybackRate { get; set; } = true; public double DistanceSpacing { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 414b7cd12b..e4fdb3d471 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -90,14 +90,7 @@ namespace osu.Game.Beatmaps { Beatmaps = { - new BeatmapInfo - { - Difficulty = new BeatmapDifficulty(), - Ruleset = ruleset, - Metadata = metadata, - WidescreenStoryboard = true, - SamplesMatchPlaybackRate = true, - } + new BeatmapInfo(ruleset, new BeatmapDifficulty(), metadata) } }; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 35d1cefeca..01ba0fcc9f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -56,6 +56,8 @@ namespace osu.Game.Beatmaps.Formats this.beatmap = beatmap; this.beatmap.BeatmapInfo.BeatmapVersion = FormatVersion; + applyLegacyDefaults(this.beatmap.BeatmapInfo); + base.ParseStreamInto(stream, beatmap); flushPendingPoints(); @@ -70,6 +72,19 @@ namespace osu.Game.Beatmaps.Formats hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.Difficulty); } + /// + /// Some `BeatmapInfo` members have default values that differ from the default values used by stable. + /// In addition, legacy beatmaps will sometimes not contain some configuration keys, in which case + /// the legacy default values should be used. + /// This method's intention is to restore those legacy defaults. + /// See also: https://osu.ppy.sh/wiki/en/Client/File_formats/Osu_%28file_format%29 + /// + private void applyLegacyDefaults(BeatmapInfo beatmapInfo) + { + beatmapInfo.WidescreenStoryboard = false; + beatmapInfo.SamplesMatchPlaybackRate = false; + } + protected override bool ShouldSkipLine(string line) => base.ShouldSkipLine(line) || line.StartsWith(' ') || line.StartsWith('_'); protected override void ParseLine(Beatmap beatmap, Section section, string line) From 6674567af1d678f33eb7c54920e3f11cba7301a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 27 Jan 2022 21:51:51 +0100 Subject: [PATCH 092/256] Use -1 as the default preview time globally in metadata --- osu.Game/Beatmaps/BeatmapMetadata.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index cb38373bd3..1514d3af7a 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -39,7 +39,7 @@ namespace osu.Game.Beatmaps /// The time in milliseconds to begin playing the track for preview purposes. /// If -1, the track should begin playing at 40% of its length. /// - public int PreviewTime { get; set; } + public int PreviewTime { get; set; } = -1; public string AudioFile { get; set; } = string.Empty; public string BackgroundFile { get; set; } = string.Empty; From 3e068e564d79d6b446b93a69904268abd19ba194 Mon Sep 17 00:00:00 2001 From: Spooghetti420 Date: Thu, 27 Jan 2022 20:56:51 +0000 Subject: [PATCH 093/256] Update mod per discussion + create test --- .../Mods/TestSceneManiaModNoHolds.cs | 52 +++++++++++++ osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- .../Mods/ManiaModNoHolds.cs | 75 +++++++++++++++++++ 3 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs create mode 100644 osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs new file mode 100644 index 0000000000..411f891f43 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Tests.Visual; +using osu.Game.Rulesets.Mania.Tests; +using System.Collections.Generic; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Mania.Beatmaps; + +namespace osu.Game.Rulesets.Mania.Tests.Mods +{ + public class TestSceneManiaModNoHolds : ModTestScene + { + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + + [Test] + public void TestMapHasNoHeldNotes() + { + var testBeatmap = createBeatmap(); + Assert.That(!testBeatmap.HitObjects.OfType().Any()); + } + + + private static IBeatmap createBeatmap() + { + var beatmap = createRawBeatmap(); + var noHoldsMod = new ManiaModNoHolds(); + + foreach (var hitObject in beatmap.HitObjects) + hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + noHoldsMod.ApplyToBeatmap(beatmap); + + return beatmap; + } + private static IBeatmap createRawBeatmap() + { + var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); + beatmap.HitObjects.Add(new Note { StartTime = 1000 }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, EndTime = 3000 }); + return beatmap; + } + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 3471f4ca66..55ca1a465e 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -244,7 +244,7 @@ namespace osu.Game.Rulesets.Mania new ManiaModClassic(), new ManiaModInvert(), new ManiaModConstantSpeed(), - new ManiaModNoLongNotes() + new ManiaModNoHolds() }; case ModType.Automation: diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs new file mode 100644 index 0000000000..0d5ca44898 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs @@ -0,0 +1,75 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mods; +using osu.Framework.Graphics.Sprites; +using System; +using System.Collections.Generic; +using osu.Game.Audio; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Framework.Utils; +using osu.Game.Overlays.Settings; +using osu.Framework.Bindables; +using osu.Game.Configuration; +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModNoHolds : Mod, IApplicableAfterBeatmapConversion + { + public override string Name => "No Holds"; + + public override string Acronym => "NH"; + + public override double ScoreMultiplier => 1; + + public override string Description => @"Turns all hold notes into normal notes. No coordination required."; + + public override IconUsage? Icon => FontAwesome.Solid.DotCircle; + + public override ModType Type => ModType.Conversion; + + [SettingSource("Add end notes", "Also add a note at the end of a hold note")] + public BindableBool AddEndNotes { get; } = new BindableBool + { + Default = true, + Value = true + }; + + public void ApplyToBeatmap(IBeatmap beatmap) + { + var maniaBeatmap = (ManiaBeatmap)beatmap; + + var newObjects = new List(); + foreach (var h in beatmap.HitObjects.OfType()) + { + // Add a note for the beginning of the hold note + newObjects.Add(new Note + { + Column = h.Column, + StartTime = h.StartTime, + Samples = h.GetNodeSamples(0) + }); + + // Don't add an end note if the duration is shorter than some threshold, or end notes are disabled + if (AddEndNotes.Value && h.Duration > 200) + { + newObjects.Add(new Note + { + Column = h.Column, + StartTime = h.EndTime, + Samples = h.GetNodeSamples((h.NodeSamples?.Count - 1) ?? 1) + }); + } + } + + maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType().Concat(newObjects).OrderBy(h => h.StartTime).ToList(); + } + + } + +} From 146c54a2c14608b95363647a0e965aeeb47aa03e Mon Sep 17 00:00:00 2001 From: Spooghetti420 Date: Thu, 27 Jan 2022 21:02:59 +0000 Subject: [PATCH 094/256] Fix code formatting --- .../Mods/TestSceneManiaModNoHolds.cs | 1 - osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs index 411f891f43..f22b724c99 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs @@ -28,7 +28,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Assert.That(!testBeatmap.HitObjects.OfType().Any()); } - private static IBeatmap createBeatmap() { var beatmap = createRawBeatmap(); diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs index 0d5ca44898..a18dbad4a1 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs @@ -69,7 +69,5 @@ namespace osu.Game.Rulesets.Mania.Mods maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType().Concat(newObjects).OrderBy(h => h.StartTime).ToList(); } - } - -} +} \ No newline at end of file From f59828e2d9dc745e352ecbbf01fe811389e2ed47 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 28 Jan 2022 13:27:12 +0900 Subject: [PATCH 095/256] Add audio feedback to song select 'random' --- osu.Game/Screens/Select/BeatmapCarousel.cs | 29 +++++++++++++++++++++- osu.Game/Screens/Select/SongSelect.cs | 21 +++++++++++++--- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c27915c383..ef19eb187a 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -6,6 +6,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Graphics; @@ -151,6 +153,10 @@ namespace osu.Game.Screens.Select private readonly DrawablePool setPool = new DrawablePool(100); + private Sample spinSample; + + private int visibleSetsCount; + public BeatmapCarousel() { root = new CarouselRoot(this); @@ -169,8 +175,10 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, AudioManager audio) { + spinSample = audio.Samples.Get("SongSelect/random-spin"); + config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm); config.BindWith(OsuSetting.SongSelectRightMouseScroll, RightClickScrollingEnabled); @@ -419,6 +427,9 @@ namespace osu.Game.Screens.Select return false; var visibleSets = beatmapSets.Where(s => !s.Filtered.Value).ToList(); + + visibleSetsCount = visibleSets.Count; + if (!visibleSets.Any()) return false; @@ -450,6 +461,9 @@ namespace osu.Game.Screens.Select else set = visibleSets.ElementAt(RNG.Next(visibleSets.Count)); + if (selectedBeatmapSet != null) + playSpinSample(distanceBetween(set, selectedBeatmapSet)); + select(set); return true; } @@ -464,12 +478,25 @@ namespace osu.Game.Screens.Select { if (RandomAlgorithm.Value == RandomSelectAlgorithm.RandomPermutation) previouslyVisitedRandomSets.Remove(selectedBeatmapSet); + + if (selectedBeatmapSet != null) + playSpinSample(distanceBetween(beatmap, selectedBeatmapSet)); + select(beatmap); break; } } } + private double distanceBetween(CarouselItem item1, CarouselItem item2) => Math.Ceiling(Math.Abs(item1.CarouselYPosition - item2.CarouselYPosition) / DrawableCarouselItem.MAX_HEIGHT); + + private void playSpinSample(double distance) + { + var chan = spinSample.GetChannel(); + chan.Frequency.Value = 1f + Math.Min(1f, distance / visibleSetsCount); + chan.Play(); + } + private void select(CarouselItem item) { if (!AllowSelection) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 10150fcd9f..6fbadfe6cf 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -100,6 +100,7 @@ namespace osu.Game.Screens.Select private Sample sampleChangeDifficulty; private Sample sampleChangeBeatmap; + private Sample sampleRandomBeatmap; private Container carouselContainer; @@ -109,6 +110,8 @@ namespace osu.Game.Screens.Select private double audioFeedbackLastPlaybackTime; + private bool randomClicked; + [Resolved] private MusicController music { get; set; } @@ -288,6 +291,7 @@ namespace osu.Game.Screens.Select sampleChangeDifficulty = audio.Samples.Get(@"SongSelect/select-difficulty"); sampleChangeBeatmap = audio.Samples.Get(@"SongSelect/select-expand"); + sampleRandomBeatmap = audio.Samples.Get(@"SongSelect/select-random"); SampleConfirm = audio.Samples.Get(@"SongSelect/confirm-selection"); if (dialogOverlay != null) @@ -315,8 +319,16 @@ namespace osu.Game.Screens.Select (new FooterButtonMods { Current = Mods }, ModSelect), (new FooterButtonRandom { - NextRandom = () => Carousel.SelectNextRandom(), - PreviousRandom = Carousel.SelectPreviousRandom + NextRandom = () => + { + randomClicked = true; + Carousel.SelectNextRandom(); + }, + PreviousRandom = () => + { + randomClicked = true; + Carousel.SelectPreviousRandom(); + } }, null), (new FooterButtonOptions(), BeatmapOptions) }; @@ -486,7 +498,9 @@ namespace osu.Game.Screens.Select { if (beatmap != null && beatmapInfoPrevious != null && Time.Current - audioFeedbackLastPlaybackTime >= 50) { - if (beatmap.BeatmapSet?.ID == beatmapInfoPrevious.BeatmapSet?.ID) + if (randomClicked) + sampleRandomBeatmap.Play(); + else if (beatmap.BeatmapSet?.ID == beatmapInfoPrevious.BeatmapSet?.ID) sampleChangeDifficulty.Play(); else sampleChangeBeatmap.Play(); @@ -494,6 +508,7 @@ namespace osu.Game.Screens.Select audioFeedbackLastPlaybackTime = Time.Current; } + randomClicked = false; beatmapInfoPrevious = beatmap; } From c44af4853d6aabb82006d7944e146d82c06401fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 13:43:53 +0900 Subject: [PATCH 096/256] Add thread safety to `PollingComponent.Poll` implementations --- .../Components/ListingPollingComponent.cs | 14 ++++++++------ .../Components/SelectionPollingComponent.cs | 15 +++++++++------ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs index fcf7767958..666d425f62 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.OnlinePlay.Components }); } - private GetRoomsRequest pollReq; + private GetRoomsRequest lastPollRequest; protected override Task Poll() { @@ -45,10 +45,11 @@ namespace osu.Game.Screens.OnlinePlay.Components var tcs = new TaskCompletionSource(); - pollReq?.Cancel(); - pollReq = new GetRoomsRequest(Filter.Value.Status, Filter.Value.Category); + lastPollRequest?.Cancel(); - pollReq.Success += result => + var req = new GetRoomsRequest(Filter.Value.Status, Filter.Value.Category); + + req.Success += result => { foreach (var existing in RoomManager.Rooms.ToArray()) { @@ -66,10 +67,11 @@ namespace osu.Game.Screens.OnlinePlay.Components tcs.SetResult(true); }; - pollReq.Failure += _ => tcs.SetResult(false); + req.Failure += _ => tcs.SetResult(false); - API.Queue(pollReq); + API.Queue(req); + lastPollRequest = req; return tcs.Task; } } diff --git a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs index 22842fbb9e..e05bdf8c8e 100644 --- a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.OnlinePlay.Components this.room = room; } - private GetRoomRequest pollReq; + private GetRoomRequest lastPollRequest; protected override Task Poll() { @@ -30,19 +30,22 @@ namespace osu.Game.Screens.OnlinePlay.Components var tcs = new TaskCompletionSource(); - pollReq?.Cancel(); - pollReq = new GetRoomRequest(room.RoomID.Value.Value); + lastPollRequest?.Cancel(); - pollReq.Success += result => + var req = new GetRoomRequest(room.RoomID.Value.Value); + + req.Success += result => { result.RemoveExpiredPlaylistItems(); RoomManager.AddOrUpdateRoom(result); tcs.SetResult(true); }; - pollReq.Failure += _ => tcs.SetResult(false); + req.Failure += _ => tcs.SetResult(false); - API.Queue(pollReq); + API.Queue(req); + + lastPollRequest = req; return tcs.Task; } From c953a5d5034416bb32ee2caada7c0a74f0b4551a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 13:44:09 +0900 Subject: [PATCH 097/256] Ensure `PollingComponent.Poll` is always called from the update thread Not strictly required since all `Poll` implementations are now threadsafe, but extra safety is never a bad thing? --- osu.Game/Online/PollingComponent.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/PollingComponent.cs b/osu.Game/Online/PollingComponent.cs index 243be8da44..5eddb3b49d 100644 --- a/osu.Game/Online/PollingComponent.cs +++ b/osu.Game/Online/PollingComponent.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.Threading.Tasks; using osu.Framework.Bindables; +using osu.Framework.Development; using osu.Framework.Graphics.Containers; using osu.Framework.Threading; @@ -66,6 +68,8 @@ namespace osu.Game.Online private void doPoll() { + Debug.Assert(ThreadSafety.IsUpdateThread); + scheduledPoll = null; pollingActive = true; Poll().ContinueWith(_ => pollComplete()); @@ -96,13 +100,13 @@ namespace osu.Game.Online if (!lastTimePolled.HasValue) { - doPoll(); + Scheduler.AddOnce(doPoll); return; } if (Time.Current - lastTimePolled.Value > TimeBetweenPolls.Value) { - doPoll(); + Scheduler.AddOnce(doPoll); return; } From 91be77ad3d61f844c997681cfdf5b2643115bd43 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 14:01:10 +0900 Subject: [PATCH 098/256] Fix null ref in `ComposeScreen` when ruleset doesn't provide a composer --- osu.Game/Screens/Edit/Compose/ComposeScreen.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 2bdf59b21c..2cde962b12 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -82,6 +82,11 @@ namespace osu.Game.Screens.Edit.Compose protected override void LoadComplete() { base.LoadComplete(); + + // May be null in the case of a ruleset that doesn't have editor support, see CreateMainContent(). + if (composer == null) + return; + EditorBeatmap.SelectedHitObjects.BindCollectionChanged((_, __) => updateClipboardActionAvailability()); clipboard.BindValueChanged(_ => updateClipboardActionAvailability()); composer.OnLoadComplete += _ => updateClipboardActionAvailability(); From b3856c900541e1af68aa8e4a5c55b9a03f62356f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 14:01:31 +0900 Subject: [PATCH 099/256] Fix editor crashing on custom rulesets due to `ChangeHandler` not being supported As per https://github.com/ppy/osu/discussions/16668, even without proper saving support some ruleset developers do want to work on the editor. This brings things back into a workable state. --- osu.Game/Screens/Edit/Editor.cs | 46 +++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 4812da98f4..2fead84deb 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -28,6 +28,8 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Online.API; using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; +using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; @@ -61,7 +63,16 @@ namespace osu.Game.Screens.Edit public override bool? AllowTrackAdjustments => false; - protected bool HasUnsavedChanges => lastSavedHash != changeHandler.CurrentStateHash; + protected bool HasUnsavedChanges + { + get + { + if (!canSave) + return false; + + return lastSavedHash != changeHandler?.CurrentStateHash; + } + } [Resolved] private BeatmapManager beatmapManager { get; set; } @@ -72,10 +83,15 @@ namespace osu.Game.Screens.Edit [Resolved(canBeNull: true)] private DialogOverlay dialogOverlay { get; set; } + [Resolved(canBeNull: true)] + private NotificationOverlay notifications { get; set; } + public IBindable SamplePlaybackDisabled => samplePlaybackDisabled; private readonly Bindable samplePlaybackDisabled = new Bindable(); + private bool canSave; + private bool exitConfirmed; private string lastSavedHash; @@ -92,6 +108,8 @@ namespace osu.Game.Screens.Edit private IBeatmap playableBeatmap; private EditorBeatmap editorBeatmap; + + [CanBeNull] // Should be non-null once it can support custom rulesets. private EditorChangeHandler changeHandler; private EditorMenuBar menuBar; @@ -172,8 +190,14 @@ namespace osu.Game.Screens.Edit AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap, loadableBeatmap.GetSkin(), loadableBeatmap.BeatmapInfo)); dependencies.CacheAs(editorBeatmap); - changeHandler = new EditorChangeHandler(editorBeatmap); - dependencies.CacheAs(changeHandler); + + canSave = editorBeatmap.BeatmapInfo.Ruleset.CreateInstance() is ILegacyRuleset; + + if (canSave) + { + changeHandler = new EditorChangeHandler(editorBeatmap); + dependencies.CacheAs(changeHandler); + } beatDivisor.Value = editorBeatmap.BeatmapInfo.BeatDivisor; beatDivisor.BindValueChanged(divisor => editorBeatmap.BeatmapInfo.BeatDivisor = divisor.NewValue); @@ -311,8 +335,8 @@ namespace osu.Game.Screens.Edit } }); - changeHandler.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true); - changeHandler.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true); + changeHandler?.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true); + changeHandler?.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true); menuBar.Mode.ValueChanged += onModeChanged; } @@ -353,6 +377,12 @@ namespace osu.Game.Screens.Edit protected void Save() { + if (!canSave) + { + notifications?.Post(new SimpleErrorNotification { Text = "Saving is not supported for this ruleset yet, sorry!" }); + return; + } + // no longer new after first user-triggered save. isNewBeatmap = false; @@ -648,9 +678,9 @@ namespace osu.Game.Screens.Edit #endregion - protected void Undo() => changeHandler.RestoreState(-1); + protected void Undo() => changeHandler?.RestoreState(-1); - protected void Redo() => changeHandler.RestoreState(1); + protected void Redo() => changeHandler?.RestoreState(1); private void resetTrack(bool seekToStart = false) { @@ -761,7 +791,7 @@ namespace osu.Game.Screens.Edit private void updateLastSavedHash() { - lastSavedHash = changeHandler.CurrentStateHash; + lastSavedHash = changeHandler?.CurrentStateHash; } private List createFileMenuItems() From b7d8c9bf067f64021a4648ecfd413648258f373f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 14:18:10 +0900 Subject: [PATCH 100/256] Fix a couple of cases of incorrect equality checks in the case both values are null --- .../BeatmapSet/Scores/TopScoreStatisticsSection.cs | 3 +++ .../Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 1f3f73a60a..ec795cf6b2 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -107,6 +107,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { set { + if (score == null && value == null) + return; + if (score?.Equals(value) == true) return; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 2070e53257..31cbe91f5c 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -38,6 +38,14 @@ namespace osu.Game.Screens.Select.Leaderboards get => beatmapInfo; set { + if (beatmapInfo == null && value == null) + { + // always null scores to ensure a correct initial display. + // see weird `scoresLoadedOnce` logic in base implementation. + Scores = null; + return; + } + if (beatmapInfo?.Equals(value) == true) return; From f32d56e213fa26566b38ca12bb44cf9d8f0f0100 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 14:48:17 +0900 Subject: [PATCH 101/256] Bring `HoldForMenuButton` tests up-to-date in code quality --- .../Gameplay/TestSceneHoldForMenuButton.cs | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs index 235842acc9..e0765ab5fb 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs @@ -3,9 +3,10 @@ using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; +using osu.Game.Graphics.Containers; using osu.Game.Screens.Play.HUD; using osuTK; using osuTK.Input; @@ -19,28 +20,35 @@ namespace osu.Game.Tests.Visual.Gameplay protected override double TimePerAction => 100; // required for the early exit test, since hold-to-confirm delay is 200ms - [BackgroundDependencyLoader] - private void load() + private HoldForMenuButton holdForMenuButton; + + [SetUpSteps] + public void SetUpSteps() { - HoldForMenuButton holdForMenuButton; - - Add(holdForMenuButton = new HoldForMenuButton + AddStep("create button", () => { - Origin = Anchor.BottomRight, - Anchor = Anchor.BottomRight, - Action = () => exitAction = true + exitAction = false; + + Child = holdForMenuButton = new HoldForMenuButton + { + Scale = new Vector2(2), + Origin = Anchor.CentreRight, + Anchor = Anchor.CentreRight, + Action = () => exitAction = true + }; }); + } - var text = holdForMenuButton.Children.OfType().First(); - + [Test] + public void TestMovementAndTrigger() + { AddStep("Trigger text fade in", () => InputManager.MoveMouseTo(holdForMenuButton)); - AddUntilStep("Text visible", () => text.IsPresent && !exitAction); + AddUntilStep("Text visible", () => getSpriteText().IsPresent && !exitAction); AddStep("Trigger text fade out", () => InputManager.MoveMouseTo(Vector2.One)); - AddUntilStep("Text is not visible", () => !text.IsPresent && !exitAction); + AddUntilStep("Text is not visible", () => !getSpriteText().IsPresent && !exitAction); AddStep("Trigger exit action", () => { - exitAction = false; InputManager.MoveMouseTo(holdForMenuButton); InputManager.PressButton(MouseButton.Left); }); @@ -50,6 +58,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Trigger exit action", () => InputManager.PressButton(MouseButton.Left)); AddUntilStep($"{nameof(holdForMenuButton.Action)} was triggered", () => exitAction); + AddStep("Release", () => InputManager.ReleaseButton(MouseButton.Left)); } } } From 28c8e07e3f223aaaa15fe840fc8ee375c31dc48d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 14:48:34 +0900 Subject: [PATCH 102/256] Ensure hold for menu button fades out if the cursor is never moved Closes https://github.com/ppy/osu/discussions/16669. --- .../Visual/Gameplay/TestSceneHoldForMenuButton.cs | 10 ++++++++++ osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs index e0765ab5fb..feec06f372 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs @@ -60,5 +60,15 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep($"{nameof(holdForMenuButton.Action)} was triggered", () => exitAction); AddStep("Release", () => InputManager.ReleaseButton(MouseButton.Left)); } + + [Test] + public void TestFadeOnNoInput() + { + AddStep("move mouse away", () => InputManager.MoveMouseTo(Vector2.One)); + AddUntilStep("wait for text fade out", () => !getSpriteText().IsPresent); + AddUntilStep("wait for button fade out", () => holdForMenuButton.Alpha < 0.1f); + } + + private SpriteText getSpriteText() => holdForMenuButton.Children.OfType().First(); } } diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 5a7ef786d3..430f001427 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -80,7 +80,7 @@ namespace osu.Game.Screens.Play.HUD base.LoadComplete(); } - private float positionalAdjust; + private float positionalAdjust = 1; // Start at 1 to handle the case where a user never send positional input. protected override bool OnMouseMove(MouseMoveEvent e) { From 32f9299fe082f7347aebfceef7aa6f8dd017f2f6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 28 Jan 2022 15:26:29 +0900 Subject: [PATCH 103/256] Remove unused using --- osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs index feec06f372..ddb0872541 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs @@ -6,7 +6,6 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; -using osu.Game.Graphics.Containers; using osu.Game.Screens.Play.HUD; using osuTK; using osuTK.Input; From cb7ae413feea2e53fd4b5aa3b59167ae0a398c13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 15:50:13 +0900 Subject: [PATCH 104/256] Ensure test game is always active --- osu.Game/Tests/Visual/OsuGameTestScene.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index ebbd9bb5dd..dabf49c15e 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -158,6 +158,14 @@ namespace osu.Game.Tests.Visual Dependencies.Get().SetValue(Static.MutedAudioNotificationShownOnce, true); } + + protected override void Update() + { + base.Update(); + + // when running in visual tests and the window loses focus, we generally don't want the game to pause. + ((Bindable)IsActive).Value = true; + } } public class TestLoader : Loader From 778eebc94df6b2ff8a068b014aaee05de8bd594b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 15:50:22 +0900 Subject: [PATCH 105/256] Add test coverage of local score import and deletion --- .../Navigation/TestSceneScreenNavigation.cs | 90 +++++++++++++++---- osu.Game/Tests/Visual/OsuGameTestScene.cs | 3 + 2 files changed, 74 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 89dca77af4..6d89feef52 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -10,16 +11,19 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Leaderboards; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Overlays.Toolbar; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Scoring; using osu.Game.Screens.Menu; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Screens.Select; +using osu.Game.Screens.Select.Leaderboards; using osu.Game.Screens.Select.Options; using osu.Game.Tests.Beatmaps.IO; using osuTK; @@ -96,35 +100,52 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestRetryFromResults() { - Player player = null; - ResultsScreen results = null; + var getOriginalPlayer = playToResults(); - IWorkingBeatmap beatmap() => Game.Beatmap.Value; + AddStep("attempt to retry", () => ((ResultsScreen)Game.ScreenStack.CurrentScreen).ChildrenOfType().First().Action()); + AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != getOriginalPlayer() && Game.ScreenStack.CurrentScreen is Player); + } - Screens.Select.SongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); - AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + [Test] + public void TestDeleteScoreAfterPlaying() + { + playToResults(); - AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + ScoreInfo score = null; + LeaderboardScore scorePanel = null; - AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + AddStep("get score", () => score = ((ResultsScreen)Game.ScreenStack.CurrentScreen).Score); - AddStep("set mods", () => Game.SelectedMods.Value = new Mod[] { new OsuModNoFail(), new OsuModDoubleTime { SpeedChange = { Value = 2 } } }); + AddAssert("ensure score is databased", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == false)); - AddStep("press enter", () => InputManager.Key(Key.Enter)); + AddStep("press back button", () => Game.ChildrenOfType().First().Action()); - AddUntilStep("wait for player", () => + AddStep("show local scores", () => Game.ChildrenOfType().First().Current.Value = new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Local)); + + AddUntilStep("wait for score displayed", () => (scorePanel = Game.ChildrenOfType().FirstOrDefault(s => s.Score.Equals(score))) != null); + + AddStep("right click panel", () => { - // dismiss any notifications that may appear (ie. muted notification). - clickMouseInCentre(); - return (player = Game.ScreenStack.CurrentScreen as Player) != null; + InputManager.MoveMouseTo(scorePanel); + InputManager.Click(MouseButton.Right); }); - AddUntilStep("wait for track playing", () => beatmap().Track.IsRunning); - AddStep("seek to near end", () => player.ChildrenOfType().First().Seek(beatmap().Beatmap.HitObjects[^1].StartTime - 1000)); - AddUntilStep("wait for pass", () => (results = Game.ScreenStack.CurrentScreen as ResultsScreen) != null && results.IsLoaded); - AddStep("attempt to retry", () => results.ChildrenOfType().First().Action()); - AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != player && Game.ScreenStack.CurrentScreen is Player); + AddStep("click delete", () => + { + var dropdownItem = Game + .ChildrenOfType().First() + .ChildrenOfType().First() + .ChildrenOfType().First(i => i.Item.Text.ToString() == "Delete"); + + InputManager.MoveMouseTo(dropdownItem); + InputManager.Click(MouseButton.Left); + }); + + AddStep("confirm deletion", () => InputManager.Key(Key.Number1)); + + AddAssert("ensure score is pending deletion", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == true)); + + AddUntilStep("wait for score panel removal", () => scorePanel.Parent == null); } [TestCase(true)] @@ -432,6 +453,37 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("test dispose doesn't crash", () => Game.Dispose()); } + private Func playToResults() + { + Player player = null; + + IWorkingBeatmap beatmap() => Game.Beatmap.Value; + + Screens.Select.SongSelect songSelect = null; + PushAndConfirm(() => songSelect = new TestPlaySongSelect()); + AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + + AddStep("set mods", () => Game.SelectedMods.Value = new Mod[] { new OsuModNoFail(), new OsuModDoubleTime { SpeedChange = { Value = 2 } } }); + + AddStep("press enter", () => InputManager.Key(Key.Enter)); + + AddUntilStep("wait for player", () => + { + // dismiss any notifications that may appear (ie. muted notification). + clickMouseInCentre(); + return (player = Game.ScreenStack.CurrentScreen as Player) != null; + }); + + AddUntilStep("wait for track playing", () => beatmap().Track.IsRunning); + AddStep("seek to near end", () => player.ChildrenOfType().First().Seek(beatmap().Beatmap.HitObjects[^1].StartTime - 1000)); + AddUntilStep("wait for pass", () => (Game.ScreenStack.CurrentScreen as ResultsScreen)?.IsLoaded == true); + return () => player; + } + private void clickMouseInCentre() { InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre); diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index dabf49c15e..3b8d9a4cd1 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -14,6 +14,7 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Overlays; @@ -111,6 +112,8 @@ namespace osu.Game.Tests.Visual public new ScreenStack ScreenStack => base.ScreenStack; + public RealmAccess Realm => Dependencies.Get(); + public new BackButton BackButton => base.BackButton; public new BeatmapManager BeatmapManager => base.BeatmapManager; From 2453bf5ed0b8b3660c2a109ad2720ba125b1fc2c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 15:54:51 +0900 Subject: [PATCH 106/256] Add test coverage of the same thing but via "clear all scores" button --- .../Navigation/TestSceneScreenNavigation.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 6d89feef52..3be7cf7c5c 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -106,6 +106,35 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != getOriginalPlayer() && Game.ScreenStack.CurrentScreen is Player); } + [Test] + public void TestDeleteAllScoresAfterPlaying() + { + playToResults(); + + ScoreInfo score = null; + LeaderboardScore scorePanel = null; + + AddStep("get score", () => score = ((ResultsScreen)Game.ScreenStack.CurrentScreen).Score); + + AddAssert("ensure score is databased", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == false)); + + AddStep("press back button", () => Game.ChildrenOfType().First().Action()); + + AddStep("show local scores", () => Game.ChildrenOfType().First().Current.Value = new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Local)); + + AddUntilStep("wait for score displayed", () => (scorePanel = Game.ChildrenOfType().FirstOrDefault(s => s.Score.Equals(score))) != null); + + AddStep("open options", () => InputManager.Key(Key.F3)); + + AddStep("choose clear all scores", () => InputManager.Key(Key.Number4)); + + AddStep("confirm deletion", () => InputManager.Key(Key.Number1)); + + AddAssert("ensure score is pending deletion", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == true)); + + AddUntilStep("wait for score panel removal", () => scorePanel.Parent == null); + } + [Test] public void TestDeleteScoreAfterPlaying() { From 0d3ac4fd9c72db8eb2049d07652375c0de419f0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 15:08:39 +0900 Subject: [PATCH 107/256] Fix delete local scores crashing the game --- osu.Game/Scoring/ScoreManager.cs | 9 +++++++++ osu.Game/Screens/Select/BeatmapClearScoresDialog.cs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 8f665224ee..6f9cce2d3c 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -266,6 +266,15 @@ namespace osu.Game.Scoring }); } + public void Delete(BeatmapInfo beatmap, bool silent = false) + { + realm.Run(r => + { + var beatmapScores = r.Find(beatmap.ID).Scores.ToList(); + scoreModelManager.Delete(beatmapScores, silent); + }); + } + public void Delete(List items, bool silent = false) { scoreModelManager.Delete(items, silent); diff --git a/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs b/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs index 774d3b4b28..4a16be4a3a 100644 --- a/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs +++ b/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Select Text = @"Yes. Please.", Action = () => { - Task.Run(() => scoreManager.Delete(s => !s.DeletePending && s.BeatmapInfo.ID == beatmapInfo.ID)) + Task.Run(() => scoreManager.Delete(beatmapInfo)) .ContinueWith(_ => onCompletion); } }, From 4d9b61212b22d154c0632c0d50978e90ecc59f9d Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 28 Jan 2022 18:13:51 +0900 Subject: [PATCH 108/256] Add 'cursor tap' audio feedback --- osu.Game/Graphics/Cursor/MenuCursor.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 8e272f637f..a89d8dac71 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -10,6 +10,8 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using System; using JetBrains.Annotations; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; @@ -30,13 +32,17 @@ namespace osu.Game.Graphics.Cursor private DragRotationState dragRotationState; private Vector2 positionMouseDown; + private Sample tapSample; + [BackgroundDependencyLoader(true)] - private void load([NotNull] OsuConfigManager config, [CanBeNull] ScreenshotManager screenshotManager) + private void load([NotNull] OsuConfigManager config, [CanBeNull] ScreenshotManager screenshotManager, AudioManager audio) { cursorRotate = config.GetBindable(OsuSetting.CursorRotation); if (screenshotManager != null) screenshotCursorVisibility.BindTo(screenshotManager.CursorVisibility); + + tapSample = audio.Samples.Get(@"UI/cursor-tap"); } protected override bool OnMouseMove(MouseMoveEvent e) @@ -70,6 +76,18 @@ namespace osu.Game.Graphics.Cursor return base.OnMouseMove(e); } + protected override bool OnClick(ClickEvent e) + { + var channel = tapSample.GetChannel(); + + // scale to [-0.75, 0.75] so that the sample isn't fully panned left or right (sounds weird) + channel.Balance.Value = ((activeCursor.X / DrawWidth) * 2 - 1) * 0.75; + channel.Frequency.Value = 0.99 + RNG.NextDouble(0.02); + channel.Play(); + + return base.OnClick(e); + } + protected override bool OnMouseDown(MouseDownEvent e) { if (State.Value == Visibility.Visible) From 17f0d7897b8a361a7a1fc5e6faa2379efc0a9a8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 18:35:59 +0900 Subject: [PATCH 109/256] Increase lenience of alpha check in `TestSceneOsuModNoScope` I believe the [test failures](https://github.com/ppy/osu/runs/4977283066?check_suite_focus=true) we're seeing here are due to the implementation of interpolation of the alpha being frame dependent (in a way that doesn't interact well with tests). The reason for never hitting the expected value is that the beatmap ends, causing the cursor to become fully visible again. It's probably already good-enough for most cases, so let's attempt to silence these test failures by not checking so precisely for the alpha value. We're checking for either 1 or 0 so it's not too important how close it is to either. --- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs index 8e226c7ded..44404ca245 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs @@ -145,6 +145,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods private bool isBreak() => Player.IsBreakTime.Value; - private bool cursorAlphaAlmostEquals(float alpha) => Precision.AlmostEquals(Player.DrawableRuleset.Cursor.Alpha, alpha); + private bool cursorAlphaAlmostEquals(float alpha) => Precision.AlmostEquals(Player.DrawableRuleset.Cursor.Alpha, alpha, 0.1f); } } From 142a67e1634fb379e8725d3ad51e5cf4dd9825a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 18:53:28 +0900 Subject: [PATCH 110/256] Fix approach rate not being transferred from OD on older beatmaps --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 01ba0fcc9f..07ada8ecc4 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -311,10 +311,13 @@ namespace osu.Game.Beatmaps.Formats case @"OverallDifficulty": difficulty.OverallDifficulty = Parsing.ParseFloat(pair.Value); + if (!hasApproachRate) + difficulty.ApproachRate = difficulty.OverallDifficulty; break; case @"ApproachRate": difficulty.ApproachRate = Parsing.ParseFloat(pair.Value); + hasApproachRate = true; break; case @"SliderMultiplier": @@ -432,6 +435,7 @@ namespace osu.Game.Beatmaps.Formats private readonly List pendingControlPoints = new List(); private readonly HashSet pendingControlPointTypes = new HashSet(); private double pendingControlPointsTime; + private bool hasApproachRate; private void addControlPoint(double time, ControlPoint point, bool timingChange) { From 53ca597e2b2c85431c722f1801f00805179b150b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 19:12:32 +0900 Subject: [PATCH 111/256] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index f85a96f819..2902c74f0a 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index aa6fb93aa0..b74d51e8f1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index fbb4688588..90ffb9605f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - + From bda3cdc9a773ac76c36470174e13db398e33fd7e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 28 Jan 2022 19:28:42 +0900 Subject: [PATCH 112/256] Add tests --- .../Formats/LegacyBeatmapDecoderTest.cs | 42 +++++++++++++++++++ ...approach-rate-after-overall-difficulty.osu | 3 ++ ...pproach-rate-before-overall-difficulty.osu | 3 ++ .../Resources/undefined-approach-rate.osu | 2 + 4 files changed, 50 insertions(+) create mode 100644 osu.Game.Tests/Resources/approach-rate-after-overall-difficulty.osu create mode 100644 osu.Game.Tests/Resources/approach-rate-before-overall-difficulty.osu create mode 100644 osu.Game.Tests/Resources/undefined-approach-rate.osu diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 3a6051ed43..468cb7683c 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -821,5 +821,47 @@ namespace osu.Game.Tests.Beatmaps.Formats }); } } + + [Test] + public void TestUndefinedApproachRateInheritsOverallDifficulty() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("undefined-approach-rate.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var decoded = decoder.Decode(stream); + Assert.That(decoded.Difficulty.ApproachRate, Is.EqualTo(1)); + Assert.That(decoded.Difficulty.OverallDifficulty, Is.EqualTo(1)); + } + } + + [Test] + public void TestApproachRateDefinedBeforeOverallDifficulty() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("approach-rate-before-overall-difficulty.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var decoded = decoder.Decode(stream); + Assert.That(decoded.Difficulty.ApproachRate, Is.EqualTo(9)); + Assert.That(decoded.Difficulty.OverallDifficulty, Is.EqualTo(1)); + } + } + + [Test] + public void TestApproachRateDefinedAfterOverallDifficulty() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("approach-rate-after-overall-difficulty.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var decoded = decoder.Decode(stream); + Assert.That(decoded.Difficulty.ApproachRate, Is.EqualTo(9)); + Assert.That(decoded.Difficulty.OverallDifficulty, Is.EqualTo(1)); + } + } } } diff --git a/osu.Game.Tests/Resources/approach-rate-after-overall-difficulty.osu b/osu.Game.Tests/Resources/approach-rate-after-overall-difficulty.osu new file mode 100644 index 0000000000..23732aef8c --- /dev/null +++ b/osu.Game.Tests/Resources/approach-rate-after-overall-difficulty.osu @@ -0,0 +1,3 @@ +[Difficulty] +OverallDifficulty:1 +ApproachRate:9 \ No newline at end of file diff --git a/osu.Game.Tests/Resources/approach-rate-before-overall-difficulty.osu b/osu.Game.Tests/Resources/approach-rate-before-overall-difficulty.osu new file mode 100644 index 0000000000..18885c6624 --- /dev/null +++ b/osu.Game.Tests/Resources/approach-rate-before-overall-difficulty.osu @@ -0,0 +1,3 @@ +[Difficulty] +ApproachRate:9 +OverallDifficulty:1 \ No newline at end of file diff --git a/osu.Game.Tests/Resources/undefined-approach-rate.osu b/osu.Game.Tests/Resources/undefined-approach-rate.osu new file mode 100644 index 0000000000..0de24238bf --- /dev/null +++ b/osu.Game.Tests/Resources/undefined-approach-rate.osu @@ -0,0 +1,2 @@ +[Difficulty] +OverallDifficulty:1 \ No newline at end of file From 397971c6313eff22d645c07d782547a4e7683a2e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 28 Jan 2022 22:06:34 +0900 Subject: [PATCH 113/256] Change FrameDataBundle.Frames into an IList --- osu.Game/Online/Spectator/FrameDataBundle.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Spectator/FrameDataBundle.cs b/osu.Game/Online/Spectator/FrameDataBundle.cs index 0e59cdf4ce..a4c4972989 100644 --- a/osu.Game/Online/Spectator/FrameDataBundle.cs +++ b/osu.Game/Online/Spectator/FrameDataBundle.cs @@ -20,16 +20,16 @@ namespace osu.Game.Online.Spectator public FrameHeader Header { get; set; } [Key(1)] - public IEnumerable Frames { get; set; } + public IList Frames { get; set; } - public FrameDataBundle(ScoreInfo score, IEnumerable frames) + public FrameDataBundle(ScoreInfo score, IList frames) { Frames = frames; Header = new FrameHeader(score); } [JsonConstructor] - public FrameDataBundle(FrameHeader header, IEnumerable frames) + public FrameDataBundle(FrameHeader header, IList frames) { Header = header; Frames = frames; From 3037a3a7696b7f00cef3820c5c5994949de78622 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 28 Jan 2022 22:26:05 +0900 Subject: [PATCH 114/256] Purge final spectator frames before ending play --- .../Visual/Gameplay/TestSceneSpectator.cs | 18 ++++++++++++++++++ osu.Game/Online/Spectator/SpectatorClient.cs | 10 ++++++++-- .../Visual/Spectator/TestSpectatorClient.cs | 11 +++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 0afd2c76dd..d1a25b07ec 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -15,11 +15,14 @@ using osu.Game.Online.Spectator; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Visual.Multiplayer; using osu.Game.Tests.Visual.Spectator; +using osuTK; namespace osu.Game.Tests.Visual.Gameplay { @@ -200,6 +203,21 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("screen didn't change", () => Stack.CurrentScreen is SoloSpectator); } + [Test] + public void TestFinalFramesPurgedBeforeEndingPlay() + { + AddStep("begin playing", () => spectatorClient.BeginPlaying(new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()), new Score())); + + AddStep("send frames and finish play", () => + { + spectatorClient.HandleFrame(new OsuReplayFrame(1000, Vector2.Zero)); + spectatorClient.EndPlaying(); + }); + + // We can't access API because we're an "online" test. + AddAssert("last received frame has time = 1000", () => spectatorClient.LastReceivedFrame.First().Value.Time == 1000); + } + private OsuFramedReplayInputHandler replayHandler => (OsuFramedReplayInputHandler)Stack.ChildrenOfType().First().ReplayInputHandler; diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 4da9bace70..fddb94fad7 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -167,6 +167,9 @@ namespace osu.Game.Online.Spectator if (!IsPlaying) return; + if (pendingFrames.Count > 0) + purgePendingFrames(true); + IsPlaying = false; currentBeatmap = null; @@ -238,9 +241,12 @@ namespace osu.Game.Online.Spectator purgePendingFrames(); } - private void purgePendingFrames() + private void purgePendingFrames(bool force = false) { - if (lastSend?.IsCompleted == false) + if (lastSend?.IsCompleted == false && !force) + return; + + if (pendingFrames.Count == 0) return; var frames = pendingFrames.ToArray(); diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index f206d4f8b0..3f3d0f5b01 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -14,6 +14,7 @@ using osu.Framework.Utils; using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Replays.Legacy; +using osu.Game.Rulesets.Replays; using osu.Game.Scoring; namespace osu.Game.Tests.Visual.Spectator @@ -27,12 +28,22 @@ namespace osu.Game.Tests.Visual.Spectator public override IBindable IsConnected { get; } = new Bindable(true); + public readonly Dictionary LastReceivedFrame = new Dictionary(); private readonly Dictionary userBeatmapDictionary = new Dictionary(); private readonly Dictionary userNextFrameDictionary = new Dictionary(); [Resolved] private IAPIProvider api { get; set; } = null!; + public TestSpectatorClient() + { + OnNewFrames += (i, bundle) => + { + if (PlayingUsers.Contains(i)) + LastReceivedFrame[i] = bundle.Frames[^1]; + }; + } + /// /// Starts play for an arbitrary user. /// From 1253e1ecc1f7ec68f78f7b30878b673b2c37345d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 28 Jan 2022 20:25:12 +0100 Subject: [PATCH 115/256] Replace LINQ `Count()` invocation with count property access --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 4790bd44db..90abdf2ba3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -184,7 +184,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void onNewFrames(int userId, FrameDataBundle frames) { - Logger.Log($"Received {frames.Frames.Count()} new frames ({string.Join(',', frames.Frames.Select(f => ((int)f.Time).ToString()))})"); + Logger.Log($"Received {frames.Frames.Count} new frames ({string.Join(',', frames.Frames.Select(f => ((int)f.Time).ToString()))})"); foreach (var legacyFrame in frames.Frames) { From a4aa501bb540cd83f988febd90d128f1c890b0b9 Mon Sep 17 00:00:00 2001 From: Spooghetti420 Date: Fri, 28 Jan 2022 21:59:53 +0000 Subject: [PATCH 116/256] Change threshold from ms to beat-based, add tests --- .../Mods/TestSceneManiaModHoldOff.cs | 130 ++++++++++++++++++ .../Mods/TestSceneManiaModNoHolds.cs | 51 ------- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- ...{ManiaModNoHolds.cs => ManiaModHoldOff.cs} | 34 +++-- .../Mods/ManiaModNoLongNotes.cs | 86 ------------ 5 files changed, 155 insertions(+), 148 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs delete mode 100644 osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs rename osu.Game.Rulesets.Mania/Mods/{ManiaModNoHolds.cs => ManiaModHoldOff.cs} (66%) delete mode 100644 osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs new file mode 100644 index 0000000000..e157cb50b1 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs @@ -0,0 +1,130 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Tests.Visual; +using System.Collections.Generic; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Difficulty; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Mania.Tests.Mods +{ + public class TestSceneManiaModHoldOff : ModTestScene + { + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + + [Test] + public void TestMapHasNoHoldNotes() + { + var testBeatmap = createModdedBeatmap(); + Assert.False(testBeatmap.HitObjects.OfType().Any()); + } + + [Test] + public void TestCorrectNoteValues() + { + var testBeatmap = createRawBeatmap(); + var noteValues = new List(testBeatmap.HitObjects.OfType().Count()); + foreach (HoldNote h in testBeatmap.HitObjects.OfType()) { + noteValues.Add(ManiaModHoldOff.getNoteValue(h, (ManiaBeatmap)testBeatmap)); + } + noteValues.Sort(); + Assert.AreEqual(noteValues, new List { 0.125, 0.250, 0.500, 1.000, 2.000 }); + } + + [TestCase(ManiaModHoldOff.BeatDivisors.Whole)] + [TestCase(ManiaModHoldOff.BeatDivisors.Half)] + [TestCase(ManiaModHoldOff.BeatDivisors.Quarter)] + [TestCase(ManiaModHoldOff.BeatDivisors.Eighth)] + public void TestCorrectObjectCount(ManiaModHoldOff.BeatDivisors minBeatSnap) { + /* + This test is to ensure that, given that end notes are enabled, + the mod produces the expected number of objects when the mod is applied. + */ + + // Mod settings will be set to include the correct beat snap value + var rawBeatmap = createRawBeatmap(); + var testBeatmap = createModdedBeatmap(minBeatSnap); + + // Calculate expected number of objects + int expectedObjectCount = 0; + double beatSnapValue = 1/(Math.Pow(2, (int)minBeatSnap)); + + foreach (ManiaHitObject h in rawBeatmap.HitObjects) { + // Both notes and hold notes account for at least one object + expectedObjectCount++; + if (h.GetType() == typeof(HoldNote)) { + var noteValue = ManiaModHoldOff.getNoteValue((HoldNote)h, (ManiaBeatmap)rawBeatmap); + if (noteValue >= beatSnapValue) { + // Should generate an end note if it's longer than the minimum note value + expectedObjectCount++; + } + } + } + + Assert.That(testBeatmap.HitObjects.Count() == expectedObjectCount); + } + + [Test] + public void TestDifficultyIncrease() { + // A lower minimum beat snap divisor should only make the map harder, never easier + // (as more notes can be spawned) + var beatmaps = new ManiaBeatmap[] { + createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Whole), + createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Half), + createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Quarter), + createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Eighth), + createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Sixteenth) + }; + + var mapDifficulties = new double[beatmaps.Length]; + for (int i = 0; i < mapDifficulties.Length; i++) { + var workingBeatmap = new TestWorkingBeatmap(beatmaps[i]); + var difficultyCalculator = new ManiaDifficultyCalculator(new ManiaRuleset().RulesetInfo, workingBeatmap); + mapDifficulties[i] = difficultyCalculator.Calculate().StarRating; + if (i > 0) { + Assert.LessOrEqual(mapDifficulties[i-1], mapDifficulties[i]); + Assert.LessOrEqual(beatmaps[i-1].HitObjects.Count, beatmaps[i].HitObjects.Count); + } + } + } + + private static ManiaBeatmap createModdedBeatmap(ManiaModHoldOff.BeatDivisors minBeatSnap=ManiaModHoldOff.BeatDivisors.Whole) + { + var beatmap = createRawBeatmap(); + var holdOffMod = new ManiaModHoldOff(); + holdOffMod.MinBeatSnap.Value = minBeatSnap; // Set the specified beat snap setting + Assert.AreEqual(holdOffMod.MinBeatSnap.Value, minBeatSnap); + + foreach (var hitObject in beatmap.HitObjects) + hitObject.ApplyDefaults(beatmap.ControlPointInfo, new BeatmapDifficulty()); + + holdOffMod.ApplyToBeatmap(beatmap); + + return (ManiaBeatmap)beatmap; + } + private static ManiaBeatmap createRawBeatmap() + { + var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); + beatmap.ControlPointInfo.Add(0.0, new TimingControlPoint { BeatLength = 1000 } ); // Set BPM to 60 + + // Add test hit objects + beatmap.HitObjects.Add(new Note { StartTime = 4000 }); + beatmap.HitObjects.Add(new Note { StartTime = 4500 }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 125 }); // 1/8 note + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 250 }); // 1/4 note + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 500 }); // 1/2 note + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 1000 }); // 1/1 note + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 2000 }); // 2/1 note + + return beatmap; + } + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs deleted file mode 100644 index f22b724c99..0000000000 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Linq; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Testing; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mania.Mods; -using osu.Game.Tests.Visual; -using osu.Game.Rulesets.Mania.Tests; -using System.Collections.Generic; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Objects; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Mania.Beatmaps; - -namespace osu.Game.Rulesets.Mania.Tests.Mods -{ - public class TestSceneManiaModNoHolds : ModTestScene - { - protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); - - [Test] - public void TestMapHasNoHeldNotes() - { - var testBeatmap = createBeatmap(); - Assert.That(!testBeatmap.HitObjects.OfType().Any()); - } - - private static IBeatmap createBeatmap() - { - var beatmap = createRawBeatmap(); - var noHoldsMod = new ManiaModNoHolds(); - - foreach (var hitObject in beatmap.HitObjects) - hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - - noHoldsMod.ApplyToBeatmap(beatmap); - - return beatmap; - } - private static IBeatmap createRawBeatmap() - { - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); - beatmap.HitObjects.Add(new Note { StartTime = 1000 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, EndTime = 3000 }); - return beatmap; - } - } -} diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 55ca1a465e..2098c7f5d8 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -244,7 +244,7 @@ namespace osu.Game.Rulesets.Mania new ManiaModClassic(), new ManiaModInvert(), new ManiaModConstantSpeed(), - new ManiaModNoHolds() + new ManiaModHoldOff() }; case ModType.Automation: diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs similarity index 66% rename from osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs rename to osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index a18dbad4a1..cfdb58ee67 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -2,28 +2,23 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; using osu.Framework.Graphics.Sprites; using System; using System.Collections.Generic; -using osu.Game.Audio; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Framework.Utils; -using osu.Game.Overlays.Settings; using osu.Framework.Bindables; using osu.Game.Configuration; -using osu.Game.Graphics; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModNoHolds : Mod, IApplicableAfterBeatmapConversion + public class ManiaModHoldOff : Mod, IApplicableAfterBeatmapConversion { - public override string Name => "No Holds"; + public override string Name => "Hold Off"; - public override string Acronym => "NH"; + public override string Acronym => "HO"; public override double ScoreMultiplier => 1; @@ -40,11 +35,15 @@ namespace osu.Game.Rulesets.Mania.Mods Value = true }; + [SettingSource("Minimum end note beat snap", "Don't add end notes for hold notes shorter than this beat division")] + public Bindable MinBeatSnap { get; } = new Bindable(defaultValue: BeatDivisors.Half); + public void ApplyToBeatmap(IBeatmap beatmap) { var maniaBeatmap = (ManiaBeatmap)beatmap; var newObjects = new List(); + var beatSnap = 1/(Math.Pow(2, (double)MinBeatSnap.Value)); foreach (var h in beatmap.HitObjects.OfType()) { // Add a note for the beginning of the hold note @@ -56,7 +55,8 @@ namespace osu.Game.Rulesets.Mania.Mods }); // Don't add an end note if the duration is shorter than some threshold, or end notes are disabled - if (AddEndNotes.Value && h.Duration > 200) + var noteValue = getNoteValue(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc. + if (AddEndNotes.Value && noteValue >= beatSnap) { newObjects.Add(new Note { @@ -66,8 +66,22 @@ namespace osu.Game.Rulesets.Mania.Mods }); } } - maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType().Concat(newObjects).OrderBy(h => h.StartTime).ToList(); } + + public static double getNoteValue(HoldNote holdNote, ManiaBeatmap beatmap) { + var bpmAtNoteTime = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BPM; + var noteValue = (60*holdNote.Duration)/(1000*bpmAtNoteTime); + return noteValue; + } + + public enum BeatDivisors + { + Whole, + Half, + Quarter, + Eighth, + Sixteenth + } } } \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs deleted file mode 100644 index d10003b783..0000000000 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Linq; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mods; -using osu.Framework.Graphics.Sprites; -using System; -using System.Collections.Generic; -using osu.Game.Audio; -using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Framework.Utils; -using osu.Game.Overlays.Settings; -using osu.Framework.Bindables; -using osu.Game.Configuration; -using osu.Game.Graphics; - - - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModNoLongNotes : Mod, IApplicableAfterBeatmapConversion - { - - public override string Name => "No Long Notes"; - - public override string Acronym => "NL"; - - public override double ScoreMultiplier => 1; - - public override string Description => @"Turns all held notes into tap notes. No coordination required."; - - public override IconUsage? Icon => FontAwesome.Solid.DotCircle; - - public override ModType Type => ModType.Conversion; - - [SettingSource("Add end notes", "Also add a note at the end of a held note")] - public BindableBool AddEndNotes { get; } = new BindableBool - { - Default = true, - Value = true - }; - - [SettingSource("Length threshold", "Only add an end note for held notes longer than this threshold (in milliseconds)")] - public BindableNumber Threshold { get; } = new BindableDouble - { - MinValue = 1.0, - MaxValue = 1990.0, - Default = 200.0, - Value = 200.0, - Precision = 1.0, - }; - - public void ApplyToBeatmap(IBeatmap beatmap) - { - var maniaBeatmap = (ManiaBeatmap)beatmap; - - var newObjects = new List(); - beatmap.HitObjects.OfType().ForEach(h => - { - // Add a note for the beginning of the hold note - newObjects.Add(new Note - { - Column = h.Column, - StartTime = h.StartTime, - Samples = h.Samples - }); - - // Don't add an end note if the duration is below the threshold, or end notes are disabled - if (AddEndNotes.Value && h.Duration > Threshold.Value) - { - newObjects.Add(new Note - { - Column = h.Column, - StartTime = h.EndTime, - Samples = h.Samples - }); - } - }); - - maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType().Concat(newObjects).OrderBy(h => h.StartTime).ToList(); - } - } -} From b2ebcabdd5c56fa734a6eaf8eacc8b8c5c3db9a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 13:27:19 +0900 Subject: [PATCH 117/256] Fix potential crash during stable install migration due to multiple configuration files Apparently this can be a thing on windows. Closes https://github.com/ppy/osu/discussions/16689. --- osu.Game/IO/StableStorage.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/IO/StableStorage.cs b/osu.Game/IO/StableStorage.cs index f5a8c4dc9e..84b7da91fc 100644 --- a/osu.Game/IO/StableStorage.cs +++ b/osu.Game/IO/StableStorage.cs @@ -34,11 +34,17 @@ namespace osu.Game.IO private string locateSongsDirectory() { - string configFile = GetFiles(".", $"osu!.{Environment.UserName}.cfg").SingleOrDefault(); + var configurationFiles = GetFiles(".", $"osu!.{Environment.UserName}.cfg"); - if (configFile != null) + // GetFiles returns case insensitive results, so multiple files could exist. + // Prefer a case-correct match, but fallback to any available. + string usableConfigFile = + configurationFiles.FirstOrDefault(f => f.Contains(Environment.UserName, StringComparison.Ordinal)) + ?? configurationFiles.FirstOrDefault(); + + if (usableConfigFile != null) { - using (var stream = GetStream(configFile)) + using (var stream = GetStream(usableConfigFile)) using (var textReader = new StreamReader(stream)) { string line; From c75ffe9b0764effd0c5270ac6b0a0bcd02c6762a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 13:47:04 +0900 Subject: [PATCH 118/256] Apply code style changes --- osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index cfdb58ee67..aa447356ff 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -43,7 +43,8 @@ namespace osu.Game.Rulesets.Mania.Mods var maniaBeatmap = (ManiaBeatmap)beatmap; var newObjects = new List(); - var beatSnap = 1/(Math.Pow(2, (double)MinBeatSnap.Value)); + double beatSnap = 1 / (Math.Pow(2, (double)MinBeatSnap.Value)); + foreach (var h in beatmap.HitObjects.OfType()) { // Add a note for the beginning of the hold note @@ -55,7 +56,8 @@ namespace osu.Game.Rulesets.Mania.Mods }); // Don't add an end note if the duration is shorter than some threshold, or end notes are disabled - var noteValue = getNoteValue(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc. + double noteValue = getNoteValue(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc. + if (AddEndNotes.Value && noteValue >= beatSnap) { newObjects.Add(new Note @@ -66,13 +68,14 @@ namespace osu.Game.Rulesets.Mania.Mods }); } } + maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType().Concat(newObjects).OrderBy(h => h.StartTime).ToList(); } - public static double getNoteValue(HoldNote holdNote, ManiaBeatmap beatmap) { - var bpmAtNoteTime = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BPM; - var noteValue = (60*holdNote.Duration)/(1000*bpmAtNoteTime); - return noteValue; + private static double getNoteValue(HoldNote holdNote, ManiaBeatmap beatmap) + { + double bpmAtNoteTime = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BPM; + return (60 * holdNote.Duration) / (1000 * bpmAtNoteTime); } public enum BeatDivisors @@ -84,4 +87,4 @@ namespace osu.Game.Rulesets.Mania.Mods Sixteenth } } -} \ No newline at end of file +} From 035a84e75c4b84f655ad8fadd27b24677bd66ae2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 14:05:23 +0900 Subject: [PATCH 119/256] Rename function and make `public` again for test usage --- .../Mods/TestSceneManiaModHoldOff.cs | 54 ++++++++++++------- .../Mods/ManiaModHoldOff.cs | 4 +- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs index e157cb50b1..ddf09aab33 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs @@ -32,9 +32,12 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods { var testBeatmap = createRawBeatmap(); var noteValues = new List(testBeatmap.HitObjects.OfType().Count()); - foreach (HoldNote h in testBeatmap.HitObjects.OfType()) { - noteValues.Add(ManiaModHoldOff.getNoteValue(h, (ManiaBeatmap)testBeatmap)); + + foreach (HoldNote h in testBeatmap.HitObjects.OfType()) + { + noteValues.Add(ManiaModHoldOff.GetNoteDurationInBeatLength(h, (ManiaBeatmap)testBeatmap)); } + noteValues.Sort(); Assert.AreEqual(noteValues, new List { 0.125, 0.250, 0.500, 1.000, 2.000 }); } @@ -43,7 +46,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods [TestCase(ManiaModHoldOff.BeatDivisors.Half)] [TestCase(ManiaModHoldOff.BeatDivisors.Quarter)] [TestCase(ManiaModHoldOff.BeatDivisors.Eighth)] - public void TestCorrectObjectCount(ManiaModHoldOff.BeatDivisors minBeatSnap) { + public void TestCorrectObjectCount(ManiaModHoldOff.BeatDivisors minBeatSnap) + { /* This test is to ensure that, given that end notes are enabled, the mod produces the expected number of objects when the mod is applied. @@ -55,14 +59,19 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods // Calculate expected number of objects int expectedObjectCount = 0; - double beatSnapValue = 1/(Math.Pow(2, (int)minBeatSnap)); + double beatSnapValue = 1 / (Math.Pow(2, (int)minBeatSnap)); - foreach (ManiaHitObject h in rawBeatmap.HitObjects) { + foreach (ManiaHitObject h in rawBeatmap.HitObjects) + { // Both notes and hold notes account for at least one object expectedObjectCount++; - if (h.GetType() == typeof(HoldNote)) { - var noteValue = ManiaModHoldOff.getNoteValue((HoldNote)h, (ManiaBeatmap)rawBeatmap); - if (noteValue >= beatSnapValue) { + + if (h.GetType() == typeof(HoldNote)) + { + var noteValue = ManiaModHoldOff.GetNoteDurationInBeatLength((HoldNote)h, (ManiaBeatmap)rawBeatmap); + + if (noteValue >= beatSnapValue) + { // Should generate an end note if it's longer than the minimum note value expectedObjectCount++; } @@ -73,10 +82,12 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods } [Test] - public void TestDifficultyIncrease() { + public void TestDifficultyIncrease() + { // A lower minimum beat snap divisor should only make the map harder, never easier // (as more notes can be spawned) - var beatmaps = new ManiaBeatmap[] { + var beatmaps = new ManiaBeatmap[] + { createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Whole), createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Half), createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Quarter), @@ -85,18 +96,22 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods }; var mapDifficulties = new double[beatmaps.Length]; - for (int i = 0; i < mapDifficulties.Length; i++) { + + for (int i = 0; i < mapDifficulties.Length; i++) + { var workingBeatmap = new TestWorkingBeatmap(beatmaps[i]); var difficultyCalculator = new ManiaDifficultyCalculator(new ManiaRuleset().RulesetInfo, workingBeatmap); mapDifficulties[i] = difficultyCalculator.Calculate().StarRating; - if (i > 0) { - Assert.LessOrEqual(mapDifficulties[i-1], mapDifficulties[i]); - Assert.LessOrEqual(beatmaps[i-1].HitObjects.Count, beatmaps[i].HitObjects.Count); + + if (i > 0) + { + Assert.LessOrEqual(mapDifficulties[i - 1], mapDifficulties[i]); + Assert.LessOrEqual(beatmaps[i - 1].HitObjects.Count, beatmaps[i].HitObjects.Count); } } } - private static ManiaBeatmap createModdedBeatmap(ManiaModHoldOff.BeatDivisors minBeatSnap=ManiaModHoldOff.BeatDivisors.Whole) + private static ManiaBeatmap createModdedBeatmap(ManiaModHoldOff.BeatDivisors minBeatSnap = ManiaModHoldOff.BeatDivisors.Whole) { var beatmap = createRawBeatmap(); var holdOffMod = new ManiaModHoldOff(); @@ -110,17 +125,18 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods return (ManiaBeatmap)beatmap; } + private static ManiaBeatmap createRawBeatmap() { var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); - beatmap.ControlPointInfo.Add(0.0, new TimingControlPoint { BeatLength = 1000 } ); // Set BPM to 60 + beatmap.ControlPointInfo.Add(0.0, new TimingControlPoint { BeatLength = 1000 }); // Set BPM to 60 // Add test hit objects beatmap.HitObjects.Add(new Note { StartTime = 4000 }); beatmap.HitObjects.Add(new Note { StartTime = 4500 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 125 }); // 1/8 note - beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 250 }); // 1/4 note - beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 500 }); // 1/2 note + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 125 }); // 1/8 note + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 250 }); // 1/4 note + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 500 }); // 1/2 note beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 1000 }); // 1/1 note beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 2000 }); // 2/1 note diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index aa447356ff..637142aed6 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.Mods }); // Don't add an end note if the duration is shorter than some threshold, or end notes are disabled - double noteValue = getNoteValue(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc. + double noteValue = GetNoteDurationInBeatLength(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc. if (AddEndNotes.Value && noteValue >= beatSnap) { @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Mania.Mods maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType().Concat(newObjects).OrderBy(h => h.StartTime).ToList(); } - private static double getNoteValue(HoldNote holdNote, ManiaBeatmap beatmap) + public static double GetNoteDurationInBeatLength(HoldNote holdNote, ManiaBeatmap beatmap) { double bpmAtNoteTime = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BPM; return (60 * holdNote.Duration) / (1000 * bpmAtNoteTime); From c7580a5177eb24aad1a667bee76e098dcb60f559 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 14:06:43 +0900 Subject: [PATCH 120/256] Fix inspections in test scene --- .../Mods/TestSceneManiaModHoldOff.cs | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs index ddf09aab33..e74f63abb3 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods foreach (HoldNote h in testBeatmap.HitObjects.OfType()) { - noteValues.Add(ManiaModHoldOff.GetNoteDurationInBeatLength(h, (ManiaBeatmap)testBeatmap)); + noteValues.Add(ManiaModHoldOff.GetNoteDurationInBeatLength(h, testBeatmap)); } noteValues.Sort(); @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods if (h.GetType() == typeof(HoldNote)) { - var noteValue = ManiaModHoldOff.GetNoteDurationInBeatLength((HoldNote)h, (ManiaBeatmap)rawBeatmap); + double noteValue = ManiaModHoldOff.GetNoteDurationInBeatLength((HoldNote)h, rawBeatmap); if (noteValue >= beatSnapValue) { @@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods } } - Assert.That(testBeatmap.HitObjects.Count() == expectedObjectCount); + Assert.That(testBeatmap.HitObjects.Count == expectedObjectCount); } [Test] @@ -86,16 +86,16 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods { // A lower minimum beat snap divisor should only make the map harder, never easier // (as more notes can be spawned) - var beatmaps = new ManiaBeatmap[] + var beatmaps = new[] { - createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Whole), + createModdedBeatmap(), createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Half), createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Quarter), createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Eighth), createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Sixteenth) }; - var mapDifficulties = new double[beatmaps.Length]; + double[] mapDifficulties = new double[beatmaps.Length]; for (int i = 0; i < mapDifficulties.Length; i++) { @@ -114,8 +114,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods private static ManiaBeatmap createModdedBeatmap(ManiaModHoldOff.BeatDivisors minBeatSnap = ManiaModHoldOff.BeatDivisors.Whole) { var beatmap = createRawBeatmap(); - var holdOffMod = new ManiaModHoldOff(); - holdOffMod.MinBeatSnap.Value = minBeatSnap; // Set the specified beat snap setting + var holdOffMod = new ManiaModHoldOff + { + MinBeatSnap = { Value = minBeatSnap } + }; Assert.AreEqual(holdOffMod.MinBeatSnap.Value, minBeatSnap); foreach (var hitObject in beatmap.HitObjects) @@ -123,7 +125,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods holdOffMod.ApplyToBeatmap(beatmap); - return (ManiaBeatmap)beatmap; + return beatmap; } private static ManiaBeatmap createRawBeatmap() From 4c97ed676fc335a8640bcf1e39085e656b4e997f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 14:46:24 +0900 Subject: [PATCH 121/256] Fix score presentation tests not correctly entering song select before running --- osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index 7656bf79dc..6c32171b29 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -16,6 +16,7 @@ using osu.Game.Screens; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; +using osu.Game.Screens.Select; namespace osu.Game.Tests.Visual.Navigation { @@ -92,6 +93,9 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestFromSongSelect([Values] ScorePresentType type) { + AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo.Invoke()); + AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded); + var firstImport = importScore(1); presentAndConfirm(firstImport, type); @@ -102,6 +106,9 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestFromSongSelectDifferentRuleset([Values] ScorePresentType type) { + AddStep("enter song select", () => Game.ChildrenOfType().Single().OnSolo.Invoke()); + AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded); + var firstImport = importScore(1); presentAndConfirm(firstImport, type); From e7823982d827b3371f80b4f74a5c567933c62551 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 18:38:45 +0900 Subject: [PATCH 122/256] Fix ruleset value not being transferred when `FinaliseSelection` is not called --- osu.Game/Screens/Select/SongSelect.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 10150fcd9f..f5b11448f8 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -619,6 +619,10 @@ namespace osu.Game.Screens.Select public override void OnSuspending(IScreen next) { + // Handle the case where FinaliseSelection is never called (ie. when a screen is pushed externally). + // Without this, it's possible for a transfer to happen while we are not the current screen. + transferRulesetValue(); + ModSelect.SelectedMods.UnbindFrom(selectedMods); ModSelect.Hide(); From aa582fb0e1a15de97b48203a7bcaed3c84a4d6c7 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 20:38:12 +0800 Subject: [PATCH 123/256] add Alternate Mod --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 45 +++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 1 + osu.Game/Rulesets/Mods/ModAlternate.cs | 77 +++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs create mode 100644 osu.Game/Rulesets/Mods/ModAlternate.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs new file mode 100644 index 0000000000..9a9074ee52 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModAlternate : ModAlternate + { + private const double flash_duration = 1000; + private OsuAction? lastActionPressed; + private DrawableRuleset ruleset; + + public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + ruleset = drawableRuleset; + base.ApplyToDrawableRuleset(drawableRuleset); + } + + protected override void Reset() + { + lastActionPressed = null; + } + + protected override bool OnPressed(OsuAction key) + { + if (lastActionPressed == key) + { + ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint); + return true; + } + + lastActionPressed = key; + + return false; + } + + protected override void OnReleased(OsuAction key) + { + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 18e4bb259c..7e8974b5ed 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -159,6 +159,7 @@ namespace osu.Game.Rulesets.Osu new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()), new OsuModHidden(), new MultiMod(new OsuModFlashlight(), new OsuModBlinds()), + new OsuModAlternate(), }; case ModType.Conversion: diff --git a/osu.Game/Rulesets/Mods/ModAlternate.cs b/osu.Game/Rulesets/Mods/ModAlternate.cs new file mode 100644 index 0000000000..683654f605 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModAlternate.cs @@ -0,0 +1,77 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModAlternate : Mod + { + public override string Name => @"Alternate"; + public override string Acronym => @"AL"; + public override string Description => @"Never hit the same key twice!"; + public override double ScoreMultiplier => 1.0; + public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) }; + public override ModType Type => ModType.DifficultyIncrease; + public override IconUsage? Icon => FontAwesome.Solid.Keyboard; + } + + public abstract class ModAlternate : ModAlternate, IApplicableToDrawableRuleset, IApplicableToPlayer + where THitObject : HitObject + where TAction : struct + { + public bool CanIntercept => !isBreakTime.Value; + + private IBindable isBreakTime; + + public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this)); + } + + public void ApplyToPlayer(Player player) + { + isBreakTime = player.IsBreakTime.GetBoundCopy(); + isBreakTime.ValueChanged += e => + { + if (e.NewValue) + Reset(); + }; + } + + protected abstract void Reset(); + + protected abstract bool OnPressed(TAction key); + + protected abstract void OnReleased(TAction key); + + private class InputInterceptor : Component, IKeyBindingHandler + { + private readonly ModAlternate mod; + + public InputInterceptor(ModAlternate mod) + { + this.mod = mod; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + return mod.CanIntercept && mod.OnPressed(e.Action); + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + if (mod.CanIntercept) + mod.OnReleased(e.Action); + } + } + } +} From 2326c36836aa317635c869610d7daff18ecbbee3 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 21:09:36 +0800 Subject: [PATCH 124/256] remove unused method and fix description --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 4 ---- osu.Game/Rulesets/Mods/ModAlternate.cs | 9 ++++----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index 9a9074ee52..34802bab43 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -37,9 +37,5 @@ namespace osu.Game.Rulesets.Osu.Mods return false; } - - protected override void OnReleased(OsuAction key) - { - } } } diff --git a/osu.Game/Rulesets/Mods/ModAlternate.cs b/osu.Game/Rulesets/Mods/ModAlternate.cs index 683654f605..17d8b92469 100644 --- a/osu.Game/Rulesets/Mods/ModAlternate.cs +++ b/osu.Game/Rulesets/Mods/ModAlternate.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => @"Alternate"; public override string Acronym => @"AL"; - public override string Description => @"Never hit the same key twice!"; + public override string Description => @"Don't use the same key twice in a row!"; public override double ScoreMultiplier => 1.0; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) }; public override ModType Type => ModType.DifficultyIncrease; @@ -28,6 +28,9 @@ namespace osu.Game.Rulesets.Mods where THitObject : HitObject where TAction : struct { + /// + /// Whether incoming input must be checked by . + /// public bool CanIntercept => !isBreakTime.Value; private IBindable isBreakTime; @@ -51,8 +54,6 @@ namespace osu.Game.Rulesets.Mods protected abstract bool OnPressed(TAction key); - protected abstract void OnReleased(TAction key); - private class InputInterceptor : Component, IKeyBindingHandler { private readonly ModAlternate mod; @@ -69,8 +70,6 @@ namespace osu.Game.Rulesets.Mods public void OnReleased(KeyBindingReleaseEvent e) { - if (mod.CanIntercept) - mod.OnReleased(e.Action); } } } From 98d8b26a9c24dee403ba9d7199d0652910c72c0b Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 21:49:40 +0800 Subject: [PATCH 125/256] move `ModAlternate` to `OsuModAlternate` and check if intro has ended --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 67 ++++++++++++++-- osu.Game/Rulesets/Mods/ModAlternate.cs | 76 ------------------- 2 files changed, 61 insertions(+), 82 deletions(-) delete mode 100644 osu.Game/Rulesets/Mods/ModAlternate.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index 34802bab43..f366481c7d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -1,31 +1,63 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModAlternate : ModAlternate + public class OsuModAlternate : Mod, IApplicableToDrawableRuleset, IApplicableToPlayer, IUpdatableByPlayfield { + public override string Name => @"Alternate"; + public override string Acronym => @"AL"; + public override string Description => @"Don't use the same key twice in a row!"; + public override double ScoreMultiplier => 1.0; + public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) }; + public override ModType Type => ModType.DifficultyIncrease; + public override IconUsage? Icon => FontAwesome.Solid.Keyboard; + + /// + /// Whether incoming input must be checked by . + /// + public bool CanIntercept => !isBreakTime.Value && introEnded; + + private bool introEnded; + private double earliestStartTime; + private IBindable isBreakTime; private const double flash_duration = 1000; private OsuAction? lastActionPressed; private DrawableRuleset ruleset; - public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { ruleset = drawableRuleset; - base.ApplyToDrawableRuleset(drawableRuleset); + drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this)); + + var firstHitObject = ruleset.Objects.FirstOrDefault(); + earliestStartTime = (firstHitObject?.StartTime ?? 0) - (firstHitObject?.HitWindows.WindowFor(HitResult.Meh) ?? 0); } - protected override void Reset() + public void ApplyToPlayer(Player player) { - lastActionPressed = null; + isBreakTime = player.IsBreakTime.GetBoundCopy(); + isBreakTime.ValueChanged += e => + { + if (e.NewValue) + lastActionPressed = null; + }; } - protected override bool OnPressed(OsuAction key) + private bool onPressed(OsuAction key) { if (lastActionPressed == key) { @@ -37,5 +69,28 @@ namespace osu.Game.Rulesets.Osu.Mods return false; } + + public void Update(Playfield playfield) + { + if (!introEnded) + introEnded = playfield.Clock.CurrentTime > earliestStartTime; + } + + private class InputInterceptor : Component, IKeyBindingHandler + { + private readonly OsuModAlternate mod; + + public InputInterceptor(OsuModAlternate mod) + { + this.mod = mod; + } + + public bool OnPressed(KeyBindingPressEvent e) + => mod.CanIntercept && mod.onPressed(e.Action); + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + } } } diff --git a/osu.Game/Rulesets/Mods/ModAlternate.cs b/osu.Game/Rulesets/Mods/ModAlternate.cs deleted file mode 100644 index 17d8b92469..0000000000 --- a/osu.Game/Rulesets/Mods/ModAlternate.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; - -namespace osu.Game.Rulesets.Mods -{ - public abstract class ModAlternate : Mod - { - public override string Name => @"Alternate"; - public override string Acronym => @"AL"; - public override string Description => @"Don't use the same key twice in a row!"; - public override double ScoreMultiplier => 1.0; - public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) }; - public override ModType Type => ModType.DifficultyIncrease; - public override IconUsage? Icon => FontAwesome.Solid.Keyboard; - } - - public abstract class ModAlternate : ModAlternate, IApplicableToDrawableRuleset, IApplicableToPlayer - where THitObject : HitObject - where TAction : struct - { - /// - /// Whether incoming input must be checked by . - /// - public bool CanIntercept => !isBreakTime.Value; - - private IBindable isBreakTime; - - public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) - { - drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this)); - } - - public void ApplyToPlayer(Player player) - { - isBreakTime = player.IsBreakTime.GetBoundCopy(); - isBreakTime.ValueChanged += e => - { - if (e.NewValue) - Reset(); - }; - } - - protected abstract void Reset(); - - protected abstract bool OnPressed(TAction key); - - private class InputInterceptor : Component, IKeyBindingHandler - { - private readonly ModAlternate mod; - - public InputInterceptor(ModAlternate mod) - { - this.mod = mod; - } - - public bool OnPressed(KeyBindingPressEvent e) - { - return mod.CanIntercept && mod.OnPressed(e.Action); - } - - public void OnReleased(KeyBindingReleaseEvent e) - { - } - } - } -} From d48fae11004b7142a58b8d701c0cbfd073e43992 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 23:13:19 +0900 Subject: [PATCH 126/256] Revert "Remove all EF migrations" This reverts commit bb5b9458e8130a2a5f213e968f5590d00ede3d87. --- .../20171019041408_InitialCreate.Designer.cs | 293 ++++++++++ .../20171019041408_InitialCreate.cs | 311 +++++++++++ ...025071459_AddMissingIndexRules.Designer.cs | 299 ++++++++++ .../20171025071459_AddMissingIndexRules.cs | 80 +++ ...eatmapOnlineIDUniqueConstraint.Designer.cs | 302 ++++++++++ ...5731_AddBeatmapOnlineIDUniqueConstraint.cs | 23 + ...034410_AddRulesetInfoShortName.Designer.cs | 307 +++++++++++ .../20171209034410_AddRulesetInfoShortName.cs | 33 ++ .../20180125143340_Settings.Designer.cs | 329 +++++++++++ .../Migrations/20180125143340_Settings.cs | 55 ++ .../20180131154205_AddMuteBinding.cs | 23 + .../20180219060912_AddSkins.Designer.cs | 379 +++++++++++++ .../Migrations/20180219060912_AddSkins.cs | 71 +++ ...54_RemoveUniqueHashConstraints.Designer.cs | 377 +++++++++++++ ...80529055154_RemoveUniqueHashConstraints.cs | 51 ++ ...111_UpdateTaikoDefaultBindings.Designer.cs | 376 +++++++++++++ ...180621044111_UpdateTaikoDefaultBindings.cs | 17 + ...628011956_RemoveNegativeSetIDs.Designer.cs | 376 +++++++++++++ .../20180628011956_RemoveNegativeSetIDs.cs | 18 + .../20180913080842_AddRankStatus.Designer.cs | 380 +++++++++++++ .../20180913080842_AddRankStatus.cs | 33 ++ ...0181007180454_StandardizePaths.Designer.cs | 380 +++++++++++++ .../20181007180454_StandardizePaths.cs | 26 + ...20181128100659_AddSkinInfoHash.Designer.cs | 387 +++++++++++++ .../20181128100659_AddSkinInfoHash.cs | 41 ++ ...81130113755_AddScoreInfoTables.Designer.cs | 484 ++++++++++++++++ .../20181130113755_AddScoreInfoTables.cs | 112 ++++ ...20190225062029_AddUserIDColumn.Designer.cs | 487 +++++++++++++++++ .../20190225062029_AddUserIDColumn.cs | 22 + .../20190525060824_SkinSettings.Designer.cs | 498 +++++++++++++++++ .../Migrations/20190525060824_SkinSettings.cs | 54 ++ ...AddDateAddedColumnToBeatmapSet.Designer.cs | 489 +++++++++++++++++ ...05091246_AddDateAddedColumnToBeatmapSet.cs | 24 + ...8070844_AddBPMAndLengthColumns.Designer.cs | 504 +++++++++++++++++ .../20190708070844_AddBPMAndLengthColumns.cs | 33 ++ ...20190913104727_AddBeatmapVideo.Designer.cs | 506 +++++++++++++++++ .../20190913104727_AddBeatmapVideo.cs | 22 + ...02094919_RefreshVolumeBindings.Designer.cs | 506 +++++++++++++++++ .../20200302094919_RefreshVolumeBindings.cs | 16 + ...01019224408_AddEpilepsyWarning.Designer.cs | 508 +++++++++++++++++ .../20201019224408_AddEpilepsyWarning.cs | 23 + ...700_RefreshVolumeBindingsAgain.Designer.cs | 506 +++++++++++++++++ ...210412045700_RefreshVolumeBindingsAgain.cs | 16 + ...60743_AddSkinInstantiationInfo.Designer.cs | 508 +++++++++++++++++ ...20210511060743_AddSkinInstantiationInfo.cs | 22 + ...9_AddAuthorIdToBeatmapMetadata.Designer.cs | 511 +++++++++++++++++ ...0514062639_AddAuthorIdToBeatmapMetadata.cs | 23 + ...824185035_AddCountdownSettings.Designer.cs | 513 +++++++++++++++++ .../20210824185035_AddCountdownSettings.cs | 23 + ...11_AddSamplesMatchPlaybackRate.Designer.cs | 515 ++++++++++++++++++ ...10912144011_AddSamplesMatchPlaybackRate.cs | 23 + .../20211020081609_ResetSkinHashes.cs | 23 + .../Migrations/OsuDbContextModelSnapshot.cs | 513 +++++++++++++++++ 53 files changed, 12451 insertions(+) create mode 100644 osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs create mode 100644 osu.Game/Migrations/20171019041408_InitialCreate.cs create mode 100644 osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs create mode 100644 osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs create mode 100644 osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs create mode 100644 osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs create mode 100644 osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs create mode 100644 osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs create mode 100644 osu.Game/Migrations/20180125143340_Settings.Designer.cs create mode 100644 osu.Game/Migrations/20180125143340_Settings.cs create mode 100644 osu.Game/Migrations/20180131154205_AddMuteBinding.cs create mode 100644 osu.Game/Migrations/20180219060912_AddSkins.Designer.cs create mode 100644 osu.Game/Migrations/20180219060912_AddSkins.cs create mode 100644 osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs create mode 100644 osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs create mode 100644 osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs create mode 100644 osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs create mode 100644 osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs create mode 100644 osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs create mode 100644 osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs create mode 100644 osu.Game/Migrations/20180913080842_AddRankStatus.cs create mode 100644 osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs create mode 100644 osu.Game/Migrations/20181007180454_StandardizePaths.cs create mode 100644 osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs create mode 100644 osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs create mode 100644 osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs create mode 100644 osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs create mode 100644 osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs create mode 100644 osu.Game/Migrations/20190225062029_AddUserIDColumn.cs create mode 100644 osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs create mode 100644 osu.Game/Migrations/20190525060824_SkinSettings.cs create mode 100644 osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs create mode 100644 osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs create mode 100644 osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs create mode 100644 osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs create mode 100644 osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs create mode 100644 osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs create mode 100644 osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs create mode 100644 osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs create mode 100644 osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs create mode 100644 osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs create mode 100644 osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs create mode 100644 osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs create mode 100644 osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs create mode 100644 osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs create mode 100644 osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs create mode 100644 osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs create mode 100644 osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs create mode 100644 osu.Game/Migrations/20210824185035_AddCountdownSettings.cs create mode 100644 osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs create mode 100644 osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs create mode 100644 osu.Game/Migrations/20211020081609_ResetSkinHashes.cs create mode 100644 osu.Game/Migrations/OsuDbContextModelSnapshot.cs diff --git a/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs b/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs new file mode 100644 index 0000000000..c751530bf4 --- /dev/null +++ b/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs @@ -0,0 +1,293 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20171019041408_InitialCreate")] + partial class InitialCreate + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash"); + + b.HasIndex("MetadataID"); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20171019041408_InitialCreate.cs b/osu.Game/Migrations/20171019041408_InitialCreate.cs new file mode 100644 index 0000000000..9b6881f98c --- /dev/null +++ b/osu.Game/Migrations/20171019041408_InitialCreate.cs @@ -0,0 +1,311 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class InitialCreate : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "BeatmapDifficulty", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ApproachRate = table.Column(type: "REAL", nullable: false), + CircleSize = table.Column(type: "REAL", nullable: false), + DrainRate = table.Column(type: "REAL", nullable: false), + OverallDifficulty = table.Column(type: "REAL", nullable: false), + SliderMultiplier = table.Column(type: "REAL", nullable: false), + SliderTickRate = table.Column(type: "REAL", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapDifficulty", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "BeatmapMetadata", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Artist = table.Column(type: "TEXT", nullable: true), + ArtistUnicode = table.Column(type: "TEXT", nullable: true), + AudioFile = table.Column(type: "TEXT", nullable: true), + Author = table.Column(type: "TEXT", nullable: true), + BackgroundFile = table.Column(type: "TEXT", nullable: true), + PreviewTime = table.Column(type: "INTEGER", nullable: false), + Source = table.Column(type: "TEXT", nullable: true), + Tags = table.Column(type: "TEXT", nullable: true), + Title = table.Column(type: "TEXT", nullable: true), + TitleUnicode = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapMetadata", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "FileInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Hash = table.Column(type: "TEXT", nullable: true), + ReferenceCount = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_FileInfo", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "KeyBinding", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Action = table.Column(type: "INTEGER", nullable: false), + Keys = table.Column(type: "TEXT", nullable: true), + RulesetID = table.Column(type: "INTEGER", nullable: true), + Variant = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_KeyBinding", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "RulesetInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Available = table.Column(type: "INTEGER", nullable: false), + InstantiationInfo = table.Column(type: "TEXT", nullable: true), + Name = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_RulesetInfo", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "BeatmapSetInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + DeletePending = table.Column(type: "INTEGER", nullable: false), + Hash = table.Column(type: "TEXT", nullable: true), + MetadataID = table.Column(type: "INTEGER", nullable: true), + OnlineBeatmapSetID = table.Column(type: "INTEGER", nullable: true), + Protected = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapSetInfo", x => x.ID); + table.ForeignKey( + name: "FK_BeatmapSetInfo_BeatmapMetadata_MetadataID", + column: x => x.MetadataID, + principalTable: "BeatmapMetadata", + principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "BeatmapInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + AudioLeadIn = table.Column(type: "INTEGER", nullable: false), + BaseDifficultyID = table.Column(type: "INTEGER", nullable: false), + BeatDivisor = table.Column(type: "INTEGER", nullable: false), + BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false), + Countdown = table.Column(type: "INTEGER", nullable: false), + DistanceSpacing = table.Column(type: "REAL", nullable: false), + GridSize = table.Column(type: "INTEGER", nullable: false), + Hash = table.Column(type: "TEXT", nullable: true), + Hidden = table.Column(type: "INTEGER", nullable: false), + LetterboxInBreaks = table.Column(type: "INTEGER", nullable: false), + MD5Hash = table.Column(type: "TEXT", nullable: true), + MetadataID = table.Column(type: "INTEGER", nullable: true), + OnlineBeatmapID = table.Column(type: "INTEGER", nullable: true), + Path = table.Column(type: "TEXT", nullable: true), + RulesetID = table.Column(type: "INTEGER", nullable: false), + SpecialStyle = table.Column(type: "INTEGER", nullable: false), + StackLeniency = table.Column(type: "REAL", nullable: false), + StarDifficulty = table.Column(type: "REAL", nullable: false), + StoredBookmarks = table.Column(type: "TEXT", nullable: true), + TimelineZoom = table.Column(type: "REAL", nullable: false), + Version = table.Column(type: "TEXT", nullable: true), + WidescreenStoryboard = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapInfo", x => x.ID); + table.ForeignKey( + name: "FK_BeatmapInfo_BeatmapDifficulty_BaseDifficultyID", + column: x => x.BaseDifficultyID, + principalTable: "BeatmapDifficulty", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_BeatmapInfo_BeatmapSetInfo_BeatmapSetInfoID", + column: x => x.BeatmapSetInfoID, + principalTable: "BeatmapSetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_BeatmapInfo_BeatmapMetadata_MetadataID", + column: x => x.MetadataID, + principalTable: "BeatmapMetadata", + principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_BeatmapInfo_RulesetInfo_RulesetID", + column: x => x.RulesetID, + principalTable: "RulesetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "BeatmapSetFileInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false), + FileInfoID = table.Column(type: "INTEGER", nullable: false), + Filename = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapSetFileInfo", x => x.ID); + table.ForeignKey( + name: "FK_BeatmapSetFileInfo_BeatmapSetInfo_BeatmapSetInfoID", + column: x => x.BeatmapSetInfoID, + principalTable: "BeatmapSetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_BeatmapSetFileInfo_FileInfo_FileInfoID", + column: x => x.FileInfoID, + principalTable: "FileInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_BaseDifficultyID", + table: "BeatmapInfo", + column: "BaseDifficultyID"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_BeatmapSetInfoID", + table: "BeatmapInfo", + column: "BeatmapSetInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo", + column: "Hash"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo", + column: "MD5Hash"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_MetadataID", + table: "BeatmapInfo", + column: "MetadataID"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_RulesetID", + table: "BeatmapInfo", + column: "RulesetID"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetFileInfo_BeatmapSetInfoID", + table: "BeatmapSetFileInfo", + column: "BeatmapSetInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetFileInfo_FileInfoID", + table: "BeatmapSetFileInfo", + column: "FileInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_DeletePending", + table: "BeatmapSetInfo", + column: "DeletePending"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_Hash", + table: "BeatmapSetInfo", + column: "Hash"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_MetadataID", + table: "BeatmapSetInfo", + column: "MetadataID"); + + migrationBuilder.CreateIndex( + name: "IX_FileInfo_Hash", + table: "FileInfo", + column: "Hash", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_FileInfo_ReferenceCount", + table: "FileInfo", + column: "ReferenceCount"); + + migrationBuilder.CreateIndex( + name: "IX_KeyBinding_Action", + table: "KeyBinding", + column: "Action"); + + migrationBuilder.CreateIndex( + name: "IX_KeyBinding_Variant", + table: "KeyBinding", + column: "Variant"); + + migrationBuilder.CreateIndex( + name: "IX_RulesetInfo_Available", + table: "RulesetInfo", + column: "Available"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BeatmapInfo"); + + migrationBuilder.DropTable( + name: "BeatmapSetFileInfo"); + + migrationBuilder.DropTable( + name: "KeyBinding"); + + migrationBuilder.DropTable( + name: "BeatmapDifficulty"); + + migrationBuilder.DropTable( + name: "RulesetInfo"); + + migrationBuilder.DropTable( + name: "BeatmapSetInfo"); + + migrationBuilder.DropTable( + name: "FileInfo"); + + migrationBuilder.DropTable( + name: "BeatmapMetadata"); + } + } +} diff --git a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs new file mode 100644 index 0000000000..4cd234f2ef --- /dev/null +++ b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs @@ -0,0 +1,299 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20171025071459_AddMissingIndexRules")] + partial class AddMissingIndexRules + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MD5Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs new file mode 100644 index 0000000000..c9fc59c5a2 --- /dev/null +++ b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs @@ -0,0 +1,80 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddMissingIndexRules : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_BeatmapSetInfo_Hash", + table: "BeatmapSetInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_Hash", + table: "BeatmapSetInfo", + column: "Hash", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_OnlineBeatmapSetID", + table: "BeatmapSetInfo", + column: "OnlineBeatmapSetID", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo", + column: "Hash", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo", + column: "MD5Hash", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_BeatmapSetInfo_Hash", + table: "BeatmapSetInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapSetInfo_OnlineBeatmapSetID", + table: "BeatmapSetInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_Hash", + table: "BeatmapSetInfo", + column: "Hash"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo", + column: "Hash"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo", + column: "MD5Hash"); + } + } +} diff --git a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs new file mode 100644 index 0000000000..006acf12cd --- /dev/null +++ b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs @@ -0,0 +1,302 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20171119065731_AddBeatmapOnlineIDUniqueConstraint")] + partial class AddBeatmapOnlineIDUniqueConstraint + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MD5Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs new file mode 100644 index 0000000000..084ae67940 --- /dev/null +++ b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddBeatmapOnlineIDUniqueConstraint : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_OnlineBeatmapID", + table: "BeatmapInfo", + column: "OnlineBeatmapID", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_OnlineBeatmapID", + table: "BeatmapInfo"); + } + } +} diff --git a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs new file mode 100644 index 0000000000..fc2496bc24 --- /dev/null +++ b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs @@ -0,0 +1,307 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20171209034410_AddRulesetInfoShortName")] + partial class AddRulesetInfoShortName + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MD5Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs new file mode 100644 index 0000000000..09cf0af89c --- /dev/null +++ b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddRulesetInfoShortName : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ShortName", + table: "RulesetInfo", + type: "TEXT", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_RulesetInfo_ShortName", + table: "RulesetInfo", + column: "ShortName", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_RulesetInfo_ShortName", + table: "RulesetInfo"); + + migrationBuilder.DropColumn( + name: "ShortName", + table: "RulesetInfo"); + } + } +} diff --git a/osu.Game/Migrations/20180125143340_Settings.Designer.cs b/osu.Game/Migrations/20180125143340_Settings.Designer.cs new file mode 100644 index 0000000000..4bb599eec1 --- /dev/null +++ b/osu.Game/Migrations/20180125143340_Settings.Designer.cs @@ -0,0 +1,329 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180125143340_Settings")] + partial class Settings + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MD5Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20180125143340_Settings.cs b/osu.Game/Migrations/20180125143340_Settings.cs new file mode 100644 index 0000000000..166d3c086d --- /dev/null +++ b/osu.Game/Migrations/20180125143340_Settings.cs @@ -0,0 +1,55 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class Settings : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_KeyBinding_Variant", + table: "KeyBinding"); + + migrationBuilder.CreateTable( + name: "Settings", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Key = table.Column(type: "TEXT", nullable: false), + RulesetID = table.Column(type: "INTEGER", nullable: true), + Value = table.Column(type: "TEXT", nullable: true), + Variant = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Settings", x => x.ID); + }); + + migrationBuilder.CreateIndex( + name: "IX_KeyBinding_RulesetID_Variant", + table: "KeyBinding", + columns: new[] { "RulesetID", "Variant" }); + + migrationBuilder.CreateIndex( + name: "IX_Settings_RulesetID_Variant", + table: "Settings", + columns: new[] { "RulesetID", "Variant" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Settings"); + + migrationBuilder.DropIndex( + name: "IX_KeyBinding_RulesetID_Variant", + table: "KeyBinding"); + + migrationBuilder.CreateIndex( + name: "IX_KeyBinding_Variant", + table: "KeyBinding", + column: "Variant"); + } + } +} diff --git a/osu.Game/Migrations/20180131154205_AddMuteBinding.cs b/osu.Game/Migrations/20180131154205_AddMuteBinding.cs new file mode 100644 index 0000000000..5564a30bbf --- /dev/null +++ b/osu.Game/Migrations/20180131154205_AddMuteBinding.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Infrastructure; +using osu.Game.Database; +using osu.Game.Input.Bindings; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180131154205_AddMuteBinding")] + public partial class AddMuteBinding : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql($"UPDATE KeyBinding SET Action = Action + 1 WHERE RulesetID IS NULL AND Variant IS NULL AND Action >= {(int)GlobalAction.ToggleMute}"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql($"DELETE FROM KeyBinding WHERE RulesetID IS NULL AND Variant IS NULL AND Action = {(int)GlobalAction.ToggleMute}"); + migrationBuilder.Sql($"UPDATE KeyBinding SET Action = Action - 1 WHERE RulesetID IS NULL AND Variant IS NULL AND Action > {(int)GlobalAction.ToggleMute}"); + } + } +} diff --git a/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs b/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs new file mode 100644 index 0000000000..cdc4ef2e66 --- /dev/null +++ b/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs @@ -0,0 +1,379 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180219060912_AddSkins")] + partial class AddSkins + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MD5Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20180219060912_AddSkins.cs b/osu.Game/Migrations/20180219060912_AddSkins.cs new file mode 100644 index 0000000000..a0270ab0fd --- /dev/null +++ b/osu.Game/Migrations/20180219060912_AddSkins.cs @@ -0,0 +1,71 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddSkins : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "SkinInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Creator = table.Column(type: "TEXT", nullable: true), + DeletePending = table.Column(type: "INTEGER", nullable: false), + Name = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_SkinInfo", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "SkinFileInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + FileInfoID = table.Column(type: "INTEGER", nullable: false), + Filename = table.Column(type: "TEXT", nullable: false), + SkinInfoID = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SkinFileInfo", x => x.ID); + table.ForeignKey( + name: "FK_SkinFileInfo_FileInfo_FileInfoID", + column: x => x.FileInfoID, + principalTable: "FileInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_SkinFileInfo_SkinInfo_SkinInfoID", + column: x => x.SkinInfoID, + principalTable: "SkinInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_SkinFileInfo_FileInfoID", + table: "SkinFileInfo", + column: "FileInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_SkinFileInfo_SkinInfoID", + table: "SkinFileInfo", + column: "SkinInfoID"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "SkinFileInfo"); + + migrationBuilder.DropTable( + name: "SkinInfo"); + } + } +} diff --git a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs new file mode 100644 index 0000000000..f28408bfb3 --- /dev/null +++ b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs @@ -0,0 +1,377 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180529055154_RemoveUniqueHashConstraints")] + partial class RemoveUniqueHashConstraints + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.3-rtm-10026"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs new file mode 100644 index 0000000000..27269cc5fc --- /dev/null +++ b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs @@ -0,0 +1,51 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class RemoveUniqueHashConstraints : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo", + column: "Hash"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo", + column: "MD5Hash"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo", + column: "Hash", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo", + column: "MD5Hash", + unique: true); + } + } +} diff --git a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs new file mode 100644 index 0000000000..aaa11e88b6 --- /dev/null +++ b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs @@ -0,0 +1,376 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180621044111_UpdateTaikoDefaultBindings")] + partial class UpdateTaikoDefaultBindings + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs new file mode 100644 index 0000000000..71304ea979 --- /dev/null +++ b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class UpdateTaikoDefaultBindings : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("DELETE FROM KeyBinding WHERE RulesetID = 1"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + // we can't really tell if these should be restored or not, so let's just not do so. + } + } +} diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs new file mode 100644 index 0000000000..7eeacd56d7 --- /dev/null +++ b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs @@ -0,0 +1,376 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180628011956_RemoveNegativeSetIDs")] + partial class RemoveNegativeSetIDs + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs new file mode 100644 index 0000000000..506d65f761 --- /dev/null +++ b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs @@ -0,0 +1,18 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class RemoveNegativeSetIDs : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + // There was a change that beatmaps were being loaded with "-1" online IDs, which is completely incorrect. + // This ensures there will not be unique key conflicts as a result of these incorrectly imported beatmaps. + migrationBuilder.Sql("UPDATE BeatmapSetInfo SET OnlineBeatmapSetID = null WHERE OnlineBeatmapSetID <= 0"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} diff --git a/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs b/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs new file mode 100644 index 0000000000..5ab43da046 --- /dev/null +++ b/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs @@ -0,0 +1,380 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180913080842_AddRankStatus")] + partial class AddRankStatus + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.2-rtm-30932"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20180913080842_AddRankStatus.cs b/osu.Game/Migrations/20180913080842_AddRankStatus.cs new file mode 100644 index 0000000000..bba4944bb7 --- /dev/null +++ b/osu.Game/Migrations/20180913080842_AddRankStatus.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddRankStatus : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Status", + table: "BeatmapSetInfo", + nullable: false, + defaultValue: -3); // NONE + + migrationBuilder.AddColumn( + name: "Status", + table: "BeatmapInfo", + nullable: false, + defaultValue: -3); // NONE + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Status", + table: "BeatmapSetInfo"); + + migrationBuilder.DropColumn( + name: "Status", + table: "BeatmapInfo"); + } + } +} diff --git a/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs b/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs new file mode 100644 index 0000000000..b387a45ecf --- /dev/null +++ b/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs @@ -0,0 +1,380 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20181007180454_StandardizePaths")] + partial class StandardizePaths + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20181007180454_StandardizePaths.cs b/osu.Game/Migrations/20181007180454_StandardizePaths.cs new file mode 100644 index 0000000000..274b8030a9 --- /dev/null +++ b/osu.Game/Migrations/20181007180454_StandardizePaths.cs @@ -0,0 +1,26 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using System.IO; + +namespace osu.Game.Migrations +{ + public partial class StandardizePaths : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + string windowsStyle = @"\"; + string standardized = "/"; + + // Escaping \ does not seem to be needed. + migrationBuilder.Sql($"UPDATE `BeatmapInfo` SET `Path` = REPLACE(`Path`, '{windowsStyle}', '{standardized}')"); + migrationBuilder.Sql($"UPDATE `BeatmapMetadata` SET `AudioFile` = REPLACE(`AudioFile`, '{windowsStyle}', '{standardized}')"); + migrationBuilder.Sql($"UPDATE `BeatmapMetadata` SET `BackgroundFile` = REPLACE(`BackgroundFile`, '{windowsStyle}', '{standardized}')"); + migrationBuilder.Sql($"UPDATE `BeatmapSetFileInfo` SET `Filename` = REPLACE(`Filename`, '{windowsStyle}', '{standardized}')"); + migrationBuilder.Sql($"UPDATE `SkinFileInfo` SET `Filename` = REPLACE(`Filename`, '{windowsStyle}', '{standardized}')"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} diff --git a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs new file mode 100644 index 0000000000..120674671a --- /dev/null +++ b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs @@ -0,0 +1,387 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20181128100659_AddSkinInfoHash")] + partial class AddSkinInfoHash + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.4-rtm-31024"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs new file mode 100644 index 0000000000..860264a7dd --- /dev/null +++ b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs @@ -0,0 +1,41 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddSkinInfoHash : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Hash", + table: "SkinInfo", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_SkinInfo_DeletePending", + table: "SkinInfo", + column: "DeletePending"); + + migrationBuilder.CreateIndex( + name: "IX_SkinInfo_Hash", + table: "SkinInfo", + column: "Hash", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_SkinInfo_DeletePending", + table: "SkinInfo"); + + migrationBuilder.DropIndex( + name: "IX_SkinInfo_Hash", + table: "SkinInfo"); + + migrationBuilder.DropColumn( + name: "Hash", + table: "SkinInfo"); + } + } +} diff --git a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs new file mode 100644 index 0000000000..eee53182ce --- /dev/null +++ b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs @@ -0,0 +1,484 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20181130113755_AddScoreInfoTables")] + partial class AddScoreInfoTables + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.4-rtm-31024"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany() + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs new file mode 100644 index 0000000000..2b6f94c5a4 --- /dev/null +++ b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs @@ -0,0 +1,112 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddScoreInfoTables : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ScoreInfo", + columns: table => new + { + ID = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Rank = table.Column(nullable: false), + TotalScore = table.Column(nullable: false), + Accuracy = table.Column(type: "DECIMAL(1,4)", nullable: false), + PP = table.Column(nullable: true), + MaxCombo = table.Column(nullable: false), + Combo = table.Column(nullable: false), + RulesetID = table.Column(nullable: false), + Mods = table.Column(nullable: true), + User = table.Column(nullable: true), + BeatmapInfoID = table.Column(nullable: false), + OnlineScoreID = table.Column(nullable: true), + Date = table.Column(nullable: false), + Statistics = table.Column(nullable: true), + Hash = table.Column(nullable: true), + DeletePending = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ScoreInfo", x => x.ID); + table.ForeignKey( + name: "FK_ScoreInfo_BeatmapInfo_BeatmapInfoID", + column: x => x.BeatmapInfoID, + principalTable: "BeatmapInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ScoreInfo_RulesetInfo_RulesetID", + column: x => x.RulesetID, + principalTable: "RulesetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ScoreFileInfo", + columns: table => new + { + ID = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + FileInfoID = table.Column(nullable: false), + Filename = table.Column(nullable: false), + ScoreInfoID = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ScoreFileInfo", x => x.ID); + table.ForeignKey( + name: "FK_ScoreFileInfo_FileInfo_FileInfoID", + column: x => x.FileInfoID, + principalTable: "FileInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ScoreFileInfo_ScoreInfo_ScoreInfoID", + column: x => x.ScoreInfoID, + principalTable: "ScoreInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_ScoreFileInfo_FileInfoID", + table: "ScoreFileInfo", + column: "FileInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_ScoreFileInfo_ScoreInfoID", + table: "ScoreFileInfo", + column: "ScoreInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_ScoreInfo_BeatmapInfoID", + table: "ScoreInfo", + column: "BeatmapInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_ScoreInfo_OnlineScoreID", + table: "ScoreInfo", + column: "OnlineScoreID", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ScoreInfo_RulesetID", + table: "ScoreInfo", + column: "RulesetID"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ScoreFileInfo"); + + migrationBuilder.DropTable( + name: "ScoreInfo"); + } + } +} diff --git a/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs b/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs new file mode 100644 index 0000000000..8e1e3a59f3 --- /dev/null +++ b/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs @@ -0,0 +1,487 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20190225062029_AddUserIDColumn")] + partial class AddUserIDColumn + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.1-servicing-10028"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs b/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs new file mode 100644 index 0000000000..0720e0eac7 --- /dev/null +++ b/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddUserIDColumn : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UserID", + table: "ScoreInfo", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "UserID", + table: "ScoreInfo"); + } + } +} diff --git a/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs b/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs new file mode 100644 index 0000000000..348c42adb9 --- /dev/null +++ b/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs @@ -0,0 +1,498 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20190525060824_SkinSettings")] + partial class SkinSettings + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20190525060824_SkinSettings.cs b/osu.Game/Migrations/20190525060824_SkinSettings.cs new file mode 100644 index 0000000000..99237419b7 --- /dev/null +++ b/osu.Game/Migrations/20190525060824_SkinSettings.cs @@ -0,0 +1,54 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class SkinSettings : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql(@"create table Settings_dg_tmp + ( + ID INTEGER not null + constraint PK_Settings + primary key autoincrement, + Key TEXT not null, + RulesetID INTEGER, + Value TEXT, + Variant INTEGER, + SkinInfoID int + constraint Settings_SkinInfo_ID_fk + references SkinInfo + on delete restrict + ); + + insert into Settings_dg_tmp(ID, Key, RulesetID, Value, Variant) select ID, Key, RulesetID, Value, Variant from Settings; + + drop table Settings; + + alter table Settings_dg_tmp rename to Settings; + + create index IX_Settings_RulesetID_Variant + on Settings (RulesetID, Variant); + + create index Settings_SkinInfoID_index + on Settings (SkinInfoID); + + "); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Settings_SkinInfo_SkinInfoID", + table: "Settings"); + + migrationBuilder.DropIndex( + name: "IX_Settings_SkinInfoID", + table: "Settings"); + + migrationBuilder.DropColumn( + name: "SkinInfoID", + table: "Settings"); + } + } +} diff --git a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs new file mode 100644 index 0000000000..9477369aa0 --- /dev/null +++ b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs @@ -0,0 +1,489 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20190605091246_AddDateAddedColumnToBeatmapSet")] + partial class AddDateAddedColumnToBeatmapSet + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs new file mode 100644 index 0000000000..55dc18b6a3 --- /dev/null +++ b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs @@ -0,0 +1,24 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddDateAddedColumnToBeatmapSet : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "DateAdded", + table: "BeatmapSetInfo", + nullable: false, + defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "DateAdded", + table: "BeatmapSetInfo"); + } + } +} diff --git a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs new file mode 100644 index 0000000000..c5fcc16f84 --- /dev/null +++ b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs @@ -0,0 +1,504 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20190708070844_AddBPMAndLengthColumns")] + partial class AddBPMAndLengthColumns + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs new file mode 100644 index 0000000000..f5963ebf5e --- /dev/null +++ b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddBPMAndLengthColumns : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "BPM", + table: "BeatmapInfo", + nullable: false, + defaultValue: 0.0); + + migrationBuilder.AddColumn( + name: "Length", + table: "BeatmapInfo", + nullable: false, + defaultValue: 0.0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "BPM", + table: "BeatmapInfo"); + + migrationBuilder.DropColumn( + name: "Length", + table: "BeatmapInfo"); + } + } +} diff --git a/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs new file mode 100644 index 0000000000..826233a2b0 --- /dev/null +++ b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs @@ -0,0 +1,506 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20190913104727_AddBeatmapVideo")] + partial class AddBeatmapVideo + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.Property("VideoFile"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs new file mode 100644 index 0000000000..9ed0943acd --- /dev/null +++ b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddBeatmapVideo : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "VideoFile", + table: "BeatmapMetadata", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "VideoFile", + table: "BeatmapMetadata"); + } + } +} diff --git a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs new file mode 100644 index 0000000000..22316b0380 --- /dev/null +++ b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs @@ -0,0 +1,506 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20200302094919_RefreshVolumeBindings")] + partial class RefreshVolumeBindings + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.Property("VideoFile"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs new file mode 100644 index 0000000000..ec4475971c --- /dev/null +++ b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class RefreshVolumeBindings : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("DELETE FROM KeyBinding WHERE action in (6,7)"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} diff --git a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs new file mode 100644 index 0000000000..1c05de832e --- /dev/null +++ b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs @@ -0,0 +1,508 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20201019224408_AddEpilepsyWarning")] + partial class AddEpilepsyWarning + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("EpilepsyWarning"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.Property("VideoFile"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs new file mode 100644 index 0000000000..be6968aa5d --- /dev/null +++ b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddEpilepsyWarning : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "EpilepsyWarning", + table: "BeatmapInfo", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "EpilepsyWarning", + table: "BeatmapInfo"); + } + } +} diff --git a/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs new file mode 100644 index 0000000000..2c100d39b9 --- /dev/null +++ b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs @@ -0,0 +1,506 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20210412045700_RefreshVolumeBindingsAgain")] + partial class RefreshVolumeBindingsAgain + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.Property("VideoFile"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs new file mode 100644 index 0000000000..155d6670a8 --- /dev/null +++ b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class RefreshVolumeBindingsAgain : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("DELETE FROM KeyBinding WHERE action in (6,7)"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} diff --git a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs new file mode 100644 index 0000000000..b808c648da --- /dev/null +++ b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs @@ -0,0 +1,508 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20210511060743_AddSkinInstantiationInfo")] + partial class AddSkinInstantiationInfo + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("EpilepsyWarning"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs new file mode 100644 index 0000000000..1d5b0769a4 --- /dev/null +++ b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddSkinInstantiationInfo : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "InstantiationInfo", + table: "SkinInfo", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "InstantiationInfo", + table: "SkinInfo"); + } + } +} diff --git a/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs new file mode 100644 index 0000000000..89bab3a0fa --- /dev/null +++ b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs @@ -0,0 +1,511 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20210514062639_AddAuthorIdToBeatmapMetadata")] + partial class AddAuthorIdToBeatmapMetadata + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("EpilepsyWarning"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorID") + .HasColumnName("AuthorID"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs new file mode 100644 index 0000000000..98fe9b5e13 --- /dev/null +++ b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddAuthorIdToBeatmapMetadata : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AuthorID", + table: "BeatmapMetadata", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "AuthorID", + table: "BeatmapMetadata"); + } + } +} diff --git a/osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs b/osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs new file mode 100644 index 0000000000..afeb42130d --- /dev/null +++ b/osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs @@ -0,0 +1,513 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20210824185035_AddCountdownSettings")] + partial class AddCountdownSettings + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("CountdownOffset"); + + b.Property("DistanceSpacing"); + + b.Property("EpilepsyWarning"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorID") + .HasColumnName("AuthorID"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs b/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs new file mode 100644 index 0000000000..564f5f4520 --- /dev/null +++ b/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddCountdownSettings : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CountdownOffset", + table: "BeatmapInfo", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "CountdownOffset", + table: "BeatmapInfo"); + } + } +} diff --git a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs new file mode 100644 index 0000000000..6e53d7fae0 --- /dev/null +++ b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs @@ -0,0 +1,515 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20210912144011_AddSamplesMatchPlaybackRate")] + partial class AddSamplesMatchPlaybackRate + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("CountdownOffset"); + + b.Property("DistanceSpacing"); + + b.Property("EpilepsyWarning"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SamplesMatchPlaybackRate"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorID") + .HasColumnName("AuthorID"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs new file mode 100644 index 0000000000..bf3f855d5f --- /dev/null +++ b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddSamplesMatchPlaybackRate : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "SamplesMatchPlaybackRate", + table: "BeatmapInfo", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "SamplesMatchPlaybackRate", + table: "BeatmapInfo"); + } + } +} diff --git a/osu.Game/Migrations/20211020081609_ResetSkinHashes.cs b/osu.Game/Migrations/20211020081609_ResetSkinHashes.cs new file mode 100644 index 0000000000..6d53c019ec --- /dev/null +++ b/osu.Game/Migrations/20211020081609_ResetSkinHashes.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20211020081609_ResetSkinHashes")] + public partial class ResetSkinHashes : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql($"UPDATE SkinInfo SET Hash = null"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs new file mode 100644 index 0000000000..036c26cb0a --- /dev/null +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -0,0 +1,513 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + partial class OsuDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("CountdownOffset"); + + b.Property("DistanceSpacing"); + + b.Property("EpilepsyWarning"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SamplesMatchPlaybackRate"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorID") + .HasColumnName("AuthorID"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} From 1a14ccc7ee37f2daa4b3500483668621b7c7abef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 22:42:34 +0900 Subject: [PATCH 127/256] Run EF migrations before migrating to realm Turns out that there are more than zero users that are upgrading from old databases. I think we probably want to support this for now. Tested against database in https://github.com/ppy/osu/discussions/16700 and one other I had locally, both work correctly. --- osu.Game/Database/EFToRealmMigrator.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index a53704df9d..0bb5388d55 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -133,6 +133,8 @@ namespace osu.Game.Database r.RemoveAll(); }); + ef.Migrate(); + migrateSettings(ef); migrateSkins(ef); migrateBeatmaps(ef); From 24f9ef4005827338702e3e8766a835b755c7a5d0 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 22:31:04 +0800 Subject: [PATCH 128/256] make xmldoc more verbose --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index f366481c7d..9eeb060135 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override IconUsage? Icon => FontAwesome.Solid.Keyboard; /// - /// Whether incoming input must be checked by . + /// Whether incoming input must be checked by before it is passed to gameplay. /// public bool CanIntercept => !isBreakTime.Value && introEnded; From b4e516c535032f91c70f67e0ae8aebbe156d8d3e Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 22:31:24 +0800 Subject: [PATCH 129/256] allow test scenes to specify replays manually --- osu.Game/Tests/Visual/ModTestScene.cs | 33 +++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index a71d008eb9..524482237a 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -5,8 +5,12 @@ using System; using System.Collections.Generic; using JetBrains.Annotations; using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Replays; +using osu.Game.Scoring; namespace osu.Game.Tests.Visual { @@ -50,18 +54,37 @@ namespace osu.Game.Tests.Visual return CreateModPlayer(ruleset); } - protected virtual TestPlayer CreateModPlayer(Ruleset ruleset) => new ModTestPlayer(AllowFail); + protected virtual TestPlayer CreateModPlayer(Ruleset ruleset) => new ModTestPlayer(currentTestData, AllowFail); protected class ModTestPlayer : TestPlayer { private readonly bool allowFail; + private ModTestData currentTestData; protected override bool CheckModsAllowFailure() => allowFail; - public ModTestPlayer(bool allowFail) + public ModTestPlayer(ModTestData data, bool allowFail) : base(false, false) { this.allowFail = allowFail; + currentTestData = data; + } + + protected override void PrepareReplay() + { + if (currentTestData.Autoplay && currentTestData.Frames?.Count > 0) + throw new InvalidOperationException(@$"{nameof(ModTestData.Autoplay)} must be false when {nameof(ModTestData.Frames)} is specified."); + + if (currentTestData.Frames != null) + { + DrawableRuleset?.SetReplayScore(new Score + { + Replay = new Replay { Frames = currentTestData.Frames }, + ScoreInfo = new ScoreInfo { User = new APIUser { Username = @"Test" } }, + }); + } + + base.PrepareReplay(); } } @@ -72,6 +95,12 @@ namespace osu.Game.Tests.Visual /// public bool Autoplay = true; + /// + /// The frames to use for replay. must be set to false. + /// + [CanBeNull] + public List Frames; + /// /// The beatmap for this test case. /// From 1087d8b1cedfaad85e048151e20638c6cf18ff18 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 22:31:31 +0800 Subject: [PATCH 130/256] add tests --- .../Mods/TestSceneOsuModAlternate.cs | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs new file mode 100644 index 0000000000..7b3eaa7ffd --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs @@ -0,0 +1,154 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Replays; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public class TestSceneOsuModAlternate : OsuModTestScene + { + [Test] + public void TestInputAtIntro() => CreateModTest(new ModTestData + { + Mod = new OsuModAlternate(), + PassCondition = () => Player.ScoreProcessor.Combo.Value == 1, + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new HitCircle + { + StartTime = 1000, + Position = new Vector2(100), + }, + }, + }, + Frames = new List + { + new OsuReplayFrame(500, new Vector2(200), OsuAction.LeftButton), + new OsuReplayFrame(501, new Vector2(200)), + new OsuReplayFrame(1000, new Vector2(100), OsuAction.LeftButton), + } + }); + + [Test] + public void TestInputAlternating() => CreateModTest(new ModTestData + { + Mod = new OsuModAlternate(), + PassCondition = () => Player.ScoreProcessor.Combo.Value == 4, + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new HitCircle + { + StartTime = 500, + Position = new Vector2(100), + }, + new HitCircle + { + StartTime = 1000, + Position = new Vector2(200, 100), + }, + new HitCircle + { + StartTime = 1500, + Position = new Vector2(300, 100), + }, + new HitCircle + { + StartTime = 2000, + Position = new Vector2(400, 100), + }, + }, + }, + Frames = new List + { + new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), + new OsuReplayFrame(501, new Vector2(100)), + new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.RightButton), + new OsuReplayFrame(1001, new Vector2(200, 100)), + new OsuReplayFrame(1500, new Vector2(300, 100), OsuAction.LeftButton), + new OsuReplayFrame(1501, new Vector2(300, 100)), + new OsuReplayFrame(2000, new Vector2(400, 100), OsuAction.RightButton), + new OsuReplayFrame(2001, new Vector2(400, 100)), + } + }); + + [Test] + public void TestInputSingular() => CreateModTest(new ModTestData + { + Mod = new OsuModAlternate(), + PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 1, + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new HitCircle + { + StartTime = 500, + Position = new Vector2(100), + }, + new HitCircle + { + StartTime = 1000, + Position = new Vector2(200, 100), + }, + }, + }, + Frames = new List + { + new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), + new OsuReplayFrame(501, new Vector2(100)), + new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.LeftButton), + } + }); + + [Test] + public void TestInputSingularWithBreak() => CreateModTest(new ModTestData + { + Mod = new OsuModAlternate(), + PassCondition = () => Player.ScoreProcessor.Combo.Value == 2, + Autoplay = false, + Beatmap = new Beatmap + { + Breaks = new List + { + new BreakPeriod(500, 2250), + }, + HitObjects = new List + { + new HitCircle + { + StartTime = 500, + Position = new Vector2(100), + }, + new HitCircle + { + StartTime = 2500, + Position = new Vector2(100), + } + } + }, + Frames = new List + { + new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), + new OsuReplayFrame(501, new Vector2(100)), + new OsuReplayFrame(2500, new Vector2(100), OsuAction.LeftButton), + new OsuReplayFrame(2501, new Vector2(100)), + } + }); + } +} From a8eb3f95df2501659941c8d4d242fd8d08b897c8 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 22:54:17 +0800 Subject: [PATCH 131/256] add readonly modifier --- osu.Game/Tests/Visual/ModTestScene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index 524482237a..96fafa8727 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual protected class ModTestPlayer : TestPlayer { private readonly bool allowFail; - private ModTestData currentTestData; + private readonly ModTestData currentTestData; protected override bool CheckModsAllowFailure() => allowFail; From c6d303a5b44ba2f0d0c221178e9abab50dc2fbaa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 21:16:29 +0900 Subject: [PATCH 132/256] Add xmldoc to `Leaderboard` class --- osu.Game/Online/Leaderboards/Leaderboard.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 515cc6fd73..e2a52b5db9 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -23,6 +23,12 @@ using osuTK.Graphics; namespace osu.Game.Online.Leaderboards { + /// + /// A leaderboard which displays a scrolling list of top scores, along with a single "user best" + /// for the local user. + /// + /// The scope of the leaderboard (ie. global or local). + /// The score model class. public abstract class Leaderboard : Container { private const double fade_duration = 300; From aee93934d5c42792b41ef3b13c7baa748b7c3ee0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 21:22:09 +0900 Subject: [PATCH 133/256] Rename methods to make more sense (and always run through `AddOnce`) --- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 2 +- osu.Game/Online/Leaderboards/Leaderboard.cs | 10 +++++----- .../OnlinePlay/Match/Components/MatchLeaderboard.cs | 2 +- .../OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs | 2 +- .../Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 10 +++++----- osu.Game/Screens/Select/PlayBeatmapDetailArea.cs | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 4826d2fb33..4938fd1271 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.UserInterface leaderboard.FinishTransforms(true); // After setting scores, we may be waiting for transforms to expire drawables leaderboard.BeatmapInfo = beatmapInfo; - leaderboard.RefreshScores(); // Required in the case that the beatmap hasn't changed + leaderboard.RefetchScores(); // Required in the case that the beatmap hasn't changed }); [SetUpSteps] diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index e2a52b5db9..cd67fc7301 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -132,7 +132,7 @@ namespace osu.Game.Online.Leaderboards return; scope = value; - RefreshScores(); + RefetchScores(); } } @@ -160,7 +160,7 @@ namespace osu.Game.Online.Leaderboards case PlaceholderState.NetworkFailure: replacePlaceholder(new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync) { - Action = RefreshScores + Action = RefetchScores }); break; @@ -272,15 +272,15 @@ namespace osu.Game.Online.Leaderboards case APIState.Online: case APIState.Offline: if (IsOnlineScope) - RefreshScores(); + RefetchScores(); break; } }); - public void RefreshScores() => Scheduler.AddOnce(UpdateScores); + public void RefetchScores() => Scheduler.AddOnce(refetchScores); - protected void UpdateScores() + private void refetchScores() { // don't display any scores or placeholder until the first Scores_Set has been called. // this avoids scope changes flickering a "no scores" placeholder before initialisation of song select is finished. diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs index 134e083c42..e4ec3ac4a5 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components return; Scores = null; - UpdateScores(); + RefetchScores(); }, true); } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 4114a5e9a0..542851cb0f 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -220,7 +220,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists protected override Screen CreateGameplayScreen() => new PlayerLoader(() => new PlaylistsPlayer(Room, SelectedItem.Value) { - Exited = () => leaderboard.RefreshScores() + Exited = () => leaderboard.RefetchScores() }); } } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 31cbe91f5c..02e4e162dd 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Select.Leaderboards Scores = null; if (IsOnlineScope) - UpdateScores(); + RefetchScores(); else { if (IsLoaded) @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Select.Leaderboards filterMods = value; - UpdateScores(); + RefetchScores(); } } @@ -96,11 +96,11 @@ namespace osu.Game.Screens.Select.Leaderboards [BackgroundDependencyLoader] private void load() { - ruleset.ValueChanged += _ => UpdateScores(); + ruleset.ValueChanged += _ => RefetchScores(); mods.ValueChanged += _ => { if (filterMods) - UpdateScores(); + RefetchScores(); }; } @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Select.Leaderboards (_, changes, ___) => { if (!IsOnlineScope) - RefreshScores(); + RefetchScores(); }); } diff --git a/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs b/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs index b8b8e3e4bc..09f75b7658 100644 --- a/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Select { base.Refresh(); - Leaderboard.RefreshScores(); + Leaderboard.RefetchScores(); } protected override void OnTabChanged(BeatmapDetailAreaTabItem tab, bool selectedMods) From b9dac6c3b2d312bc79fefff4d5abb1c24d78c00c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 21:33:22 +0900 Subject: [PATCH 134/256] Reorder and tidy up bindable flows --- osu.Game/Online/Leaderboards/Leaderboard.cs | 81 ++++++++++++--------- 1 file changed, 45 insertions(+), 36 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index cd67fc7301..af448b7d7b 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -46,6 +46,18 @@ namespace osu.Game.Online.Leaderboards private bool scoresLoadedOnce; + private APIRequest getScoresRequest; + private ScheduledDelegate getScoresRequestCallback; + + protected abstract bool IsOnlineScope { get; } + + [Resolved(CanBeNull = true)] + private IAPIProvider api { get; set; } + + private ScheduledDelegate pendingUpdateScores; + + private readonly IBindable apiState = new Bindable(); + private readonly Container content; protected override Container Content => content; @@ -239,44 +251,34 @@ namespace osu.Game.Online.Leaderboards protected virtual void Reset() { - getScoresRequest?.Cancel(); - getScoresRequest = null; + cancelPendingWork(); + Scores = null; } - [Resolved(CanBeNull = true)] - private IAPIProvider api { get; set; } - - private ScheduledDelegate pendingUpdateScores; - - private readonly IBindable apiState = new Bindable(); - - [BackgroundDependencyLoader] - private void load() + protected override void LoadComplete() { + base.LoadComplete(); + if (api != null) - apiState.BindTo(api.State); - - apiState.BindValueChanged(onlineStateChanged, true); - } - - private APIRequest getScoresRequest; - private ScheduledDelegate getScoresRequestCallback; - - protected abstract bool IsOnlineScope { get; } - - private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => - { - switch (state.NewValue) { - case APIState.Online: - case APIState.Offline: - if (IsOnlineScope) - RefetchScores(); + apiState.BindTo(api.State); + apiState.BindValueChanged(state => + { + switch (state.NewValue) + { + case APIState.Online: + case APIState.Offline: + if (IsOnlineScope) + RefetchScores(); - break; + break; + } + }); } - }); + + RefetchScores(); + } public void RefetchScores() => Scheduler.AddOnce(refetchScores); @@ -286,13 +288,8 @@ namespace osu.Game.Online.Leaderboards // this avoids scope changes flickering a "no scores" placeholder before initialisation of song select is finished. if (!scoresLoadedOnce) return; - getScoresRequest?.Cancel(); - getScoresRequest = null; + cancelPendingWork(); - getScoresRequestCallback?.Cancel(); - getScoresRequestCallback = null; - - pendingUpdateScores?.Cancel(); pendingUpdateScores = Schedule(() => { PlaceholderState = PlaceholderState.Retrieving; @@ -319,6 +316,18 @@ namespace osu.Game.Online.Leaderboards }); } + private void cancelPendingWork() + { + getScoresRequest?.Cancel(); + getScoresRequest = null; + + getScoresRequestCallback?.Cancel(); + getScoresRequestCallback = null; + + pendingUpdateScores?.Cancel(); + pendingUpdateScores = null; + } + /// /// Performs a fetch/refresh of scores to be displayed. /// From 64925b3feaafa353bbfb87af39e13ee9e762a8bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 21:38:23 +0900 Subject: [PATCH 135/256] Remove unused `Content` override --- osu.Game/Online/Leaderboards/Leaderboard.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index af448b7d7b..785c2e2ecf 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -29,7 +29,7 @@ namespace osu.Game.Online.Leaderboards /// /// The scope of the leaderboard (ie. global or local). /// The score model class. - public abstract class Leaderboard : Container + public abstract class Leaderboard : CompositeDrawable { private const double fade_duration = 300; @@ -58,10 +58,6 @@ namespace osu.Game.Online.Leaderboards private readonly IBindable apiState = new Bindable(); - private readonly Container content; - - protected override Container Content => content; - private ICollection scores; public ICollection Scores @@ -231,7 +227,7 @@ namespace osu.Game.Online.Leaderboards }, new Drawable[] { - content = new Container + new Container { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, From 17aa9f304000505d5aff1d04239f5fa828873871 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 21:47:28 +0900 Subject: [PATCH 136/256] Remove pointless level of schedule/cancel logic --- osu.Game/Online/Leaderboards/Leaderboard.cs | 39 ++++++++------------- 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 785c2e2ecf..7ab3d0343a 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -54,8 +54,6 @@ namespace osu.Game.Online.Leaderboards [Resolved(CanBeNull = true)] private IAPIProvider api { get; set; } - private ScheduledDelegate pendingUpdateScores; - private readonly IBindable apiState = new Bindable(); private ICollection scores; @@ -248,7 +246,6 @@ namespace osu.Game.Online.Leaderboards protected virtual void Reset() { cancelPendingWork(); - Scores = null; } @@ -286,30 +283,27 @@ namespace osu.Game.Online.Leaderboards cancelPendingWork(); - pendingUpdateScores = Schedule(() => + PlaceholderState = PlaceholderState.Retrieving; + loading.Show(); + + getScoresRequest = FetchScores(scores => getScoresRequestCallback = Schedule(() => { - PlaceholderState = PlaceholderState.Retrieving; - loading.Show(); + Scores = scores.ToArray(); + PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; + })); - getScoresRequest = FetchScores(scores => getScoresRequestCallback = Schedule(() => - { - Scores = scores.ToArray(); - PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; - })); + if (getScoresRequest == null) + return; - if (getScoresRequest == null) + getScoresRequest.Failure += e => getScoresRequestCallback = Schedule(() => + { + if (e is OperationCanceledException) return; - getScoresRequest.Failure += e => getScoresRequestCallback = Schedule(() => - { - if (e is OperationCanceledException) - return; - - PlaceholderState = PlaceholderState.NetworkFailure; - }); - - api?.Queue(getScoresRequest); + PlaceholderState = PlaceholderState.NetworkFailure; }); + + api?.Queue(getScoresRequest); } private void cancelPendingWork() @@ -319,9 +313,6 @@ namespace osu.Game.Online.Leaderboards getScoresRequestCallback?.Cancel(); getScoresRequestCallback = null; - - pendingUpdateScores?.Cancel(); - pendingUpdateScores = null; } /// From c5486586623acd425be534fced770e456493c85c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 21:49:52 +0900 Subject: [PATCH 137/256] Remove move unused pieces --- osu.Game/Online/Leaderboards/Leaderboard.cs | 29 ++++++++------------- 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 7ab3d0343a..a426d2b448 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -82,7 +82,13 @@ namespace osu.Game.Online.Leaderboards // ensure placeholder is hidden when displaying scores PlaceholderState = PlaceholderState.Successful; - var scoreFlow = CreateScoreFlow(); + var scoreFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, 5f), + Padding = new MarginPadding { Top = 10, Bottom = 5 }, + }; scoreFlow.ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)); // schedule because we may not be loaded yet (LoadComponentAsync complains). @@ -118,15 +124,6 @@ namespace osu.Game.Online.Leaderboards } } - protected virtual FillFlowContainer CreateScoreFlow() - => new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0f, 5f), - Padding = new MarginPadding { Top = 10, Bottom = 5 }, - }; - private TScope scope; public TScope Scope @@ -345,9 +342,6 @@ namespace osu.Game.Online.Leaderboards currentPlaceholder = placeholder; } - protected virtual bool FadeBottom => true; - protected virtual bool FadeTop => false; - protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); @@ -366,22 +360,21 @@ namespace osu.Game.Online.Leaderboards float topY = c.ToSpaceOfOtherDrawable(Vector2.Zero, scrollFlow).Y; float bottomY = topY + LeaderboardScore.HEIGHT; - bool requireTopFade = FadeTop && topY <= fadeTop; - bool requireBottomFade = FadeBottom && bottomY >= fadeBottom; + bool requireBottomFade = bottomY >= fadeBottom; - if (!requireTopFade && !requireBottomFade) + if (!requireBottomFade) c.Colour = Color4.White; else if (topY > fadeBottom + LeaderboardScore.HEIGHT || bottomY < fadeTop - LeaderboardScore.HEIGHT) c.Colour = Color4.Transparent; else { - if (bottomY - fadeBottom > 0 && FadeBottom) + if (bottomY - fadeBottom > 0) { c.Colour = ColourInfo.GradientVertical( Color4.White.Opacity(Math.Min(1 - (topY - fadeBottom) / LeaderboardScore.HEIGHT, 1)), Color4.White.Opacity(Math.Min(1 - (bottomY - fadeBottom) / LeaderboardScore.HEIGHT, 1))); } - else if (FadeTop) + else { c.Colour = ColourInfo.GradientVertical( Color4.White.Opacity(Math.Min(1 - (fadeTop - topY) / LeaderboardScore.HEIGHT, 1)), From b85b2c01fb248db8a3deb773ba1ed5da930d07c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 21:53:12 +0900 Subject: [PATCH 138/256] Reorder based on accessibility and add regions --- osu.Game/Online/Leaderboards/Leaderboard.cs | 268 ++++++++++---------- 1 file changed, 138 insertions(+), 130 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index a426d2b448..d843dcb2bb 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -139,6 +139,139 @@ namespace osu.Game.Online.Leaderboards } } + protected Leaderboard() + { + InternalChildren = new Drawable[] + { + new OsuContextMenuContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] + { + scrollContainer = new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, + } + }, + new Drawable[] + { + new Container + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Child = topScoreContainer = new UserTopScoreContainer(CreateDrawableTopScore) + }, + }, + }, + }, + }, + loading = new LoadingSpinner(), + placeholderContainer = new Container + { + RelativeSizeAxes = Axes.Both + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (api != null) + { + apiState.BindTo(api.State); + apiState.BindValueChanged(state => + { + switch (state.NewValue) + { + case APIState.Online: + case APIState.Offline: + if (IsOnlineScope) + RefetchScores(); + + break; + } + }); + } + + RefetchScores(); + } + + public void RefetchScores() => Scheduler.AddOnce(refetchScores); + + protected virtual void Reset() + { + cancelPendingWork(); + Scores = null; + } + + /// + /// Performs a fetch/refresh of scores to be displayed. + /// + /// A callback which should be called when fetching is completed. Scheduling is not required. + /// An responsible for the fetch operation. This will be queued and performed automatically. + protected abstract APIRequest FetchScores(Action> scoresCallback); + + protected abstract LeaderboardScore CreateDrawableScore(TScoreInfo model, int index); + + protected abstract LeaderboardScore CreateDrawableTopScore(TScoreInfo model); + + private void refetchScores() + { + // don't display any scores or placeholder until the first Scores_Set has been called. + // this avoids scope changes flickering a "no scores" placeholder before initialisation of song select is finished. + if (!scoresLoadedOnce) return; + + cancelPendingWork(); + + PlaceholderState = PlaceholderState.Retrieving; + loading.Show(); + + getScoresRequest = FetchScores(scores => getScoresRequestCallback = Schedule(() => + { + Scores = scores.ToArray(); + PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; + })); + + if (getScoresRequest == null) + return; + + getScoresRequest.Failure += e => getScoresRequestCallback = Schedule(() => + { + if (e is OperationCanceledException) + return; + + PlaceholderState = PlaceholderState.NetworkFailure; + }); + + api?.Queue(getScoresRequest); + } + + private void cancelPendingWork() + { + getScoresRequest?.Cancel(); + getScoresRequest = null; + + getScoresRequestCallback?.Cancel(); + getScoresRequestCallback = null; + } + + #region Placeholder handling + + private Placeholder currentPlaceholder; + private PlaceholderState placeholderState; /// @@ -194,133 +327,6 @@ namespace osu.Game.Online.Leaderboards } } - protected Leaderboard() - { - InternalChildren = new Drawable[] - { - new OsuContextMenuContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Child = new GridContainer - { - RelativeSizeAxes = Axes.Both, - RowDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] - { - new Drawable[] - { - scrollContainer = new OsuScrollContainer - { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - } - }, - new Drawable[] - { - new Container - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Child = topScoreContainer = new UserTopScoreContainer(CreateDrawableTopScore) - }, - }, - }, - }, - }, - loading = new LoadingSpinner(), - placeholderContainer = new Container - { - RelativeSizeAxes = Axes.Both - }, - }; - } - - protected virtual void Reset() - { - cancelPendingWork(); - Scores = null; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - if (api != null) - { - apiState.BindTo(api.State); - apiState.BindValueChanged(state => - { - switch (state.NewValue) - { - case APIState.Online: - case APIState.Offline: - if (IsOnlineScope) - RefetchScores(); - - break; - } - }); - } - - RefetchScores(); - } - - public void RefetchScores() => Scheduler.AddOnce(refetchScores); - - private void refetchScores() - { - // don't display any scores or placeholder until the first Scores_Set has been called. - // this avoids scope changes flickering a "no scores" placeholder before initialisation of song select is finished. - if (!scoresLoadedOnce) return; - - cancelPendingWork(); - - PlaceholderState = PlaceholderState.Retrieving; - loading.Show(); - - getScoresRequest = FetchScores(scores => getScoresRequestCallback = Schedule(() => - { - Scores = scores.ToArray(); - PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; - })); - - if (getScoresRequest == null) - return; - - getScoresRequest.Failure += e => getScoresRequestCallback = Schedule(() => - { - if (e is OperationCanceledException) - return; - - PlaceholderState = PlaceholderState.NetworkFailure; - }); - - api?.Queue(getScoresRequest); - } - - private void cancelPendingWork() - { - getScoresRequest?.Cancel(); - getScoresRequest = null; - - getScoresRequestCallback?.Cancel(); - getScoresRequestCallback = null; - } - - /// - /// Performs a fetch/refresh of scores to be displayed. - /// - /// A callback which should be called when fetching is completed. Scheduling is not required. - /// An responsible for the fetch operation. This will be queued and performed automatically. - protected abstract APIRequest FetchScores(Action> scoresCallback); - - private Placeholder currentPlaceholder; - private void replacePlaceholder(Placeholder placeholder) { if (placeholder != null && placeholder.Equals(currentPlaceholder)) @@ -342,6 +348,10 @@ namespace osu.Game.Online.Leaderboards currentPlaceholder = placeholder; } + #endregion + + #region Fade handling + protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); @@ -384,8 +394,6 @@ namespace osu.Game.Online.Leaderboards } } - protected abstract LeaderboardScore CreateDrawableScore(TScoreInfo model, int index); - - protected abstract LeaderboardScore CreateDrawableTopScore(TScoreInfo model); + #endregion } } From 661fec7c8abd1fba873a5003092d9f5f45da475c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 21:59:29 +0900 Subject: [PATCH 139/256] Make score setter private --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 5 ++++- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 3 --- osu.Game/Online/Leaderboards/Leaderboard.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index e31be1d51a..7ffab2eebc 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -100,7 +101,7 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestGlobalScoresDisplay() { AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Global); - AddStep(@"New Scores", () => leaderboard.Scores = generateSampleScores(null)); + AddStep(@"New Scores", () => leaderboard.SetScores(generateSampleScores(null))); } [Test] @@ -422,6 +423,8 @@ namespace osu.Game.Tests.Visual.SongSelect { PlaceholderState = state; } + + public void SetScores(ICollection scores) => Scores = scores; } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 4938fd1271..985ff6f251 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -128,9 +128,6 @@ namespace osu.Game.Tests.Visual.UserInterface scoreManager.Undelete(r.All().Where(s => s.DeletePending).ToList()); }); - leaderboard.Scores = null; - leaderboard.FinishTransforms(true); // After setting scores, we may be waiting for transforms to expire drawables - leaderboard.BeatmapInfo = beatmapInfo; leaderboard.RefetchScores(); // Required in the case that the beatmap hasn't changed }); diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index d843dcb2bb..0c58faa178 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -61,7 +61,7 @@ namespace osu.Game.Online.Leaderboards public ICollection Scores { get => scores; - set + protected set { scores = value; From a700ad38499ed2fbaed093e5de5570b27f5cd0c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 22:04:34 +0900 Subject: [PATCH 140/256] Remove `scoresLoadedOnce` weirdness --- osu.Game/Online/Leaderboards/Leaderboard.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 0c58faa178..238e883d24 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -44,8 +44,6 @@ namespace osu.Game.Online.Leaderboards private ScheduledDelegate showScoresDelegate; private CancellationTokenSource showScoresCancellationSource; - private bool scoresLoadedOnce; - private APIRequest getScoresRequest; private ScheduledDelegate getScoresRequestCallback; @@ -65,8 +63,6 @@ namespace osu.Game.Online.Leaderboards { scores = value; - scoresLoadedOnce = true; - scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire(); scrollFlow = null; @@ -230,10 +226,6 @@ namespace osu.Game.Online.Leaderboards private void refetchScores() { - // don't display any scores or placeholder until the first Scores_Set has been called. - // this avoids scope changes flickering a "no scores" placeholder before initialisation of song select is finished. - if (!scoresLoadedOnce) return; - cancelPendingWork(); PlaceholderState = PlaceholderState.Retrieving; From c48e9f2bbdc2dba7360e5d0b7ef9e4dad12256e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 22:13:52 +0900 Subject: [PATCH 141/256] Remove more unnecessary schedule/cancel logic --- osu.Game/Online/Leaderboards/Leaderboard.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 238e883d24..e8fc2d126e 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -3,10 +3,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Development; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -41,7 +43,6 @@ namespace osu.Game.Online.Leaderboards private readonly LoadingSpinner loading; - private ScheduledDelegate showScoresDelegate; private CancellationTokenSource showScoresCancellationSource; private APIRequest getScoresRequest; @@ -61,15 +62,16 @@ namespace osu.Game.Online.Leaderboards get => scores; protected set { + Debug.Assert(ThreadSafety.IsUpdateThread); + scores = value; scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire(); scrollFlow = null; - showScoresDelegate?.Cancel(); showScoresCancellationSource?.Cancel(); - if (scores == null || !scores.Any()) + if (scores?.Any() != true) { loading.Hide(); return; @@ -84,11 +86,11 @@ namespace osu.Game.Online.Leaderboards AutoSizeAxes = Axes.Y, Spacing = new Vector2(0f, 5f), Padding = new MarginPadding { Top = 10, Bottom = 5 }, + ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)) }; - scoreFlow.ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)); // schedule because we may not be loaded yet (LoadComponentAsync complains). - showScoresDelegate = Schedule(() => LoadComponentAsync(scoreFlow, _ => + LoadComponentAsync(scoreFlow, _ => { scrollContainer.Add(scrollFlow = scoreFlow); @@ -100,9 +102,9 @@ namespace osu.Game.Online.Leaderboards s.Show(); } - scrollContainer.ScrollTo(0f, false); + scrollContainer.ScrollToStart(false); loading.Hide(); - }, (showScoresCancellationSource = new CancellationTokenSource()).Token)); + }, (showScoresCancellationSource = new CancellationTokenSource()).Token); } } @@ -253,6 +255,9 @@ namespace osu.Game.Online.Leaderboards private void cancelPendingWork() { + showScoresCancellationSource?.Cancel(); + showScoresCancellationSource = null; + getScoresRequest?.Cancel(); getScoresRequest = null; From 13f445ddd5c49b2be0ccf2efeeff72c4703b1b8b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 22:28:13 +0900 Subject: [PATCH 142/256] Move score update code into own method --- osu.Game/Online/Leaderboards/Leaderboard.cs | 91 +++++++++++---------- 1 file changed, 48 insertions(+), 43 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index e8fc2d126e..0302568b34 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -33,6 +33,8 @@ namespace osu.Game.Online.Leaderboards /// The score model class. public abstract class Leaderboard : CompositeDrawable { + protected abstract bool IsOnlineScope { get; } + private const double fade_duration = 300; private readonly OsuScrollContainer scrollContainer; @@ -48,8 +50,6 @@ namespace osu.Game.Online.Leaderboards private APIRequest getScoresRequest; private ScheduledDelegate getScoresRequestCallback; - protected abstract bool IsOnlineScope { get; } - [Resolved(CanBeNull = true)] private IAPIProvider api { get; set; } @@ -62,49 +62,9 @@ namespace osu.Game.Online.Leaderboards get => scores; protected set { - Debug.Assert(ThreadSafety.IsUpdateThread); - scores = value; - scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire(); - scrollFlow = null; - - showScoresCancellationSource?.Cancel(); - - if (scores?.Any() != true) - { - loading.Hide(); - return; - } - - // ensure placeholder is hidden when displaying scores - PlaceholderState = PlaceholderState.Successful; - - var scoreFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0f, 5f), - Padding = new MarginPadding { Top = 10, Bottom = 5 }, - ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)) - }; - - // schedule because we may not be loaded yet (LoadComponentAsync complains). - LoadComponentAsync(scoreFlow, _ => - { - scrollContainer.Add(scrollFlow = scoreFlow); - - int i = 0; - - foreach (var s in scrollFlow.Children) - { - using (s.BeginDelayedSequence(i++ * 50)) - s.Show(); - } - - scrollContainer.ScrollToStart(false); - loading.Hide(); - }, (showScoresCancellationSource = new CancellationTokenSource()).Token); + updateScoresDrawables(); } } @@ -324,6 +284,51 @@ namespace osu.Game.Online.Leaderboards } } + private void updateScoresDrawables() + { + Debug.Assert(ThreadSafety.IsUpdateThread); + + scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire(); + scrollFlow = null; + + showScoresCancellationSource?.Cancel(); + + if (scores?.Any() != true) + { + loading.Hide(); + return; + } + + // ensure placeholder is hidden when displaying scores + PlaceholderState = PlaceholderState.Successful; + + var scoreFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, 5f), + Padding = new MarginPadding { Top = 10, Bottom = 5 }, + ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)) + }; + + // schedule because we may not be loaded yet (LoadComponentAsync complains). + LoadComponentAsync(scoreFlow, _ => + { + scrollContainer.Add(scrollFlow = scoreFlow); + + int i = 0; + + foreach (var s in scrollFlow.Children) + { + using (s.BeginDelayedSequence(i++ * 50)) + s.Show(); + } + + scrollContainer.ScrollToStart(false); + loading.Hide(); + }, (showScoresCancellationSource = new CancellationTokenSource()).Token); + } + private void replacePlaceholder(Placeholder placeholder) { if (placeholder != null && placeholder.Equals(currentPlaceholder)) From 3d59bab7c61cea8d71b362bd21f9bc183c3baf7d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 22:47:45 +0900 Subject: [PATCH 143/256] Remove fetch callback logic completely --- osu.Game/Online/Leaderboards/Leaderboard.cs | 25 +--- .../Match/Components/MatchLeaderboard.cs | 6 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 130 ++++++++---------- 3 files changed, 67 insertions(+), 94 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 0302568b34..eeb814b4cb 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -3,12 +3,11 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Development; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -178,9 +177,9 @@ namespace osu.Game.Online.Leaderboards /// /// Performs a fetch/refresh of scores to be displayed. /// - /// A callback which should be called when fetching is completed. Scheduling is not required. /// An responsible for the fetch operation. This will be queued and performed automatically. - protected abstract APIRequest FetchScores(Action> scoresCallback); + [CanBeNull] + protected abstract APIRequest FetchScores(); protected abstract LeaderboardScore CreateDrawableScore(TScoreInfo model, int index); @@ -193,11 +192,7 @@ namespace osu.Game.Online.Leaderboards PlaceholderState = PlaceholderState.Retrieving; loading.Show(); - getScoresRequest = FetchScores(scores => getScoresRequestCallback = Schedule(() => - { - Scores = scores.ToArray(); - PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; - })); + getScoresRequest = FetchScores(); if (getScoresRequest == null) return; @@ -240,11 +235,6 @@ namespace osu.Game.Online.Leaderboards get => placeholderState; set { - if (value != PlaceholderState.Successful) - { - Reset(); - } - if (value == placeholderState) return; @@ -284,10 +274,8 @@ namespace osu.Game.Online.Leaderboards } } - private void updateScoresDrawables() + private void updateScoresDrawables() => Scheduler.Add(() => { - Debug.Assert(ThreadSafety.IsUpdateThread); - scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire(); scrollFlow = null; @@ -296,6 +284,7 @@ namespace osu.Game.Online.Leaderboards if (scores?.Any() != true) { loading.Hide(); + PlaceholderState = PlaceholderState.NoScores; return; } @@ -327,7 +316,7 @@ namespace osu.Game.Online.Leaderboards scrollContainer.ScrollToStart(false); loading.Hide(); }, (showScoresCancellationSource = new CancellationTokenSource()).Token); - } + }, false); private void replacePlaceholder(Placeholder placeholder) { diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs index e4ec3ac4a5..5b60f64160 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Online.API; @@ -32,7 +30,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components protected override bool IsOnlineScope => true; - protected override APIRequest FetchScores(Action> scoresCallback) + protected override APIRequest FetchScores() { if (roomId.Value == null) return null; @@ -41,7 +39,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components req.Success += r => { - scoresCallback?.Invoke(r.Leaderboard); + Scores = r.Leaderboard; TopScore = r.UserScore; }; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 02e4e162dd..898b894541 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -25,12 +25,6 @@ namespace osu.Game.Screens.Select.Leaderboards { public Action ScoreSelected; - [Resolved] - private RulesetStore rulesets { get; set; } - - [Resolved] - private RealmAccess realm { get; set; } - private BeatmapInfo beatmapInfo; public BeatmapInfo BeatmapInfo @@ -52,13 +46,7 @@ namespace osu.Game.Screens.Select.Leaderboards beatmapInfo = value; Scores = null; - if (IsOnlineScope) - RefetchScores(); - else - { - if (IsLoaded) - refreshRealmSubscription(); - } + RefetchScores(); } } @@ -93,6 +81,14 @@ namespace osu.Game.Screens.Select.Leaderboards [Resolved] private IAPIProvider api { get; set; } + [Resolved] + private RulesetStore rulesets { get; set; } + + [Resolved] + private RealmAccess realm { get; set; } + + private IDisposable scoreSubscription; + [BackgroundDependencyLoader] private void load() { @@ -104,33 +100,6 @@ namespace osu.Game.Screens.Select.Leaderboards }; } - protected override void LoadComplete() - { - base.LoadComplete(); - - refreshRealmSubscription(); - } - - private IDisposable scoreSubscription; - - private void refreshRealmSubscription() - { - scoreSubscription?.Dispose(); - scoreSubscription = null; - - if (beatmapInfo == null) - return; - - scoreSubscription = realm.RegisterForNotifications(r => - r.All() - .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID), - (_, changes, ___) => - { - if (!IsOnlineScope) - RefetchScores(); - }); - } - protected override void Reset() { base.Reset(); @@ -141,13 +110,11 @@ namespace osu.Game.Screens.Select.Leaderboards private CancellationTokenSource loadCancellationSource; - protected override APIRequest FetchScores(Action> scoresCallback) + protected override APIRequest FetchScores() { loadCancellationSource?.Cancel(); loadCancellationSource = new CancellationTokenSource(); - var cancellationToken = loadCancellationSource.Token; - var fetchBeatmapInfo = BeatmapInfo; if (fetchBeatmapInfo == null) @@ -158,31 +125,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (Scope == BeatmapLeaderboardScope.Local) { - realm.Run(r => - { - var scores = r.All() - .AsEnumerable() - // TODO: update to use a realm filter directly (or at least figure out the beatmap part to reduce scope). - .Where(s => !s.DeletePending && s.BeatmapInfo.ID == fetchBeatmapInfo.ID && s.Ruleset.ShortName == ruleset.Value.ShortName); - - if (filterMods && !mods.Value.Any()) - { - // we need to filter out all scores that have any mods to get all local nomod scores - scores = scores.Where(s => !s.Mods.Any()); - } - else if (filterMods) - { - // otherwise find all the scores that have *any* of the currently selected mods (similar to how web applies mod filters) - // we're creating and using a string list representation of selected mods so that it can be translated into the DB query itself - var selectedMods = mods.Value.Select(m => m.Acronym); - scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym))); - } - - scores = scores.Detach(); - - scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken) - .ContinueWith(ordered => scoresCallback?.Invoke(ordered.GetResultSafely()), TaskContinuationOptions.OnlyOnRanToCompletion); - }); + subscribeToLocalScores(); return null; } @@ -217,13 +160,13 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { - scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets, fetchBeatmapInfo)).ToArray(), cancellationToken) + scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets, fetchBeatmapInfo)).ToArray(), loadCancellationSource.Token) .ContinueWith(task => Schedule(() => { - if (cancellationToken.IsCancellationRequested) + if (loadCancellationSource.IsCancellationRequested) return; - scoresCallback?.Invoke(task.GetResultSafely()); + Scores = task.GetResultSafely(); TopScore = r.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo); }), TaskContinuationOptions.OnlyOnRanToCompletion); }; @@ -241,10 +184,53 @@ namespace osu.Game.Screens.Select.Leaderboards Action = () => ScoreSelected?.Invoke(model) }; + private void subscribeToLocalScores() + { + scoreSubscription?.Dispose(); + scoreSubscription = null; + + if (beatmapInfo == null) + return; + + scoreSubscription = realm.RegisterForNotifications(r => + r.All().Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $0" + + $" AND {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $1" + + $" AND {nameof(ScoreInfo.DeletePending)} == false" + , beatmapInfo.ID, ruleset.Value.ShortName), localScoresChanged); + } + + private void localScoresChanged(IRealmCollection sender, ChangeSet changes, Exception exception) + { + if (IsOnlineScope) + return; + + var scores = sender.AsEnumerable(); + + if (filterMods && !mods.Value.Any()) + { + // we need to filter out all scores that have any mods to get all local nomod scores + scores = scores.Where(s => !s.Mods.Any()); + } + else if (filterMods) + { + // otherwise find all the scores that have *any* of the currently selected mods (similar to how web applies mod filters) + // we're creating and using a string list representation of selected mods so that it can be translated into the DB query itself + var selectedMods = mods.Value.Select(m => m.Acronym); + scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym))); + } + + scores = scores.Detach(); + + scoreManager.OrderByTotalScoreAsync(scores.ToArray(), loadCancellationSource.Token) + .ContinueWith(ordered => + { + Scores = ordered.GetResultSafely(); + }, TaskContinuationOptions.OnlyOnRanToCompletion); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - scoreSubscription?.Dispose(); } } From daea13f49158b5c464f20c4b395a85bd46206991 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 23:14:26 +0900 Subject: [PATCH 144/256] Simplify flow of cancellation token --- osu.Game/Online/Leaderboards/Leaderboard.cs | 42 ++++++------ .../Match/Components/MatchLeaderboard.cs | 6 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 65 +++++++++---------- 3 files changed, 55 insertions(+), 58 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index eeb814b4cb..0d430f4903 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using JetBrains.Annotations; @@ -13,7 +14,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Threading; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; @@ -44,10 +44,9 @@ namespace osu.Game.Online.Leaderboards private readonly LoadingSpinner loading; - private CancellationTokenSource showScoresCancellationSource; + private CancellationTokenSource currentFetchCancellationSource; - private APIRequest getScoresRequest; - private ScheduledDelegate getScoresRequestCallback; + private APIRequest fetchScoresRequest; [Resolved(CanBeNull = true)] private IAPIProvider api { get; set; } @@ -62,7 +61,6 @@ namespace osu.Game.Online.Leaderboards protected set { scores = value; - updateScoresDrawables(); } } @@ -177,9 +175,10 @@ namespace osu.Game.Online.Leaderboards /// /// Performs a fetch/refresh of scores to be displayed. /// + /// /// An responsible for the fetch operation. This will be queued and performed automatically. [CanBeNull] - protected abstract APIRequest FetchScores(); + protected abstract APIRequest FetchScores(CancellationToken cancellationToken); protected abstract LeaderboardScore CreateDrawableScore(TScoreInfo model, int index); @@ -187,37 +186,36 @@ namespace osu.Game.Online.Leaderboards private void refetchScores() { - cancelPendingWork(); + Reset(); PlaceholderState = PlaceholderState.Retrieving; loading.Show(); - getScoresRequest = FetchScores(); + currentFetchCancellationSource = new CancellationTokenSource(); - if (getScoresRequest == null) + fetchScoresRequest = FetchScores(currentFetchCancellationSource.Token); + + if (fetchScoresRequest == null) return; - getScoresRequest.Failure += e => getScoresRequestCallback = Schedule(() => + fetchScoresRequest.Failure += e => Schedule(() => { - if (e is OperationCanceledException) + if (e is OperationCanceledException || currentFetchCancellationSource.IsCancellationRequested) return; PlaceholderState = PlaceholderState.NetworkFailure; }); - api?.Queue(getScoresRequest); + api?.Queue(fetchScoresRequest); } private void cancelPendingWork() { - showScoresCancellationSource?.Cancel(); - showScoresCancellationSource = null; + currentFetchCancellationSource?.Cancel(); + currentFetchCancellationSource = null; - getScoresRequest?.Cancel(); - getScoresRequest = null; - - getScoresRequestCallback?.Cancel(); - getScoresRequestCallback = null; + fetchScoresRequest?.Cancel(); + fetchScoresRequest = null; } #region Placeholder handling @@ -279,8 +277,6 @@ namespace osu.Game.Online.Leaderboards scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire(); scrollFlow = null; - showScoresCancellationSource?.Cancel(); - if (scores?.Any() != true) { loading.Hide(); @@ -288,6 +284,8 @@ namespace osu.Game.Online.Leaderboards return; } + Debug.Assert(!currentFetchCancellationSource.IsCancellationRequested); + // ensure placeholder is hidden when displaying scores PlaceholderState = PlaceholderState.Successful; @@ -315,7 +313,7 @@ namespace osu.Game.Online.Leaderboards scrollContainer.ScrollToStart(false); loading.Hide(); - }, (showScoresCancellationSource = new CancellationTokenSource()).Token); + }, currentFetchCancellationSource.Token); }, false); private void replacePlaceholder(Placeholder placeholder) diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs index 5b60f64160..1945899a11 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Online.API; @@ -30,7 +31,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components protected override bool IsOnlineScope => true; - protected override APIRequest FetchScores() + protected override APIRequest FetchScores(CancellationToken cancellationToken) { if (roomId.Value == null) return null; @@ -39,6 +40,9 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components req.Success += r => { + if (cancellationToken.IsCancellationRequested) + return; + Scores = r.Leaderboard; TopScore = r.UserScore; }; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 898b894541..388395c9f9 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -108,13 +108,8 @@ namespace osu.Game.Screens.Select.Leaderboards protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local; - private CancellationTokenSource loadCancellationSource; - - protected override APIRequest FetchScores() + protected override APIRequest FetchScores(CancellationToken cancellationToken) { - loadCancellationSource?.Cancel(); - loadCancellationSource = new CancellationTokenSource(); - var fetchBeatmapInfo = BeatmapInfo; if (fetchBeatmapInfo == null) @@ -125,7 +120,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (Scope == BeatmapLeaderboardScope.Local) { - subscribeToLocalScores(); + subscribeToLocalScores(cancellationToken); return null; } @@ -160,10 +155,10 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { - scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets, fetchBeatmapInfo)).ToArray(), loadCancellationSource.Token) + scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets, fetchBeatmapInfo)).ToArray(), cancellationToken) .ContinueWith(task => Schedule(() => { - if (loadCancellationSource.IsCancellationRequested) + if (cancellationToken.IsCancellationRequested) return; Scores = task.GetResultSafely(); @@ -184,7 +179,7 @@ namespace osu.Game.Screens.Select.Leaderboards Action = () => ScoreSelected?.Invoke(model) }; - private void subscribeToLocalScores() + private void subscribeToLocalScores(CancellationToken cancellationToken) { scoreSubscription?.Dispose(); scoreSubscription = null; @@ -197,35 +192,35 @@ namespace osu.Game.Screens.Select.Leaderboards + $" AND {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $1" + $" AND {nameof(ScoreInfo.DeletePending)} == false" , beatmapInfo.ID, ruleset.Value.ShortName), localScoresChanged); - } - private void localScoresChanged(IRealmCollection sender, ChangeSet changes, Exception exception) - { - if (IsOnlineScope) - return; - - var scores = sender.AsEnumerable(); - - if (filterMods && !mods.Value.Any()) + void localScoresChanged(IRealmCollection sender, ChangeSet changes, Exception exception) { - // we need to filter out all scores that have any mods to get all local nomod scores - scores = scores.Where(s => !s.Mods.Any()); - } - else if (filterMods) - { - // otherwise find all the scores that have *any* of the currently selected mods (similar to how web applies mod filters) - // we're creating and using a string list representation of selected mods so that it can be translated into the DB query itself - var selectedMods = mods.Value.Select(m => m.Acronym); - scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym))); - } + if (IsOnlineScope || cancellationToken.IsCancellationRequested) + return; - scores = scores.Detach(); + var scores = sender.AsEnumerable(); - scoreManager.OrderByTotalScoreAsync(scores.ToArray(), loadCancellationSource.Token) - .ContinueWith(ordered => - { - Scores = ordered.GetResultSafely(); - }, TaskContinuationOptions.OnlyOnRanToCompletion); + if (filterMods && !mods.Value.Any()) + { + // we need to filter out all scores that have any mods to get all local nomod scores + scores = scores.Where(s => !s.Mods.Any()); + } + else if (filterMods) + { + // otherwise find all the scores that have *any* of the currently selected mods (similar to how web applies mod filters) + // we're creating and using a string list representation of selected mods so that it can be translated into the DB query itself + var selectedMods = mods.Value.Select(m => m.Acronym); + scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym))); + } + + scores = scores.Detach(); + + scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken) + .ContinueWith(ordered => + { + Scores = ordered.GetResultSafely(); + }, TaskContinuationOptions.OnlyOnRanToCompletion); + } } protected override void Dispose(bool isDisposing) From 0293d95f829f7b3596e472244e42ed040ee62717 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jan 2022 23:17:06 +0900 Subject: [PATCH 145/256] Simplify `IsOnlineScope` usage --- osu.Game/Online/Leaderboards/Leaderboard.cs | 3 +++ osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 0d430f4903..6c2f0d9425 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -32,6 +32,9 @@ namespace osu.Game.Online.Leaderboards /// The score model class. public abstract class Leaderboard : CompositeDrawable { + /// + /// Whether the current scope should refetch in response to changes in API connectivity state. + /// protected abstract bool IsOnlineScope { get; } private const double fade_duration = 300; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 388395c9f9..e55bd56e62 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -195,7 +195,7 @@ namespace osu.Game.Screens.Select.Leaderboards void localScoresChanged(IRealmCollection sender, ChangeSet changes, Exception exception) { - if (IsOnlineScope || cancellationToken.IsCancellationRequested) + if (cancellationToken.IsCancellationRequested) return; var scores = sender.AsEnumerable(); From d0b74a91fba148d56fbd0ac586e5f6e1159e2a92 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 02:12:36 +0900 Subject: [PATCH 146/256] Fix edge cases with score drawable loading --- osu.Game/Online/Leaderboards/Leaderboard.cs | 45 ++++++++++--------- .../Select/Leaderboards/BeatmapLeaderboard.cs | 1 - 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 6c2f0d9425..a1f07c469a 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading; using JetBrains.Annotations; @@ -43,11 +42,12 @@ namespace osu.Game.Online.Leaderboards private readonly Container placeholderContainer; private readonly UserTopScoreContainer topScoreContainer; - private FillFlowContainer scrollFlow; + private FillFlowContainer scoreFlowContainer; private readonly LoadingSpinner loading; private CancellationTokenSource currentFetchCancellationSource; + private CancellationTokenSource currentScoresAsyncLoadCancellationSource; private APIRequest fetchScoresRequest; @@ -64,7 +64,7 @@ namespace osu.Game.Online.Leaderboards protected set { scores = value; - updateScoresDrawables(); + Scheduler.AddOnce(updateScoresDrawables); } } @@ -239,6 +239,8 @@ namespace osu.Game.Online.Leaderboards if (value == placeholderState) return; + loading.Hide(); + switch (placeholderState = value) { case PlaceholderState.NetworkFailure: @@ -275,40 +277,41 @@ namespace osu.Game.Online.Leaderboards } } - private void updateScoresDrawables() => Scheduler.Add(() => + private void updateScoresDrawables() { - scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire(); - scrollFlow = null; + currentScoresAsyncLoadCancellationSource?.Cancel(); + currentScoresAsyncLoadCancellationSource = new CancellationTokenSource(); + + scoreFlowContainer? + .FadeOut(fade_duration, Easing.OutQuint) + .Expire(); + scoreFlowContainer = null; + + loading.Hide(); if (scores?.Any() != true) { - loading.Hide(); PlaceholderState = PlaceholderState.NoScores; return; } - Debug.Assert(!currentFetchCancellationSource.IsCancellationRequested); - // ensure placeholder is hidden when displaying scores PlaceholderState = PlaceholderState.Successful; - var scoreFlow = new FillFlowContainer + LoadComponentAsync(new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Spacing = new Vector2(0f, 5f), Padding = new MarginPadding { Top = 10, Bottom = 5 }, ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)) - }; - - // schedule because we may not be loaded yet (LoadComponentAsync complains). - LoadComponentAsync(scoreFlow, _ => + }, newFlow => { - scrollContainer.Add(scrollFlow = scoreFlow); + scrollContainer.Add(scoreFlowContainer = newFlow); int i = 0; - foreach (var s in scrollFlow.Children) + foreach (var s in scoreFlowContainer.Children) { using (s.BeginDelayedSequence(i++ * 50)) s.Show(); @@ -316,8 +319,8 @@ namespace osu.Game.Online.Leaderboards scrollContainer.ScrollToStart(false); loading.Hide(); - }, currentFetchCancellationSource.Token); - }, false); + }, currentScoresAsyncLoadCancellationSource.Token); + } private void replacePlaceholder(Placeholder placeholder) { @@ -354,12 +357,12 @@ namespace osu.Game.Online.Leaderboards if (!scrollContainer.IsScrolledToEnd()) fadeBottom -= LeaderboardScore.HEIGHT; - if (scrollFlow == null) + if (scoreFlowContainer == null) return; - foreach (var c in scrollFlow.Children) + foreach (var c in scoreFlowContainer.Children) { - float topY = c.ToSpaceOfOtherDrawable(Vector2.Zero, scrollFlow).Y; + float topY = c.ToSpaceOfOtherDrawable(Vector2.Zero, scoreFlowContainer).Y; float bottomY = topY + LeaderboardScore.HEIGHT; bool requireBottomFade = bottomY >= fadeBottom; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index e55bd56e62..31868624bb 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -121,7 +121,6 @@ namespace osu.Game.Screens.Select.Leaderboards if (Scope == BeatmapLeaderboardScope.Local) { subscribeToLocalScores(cancellationToken); - return null; } From 6f54f8ad7897cc9bfa31e3ad00660eff3efeef59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 12:18:34 +0900 Subject: [PATCH 147/256] Add more safety around `CancellationToken` usage --- osu.Game/Online/Leaderboards/Leaderboard.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index a1f07c469a..cc6d7a7b62 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -3,11 +3,13 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Development; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -189,6 +191,8 @@ namespace osu.Game.Online.Leaderboards private void refetchScores() { + Debug.Assert(ThreadSafety.IsUpdateThread); + Reset(); PlaceholderState = PlaceholderState.Retrieving; @@ -215,10 +219,8 @@ namespace osu.Game.Online.Leaderboards private void cancelPendingWork() { currentFetchCancellationSource?.Cancel(); - currentFetchCancellationSource = null; - + currentScoresAsyncLoadCancellationSource?.Cancel(); fetchScoresRequest?.Cancel(); - fetchScoresRequest = null; } #region Placeholder handling @@ -280,7 +282,6 @@ namespace osu.Game.Online.Leaderboards private void updateScoresDrawables() { currentScoresAsyncLoadCancellationSource?.Cancel(); - currentScoresAsyncLoadCancellationSource = new CancellationTokenSource(); scoreFlowContainer? .FadeOut(fade_duration, Easing.OutQuint) @@ -319,7 +320,7 @@ namespace osu.Game.Online.Leaderboards scrollContainer.ScrollToStart(false); loading.Hide(); - }, currentScoresAsyncLoadCancellationSource.Token); + }, (currentScoresAsyncLoadCancellationSource = new CancellationTokenSource()).Token); } private void replacePlaceholder(Placeholder placeholder) From a915b9cd3093bd2ede73c3d6ef9470ff12967dc1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 29 Jan 2022 23:12:57 +0900 Subject: [PATCH 148/256] Fix occasional failures in `TestSceneDeleteLocalScore` --- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 985ff6f251..da4cf9c6e3 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -135,11 +135,9 @@ namespace osu.Game.Tests.Visual.UserInterface [SetUpSteps] public void SetupSteps() { - // Ensure the leaderboard has finished async-loading drawables - AddUntilStep("wait for drawables", () => leaderboard.ChildrenOfType().Any()); - // Ensure the leaderboard items have finished showing up AddStep("finish transforms", () => leaderboard.FinishTransforms(true)); + AddUntilStep("wait for drawables", () => leaderboard.ChildrenOfType().Any()); } [Test] From 9861c50b33f8fa036e8bdf62ff1f646cab55c8ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 00:03:22 +0900 Subject: [PATCH 149/256] Remove pointless tests that no longer show anything valid --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 7ffab2eebc..b4b66e8afa 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -122,13 +122,6 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep(@"None selected", () => leaderboard.SetRetrievalState(PlaceholderState.NoneSelected)); } - [Test] - public void TestBeatmapStates() - { - foreach (BeatmapOnlineStatus status in Enum.GetValues(typeof(BeatmapOnlineStatus))) - AddStep($"{status} beatmap", () => showBeatmapWithStatus(status)); - } - private void showPersonalBestWithNullPosition() { leaderboard.TopScore = new ScoreInfo From e408d8ef0e1ced02cf3842c95318814e987c3303 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 23:19:51 +0800 Subject: [PATCH 150/256] rename `Frames` to `ReplayFrames` --- .../Mods/TestSceneOsuModAlternate.cs | 8 ++++---- osu.Game/Tests/Visual/ModTestScene.cs | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs index 7b3eaa7ffd..de1f61a0bd 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }, }, }, - Frames = new List + ReplayFrames = new List { new OsuReplayFrame(500, new Vector2(200), OsuAction.LeftButton), new OsuReplayFrame(501, new Vector2(200)), @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }, }, }, - Frames = new List + ReplayFrames = new List { new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), new OsuReplayFrame(501, new Vector2(100)), @@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }, }, }, - Frames = new List + ReplayFrames = new List { new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), new OsuReplayFrame(501, new Vector2(100)), @@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods } } }, - Frames = new List + ReplayFrames = new List { new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), new OsuReplayFrame(501, new Vector2(100)), diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index 96fafa8727..2505864d59 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -72,14 +72,14 @@ namespace osu.Game.Tests.Visual protected override void PrepareReplay() { - if (currentTestData.Autoplay && currentTestData.Frames?.Count > 0) - throw new InvalidOperationException(@$"{nameof(ModTestData.Autoplay)} must be false when {nameof(ModTestData.Frames)} is specified."); + if (currentTestData.Autoplay && currentTestData.ReplayFrames?.Count > 0) + throw new InvalidOperationException(@$"{nameof(ModTestData.Autoplay)} must be false when {nameof(ModTestData.ReplayFrames)} is specified."); - if (currentTestData.Frames != null) + if (currentTestData.ReplayFrames != null) { DrawableRuleset?.SetReplayScore(new Score { - Replay = new Replay { Frames = currentTestData.Frames }, + Replay = new Replay { Frames = currentTestData.ReplayFrames }, ScoreInfo = new ScoreInfo { User = new APIUser { Username = @"Test" } }, }); } @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual /// The frames to use for replay. must be set to false. /// [CanBeNull] - public List Frames; + public List ReplayFrames; /// /// The beatmap for this test case. From 535216a0d3f5c063d5e5519ff4cd0533c3e72df5 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 23:20:31 +0800 Subject: [PATCH 151/256] rename `CanIntercept` to `ShouldAlternate` --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index 9eeb060135..876742caba 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; @@ -25,11 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) }; public override ModType Type => ModType.DifficultyIncrease; public override IconUsage? Icon => FontAwesome.Solid.Keyboard; - - /// - /// Whether incoming input must be checked by before it is passed to gameplay. - /// - public bool CanIntercept => !isBreakTime.Value && introEnded; + public bool ShouldAlternate => !isBreakTime.Value && introEnded; private bool introEnded; private double earliestStartTime; @@ -86,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Mods } public bool OnPressed(KeyBindingPressEvent e) - => mod.CanIntercept && mod.onPressed(e.Action); + => mod.ShouldAlternate && mod.onPressed(e.Action); public void OnReleased(KeyBindingReleaseEvent e) { From 40f43344f16056d66d6713078ecc3b5ccf47372b Mon Sep 17 00:00:00 2001 From: Nitrous Date: Sat, 29 Jan 2022 23:31:26 +0800 Subject: [PATCH 152/256] remove unused using --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index 876742caba..db8bd217f6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; From 51acf79935724e5786ecd1b515f07657071b65fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 02:29:51 +0900 Subject: [PATCH 153/256] Change test exposure to property instead of method --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index b4b66e8afa..91ec1de3ad 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestGlobalScoresDisplay() { AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Global); - AddStep(@"New Scores", () => leaderboard.SetScores(generateSampleScores(null))); + AddStep(@"New Scores", () => leaderboard.Scores = generateSampleScores(null)); } [Test] @@ -417,7 +417,11 @@ namespace osu.Game.Tests.Visual.SongSelect PlaceholderState = state; } - public void SetScores(ICollection scores) => Scores = scores; + public new ICollection Scores + { + get => base.Scores; + set => base.Scores = value; + } } } } From 3d771c0fc7ed0f292ec28c0257ff80c122bcd058 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 02:34:31 +0900 Subject: [PATCH 154/256] Remove unnecessary `loading` hide call from `PlaceholderState_Set` and add more assertiveness --- osu.Game/Online/Leaderboards/Leaderboard.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index cc6d7a7b62..e20c8b5007 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -241,9 +241,11 @@ namespace osu.Game.Online.Leaderboards if (value == placeholderState) return; - loading.Hide(); + placeholderState = value; - switch (placeholderState = value) + Debug.Assert(placeholderState != PlaceholderState.Successful || scores?.Any() == true); + + switch (placeholderState) { case PlaceholderState.NetworkFailure: replacePlaceholder(new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync) From d3cb910cf82cb208bf3ae6c3059e8b851107770e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 02:36:50 +0900 Subject: [PATCH 155/256] Convert inline math to not so inline to make operation more explicit --- osu.Game/Online/Leaderboards/Leaderboard.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index e20c8b5007..460869fa54 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -312,12 +312,14 @@ namespace osu.Game.Online.Leaderboards { scrollContainer.Add(scoreFlowContainer = newFlow); - int i = 0; + double delay = 0; foreach (var s in scoreFlowContainer.Children) { - using (s.BeginDelayedSequence(i++ * 50)) + using (s.BeginDelayedSequence(delay)) s.Show(); + + delay += 50; } scrollContainer.ScrollToStart(false); From d21464ea61b8efcf53eb867970b8bf4a5013a109 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 02:54:51 +0900 Subject: [PATCH 156/256] Fix assertions to work in both directions --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 1 + osu.Game/Online/Leaderboards/Leaderboard.cs | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 91ec1de3ad..31b7b9fa8d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -414,6 +414,7 @@ namespace osu.Game.Tests.Visual.SongSelect { public void SetRetrievalState(PlaceholderState state) { + Scores = null; PlaceholderState = state; } diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 460869fa54..7b596d8381 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -243,11 +243,10 @@ namespace osu.Game.Online.Leaderboards placeholderState = value; - Debug.Assert(placeholderState != PlaceholderState.Successful || scores?.Any() == true); - switch (placeholderState) { case PlaceholderState.NetworkFailure: + Debug.Assert(scores?.Any() != true); replacePlaceholder(new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync) { Action = RefetchScores @@ -255,26 +254,32 @@ namespace osu.Game.Online.Leaderboards break; case PlaceholderState.NoneSelected: + Debug.Assert(scores?.Any() != true); replacePlaceholder(new MessagePlaceholder(@"Please select a beatmap!")); break; case PlaceholderState.Unavailable: + Debug.Assert(scores?.Any() != true); replacePlaceholder(new MessagePlaceholder(@"Leaderboards are not available for this beatmap!")); break; case PlaceholderState.NoScores: + Debug.Assert(scores?.Any() != true); replacePlaceholder(new MessagePlaceholder(@"No records yet!")); break; case PlaceholderState.NotLoggedIn: + Debug.Assert(scores?.Any() != true); replacePlaceholder(new LoginPlaceholder(@"Please sign in to view online leaderboards!")); break; case PlaceholderState.NotSupporter: + Debug.Assert(scores?.Any() != true); replacePlaceholder(new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!")); break; default: + Debug.Assert(scores?.Any() == true); replacePlaceholder(null); break; } From 9b573fbc2bdc044d0702179fa9b94a82a0fe0648 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 02:58:53 +0900 Subject: [PATCH 157/256] Add missing entries to `switch` statement and guard against out of range --- osu.Game/Online/Leaderboards/Leaderboard.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 7b596d8381..4134046320 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -278,10 +278,18 @@ namespace osu.Game.Online.Leaderboards replacePlaceholder(new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!")); break; - default: + case PlaceholderState.Retrieving: + Debug.Assert(scores?.Any() != true); + replacePlaceholder(null); + break; + + case PlaceholderState.Successful: Debug.Assert(scores?.Any() == true); replacePlaceholder(null); break; + + default: + throw new ArgumentOutOfRangeException(); } } } From 06660ff96019000f7b8302d24fdd0cf28176f460 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 03:02:56 +0900 Subject: [PATCH 158/256] Fix null beatmap in test scene --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 31b7b9fa8d..77fe3be933 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestGlobalScoresDisplay() { AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Global); - AddStep(@"New Scores", () => leaderboard.Scores = generateSampleScores(null)); + AddStep(@"New Scores", () => leaderboard.Scores = generateSampleScores(new BeatmapInfo())); } [Test] From dad9cc931524f6627c507d5831c9cbd3a79ba6fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 03:06:29 +0900 Subject: [PATCH 159/256] Ensure `Reset`/`Scores_Set` run inline where possible --- osu.Game/Online/Leaderboards/Leaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 4134046320..0ad9f46130 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -66,7 +66,7 @@ namespace osu.Game.Online.Leaderboards protected set { scores = value; - Scheduler.AddOnce(updateScoresDrawables); + Scheduler.Add(updateScoresDrawables, false); } } From b434e29a7c4f4883cb55498678933dc44b4ec185 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 03:10:01 +0900 Subject: [PATCH 160/256] Move loading hide operation inside early return to ensure not hidden too early It should only be hidden after the async load completes. --- osu.Game/Online/Leaderboards/Leaderboard.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 0ad9f46130..8cf20ea8d1 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -303,10 +303,9 @@ namespace osu.Game.Online.Leaderboards .Expire(); scoreFlowContainer = null; - loading.Hide(); - if (scores?.Any() != true) { + loading.Hide(); PlaceholderState = PlaceholderState.NoScores; return; } From 4f4f60248faad019bc3c0501e42de854fea5dbdc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 11 Aug 2021 12:16:25 +0300 Subject: [PATCH 161/256] Add failing test case --- .../TestSceneMultiSpectatorScreen.cs | 51 +++++++++++++++++-- .../Spectate/MultiSpectatorScreen.cs | 5 +- .../Multiplayer/Spectate/PlayerArea.cs | 13 ++++- osu.Game/Screens/Play/Player.cs | 7 +++ 4 files changed, 71 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index b84f7760e4..56cb6036c7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -347,19 +347,44 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert($"{PLAYER_1_ID} score quit still set", () => getLeaderboardScore(PLAYER_1_ID).HasQuit.Value); } - private void loadSpectateScreen(bool waitForPlayerLoad = true) + /// + /// Tests spectating with a gameplay start time set to a negative value. + /// Simulating beatmaps with high or negative time storyboard elements. + /// + [Test] + public void TestNegativeGameplayStartTime() { - AddStep("load screen", () => + start(PLAYER_1_ID); + + loadSpectateScreen(false, -500); + + // to ensure negative gameplay start time does not affect spectator, send frames exactly after StartGameplay(). + // (similar to real spectating sessions in which the first frames get sent between StartGameplay() and player load complete) + AddStep("send frames at gameplay start", () => getInstance(PLAYER_1_ID).OnGameplayStarted += () => SpectatorClient.SendFrames(PLAYER_1_ID, 100)); + + AddUntilStep("wait for player load", () => spectatorScreen.AllPlayersLoaded); + + AddWaitStep("wait for progression", 3); + + assertNotCatchingUp(PLAYER_1_ID); + assertRunning(PLAYER_1_ID); + } + + private void loadSpectateScreen(bool waitForPlayerLoad = true, double? gameplayStartTime = null) + { + AddStep(!gameplayStartTime.HasValue ? "load screen" : $"load screen (start = {gameplayStartTime}ms)", () => { Beatmap.Value = beatmapManager.GetWorkingBeatmap(importedBeatmap); Ruleset.Value = importedBeatmap.Ruleset; - LoadScreen(spectatorScreen = new MultiSpectatorScreen(playingUsers.ToArray())); + LoadScreen(spectatorScreen = new TestMultiSpectatorScreen(playingUsers.ToArray(), gameplayStartTime)); }); AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded)); } + private void start(int userId, int? beatmapId = null) => start(new[] { userId }, beatmapId); + private void start(int[] userIds, int? beatmapId = null) { AddStep("start play", () => @@ -419,6 +444,12 @@ namespace osu.Game.Tests.Visual.Multiplayer private void assertMuted(int userId, bool muted) => AddAssert($"{userId} {(muted ? "is" : "is not")} muted", () => getInstance(userId).Mute == muted); + private void assertRunning(int userId) + => AddAssert($"{userId} clock running", () => getInstance(userId).GameplayClock.IsRunning); + + private void assertNotCatchingUp(int userId) + => AddAssert($"{userId} in sync", () => !getInstance(userId).GameplayClock.IsCatchingUp); + private void waitForCatchup(int userId) => AddUntilStep($"{userId} not catching up", () => !getInstance(userId).GameplayClock.IsCatchingUp); @@ -429,5 +460,19 @@ namespace osu.Game.Tests.Visual.Multiplayer private GameplayLeaderboardScore getLeaderboardScore(int userId) => spectatorScreen.ChildrenOfType().Single(s => s.User?.Id == userId); private int[] getPlayerIds(int count) => Enumerable.Range(PLAYER_1_ID, count).ToArray(); + + private class TestMultiSpectatorScreen : MultiSpectatorScreen + { + private readonly double? gameplayStartTime; + + public TestMultiSpectatorScreen(MultiplayerRoomUser[] users, double? gameplayStartTime = null) + : base(users) + { + this.gameplayStartTime = gameplayStartTime; + } + + protected override MasterGameplayClockContainer CreateMasterGameplayClockContainer(WorkingBeatmap beatmap) + => new MasterGameplayClockContainer(beatmap, gameplayStartTime ?? 0, gameplayStartTime.HasValue); + } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 7350408eba..4646f42d63 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Online.Multiplayer; using osu.Game.Online.Spectator; @@ -68,7 +69,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate Container leaderboardContainer; Container scoreDisplayContainer; - masterClockContainer = new MasterGameplayClockContainer(Beatmap.Value, 0); + masterClockContainer = CreateMasterGameplayClockContainer(Beatmap.Value); InternalChildren = new[] { @@ -235,5 +236,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate return base.OnBackButton(); } + + protected virtual MasterGameplayClockContainer CreateMasterGameplayClockContainer(WorkingBeatmap beatmap) => new MasterGameplayClockContainer(beatmap, 0); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 48f153ecbe..f5a6777a62 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -24,6 +24,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public class PlayerArea : CompositeDrawable { + /// + /// Raised after is called on . + /// + public event Action OnGameplayStarted; + /// /// Whether a is loaded in the area. /// @@ -93,7 +98,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } }; - stack.Push(new MultiSpectatorPlayerLoader(Score, () => new MultiSpectatorPlayer(Score, GameplayClock))); + stack.Push(new MultiSpectatorPlayerLoader(Score, () => + { + var player = new MultiSpectatorPlayer(Score, GameplayClock); + player.OnGameplayStarted += OnGameplayStarted; + return player; + })); + loadingLayer.Hide(); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index cfca2d0a3d..0312789b12 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -45,6 +45,11 @@ namespace osu.Game.Screens.Play /// public const double RESULTS_DISPLAY_DELAY = 1000.0; + /// + /// Raised after is called. + /// + public event Action OnGameplayStarted; + public override bool AllowBackButton => false; // handled by HoldForMenuButton protected override UserActivity InitialActivity => new UserActivity.InSoloGame(Beatmap.Value.BeatmapInfo, Ruleset.Value); @@ -958,7 +963,9 @@ namespace osu.Game.Screens.Play updateGameplayState(); GameplayClockContainer.FadeInFromZero(750, Easing.OutQuint); + StartGameplay(); + OnGameplayStarted?.Invoke(); } /// From 3ec193d47e9655de747d6de21b042e36ed5f8211 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Jan 2022 20:17:57 +0300 Subject: [PATCH 162/256] Fix spectator clock container incorrectly starting catch-up clock --- .../OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs | 2 ++ osu.Game/Screens/Play/GameplayClockContainer.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs index ececa1e497..615bd41f3f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs @@ -55,6 +55,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public SpectatorGameplayClockContainer([NotNull] IClock sourceClock) : base(sourceClock) { + // the container should initially be in a stopped state until the catch-up clock is started by the sync manager. + Stop(); } protected override void Update() diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 0c9b827a41..0fd524f976 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Play /// /// Stops gameplay. /// - public virtual void Stop() => IsPaused.Value = true; + public void Stop() => IsPaused.Value = true; /// /// Resets this and the source to an initial state ready for gameplay. From c401629dd84e8b91a9389fc85c16cc84f55aa013 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 03:37:57 +0900 Subject: [PATCH 163/256] Also refactor `placeholder` logic to make more sense --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 27 +-- osu.Game/Online/Leaderboards/Leaderboard.cs | 155 +++++++++--------- ...olderState.cs => LeaderboardErrorState.cs} | 5 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 15 +- 4 files changed, 86 insertions(+), 116 deletions(-) rename osu.Game/Online/Leaderboards/{PlaceholderState.cs => LeaderboardErrorState.cs} (82%) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 77fe3be933..969bafa98b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -114,12 +114,12 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestPlaceholderStates() { - AddStep(@"Empty Scores", () => leaderboard.SetRetrievalState(PlaceholderState.NoScores)); - AddStep(@"Network failure", () => leaderboard.SetRetrievalState(PlaceholderState.NetworkFailure)); - AddStep(@"No supporter", () => leaderboard.SetRetrievalState(PlaceholderState.NotSupporter)); - AddStep(@"Not logged in", () => leaderboard.SetRetrievalState(PlaceholderState.NotLoggedIn)); - AddStep(@"Unavailable", () => leaderboard.SetRetrievalState(PlaceholderState.Unavailable)); - AddStep(@"None selected", () => leaderboard.SetRetrievalState(PlaceholderState.NoneSelected)); + AddStep(@"Empty Scores", () => leaderboard.SetErrorState(LeaderboardErrorState.NoScores)); + AddStep(@"Network failure", () => leaderboard.SetErrorState(LeaderboardErrorState.NetworkFailure)); + AddStep(@"No supporter", () => leaderboard.SetErrorState(LeaderboardErrorState.NotSupporter)); + AddStep(@"Not logged in", () => leaderboard.SetErrorState(LeaderboardErrorState.NotLoggedIn)); + AddStep(@"Unavailable", () => leaderboard.SetErrorState(LeaderboardErrorState.Unavailable)); + AddStep(@"None selected", () => leaderboard.SetErrorState(LeaderboardErrorState.NoneSelected)); } private void showPersonalBestWithNullPosition() @@ -401,22 +401,9 @@ namespace osu.Game.Tests.Visual.SongSelect }; } - private void showBeatmapWithStatus(BeatmapOnlineStatus status) - { - leaderboard.BeatmapInfo = new BeatmapInfo - { - OnlineID = 1113057, - Status = status, - }; - } - private class FailableLeaderboard : BeatmapLeaderboard { - public void SetRetrievalState(PlaceholderState state) - { - Scores = null; - PlaceholderState = state; - } + public new void SetErrorState(LeaderboardErrorState errorState) => base.SetErrorState(errorState); public new ICollection Scores { diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 8cf20ea8d1..2e552f9774 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -53,6 +53,8 @@ namespace osu.Game.Online.Leaderboards private APIRequest fetchScoresRequest; + private LeaderboardErrorState errorState; + [Resolved(CanBeNull = true)] private IAPIProvider api { get; set; } @@ -169,14 +171,37 @@ namespace osu.Game.Online.Leaderboards RefetchScores(); } + /// + /// Perform a full refetch of scores using current criteria. + /// public void RefetchScores() => Scheduler.AddOnce(refetchScores); + /// + /// Reset the leaderboard into an empty state. + /// protected virtual void Reset() { cancelPendingWork(); Scores = null; } + /// + /// Call when a retrieval or display failure happened to show a relevant message to the user. + /// + /// The state to display. + protected void SetErrorState(LeaderboardErrorState errorState) + { + switch (errorState) + { + case LeaderboardErrorState.NoError: + throw new InvalidOperationException($"State {errorState} cannot be set by a leaderboard implementation."); + } + + Debug.Assert(scores?.Any() != true); + + setErrorState(errorState); + } + /// /// Performs a fetch/refresh of scores to be displayed. /// @@ -195,7 +220,7 @@ namespace osu.Game.Online.Leaderboards Reset(); - PlaceholderState = PlaceholderState.Retrieving; + setErrorState(LeaderboardErrorState.NoError); loading.Show(); currentFetchCancellationSource = new CancellationTokenSource(); @@ -210,7 +235,7 @@ namespace osu.Game.Online.Leaderboards if (e is OperationCanceledException || currentFetchCancellationSource.IsCancellationRequested) return; - PlaceholderState = PlaceholderState.NetworkFailure; + SetErrorState(LeaderboardErrorState.NetworkFailure); }); api?.Queue(fetchScoresRequest); @@ -223,77 +248,6 @@ namespace osu.Game.Online.Leaderboards fetchScoresRequest?.Cancel(); } - #region Placeholder handling - - private Placeholder currentPlaceholder; - - private PlaceholderState placeholderState; - - /// - /// Update the placeholder visibility. - /// Setting this to anything other than PlaceholderState.Successful will cancel all existing retrieval requests and hide scores. - /// - protected PlaceholderState PlaceholderState - { - get => placeholderState; - set - { - if (value == placeholderState) - return; - - placeholderState = value; - - switch (placeholderState) - { - case PlaceholderState.NetworkFailure: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync) - { - Action = RefetchScores - }); - break; - - case PlaceholderState.NoneSelected: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(new MessagePlaceholder(@"Please select a beatmap!")); - break; - - case PlaceholderState.Unavailable: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(new MessagePlaceholder(@"Leaderboards are not available for this beatmap!")); - break; - - case PlaceholderState.NoScores: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(new MessagePlaceholder(@"No records yet!")); - break; - - case PlaceholderState.NotLoggedIn: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(new LoginPlaceholder(@"Please sign in to view online leaderboards!")); - break; - - case PlaceholderState.NotSupporter: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!")); - break; - - case PlaceholderState.Retrieving: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(null); - break; - - case PlaceholderState.Successful: - Debug.Assert(scores?.Any() == true); - replacePlaceholder(null); - break; - - default: - throw new ArgumentOutOfRangeException(); - } - } - } - private void updateScoresDrawables() { currentScoresAsyncLoadCancellationSource?.Cancel(); @@ -305,13 +259,14 @@ namespace osu.Game.Online.Leaderboards if (scores?.Any() != true) { + SetErrorState(LeaderboardErrorState.NoScores); loading.Hide(); - PlaceholderState = PlaceholderState.NoScores; return; } // ensure placeholder is hidden when displaying scores - PlaceholderState = PlaceholderState.Successful; + setErrorState(LeaderboardErrorState.NoError); + loading.Show(); LoadComponentAsync(new FillFlowContainer { @@ -339,25 +294,61 @@ namespace osu.Game.Online.Leaderboards }, (currentScoresAsyncLoadCancellationSource = new CancellationTokenSource()).Token); } - private void replacePlaceholder(Placeholder placeholder) + #region Placeholder handling + + private Placeholder placeholder; + + private void setErrorState(LeaderboardErrorState errorState) { - if (placeholder != null && placeholder.Equals(currentPlaceholder)) + if (errorState == this.errorState) return; - currentPlaceholder?.FadeOut(150, Easing.OutQuint).Expire(); + this.errorState = errorState; + + placeholder?.FadeOut(150, Easing.OutQuint).Expire(); + + placeholder = getPlaceholderFor(errorState); if (placeholder == null) - { - currentPlaceholder = null; return; - } placeholderContainer.Child = placeholder; placeholder.ScaleTo(0.8f).Then().ScaleTo(1, fade_duration * 3, Easing.OutQuint); placeholder.FadeInFromZero(fade_duration, Easing.OutQuint); + } - currentPlaceholder = placeholder; + private Placeholder getPlaceholderFor(LeaderboardErrorState errorState) + { + switch (errorState) + { + case LeaderboardErrorState.NetworkFailure: + return new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync) + { + Action = RefetchScores + }; + + case LeaderboardErrorState.NoneSelected: + return new MessagePlaceholder(@"Please select a beatmap!"); + + case LeaderboardErrorState.Unavailable: + return new MessagePlaceholder(@"Leaderboards are not available for this beatmap!"); + + case LeaderboardErrorState.NoScores: + return new MessagePlaceholder(@"No records yet!"); + + case LeaderboardErrorState.NotLoggedIn: + return new LoginPlaceholder(@"Please sign in to view online leaderboards!"); + + case LeaderboardErrorState.NotSupporter: + return new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!"); + + case LeaderboardErrorState.NoError: + return null; + + default: + throw new ArgumentOutOfRangeException(); + } } #endregion diff --git a/osu.Game/Online/Leaderboards/PlaceholderState.cs b/osu.Game/Online/Leaderboards/LeaderboardErrorState.cs similarity index 82% rename from osu.Game/Online/Leaderboards/PlaceholderState.cs rename to osu.Game/Online/Leaderboards/LeaderboardErrorState.cs index 297241fa73..17f47bb557 100644 --- a/osu.Game/Online/Leaderboards/PlaceholderState.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardErrorState.cs @@ -3,10 +3,9 @@ namespace osu.Game.Online.Leaderboards { - public enum PlaceholderState + public enum LeaderboardErrorState { - Successful, - Retrieving, + NoError, NetworkFailure, Unavailable, NoneSelected, diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 31868624bb..b0ba830076 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -33,19 +33,12 @@ namespace osu.Game.Screens.Select.Leaderboards set { if (beatmapInfo == null && value == null) - { - // always null scores to ensure a correct initial display. - // see weird `scoresLoadedOnce` logic in base implementation. - Scores = null; return; - } if (beatmapInfo?.Equals(value) == true) return; beatmapInfo = value; - Scores = null; - RefetchScores(); } } @@ -114,7 +107,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (fetchBeatmapInfo == null) { - PlaceholderState = PlaceholderState.NoneSelected; + SetErrorState(LeaderboardErrorState.NoneSelected); return null; } @@ -126,19 +119,19 @@ namespace osu.Game.Screens.Select.Leaderboards if (api?.IsLoggedIn != true) { - PlaceholderState = PlaceholderState.NotLoggedIn; + SetErrorState(LeaderboardErrorState.NotLoggedIn); return null; } if (fetchBeatmapInfo.OnlineID <= 0 || fetchBeatmapInfo.Status <= BeatmapOnlineStatus.Pending) { - PlaceholderState = PlaceholderState.Unavailable; + SetErrorState(LeaderboardErrorState.Unavailable); return null; } if (!api.LocalUser.Value.IsSupporter && (Scope != BeatmapLeaderboardScope.Global || filterMods)) { - PlaceholderState = PlaceholderState.NotSupporter; + SetErrorState(LeaderboardErrorState.NotSupporter); return null; } From acc1199addc044c2673516c1738586e16a058a87 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 16:16:00 +0900 Subject: [PATCH 164/256] Consolidate flows of `Set` operations, either result or error --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 17 ++--- osu.Game/Online/Leaderboards/Leaderboard.cs | 63 ++++++++----------- .../Match/Components/MatchLeaderboard.cs | 5 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 11 +--- 4 files changed, 37 insertions(+), 59 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 969bafa98b..e9a518b2fd 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestGlobalScoresDisplay() { AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Global); - AddStep(@"New Scores", () => leaderboard.Scores = generateSampleScores(new BeatmapInfo())); + AddStep(@"New Scores", () => leaderboard.SetScores(generateSampleScores(new BeatmapInfo()))); } [Test] @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Visual.SongSelect private void showPersonalBestWithNullPosition() { - leaderboard.TopScore = new ScoreInfo + leaderboard.SetScores(leaderboard.Scores, new ScoreInfo { Rank = ScoreRank.XH, Accuracy = 1, @@ -142,12 +142,12 @@ namespace osu.Game.Tests.Visual.SongSelect FlagName = @"ES", }, }, - }; + }); } private void showPersonalBest() { - leaderboard.TopScore = new ScoreInfo + leaderboard.SetScores(leaderboard.Scores, new ScoreInfo { Position = 999, Rank = ScoreRank.XH, @@ -166,7 +166,7 @@ namespace osu.Game.Tests.Visual.SongSelect FlagName = @"ES", }, }, - }; + }); } private void loadMoreScores(Func beatmapInfo) @@ -404,12 +404,7 @@ namespace osu.Game.Tests.Visual.SongSelect private class FailableLeaderboard : BeatmapLeaderboard { public new void SetErrorState(LeaderboardErrorState errorState) => base.SetErrorState(errorState); - - public new ICollection Scores - { - get => base.Scores; - set => base.Scores = value; - } + public new void SetScores(IEnumerable scores, ScoreInfo userScore = default) => base.SetScores(scores, userScore); } } } diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 2e552f9774..9c46739443 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -33,6 +33,11 @@ namespace osu.Game.Online.Leaderboards /// The score model class. public abstract class Leaderboard : CompositeDrawable { + /// + /// The currently displayed scores. + /// + public IEnumerable Scores => scores; + /// /// Whether the current scope should refetch in response to changes in API connectivity state. /// @@ -42,7 +47,7 @@ namespace osu.Game.Online.Leaderboards private readonly OsuScrollContainer scrollContainer; private readonly Container placeholderContainer; - private readonly UserTopScoreContainer topScoreContainer; + private readonly UserTopScoreContainer userScoreContainer; private FillFlowContainer scoreFlowContainer; @@ -62,30 +67,6 @@ namespace osu.Game.Online.Leaderboards private ICollection scores; - public ICollection Scores - { - get => scores; - protected set - { - scores = value; - Scheduler.Add(updateScoresDrawables, false); - } - } - - public TScoreInfo TopScore - { - get => topScoreContainer.Score.Value; - set - { - topScoreContainer.Score.Value = value; - - if (value == null) - topScoreContainer.Hide(); - else - topScoreContainer.Show(); - } - } - private TScope scope; public TScope Scope @@ -133,7 +114,7 @@ namespace osu.Game.Online.Leaderboards { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, - Child = topScoreContainer = new UserTopScoreContainer(CreateDrawableTopScore) + Child = userScoreContainer = new UserTopScoreContainer(CreateDrawableTopScore) }, }, }, @@ -176,15 +157,6 @@ namespace osu.Game.Online.Leaderboards /// public void RefetchScores() => Scheduler.AddOnce(refetchScores); - /// - /// Reset the leaderboard into an empty state. - /// - protected virtual void Reset() - { - cancelPendingWork(); - Scores = null; - } - /// /// Call when a retrieval or display failure happened to show a relevant message to the user. /// @@ -202,6 +174,24 @@ namespace osu.Game.Online.Leaderboards setErrorState(errorState); } + /// + /// Call when score retrieval is ready to be displayed. + /// + /// The scores to display. + /// The user top score, if any. + protected void SetScores(IEnumerable scores, TScoreInfo userScore = default) + { + this.scores = scores?.ToList(); + userScoreContainer.Score.Value = userScore; + + if (userScore == null) + userScoreContainer.Hide(); + else + userScoreContainer.Show(); + + Scheduler.Add(updateScoresDrawables, false); + } + /// /// Performs a fetch/refresh of scores to be displayed. /// @@ -218,7 +208,8 @@ namespace osu.Game.Online.Leaderboards { Debug.Assert(ThreadSafety.IsUpdateThread); - Reset(); + cancelPendingWork(); + SetScores(null); setErrorState(LeaderboardErrorState.NoError); loading.Show(); diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs index 1945899a11..ea7de917e2 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components if (id.NewValue == null) return; - Scores = null; + SetScores(null); RefetchScores(); }, true); } @@ -43,8 +43,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components if (cancellationToken.IsCancellationRequested) return; - Scores = r.Leaderboard; - TopScore = r.UserScore; + SetScores(r.Leaderboard, r.UserScore); }; return req; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index b0ba830076..3521a2ef78 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -93,12 +93,6 @@ namespace osu.Game.Screens.Select.Leaderboards }; } - protected override void Reset() - { - base.Reset(); - TopScore = null; - } - protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local; protected override APIRequest FetchScores(CancellationToken cancellationToken) @@ -153,8 +147,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (cancellationToken.IsCancellationRequested) return; - Scores = task.GetResultSafely(); - TopScore = r.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo); + SetScores(task.GetResultSafely(), r.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo)); }), TaskContinuationOptions.OnlyOnRanToCompletion); }; @@ -210,7 +203,7 @@ namespace osu.Game.Screens.Select.Leaderboards scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken) .ContinueWith(ordered => { - Scores = ordered.GetResultSafely(); + SetScores(ordered.GetResultSafely()); }, TaskContinuationOptions.OnlyOnRanToCompletion); } } From 04dbb5d3c6cc9c8285fa5d95ac23621761e4a57c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 16:16:33 +0900 Subject: [PATCH 165/256] Disallow setting "NoScores" externally as it is handled internally --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 3 ++- osu.Game/Online/Leaderboards/Leaderboard.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index e9a518b2fd..f91339e060 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -114,7 +114,8 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestPlaceholderStates() { - AddStep(@"Empty Scores", () => leaderboard.SetErrorState(LeaderboardErrorState.NoScores)); + AddStep("ensure no scores displayed", () => leaderboard.SetScores(null)); + AddStep(@"Network failure", () => leaderboard.SetErrorState(LeaderboardErrorState.NetworkFailure)); AddStep(@"No supporter", () => leaderboard.SetErrorState(LeaderboardErrorState.NotSupporter)); AddStep(@"Not logged in", () => leaderboard.SetErrorState(LeaderboardErrorState.NotLoggedIn)); diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 9c46739443..9fb0fe0e3b 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -165,6 +165,7 @@ namespace osu.Game.Online.Leaderboards { switch (errorState) { + case LeaderboardErrorState.NoScores: case LeaderboardErrorState.NoError: throw new InvalidOperationException($"State {errorState} cannot be set by a leaderboard implementation."); } @@ -250,7 +251,7 @@ namespace osu.Game.Online.Leaderboards if (scores?.Any() != true) { - SetErrorState(LeaderboardErrorState.NoScores); + setErrorState(LeaderboardErrorState.NoScores); loading.Hide(); return; } From 1dbcb5ab63a7d5656c40f370213148011d3c8fbb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 18:51:15 +0900 Subject: [PATCH 166/256] Add test coverage of intro fail scenario --- osu.Game.Tests/Visual/Menus/IntroTestScene.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs index bfea97410a..40b072c171 100644 --- a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs +++ b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs @@ -5,6 +5,8 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Threading; +using osu.Game.Overlays; using osu.Game.Screens; using osu.Game.Screens.Menu; using osuTK; @@ -22,6 +24,11 @@ namespace osu.Game.Tests.Visual.Menus private IntroScreen intro; + [Cached] + private NotificationOverlay notifications; + + private ScheduledDelegate trackResetDelegate; + protected IntroTestScene() { Children = new Drawable[] @@ -38,6 +45,11 @@ namespace osu.Game.Tests.Visual.Menus RelativePositionAxes = Axes.Both, Depth = float.MinValue, Position = new Vector2(0.5f), + }, + notifications = new NotificationOverlay + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, } }; } @@ -63,6 +75,39 @@ namespace osu.Game.Tests.Visual.Menus AddUntilStep("wait for menu", () => intro.DidLoadMenu); } + [Test] + public virtual void TestPlayIntroWithFailingAudioDevice() + { + AddStep("hide notifications", () => notifications.Hide()); + AddStep("restart sequence", () => + { + logo.FinishTransforms(); + logo.IsTracking = false; + + IntroStack?.Expire(); + + Add(IntroStack = new OsuScreenStack + { + RelativeSizeAxes = Axes.Both, + }); + + IntroStack.Push(intro = CreateScreen()); + }); + + AddStep("trigger failure", () => + { + trackResetDelegate = Scheduler.AddDelayed(() => + { + intro.Beatmap.Value.Track.Seek(0); + }, 0, true); + }); + + AddUntilStep("wait for menu", () => intro.DidLoadMenu); + AddUntilStep("wait for notification", () => notifications.UnreadCount.Value == 1); + + AddStep("uninstall delegate", () => trackResetDelegate?.Cancel()); + } + protected abstract IntroScreen CreateScreen(); } } From 52f1c2bfdbc349259bc817ac3e0a4925f9c78a8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 18:37:00 +0900 Subject: [PATCH 167/256] Add failsafe to `IntroScreen` to stop users with incorrect audio configuration getting stuck The most common case of this seems to be linux users with incorrect or unsupported audio driver configurations. It continues to be brought up in discussions as people are unsure of why their game freezes on startup, and unable to easily recover. --- osu.Game/Screens/Menu/IntroScreen.cs | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index fceb083916..98c4b15f7f 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -18,6 +18,7 @@ using osu.Game.Configuration; using osu.Game.Database; using osu.Game.IO.Archives; using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; using osu.Game.Screens.Backgrounds; using osuTK; using osuTK.Graphics; @@ -147,6 +148,36 @@ namespace osu.Game.Screens.Menu } } + public override void OnEntering(IScreen last) + { + base.OnEntering(last); + ensureEventuallyArrivingAtMenu(); + } + + [Resolved] + private NotificationOverlay notifications { get; set; } + + private void ensureEventuallyArrivingAtMenu() + { + // This intends to handle the case where an intro may get stuck. + // Historically, this could happen if the host system's audio device is in a state it can't + // play audio, causing a clock to never elapse time and the intro to never end. + // + // This safety measure gives the user a chance to fix the problem from the settings menu. + Scheduler.AddDelayed(() => + { + if (DidLoadMenu) + return; + + PrepareMenuLoad(); + LoadMenu(); + notifications.Post(new SimpleErrorNotification + { + Text = "osu! doesn't seem to be able to play audio correctly.\n\nPlease try changing your audio device to a working setting." + }); + }, 5000); + } + public override void OnResuming(IScreen last) { this.FadeIn(300); @@ -241,6 +272,9 @@ namespace osu.Game.Screens.Menu protected void PrepareMenuLoad() { + if (nextScreen != null) + return; + nextScreen = createNextScreen?.Invoke(); if (nextScreen != null) @@ -249,6 +283,9 @@ namespace osu.Game.Screens.Menu protected void LoadMenu() { + if (DidLoadMenu) + return; + beatmap.Return(); DidLoadMenu = true; From 6a21d583259b43c0d7865371f34c73d5cba04e04 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 18:59:18 +0900 Subject: [PATCH 168/256] Avoid test failures on non-triangle intro tests --- osu.Game.Tests/Visual/Menus/IntroTestScene.cs | 6 +++++- osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs | 1 + osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs | 1 + osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs | 1 + 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs index 40b072c171..82accceb23 100644 --- a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs +++ b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs @@ -20,6 +20,8 @@ namespace osu.Game.Tests.Visual.Menus [Cached] private OsuLogo logo; + protected abstract bool IntroReliesOnTrack { get; } + protected OsuScreenStack IntroStack; private IntroScreen intro; @@ -103,7 +105,9 @@ namespace osu.Game.Tests.Visual.Menus }); AddUntilStep("wait for menu", () => intro.DidLoadMenu); - AddUntilStep("wait for notification", () => notifications.UnreadCount.Value == 1); + + if (IntroReliesOnTrack) + AddUntilStep("wait for notification", () => notifications.UnreadCount.Value == 1); AddStep("uninstall delegate", () => trackResetDelegate?.Cancel()); } diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs index ffc99185fb..7ad49b5dcd 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs @@ -9,6 +9,7 @@ namespace osu.Game.Tests.Visual.Menus [TestFixture] public class TestSceneIntroCircles : IntroTestScene { + protected override bool IntroReliesOnTrack => false; protected override IntroScreen CreateScreen() => new IntroCircles(); } } diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs index 8f01e0321b..abe8936330 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs @@ -9,6 +9,7 @@ namespace osu.Game.Tests.Visual.Menus [TestFixture] public class TestSceneIntroTriangles : IntroTestScene { + protected override bool IntroReliesOnTrack => true; protected override IntroScreen CreateScreen() => new IntroTriangles(); } } diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs index 9081be3dd6..11cea25865 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs @@ -10,6 +10,7 @@ namespace osu.Game.Tests.Visual.Menus [TestFixture] public class TestSceneIntroWelcome : IntroTestScene { + protected override bool IntroReliesOnTrack => false; protected override IntroScreen CreateScreen() => new IntroWelcome(); public override void TestPlayIntro() From 82806d7aebc5a0a2273575f4efdbbd6d1c7c866f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 21:32:39 +0900 Subject: [PATCH 169/256] Ensure the background is eventually displayed when `IntroTriangles` suspends --- osu.Game/Screens/Menu/IntroTriangles.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 10f940e9de..b6b6bf2ad7 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -93,6 +93,9 @@ namespace osu.Game.Screens.Menu { base.OnSuspending(next); + // ensure the background is shown, even if the TriangleIntroSequence failed to do so. + background.ApplyToBackground(b => b.Show()); + // important as there is a clock attached to a track which will likely be disposed before returning to this screen. intro.Expire(); } From 1cec76df74df950db682ecde9de14ceeb9c1de8a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 23:18:40 +0900 Subject: [PATCH 170/256] Fix weird reading xmldoc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Online/Leaderboards/Leaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 9fb0fe0e3b..e96b12bc5d 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -176,7 +176,7 @@ namespace osu.Game.Online.Leaderboards } /// - /// Call when score retrieval is ready to be displayed. + /// Call when retrieved scores are ready to be displayed. /// /// The scores to display. /// The user top score, if any. From f8939af5e6d165d97ad733d0b39634ef6c5b1723 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 01:12:03 +0900 Subject: [PATCH 171/256] Track loading via state as well --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 12 ++-- osu.Game/Online/Leaderboards/Leaderboard.cs | 66 ++++++++++--------- ...boardErrorState.cs => LeaderboardState.cs} | 5 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 8 +-- 4 files changed, 48 insertions(+), 43 deletions(-) rename osu.Game/Online/Leaderboards/{LeaderboardErrorState.cs => LeaderboardState.cs} (82%) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index f91339e060..48230ff9e9 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -116,11 +116,11 @@ namespace osu.Game.Tests.Visual.SongSelect { AddStep("ensure no scores displayed", () => leaderboard.SetScores(null)); - AddStep(@"Network failure", () => leaderboard.SetErrorState(LeaderboardErrorState.NetworkFailure)); - AddStep(@"No supporter", () => leaderboard.SetErrorState(LeaderboardErrorState.NotSupporter)); - AddStep(@"Not logged in", () => leaderboard.SetErrorState(LeaderboardErrorState.NotLoggedIn)); - AddStep(@"Unavailable", () => leaderboard.SetErrorState(LeaderboardErrorState.Unavailable)); - AddStep(@"None selected", () => leaderboard.SetErrorState(LeaderboardErrorState.NoneSelected)); + AddStep(@"Network failure", () => leaderboard.SetErrorState(LeaderboardState.NetworkFailure)); + AddStep(@"No supporter", () => leaderboard.SetErrorState(LeaderboardState.NotSupporter)); + AddStep(@"Not logged in", () => leaderboard.SetErrorState(LeaderboardState.NotLoggedIn)); + AddStep(@"Unavailable", () => leaderboard.SetErrorState(LeaderboardState.Unavailable)); + AddStep(@"None selected", () => leaderboard.SetErrorState(LeaderboardState.NoneSelected)); } private void showPersonalBestWithNullPosition() @@ -404,7 +404,7 @@ namespace osu.Game.Tests.Visual.SongSelect private class FailableLeaderboard : BeatmapLeaderboard { - public new void SetErrorState(LeaderboardErrorState errorState) => base.SetErrorState(errorState); + public new void SetErrorState(LeaderboardState state) => base.SetErrorState(state); public new void SetScores(IEnumerable scores, ScoreInfo userScore = default) => base.SetScores(scores, userScore); } } diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index e96b12bc5d..7ac05fb0c0 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -58,7 +58,7 @@ namespace osu.Game.Online.Leaderboards private APIRequest fetchScoresRequest; - private LeaderboardErrorState errorState; + private LeaderboardState state; [Resolved(CanBeNull = true)] private IAPIProvider api { get; set; } @@ -160,19 +160,20 @@ namespace osu.Game.Online.Leaderboards /// /// Call when a retrieval or display failure happened to show a relevant message to the user. /// - /// The state to display. - protected void SetErrorState(LeaderboardErrorState errorState) + /// The state to display. + protected void SetErrorState(LeaderboardState state) { - switch (errorState) + switch (state) { - case LeaderboardErrorState.NoScores: - case LeaderboardErrorState.NoError: - throw new InvalidOperationException($"State {errorState} cannot be set by a leaderboard implementation."); + case LeaderboardState.NoScores: + case LeaderboardState.Retrieving: + case LeaderboardState.Success: + throw new InvalidOperationException($"State {state} cannot be set by a leaderboard implementation."); } Debug.Assert(scores?.Any() != true); - setErrorState(errorState); + setState(state); } /// @@ -212,8 +213,7 @@ namespace osu.Game.Online.Leaderboards cancelPendingWork(); SetScores(null); - setErrorState(LeaderboardErrorState.NoError); - loading.Show(); + setState(LeaderboardState.Retrieving); currentFetchCancellationSource = new CancellationTokenSource(); @@ -227,7 +227,7 @@ namespace osu.Game.Online.Leaderboards if (e is OperationCanceledException || currentFetchCancellationSource.IsCancellationRequested) return; - SetErrorState(LeaderboardErrorState.NetworkFailure); + SetErrorState(LeaderboardState.NetworkFailure); }); api?.Queue(fetchScoresRequest); @@ -251,15 +251,10 @@ namespace osu.Game.Online.Leaderboards if (scores?.Any() != true) { - setErrorState(LeaderboardErrorState.NoScores); - loading.Hide(); + setState(LeaderboardState.NoScores); return; } - // ensure placeholder is hidden when displaying scores - setErrorState(LeaderboardErrorState.NoError); - loading.Show(); - LoadComponentAsync(new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -269,6 +264,8 @@ namespace osu.Game.Online.Leaderboards ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)) }, newFlow => { + setState(LeaderboardState.Success); + scrollContainer.Add(scoreFlowContainer = newFlow); double delay = 0; @@ -282,7 +279,6 @@ namespace osu.Game.Online.Leaderboards } scrollContainer.ScrollToStart(false); - loading.Hide(); }, (currentScoresAsyncLoadCancellationSource = new CancellationTokenSource()).Token); } @@ -290,16 +286,21 @@ namespace osu.Game.Online.Leaderboards private Placeholder placeholder; - private void setErrorState(LeaderboardErrorState errorState) + private void setState(LeaderboardState state) { - if (errorState == this.errorState) + if (state == this.state) return; - this.errorState = errorState; + if (state == LeaderboardState.Retrieving) + loading.Show(); + else + loading.Hide(); + + this.state = state; placeholder?.FadeOut(150, Easing.OutQuint).Expire(); - placeholder = getPlaceholderFor(errorState); + placeholder = getPlaceholderFor(state); if (placeholder == null) return; @@ -310,32 +311,35 @@ namespace osu.Game.Online.Leaderboards placeholder.FadeInFromZero(fade_duration, Easing.OutQuint); } - private Placeholder getPlaceholderFor(LeaderboardErrorState errorState) + private Placeholder getPlaceholderFor(LeaderboardState state) { - switch (errorState) + switch (state) { - case LeaderboardErrorState.NetworkFailure: + case LeaderboardState.NetworkFailure: return new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync) { Action = RefetchScores }; - case LeaderboardErrorState.NoneSelected: + case LeaderboardState.NoneSelected: return new MessagePlaceholder(@"Please select a beatmap!"); - case LeaderboardErrorState.Unavailable: + case LeaderboardState.Unavailable: return new MessagePlaceholder(@"Leaderboards are not available for this beatmap!"); - case LeaderboardErrorState.NoScores: + case LeaderboardState.NoScores: return new MessagePlaceholder(@"No records yet!"); - case LeaderboardErrorState.NotLoggedIn: + case LeaderboardState.NotLoggedIn: return new LoginPlaceholder(@"Please sign in to view online leaderboards!"); - case LeaderboardErrorState.NotSupporter: + case LeaderboardState.NotSupporter: return new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!"); - case LeaderboardErrorState.NoError: + case LeaderboardState.Retrieving: + return null; + + case LeaderboardState.Success: return null; default: diff --git a/osu.Game/Online/Leaderboards/LeaderboardErrorState.cs b/osu.Game/Online/Leaderboards/LeaderboardState.cs similarity index 82% rename from osu.Game/Online/Leaderboards/LeaderboardErrorState.cs rename to osu.Game/Online/Leaderboards/LeaderboardState.cs index 17f47bb557..75e2c6e6db 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardErrorState.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardState.cs @@ -3,9 +3,10 @@ namespace osu.Game.Online.Leaderboards { - public enum LeaderboardErrorState + public enum LeaderboardState { - NoError, + Success, + Retrieving, NetworkFailure, Unavailable, NoneSelected, diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 3521a2ef78..48eb33cc05 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (fetchBeatmapInfo == null) { - SetErrorState(LeaderboardErrorState.NoneSelected); + SetErrorState(LeaderboardState.NoneSelected); return null; } @@ -113,19 +113,19 @@ namespace osu.Game.Screens.Select.Leaderboards if (api?.IsLoggedIn != true) { - SetErrorState(LeaderboardErrorState.NotLoggedIn); + SetErrorState(LeaderboardState.NotLoggedIn); return null; } if (fetchBeatmapInfo.OnlineID <= 0 || fetchBeatmapInfo.Status <= BeatmapOnlineStatus.Pending) { - SetErrorState(LeaderboardErrorState.Unavailable); + SetErrorState(LeaderboardState.Unavailable); return null; } if (!api.LocalUser.Value.IsSupporter && (Scope != BeatmapLeaderboardScope.Global || filterMods)) { - SetErrorState(LeaderboardErrorState.NotSupporter); + SetErrorState(LeaderboardState.NotSupporter); return null; } From b52153e73d354f137b221d5c14cd505d37a8ffdd Mon Sep 17 00:00:00 2001 From: Spooghetti420 Date: Sun, 30 Jan 2022 17:40:15 +0000 Subject: [PATCH 172/256] Remove settings --- .../Mods/TestSceneManiaModHoldOff.cs | 58 +++---------------- .../Mods/ManiaModHoldOff.cs | 28 +-------- 2 files changed, 10 insertions(+), 76 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs index e74f63abb3..a113b7ac80 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; @@ -11,8 +10,6 @@ using System.Collections.Generic; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.Difficulty; -using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Mania.Tests.Mods { @@ -42,24 +39,17 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Assert.AreEqual(noteValues, new List { 0.125, 0.250, 0.500, 1.000, 2.000 }); } - [TestCase(ManiaModHoldOff.BeatDivisors.Whole)] - [TestCase(ManiaModHoldOff.BeatDivisors.Half)] - [TestCase(ManiaModHoldOff.BeatDivisors.Quarter)] - [TestCase(ManiaModHoldOff.BeatDivisors.Eighth)] - public void TestCorrectObjectCount(ManiaModHoldOff.BeatDivisors minBeatSnap) + [Test] + public void TestCorrectObjectCount() { - /* - This test is to ensure that, given that end notes are enabled, - the mod produces the expected number of objects when the mod is applied. - */ + // Ensure that the mod produces the expected number of objects when applied. - // Mod settings will be set to include the correct beat snap value var rawBeatmap = createRawBeatmap(); - var testBeatmap = createModdedBeatmap(minBeatSnap); + var testBeatmap = createModdedBeatmap(); // Calculate expected number of objects int expectedObjectCount = 0; - double beatSnapValue = 1 / (Math.Pow(2, (int)minBeatSnap)); + double beatSnapValue = ManiaModHoldOff.Threshold; foreach (ManiaHitObject h in rawBeatmap.HitObjects) { @@ -81,44 +71,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Assert.That(testBeatmap.HitObjects.Count == expectedObjectCount); } - [Test] - public void TestDifficultyIncrease() - { - // A lower minimum beat snap divisor should only make the map harder, never easier - // (as more notes can be spawned) - var beatmaps = new[] - { - createModdedBeatmap(), - createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Half), - createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Quarter), - createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Eighth), - createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Sixteenth) - }; - - double[] mapDifficulties = new double[beatmaps.Length]; - - for (int i = 0; i < mapDifficulties.Length; i++) - { - var workingBeatmap = new TestWorkingBeatmap(beatmaps[i]); - var difficultyCalculator = new ManiaDifficultyCalculator(new ManiaRuleset().RulesetInfo, workingBeatmap); - mapDifficulties[i] = difficultyCalculator.Calculate().StarRating; - - if (i > 0) - { - Assert.LessOrEqual(mapDifficulties[i - 1], mapDifficulties[i]); - Assert.LessOrEqual(beatmaps[i - 1].HitObjects.Count, beatmaps[i].HitObjects.Count); - } - } - } - - private static ManiaBeatmap createModdedBeatmap(ManiaModHoldOff.BeatDivisors minBeatSnap = ManiaModHoldOff.BeatDivisors.Whole) + private static ManiaBeatmap createModdedBeatmap() { var beatmap = createRawBeatmap(); - var holdOffMod = new ManiaModHoldOff - { - MinBeatSnap = { Value = minBeatSnap } - }; - Assert.AreEqual(holdOffMod.MinBeatSnap.Value, minBeatSnap); + var holdOffMod = new ManiaModHoldOff(); foreach (var hitObject in beatmap.HitObjects) hitObject.ApplyDefaults(beatmap.ControlPointInfo, new BeatmapDifficulty()); diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index 637142aed6..5a47e1ba89 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -6,11 +6,8 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; using osu.Framework.Graphics.Sprites; -using System; using System.Collections.Generic; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Framework.Bindables; -using osu.Game.Configuration; namespace osu.Game.Rulesets.Mania.Mods { @@ -27,23 +24,13 @@ namespace osu.Game.Rulesets.Mania.Mods public override IconUsage? Icon => FontAwesome.Solid.DotCircle; public override ModType Type => ModType.Conversion; - - [SettingSource("Add end notes", "Also add a note at the end of a hold note")] - public BindableBool AddEndNotes { get; } = new BindableBool - { - Default = true, - Value = true - }; - - [SettingSource("Minimum end note beat snap", "Don't add end notes for hold notes shorter than this beat division")] - public Bindable MinBeatSnap { get; } = new Bindable(defaultValue: BeatDivisors.Half); + public const double Threshold = 1/2; public void ApplyToBeatmap(IBeatmap beatmap) { var maniaBeatmap = (ManiaBeatmap)beatmap; var newObjects = new List(); - double beatSnap = 1 / (Math.Pow(2, (double)MinBeatSnap.Value)); foreach (var h in beatmap.HitObjects.OfType()) { @@ -55,10 +42,10 @@ namespace osu.Game.Rulesets.Mania.Mods Samples = h.GetNodeSamples(0) }); - // Don't add an end note if the duration is shorter than some threshold, or end notes are disabled + // Don't add an end note if the duration is shorter than the threshold double noteValue = GetNoteDurationInBeatLength(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc. - if (AddEndNotes.Value && noteValue >= beatSnap) + if (noteValue >= Threshold) { newObjects.Add(new Note { @@ -77,14 +64,5 @@ namespace osu.Game.Rulesets.Mania.Mods double bpmAtNoteTime = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BPM; return (60 * holdNote.Duration) / (1000 * bpmAtNoteTime); } - - public enum BeatDivisors - { - Whole, - Half, - Quarter, - Eighth, - Sixteenth - } } } From 2b999f9780f4f9e96c92fbe42e4a71a86605b0ec Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Jan 2022 23:39:45 +0300 Subject: [PATCH 173/256] Add failing test case --- .../TestSceneMasterGameplayClockContainer.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs index 935bc07733..172531605a 100644 --- a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs @@ -2,7 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Framework.Utils; +using osu.Game.Configuration; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; using osu.Game.Tests.Visual; @@ -12,6 +18,14 @@ namespace osu.Game.Tests.Gameplay [HeadlessTest] public class TestSceneMasterGameplayClockContainer : OsuTestScene { + private OsuConfigManager localConfig; + + [BackgroundDependencyLoader] + private void load() + { + Dependencies.Cache(localConfig = new OsuConfigManager(LocalStorage)); + } + [Test] public void TestStartThenElapsedTime() { @@ -54,5 +68,43 @@ namespace osu.Game.Tests.Gameplay AddAssert("current time < time at reset", () => gcc.GameplayClock.CurrentTime < timeAtReset); } + + [Test] + public void TestSeekPerformsInGameplayTime( + [Values(1.0, 0.5, 2.0)] double clockRate, + [Values(0.0, 200.0, -200.0)] double userOffset, + [Values(false, true)] bool whileStopped) + { + ClockBackedTestWorkingBeatmap working = null; + GameplayClockContainer gcc = null; + + AddStep("create container", () => + { + working = new ClockBackedTestWorkingBeatmap(new OsuRuleset().RulesetInfo, new FramedClock(new ManualClock()), Audio); + working.LoadTrack(); + + Add(gcc = new MasterGameplayClockContainer(working, 0)); + + if (whileStopped) + gcc.Stop(); + + gcc.Reset(); + }); + + AddStep($"set clock rate to {clockRate}", () => working.Track.AddAdjustment(AdjustableProperty.Frequency, new BindableDouble(clockRate))); + AddStep($"set audio offset to {userOffset}", () => localConfig.SetValue(OsuSetting.AudioOffset, userOffset)); + + AddStep("seek to 2500", () => gcc.Seek(2500)); + AddAssert("gameplay clock time = 2500", () => Precision.AlmostEquals(gcc.CurrentTime, 2500, 10f)); + + AddStep("seek to 10000", () => gcc.Seek(10000)); + AddAssert("gameplay clock time = 10000", () => Precision.AlmostEquals(gcc.CurrentTime, 10000, 10f)); + } + + protected override void Dispose(bool isDisposing) + { + localConfig?.Dispose(); + base.Dispose(isDisposing); + } } } From 6556a7e3c3533b46a1f6b97989d2d6bf4f908a4a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Jan 2022 23:06:31 +0300 Subject: [PATCH 174/256] Handle different gameplay rates when seeking on master clock --- .../Play/MasterGameplayClockContainer.cs | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index aa46522dec..200921680e 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Play Precision = 0.1, }; - private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset; + private double totalAppliedOffset => userOffsetClock.RateAdjustedOffset + platformOffsetClock.RateAdjustedOffset; private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1); @@ -52,8 +52,8 @@ namespace osu.Game.Screens.Play private readonly bool startAtGameplayStart; private readonly double firstHitObjectTime; - private FramedOffsetClock userOffsetClock; - private FramedOffsetClock platformOffsetClock; + private HardwareCorrectionOffsetClock userOffsetClock; + private HardwareCorrectionOffsetClock platformOffsetClock; private MasterGameplayClock masterGameplayClock; private Bindable userAudioOffset; private double startOffset; @@ -128,7 +128,7 @@ namespace osu.Game.Screens.Play { // remove the offset component here because most of the time we want the seek to be aligned to gameplay, not the audio track. // we may want to consider reversing the application of offsets in the future as it may feel more correct. - base.Seek(time - totalOffset); + base.Seek(time - totalAppliedOffset); } /// @@ -214,13 +214,25 @@ namespace osu.Game.Screens.Play private class HardwareCorrectionOffsetClock : FramedOffsetClock { - // we always want to apply the same real-time offset, so it should be adjusted by the difference in playback rate (from realtime) to achieve this. - // base implementation already adds offset at 1.0 rate, so we only add the difference from that here. - public override double CurrentTime => base.CurrentTime + offsetAdjust; - private readonly BindableDouble pauseRateAdjust; - private double offsetAdjust; + private double offset; + + public new double Offset + { + get => offset; + set + { + if (value == offset) + return; + + offset = value; + + updateOffset(); + } + } + + public double RateAdjustedOffset => base.Offset; public HardwareCorrectionOffsetClock(IClock source, BindableDouble pauseRateAdjust) : base(source) @@ -231,10 +243,17 @@ namespace osu.Game.Screens.Play public override void ProcessFrame() { base.ProcessFrame(); + updateOffset(); + } + private void updateOffset() + { // changing this during the pause transform effect will cause a potentially large offset to be suddenly applied as we approach zero rate. if (pauseRateAdjust.Value == 1) - offsetAdjust = Offset * (Rate - 1); + { + // we always want to apply the same real-time offset, so it should be adjusted by the difference in playback rate (from realtime) to achieve this. + base.Offset = Offset * Rate; + } } } From bb8dc74e88fb3fcc3ce6958d740199a9249bc11b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 12:20:51 +0900 Subject: [PATCH 175/256] Fix constant formatting --- .../Mods/TestSceneManiaModHoldOff.cs | 3 +-- osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs index a113b7ac80..7970d5b594 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs @@ -49,7 +49,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods // Calculate expected number of objects int expectedObjectCount = 0; - double beatSnapValue = ManiaModHoldOff.Threshold; foreach (ManiaHitObject h in rawBeatmap.HitObjects) { @@ -60,7 +59,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods { double noteValue = ManiaModHoldOff.GetNoteDurationInBeatLength((HoldNote)h, rawBeatmap); - if (noteValue >= beatSnapValue) + if (noteValue >= ManiaModHoldOff.END_NOTE_ALLOW_THRESHOLD) { // Should generate an end note if it's longer than the minimum note value expectedObjectCount++; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index 5a47e1ba89..42dc7fdc1f 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -24,7 +24,8 @@ namespace osu.Game.Rulesets.Mania.Mods public override IconUsage? Icon => FontAwesome.Solid.DotCircle; public override ModType Type => ModType.Conversion; - public const double Threshold = 1/2; + + public const double END_NOTE_ALLOW_THRESHOLD = 0.5; public void ApplyToBeatmap(IBeatmap beatmap) { @@ -45,7 +46,7 @@ namespace osu.Game.Rulesets.Mania.Mods // Don't add an end note if the duration is shorter than the threshold double noteValue = GetNoteDurationInBeatLength(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc. - if (noteValue >= Threshold) + if (noteValue >= END_NOTE_ALLOW_THRESHOLD) { newObjects.Add(new Note { From 610eb9f6a45e8f3f762bac4f25a814c7169d8010 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 13:45:49 +0900 Subject: [PATCH 176/256] Remove unnecessary container level --- osu.Game/Online/Leaderboards/Leaderboard.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 7ac05fb0c0..5dd3e46b4a 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -110,12 +110,7 @@ namespace osu.Game.Online.Leaderboards }, new Drawable[] { - new Container - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Child = userScoreContainer = new UserTopScoreContainer(CreateDrawableTopScore) - }, + userScoreContainer = new UserTopScoreContainer(CreateDrawableTopScore) }, }, }, From 9c9fda84f3cf54073c7d15b62946251a3a27d328 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 13:50:53 +0900 Subject: [PATCH 177/256] Add schedule and cancellation check to score ordering step --- osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 48eb33cc05..907a2c9bda 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -201,10 +201,13 @@ namespace osu.Game.Screens.Select.Leaderboards scores = scores.Detach(); scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken) - .ContinueWith(ordered => + .ContinueWith(ordered => Schedule(() => { + if (cancellationToken.IsCancellationRequested) + return; + SetScores(ordered.GetResultSafely()); - }, TaskContinuationOptions.OnlyOnRanToCompletion); + }), TaskContinuationOptions.OnlyOnRanToCompletion); } } From a06287e76aac73bbf141d07a71fa417c7dbb31d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 12:34:08 +0900 Subject: [PATCH 178/256] Remove `DrawableCarouselItem.Update` updating of height Marginal from a performance aspect, but reads better. --- .../Select/Carousel/DrawableCarouselBeatmap.cs | 7 +++---- .../Select/Carousel/DrawableCarouselItem.cs | 14 ++++++-------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 3576b77ae8..43ddfc79b1 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -36,9 +36,9 @@ namespace osu.Game.Screens.Select.Carousel /// /// The height of a carousel beatmap, including vertical spacing. /// - public const float HEIGHT = height + CAROUSEL_BEATMAP_SPACING; + public const float HEIGHT = header_height + CAROUSEL_BEATMAP_SPACING; - private const float height = MAX_HEIGHT * 0.6f; + private const float header_height = MAX_HEIGHT * 0.6f; private readonly BeatmapInfo beatmapInfo; @@ -67,6 +67,7 @@ namespace osu.Game.Screens.Select.Carousel private CancellationTokenSource starDifficultyCancellationSource; public DrawableCarouselBeatmap(CarouselBeatmap panel) + : base(header_height) { beatmapInfo = panel.BeatmapInfo; Item = panel; @@ -75,8 +76,6 @@ namespace osu.Game.Screens.Select.Carousel [BackgroundDependencyLoader(true)] private void load(BeatmapManager manager, SongSelect songSelect) { - Header.Height = height; - if (songSelect != null) { startRequested = b => songSelect.FinaliseSelection(b); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index cde3edad39..8b38bb9a96 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -60,7 +60,7 @@ namespace osu.Game.Screens.Select.Carousel } } - protected DrawableCarouselItem() + protected DrawableCarouselItem(float headerHeight = MAX_HEIGHT) { RelativeSizeAxes = Axes.X; @@ -73,10 +73,14 @@ namespace osu.Game.Screens.Select.Carousel RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - Header = new CarouselHeader(), + Header = new CarouselHeader + { + Height = headerHeight, + }, Content = new Container { RelativeSizeAxes = Axes.Both, + Y = headerHeight, } } }, @@ -92,12 +96,6 @@ namespace osu.Game.Screens.Select.Carousel UpdateItem(); } - protected override void Update() - { - base.Update(); - Content.Y = Header.Height; - } - protected virtual void UpdateItem() { if (item == null) From c3e3b2019dcf169d74c435bde269ec53bf43a8fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 13:01:41 +0900 Subject: [PATCH 179/256] Reduce overhead of `ApplyState` by tracking previous values Even with pooling applied, there are overheads involved with transforms when quickly cycling through the carousel. The main goal here is to reduce the transforms in cases the reuse is still in the same state. Avoiding firing `FadeIn` and `FadeOut` are the main areas of saving. --- .../Carousel/DrawableCarouselBeatmap.cs | 3 + .../Select/Carousel/DrawableCarouselItem.cs | 55 ++++++++++++++----- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 43ddfc79b1..a3483aa60a 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -71,6 +71,9 @@ namespace osu.Game.Screens.Select.Carousel { beatmapInfo = panel.BeatmapInfo; Item = panel; + + // Difficulty panels should start hidden for a better initial effect. + Hide(); } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 8b38bb9a96..4a23faa650 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -64,8 +64,6 @@ namespace osu.Game.Screens.Select.Carousel { RelativeSizeAxes = Axes.X; - Alpha = 0; - InternalChildren = new Drawable[] { MovementContainer = new Container @@ -119,29 +117,56 @@ namespace osu.Game.Screens.Select.Carousel private void onStateChange(ValueChangedEvent _) => Scheduler.AddOnce(ApplyState); + private CarouselItemState? lastAppliedState; + protected virtual void ApplyState() { - // Use the fact that we know the precise height of the item from the model to avoid the need for AutoSize overhead. - // Additionally, AutoSize doesn't work well due to content starting off-screen and being masked away. - Height = Item.TotalHeight; - Debug.Assert(Item != null); - switch (Item.State.Value) + if (lastAppliedState != Item.State.Value) { - case CarouselItemState.NotSelected: - Deselected(); - break; + lastAppliedState = Item.State.Value; - case CarouselItemState.Selected: - Selected(); - break; + // Use the fact that we know the precise height of the item from the model to avoid the need for AutoSize overhead. + // Additionally, AutoSize doesn't work well due to content starting off-screen and being masked away. + Height = Item.TotalHeight; + + switch (lastAppliedState) + { + case CarouselItemState.NotSelected: + Deselected(); + break; + + case CarouselItemState.Selected: + Selected(); + break; + } } if (!Item.Visible) - this.FadeOut(300, Easing.OutQuint); + Hide(); else - this.FadeIn(250); + Show(); + } + + private bool isVisible = true; + + public override void Show() + { + if (isVisible) + return; + + isVisible = true; + this.FadeIn(250); + } + + public override void Hide() + { + if (!isVisible) + return; + + isVisible = false; + this.FadeOut(300, Easing.OutQuint); } protected virtual void Selected() From 2ee0db0ebf68bc6651171c78e077418e952d71dd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 14:07:29 +0900 Subject: [PATCH 180/256] Move fade in function local --- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 618c5cf5ec..4f19e011b2 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -124,9 +124,9 @@ namespace osu.Game.Screens.Select.Carousel background.DelayedLoadComplete += fadeContentIn; mainFlow.DelayedLoadComplete += fadeContentIn; - } - private void fadeContentIn(Drawable d) => d.FadeInFromZero(750, Easing.OutQuint); + static void fadeContentIn(Drawable d) => d.FadeInFromZero(750, Easing.OutQuint); + } protected override void Deselected() { From 8917ab78f427c1dfb2e5eef47738fafec8fa746a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jan 2022 14:14:50 +0900 Subject: [PATCH 181/256] Reduce unnecessary container nesting and adjust empty state opacity slightly --- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 ++-- .../Screens/Select/Carousel/CarouselHeader.cs | 29 ++++++++----------- .../Select/Carousel/DrawableCarouselItem.cs | 2 -- 3 files changed, 14 insertions(+), 23 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c27915c383..fbb12a86d3 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -894,10 +894,8 @@ namespace osu.Game.Screens.Select // child items (difficulties) are still visible. item.Header.X = offsetX(dist, visibleHalfHeight) - (parent?.X ?? 0); - // We are applying a multiplicative alpha (which is internally done by nesting an - // additional container and setting that container's alpha) such that we can - // layer alpha transformations on top. - item.SetMultiplicativeAlpha(Math.Clamp(1.75f - 1.5f * dist, 0, 1)); + // We are applying alpha to the header here such that we can layer alpha transformations on top. + item.Header.Alpha = Math.Clamp(1.75f - 1.5f * dist, 0, 1); } private enum PendingScrollOperation diff --git a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs index ed3aea3445..533694b265 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs @@ -21,8 +21,6 @@ namespace osu.Game.Screens.Select.Carousel { public class CarouselHeader : Container { - public Container BorderContainer; - public readonly Bindable State = new Bindable(CarouselItemState.NotSelected); private readonly HoverLayer hoverLayer; @@ -37,17 +35,14 @@ namespace osu.Game.Screens.Select.Carousel RelativeSizeAxes = Axes.X; Height = DrawableCarouselItem.MAX_HEIGHT; - InternalChild = BorderContainer = new Container + Masking = true; + CornerRadius = corner_radius; + BorderColour = new Color4(221, 255, 255, 255); + + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = corner_radius, - BorderColour = new Color4(221, 255, 255, 255), - Children = new Drawable[] - { - Content, - hoverLayer = new HoverLayer() - } + Content, + hoverLayer = new HoverLayer() }; } @@ -66,21 +61,21 @@ namespace osu.Game.Screens.Select.Carousel case CarouselItemState.NotSelected: hoverLayer.InsetForBorder = false; - BorderContainer.BorderThickness = 0; - BorderContainer.EdgeEffect = new EdgeEffectParameters + BorderThickness = 0; + EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, Offset = new Vector2(1), Radius = 10, - Colour = Color4.Black.Opacity(100), + Colour = Color4.Black.Opacity(0.5f), }; break; case CarouselItemState.Selected: hoverLayer.InsetForBorder = true; - BorderContainer.BorderThickness = border_thickness; - BorderContainer.EdgeEffect = new EdgeEffectParameters + BorderThickness = border_thickness; + EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, Colour = new Color4(130, 204, 255, 150), diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 4a23faa650..5e7ca0825a 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -85,8 +85,6 @@ namespace osu.Game.Screens.Select.Carousel }; } - public void SetMultiplicativeAlpha(float alpha) => Header.BorderContainer.Alpha = alpha; - protected override void LoadComplete() { base.LoadComplete(); From 6bc6675fa1bff93de775e10b6cf6781af9bdb3f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 14:46:16 +0900 Subject: [PATCH 182/256] Adjust fade in times slightly --- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 4f19e011b2..63c004f4bc 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -122,10 +122,8 @@ namespace osu.Game.Screens.Select.Carousel }, }; - background.DelayedLoadComplete += fadeContentIn; - mainFlow.DelayedLoadComplete += fadeContentIn; - - static void fadeContentIn(Drawable d) => d.FadeInFromZero(750, Easing.OutQuint); + background.DelayedLoadComplete += d => d.FadeInFromZero(750, Easing.OutQuint); + mainFlow.DelayedLoadComplete += d => d.FadeInFromZero(500, Easing.OutQuint); } protected override void Deselected() From 57f793aff0e6f562198b732dea85f9bd8c154dee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 15:12:08 +0900 Subject: [PATCH 183/256] Rename dictionary and make `private` for added safety --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 2 +- osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index d1a25b07ec..917b3c89a8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -215,7 +215,7 @@ namespace osu.Game.Tests.Visual.Gameplay }); // We can't access API because we're an "online" test. - AddAssert("last received frame has time = 1000", () => spectatorClient.LastReceivedFrame.First().Value.Time == 1000); + AddAssert("last received frame has time = 1000", () => spectatorClient.LastReceivedUserFrames.First().Value.Time == 1000); } private OsuFramedReplayInputHandler replayHandler => diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index 3f3d0f5b01..1a1d493249 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -28,7 +28,10 @@ namespace osu.Game.Tests.Visual.Spectator public override IBindable IsConnected { get; } = new Bindable(true); - public readonly Dictionary LastReceivedFrame = new Dictionary(); + public IReadOnlyDictionary LastReceivedUserFrames => lastReceivedUserFrames; + + private readonly Dictionary lastReceivedUserFrames = new Dictionary(); + private readonly Dictionary userBeatmapDictionary = new Dictionary(); private readonly Dictionary userNextFrameDictionary = new Dictionary(); @@ -40,7 +43,7 @@ namespace osu.Game.Tests.Visual.Spectator OnNewFrames += (i, bundle) => { if (PlayingUsers.Contains(i)) - LastReceivedFrame[i] = bundle.Frames[^1]; + lastReceivedUserFrames[i] = bundle.Frames[^1]; }; } From 9001c3a396a7f876bc52d772e1567d62161770f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 15:17:06 +0900 Subject: [PATCH 184/256] Fix test failures if `DialogOverlay` is not loaded in time As seen at https://github.com/ppy/osu/runs/4999391205?check_suite_focus=true, where `DialogOverlay` hasn't loaded in single file yet. --- osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 3be7cf7c5c..ed484e03f6 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -128,7 +128,9 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("choose clear all scores", () => InputManager.Key(Key.Number4)); + AddUntilStep("wait for dialog", () => Game.Dependencies.Get().CurrentDialog != null); AddStep("confirm deletion", () => InputManager.Key(Key.Number1)); + AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get().CurrentDialog == null); AddAssert("ensure score is pending deletion", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == true)); @@ -170,7 +172,9 @@ namespace osu.Game.Tests.Visual.Navigation InputManager.Click(MouseButton.Left); }); + AddUntilStep("wait for dialog", () => Game.Dependencies.Get().CurrentDialog != null); AddStep("confirm deletion", () => InputManager.Key(Key.Number1)); + AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get().CurrentDialog == null); AddAssert("ensure score is pending deletion", () => Game.Realm.Run(r => r.Find(score.ID)?.DeletePending == true)); From 25cbe5a0dee2ef50bdf32bdc8a2b09bc9c98446d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 15:23:31 +0900 Subject: [PATCH 185/256] Remove acronym shortening of `GameplayClockContainer` --- .../TestSceneMasterGameplayClockContainer.cs | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs index 172531605a..77b402ad3c 100644 --- a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs @@ -29,44 +29,44 @@ namespace osu.Game.Tests.Gameplay [Test] public void TestStartThenElapsedTime() { - GameplayClockContainer gcc = null; + GameplayClockContainer gameplayClockContainer = null; AddStep("create container", () => { var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); working.LoadTrack(); - Add(gcc = new MasterGameplayClockContainer(working, 0)); + Add(gameplayClockContainer = new MasterGameplayClockContainer(working, 0)); }); - AddStep("start clock", () => gcc.Start()); - AddUntilStep("elapsed greater than zero", () => gcc.GameplayClock.ElapsedFrameTime > 0); + AddStep("start clock", () => gameplayClockContainer.Start()); + AddUntilStep("elapsed greater than zero", () => gameplayClockContainer.GameplayClock.ElapsedFrameTime > 0); } [Test] public void TestElapseThenReset() { - GameplayClockContainer gcc = null; + GameplayClockContainer gameplayClockContainer = null; AddStep("create container", () => { var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); working.LoadTrack(); - Add(gcc = new MasterGameplayClockContainer(working, 0)); + Add(gameplayClockContainer = new MasterGameplayClockContainer(working, 0)); }); - AddStep("start clock", () => gcc.Start()); - AddUntilStep("current time greater 2000", () => gcc.GameplayClock.CurrentTime > 2000); + AddStep("start clock", () => gameplayClockContainer.Start()); + AddUntilStep("current time greater 2000", () => gameplayClockContainer.GameplayClock.CurrentTime > 2000); double timeAtReset = 0; AddStep("reset clock", () => { - timeAtReset = gcc.GameplayClock.CurrentTime; - gcc.Reset(); + timeAtReset = gameplayClockContainer.GameplayClock.CurrentTime; + gameplayClockContainer.Reset(); }); - AddAssert("current time < time at reset", () => gcc.GameplayClock.CurrentTime < timeAtReset); + AddAssert("current time < time at reset", () => gameplayClockContainer.GameplayClock.CurrentTime < timeAtReset); } [Test] @@ -76,29 +76,29 @@ namespace osu.Game.Tests.Gameplay [Values(false, true)] bool whileStopped) { ClockBackedTestWorkingBeatmap working = null; - GameplayClockContainer gcc = null; + GameplayClockContainer gameplayClockContainer = null; AddStep("create container", () => { working = new ClockBackedTestWorkingBeatmap(new OsuRuleset().RulesetInfo, new FramedClock(new ManualClock()), Audio); working.LoadTrack(); - Add(gcc = new MasterGameplayClockContainer(working, 0)); + Add(gameplayClockContainer = new MasterGameplayClockContainer(working, 0)); if (whileStopped) - gcc.Stop(); + gameplayClockContainer.Stop(); - gcc.Reset(); + gameplayClockContainer.Reset(); }); AddStep($"set clock rate to {clockRate}", () => working.Track.AddAdjustment(AdjustableProperty.Frequency, new BindableDouble(clockRate))); AddStep($"set audio offset to {userOffset}", () => localConfig.SetValue(OsuSetting.AudioOffset, userOffset)); - AddStep("seek to 2500", () => gcc.Seek(2500)); - AddAssert("gameplay clock time = 2500", () => Precision.AlmostEquals(gcc.CurrentTime, 2500, 10f)); + AddStep("seek to 2500", () => gameplayClockContainer.Seek(2500)); + AddAssert("gameplay clock time = 2500", () => Precision.AlmostEquals(gameplayClockContainer.CurrentTime, 2500, 10f)); - AddStep("seek to 10000", () => gcc.Seek(10000)); - AddAssert("gameplay clock time = 10000", () => Precision.AlmostEquals(gcc.CurrentTime, 10000, 10f)); + AddStep("seek to 10000", () => gameplayClockContainer.Seek(10000)); + AddAssert("gameplay clock time = 10000", () => Precision.AlmostEquals(gameplayClockContainer.CurrentTime, 10000, 10f)); } protected override void Dispose(bool isDisposing) From cc7fb0e559b096566b2874607f2ff5f57ca8e0e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 16:36:56 +0900 Subject: [PATCH 186/256] Add mouse click support and increase area to full column height --- osu.Game.Rulesets.Mania/UI/Column.cs | 72 +++++++++++++++------------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index cd5f3d2170..9bcd9ad853 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -28,7 +28,6 @@ namespace osu.Game.Rulesets.Mania.UI { public const float COLUMN_WIDTH = 80; public const float SPECIAL_COLUMN_WIDTH = 70; - public const float TOUCH_AREA_HEIGHT = 100; /// /// The index of this column as part of the whole playfield. @@ -45,37 +44,6 @@ namespace osu.Game.Rulesets.Mania.UI private readonly GameplaySampleTriggerSource sampleTriggerSource; - public class ColumnTouchInputArea : Drawable - { - public ColumnTouchInputArea() - { - RelativeSizeAxes = Axes.X; - Anchor = Anchor.BottomCentre; - Origin = Anchor.BottomCentre; - Height = TOUCH_AREA_HEIGHT; - } - - private Column column => (Column)Parent; - - protected override void LoadComplete() - { - keyBindingContainer = (ManiaInputManager.RulesetKeyBindingContainer)((ManiaInputManager)GetContainingInputManager()).KeyBindingContainer; - } - - private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } - - protected override bool OnTouchDown(TouchDownEvent e) - { - keyBindingContainer.TriggerPressed(column.Action.Value); - return false; - } - - protected override void OnTouchUp(TouchUpEvent e) - { - keyBindingContainer.TriggerReleased(column.Action.Value); - } - } - public Column(int index) { Index = index; @@ -172,5 +140,45 @@ namespace osu.Game.Rulesets.Mania.UI public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) // This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border => DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos)); + + public class ColumnTouchInputArea : Drawable + { + public ColumnTouchInputArea() + { + RelativeSizeAxes = Axes.Both; + } + + private Column column => (Column)Parent; + + protected override void LoadComplete() + { + keyBindingContainer = (ManiaInputManager.RulesetKeyBindingContainer)((ManiaInputManager)GetContainingInputManager()).KeyBindingContainer; + } + + private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } + + protected override bool OnMouseDown(MouseDownEvent e) + { + keyBindingContainer.TriggerPressed(column.Action.Value); + return base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseUpEvent e) + { + keyBindingContainer.TriggerReleased(column.Action.Value); + base.OnMouseUp(e); + } + + protected override bool OnTouchDown(TouchDownEvent e) + { + keyBindingContainer.TriggerPressed(column.Action.Value); + return true; + } + + protected override void OnTouchUp(TouchUpEvent e) + { + keyBindingContainer.TriggerReleased(column.Action.Value); + } + } } } From 9005bce0fa574ec9be7661bbeb5029e1ad8797fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 16:37:47 +0900 Subject: [PATCH 187/256] Add "counter" keyword for key overlay setting --- osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs index e1b452e322..ba9779d650 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs @@ -37,7 +37,8 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay new SettingsCheckbox { LabelText = GameplaySettingsStrings.AlwaysShowKeyOverlay, - Current = config.GetBindable(OsuSetting.KeyOverlay) + Current = config.GetBindable(OsuSetting.KeyOverlay), + Keywords = new[] { "counter" }, }, }; } From 0e764538e0ea81293ef99994b25e3ab7ceed609e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jan 2022 16:47:20 +0900 Subject: [PATCH 188/256] Retrieve `KeyBindingContainer` via DI rather than traversal lookup --- osu.Game.Rulesets.Mania/ManiaInputManager.cs | 2 ++ osu.Game.Rulesets.Mania/UI/Column.cs | 17 ++++++++++------- osu.Game/Rulesets/UI/RulesetInputManager.cs | 4 ++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaInputManager.cs b/osu.Game.Rulesets.Mania/ManiaInputManager.cs index 186fc4b15d..14ca27a11a 100644 --- a/osu.Game.Rulesets.Mania/ManiaInputManager.cs +++ b/osu.Game.Rulesets.Mania/ManiaInputManager.cs @@ -2,11 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.ComponentModel; +using osu.Framework.Allocation; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania { + [Cached] // Used for touch input, see ColumnTouchInputArea. public class ManiaInputManager : RulesetInputManager { public ManiaInputManager(RulesetInfo ruleset, int variant) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 9bcd9ad853..a6c64e9b2d 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -143,6 +143,11 @@ namespace osu.Game.Rulesets.Mania.UI public class ColumnTouchInputArea : Drawable { + [Resolved(canBeNull: true)] + private ManiaInputManager maniaInputManager { get; set; } + + private KeyBindingContainer keyBindingContainer; + public ColumnTouchInputArea() { RelativeSizeAxes = Axes.Both; @@ -152,32 +157,30 @@ namespace osu.Game.Rulesets.Mania.UI protected override void LoadComplete() { - keyBindingContainer = (ManiaInputManager.RulesetKeyBindingContainer)((ManiaInputManager)GetContainingInputManager()).KeyBindingContainer; + keyBindingContainer = maniaInputManager?.KeyBindingContainer; } - private ManiaInputManager.RulesetKeyBindingContainer keyBindingContainer { get; set; } - protected override bool OnMouseDown(MouseDownEvent e) { - keyBindingContainer.TriggerPressed(column.Action.Value); + keyBindingContainer?.TriggerPressed(column.Action.Value); return base.OnMouseDown(e); } protected override void OnMouseUp(MouseUpEvent e) { - keyBindingContainer.TriggerReleased(column.Action.Value); + keyBindingContainer?.TriggerReleased(column.Action.Value); base.OnMouseUp(e); } protected override bool OnTouchDown(TouchDownEvent e) { - keyBindingContainer.TriggerPressed(column.Action.Value); + keyBindingContainer?.TriggerPressed(column.Action.Value); return true; } protected override void OnTouchUp(TouchUpEvent e) { - keyBindingContainer.TriggerReleased(column.Action.Value); + keyBindingContainer?.TriggerReleased(column.Action.Value); } } } diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 160c0a2606..84e42818be 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -24,6 +24,8 @@ namespace osu.Game.Rulesets.UI public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler, IHasRecordingHandler where T : struct { + public readonly KeyBindingContainer KeyBindingContainer; + private ReplayRecorder recorder; public ReplayRecorder Recorder @@ -43,8 +45,6 @@ namespace osu.Game.Rulesets.UI protected override InputState CreateInitialState() => new RulesetInputManagerInputState(base.CreateInitialState()); - public readonly KeyBindingContainer KeyBindingContainer; - protected override Container Content => content; private readonly Container content; From a49a9ed0a05d778b5af4c6328071bb0f8926d7b5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 31 Jan 2022 17:19:04 +0900 Subject: [PATCH 189/256] Fix incorrect invoke --- osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index f5a6777a62..4979bd906b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -101,7 +101,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate stack.Push(new MultiSpectatorPlayerLoader(Score, () => { var player = new MultiSpectatorPlayer(Score, GameplayClock); - player.OnGameplayStarted += OnGameplayStarted; + player.OnGameplayStarted += () => OnGameplayStarted?.Invoke(); return player; })); From 4727aeda012f6c5f00f500d0eaf4c05ba8bbc58c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 31 Jan 2022 18:32:17 +0900 Subject: [PATCH 190/256] Give last bundled replay frame the frame header --- .../Visual/Gameplay/TestSceneSpectator.cs | 16 ++++++++++++++++ osu.Game/Online/Spectator/SpectatorClient.cs | 3 +++ osu.Game/Rulesets/Replays/ReplayFrame.cs | 13 +++++++++++++ osu.Game/Screens/Play/SpectatorPlayer.cs | 1 + 4 files changed, 33 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 917b3c89a8..a7b9d45f7a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -218,6 +218,22 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("last received frame has time = 1000", () => spectatorClient.LastReceivedUserFrames.First().Value.Time == 1000); } + [Test] + public void TestFinalFrameInBundleHasHeader() + { + FrameDataBundle lastBundle = null; + + AddStep("bind to client", () => spectatorClient.OnNewFrames += (_, bundle) => lastBundle = bundle); + + start(-1234); + sendFrames(); + finish(); + + AddUntilStep("bundle received", () => lastBundle != null); + AddAssert("first frame does not have header", () => lastBundle.Frames[0].Header == null); + AddAssert("last frame has header", () => lastBundle.Frames[^1].Header != null); + } + private OsuFramedReplayInputHandler replayHandler => (OsuFramedReplayInputHandler)Stack.ChildrenOfType().First().ReplayInputHandler; diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index fddb94fad7..67aa75727d 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -129,6 +129,9 @@ namespace osu.Game.Online.Spectator Task ISpectatorClient.UserSentFrames(int userId, FrameDataBundle data) { + if (data.Frames.Count > 0) + data.Frames[^1].Header = data.Header; + Schedule(() => OnNewFrames?.Invoke(userId, data)); return Task.CompletedTask; diff --git a/osu.Game/Rulesets/Replays/ReplayFrame.cs b/osu.Game/Rulesets/Replays/ReplayFrame.cs index 7de53211a2..2b67b60d8f 100644 --- a/osu.Game/Rulesets/Replays/ReplayFrame.cs +++ b/osu.Game/Rulesets/Replays/ReplayFrame.cs @@ -1,16 +1,29 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using MessagePack; +using osu.Game.Online.Spectator; namespace osu.Game.Rulesets.Replays { [MessagePackObject] public class ReplayFrame { + /// + /// The time at which this takes place. + /// [Key(0)] public double Time; + /// + /// A containing the state of a play after this takes place. + /// May be omitted where exact per-frame accuracy is not required. + /// + [IgnoreMember] + public FrameHeader? Header; + public ReplayFrame() { } diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index d42643c416..c415041081 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -72,6 +72,7 @@ namespace osu.Game.Screens.Play var convertedFrame = (ReplayFrame)convertibleFrame; convertedFrame.Time = frame.Time; + convertedFrame.Header = frame.Header; score.Replay.Frames.Add(convertedFrame); } From 0458d408bb36b15c00c47634926dea59b116205c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 31 Jan 2022 18:37:51 +0900 Subject: [PATCH 191/256] Add replay statistics frames to FramedReplayInputHandler --- .../EmptyFreeformFramedReplayInputHandler.cs | 2 +- .../PippidonFramedReplayInputHandler.cs | 2 +- .../EmptyScrollingFramedReplayInputHandler.cs | 2 +- .../PippidonFramedReplayInputHandler.cs | 2 +- .../Replays/CatchFramedReplayInputHandler.cs | 2 +- .../Replays/ManiaFramedReplayInputHandler.cs | 2 +- .../Replays/OsuFramedReplayInputHandler.cs | 2 +- .../Replays/TaikoFramedReplayInputHandler.cs | 2 +- .../Gameplay/TestSceneReplayRecorder.cs | 2 +- .../Gameplay/TestSceneSpectatorPlayback.cs | 2 +- osu.Game/Input/Handlers/ReplayInputHandler.cs | 34 +++++++++++++++++++ .../Replays/FramedReplayInputHandler.cs | 15 ++++++++ 12 files changed, 59 insertions(+), 10 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs index cc4483de31..a9bc8dc10e 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Replays protected override bool IsImportant(EmptyFreeformReplayFrame frame) => frame.Actions.Any(); - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time); diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs index e005346e1e..dbfaf8a01d 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Pippidon.Replays protected override bool IsImportant(PippidonReplayFrame frame) => true; - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time); diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs index 4b998cfca3..1d33ab8a54 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Replays protected override bool IsImportant(EmptyScrollingReplayFrame frame) => frame.Actions.Any(); - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { inputs.Add(new ReplayState { diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs index 7652357b4d..702f6fdb04 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Pippidon.Replays protected override bool IsImportant(PippidonReplayFrame frame) => frame.Actions.Any(); - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { inputs.Add(new ReplayState { diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs index bd742ce6a6..b6af88a771 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Replays protected override bool IsImportant(CatchReplayFrame frame) => frame.Actions.Any(); - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { float position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time); diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs index aa0c148caf..aa164f95da 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Replays protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any(); - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs index 7d696dfb79..ea36ecc399 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Replays protected override bool IsImportant(OsuReplayFrame frame) => frame.Actions.Any(); - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time); diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs index 138e8f9785..2f9b6c7f60 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Replays protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any(); - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index 4eab1a21da..8df32c500e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -210,7 +210,7 @@ namespace osu.Game.Tests.Visual.Gameplay { } - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) }); inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 90abdf2ba3..4af254866a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -259,7 +259,7 @@ namespace osu.Game.Tests.Visual.Gameplay { } - public override void CollectPendingInputs(List inputs) + protected override void CollectReplayInputs(List inputs) { inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) }); inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); diff --git a/osu.Game/Input/Handlers/ReplayInputHandler.cs b/osu.Game/Input/Handlers/ReplayInputHandler.cs index e4aec4edac..205a1ea1ac 100644 --- a/osu.Game/Input/Handlers/ReplayInputHandler.cs +++ b/osu.Game/Input/Handlers/ReplayInputHandler.cs @@ -9,6 +9,7 @@ using osu.Framework.Input.StateChanges; using osu.Framework.Input.StateChanges.Events; using osu.Framework.Input.States; using osu.Framework.Platform; +using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.UI; using osuTK; @@ -79,5 +80,38 @@ namespace osu.Game.Input.Handlers PressedActions = pressedActions; } } + + /// + /// An that is triggered when a frame containing replay statistics arrives. + /// + public class ReplayStatisticsFrameInput : IInput + { + /// + /// The frame containing the statistics. + /// + public ReplayFrame Frame; + + public void Apply(InputState state, IInputStateChangeHandler handler) + { + handler.HandleInputStateChange(new ReplayStatisticsFrameEvent(state, this, Frame)); + } + } + + /// + /// An that is triggered when a frame containing replay statistics arrives. + /// + public class ReplayStatisticsFrameEvent : InputStateChangeEvent + { + /// + /// The frame containing the statistics. + /// + public readonly ReplayFrame Frame; + + public ReplayStatisticsFrameEvent(InputState state, IInput input, ReplayFrame frame) + : base(state, input) + { + Frame = frame; + } + } } } diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index d3ee10dd23..f889d15485 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Input.StateChanges; using osu.Game.Input.Handlers; using osu.Game.Replays; @@ -174,5 +175,19 @@ namespace osu.Game.Rulesets.Replays return Frames[index].Time; } + + public sealed override void CollectPendingInputs(List inputs) + { + base.CollectPendingInputs(inputs); + + CollectReplayInputs(inputs); + + if (CurrentFrame?.Header != null) + inputs.Add(new ReplayStatisticsFrameInput { Frame = CurrentFrame }); + } + + protected virtual void CollectReplayInputs(List inputs) + { + } } } From 39e1d65976ba2d35e0c7729b21ca75fdf1745864 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 31 Jan 2022 18:43:10 +0900 Subject: [PATCH 192/256] Make ScoreProcessor write all judgement types --- osu.Game/Rulesets/Scoring/HitResult.cs | 2 +- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 5599ed96a3..a254f9b760 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -176,7 +176,7 @@ namespace osu.Game.Rulesets.Scoring /// /// An array of all scorable s. /// - public static readonly HitResult[] SCORABLE_TYPES = ((HitResult[])Enum.GetValues(typeof(HitResult))).Where(r => r.IsScorable()).ToArray(); + public static readonly HitResult[] ALL_TYPES = ((HitResult[])Enum.GetValues(typeof(HitResult))).ToArray(); /// /// Whether a is valid within a given range. diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index fc24972b8e..fc5e982521 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -125,6 +125,8 @@ namespace osu.Game.Rulesets.Scoring if (result.FailedAtJudgement) return; + scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) + 1; + if (!result.Type.IsScorable()) return; @@ -151,8 +153,6 @@ namespace osu.Game.Rulesets.Scoring rollingMaxBaseScore += result.Judgement.MaxNumericResult; } - scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) + 1; - hitEvents.Add(CreateHitEvent(result)); lastHitObject = result.HitObject; @@ -175,6 +175,8 @@ namespace osu.Game.Rulesets.Scoring if (result.FailedAtJudgement) return; + scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) - 1; + if (!result.Type.IsScorable()) return; @@ -186,8 +188,6 @@ namespace osu.Game.Rulesets.Scoring rollingMaxBaseScore -= result.Judgement.MaxNumericResult; } - scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) - 1; - Debug.Assert(hitEvents.Count > 0); lastHitObject = hitEvents[^1].LastHitObject; hitEvents.RemoveAt(hitEvents.Count - 1); @@ -346,7 +346,7 @@ namespace osu.Game.Rulesets.Scoring score.Accuracy = Accuracy.Value; score.Rank = Rank.Value; - foreach (var result in HitResultExtensions.SCORABLE_TYPES) + foreach (var result in HitResultExtensions.ALL_TYPES) score.Statistics[result] = GetStatistic(result); score.HitEvents = hitEvents; From 4fb565e15f1be7d6fd740090f24cd2d98552aabb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 31 Jan 2022 18:54:23 +0900 Subject: [PATCH 193/256] Reset ScoreProcessor from statistics replay frames --- .../Gameplay/TestSceneScoreProcessor.cs | 37 +++++++++ .../Rulesets/Scoring/JudgementProcessor.cs | 12 +++ osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 77 +++++++++++++++++-- osu.Game/Rulesets/UI/RulesetInputManager.cs | 32 +++++--- osu.Game/Screens/Play/Player.cs | 1 + 5 files changed, 144 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index 432e3df95e..bcaa70d062 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -1,11 +1,17 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Online.Spectator; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Tests.Visual; @@ -42,6 +48,37 @@ namespace osu.Game.Tests.Gameplay Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(Judgement.LARGE_BONUS_SCORE)); } + [Test] + public void TestResetFromReplayFrame() + { + var beatmap = new Beatmap { HitObjects = { new HitCircle() } }; + + var scoreProcessor = new ScoreProcessor(); + scoreProcessor.ApplyBeatmap(beatmap); + + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new TestJudgement(HitResult.Great)) { Type = HitResult.Great }); + Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000)); + Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1)); + + // Reset with a miss instead. + scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame + { + Header = new FrameHeader(0, 0, 0, new Dictionary { { HitResult.Miss, 1 } }, DateTimeOffset.Now) + }); + + Assert.That(scoreProcessor.TotalScore.Value, Is.Zero); + Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1)); + + // Reset with no judged hit. + scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame + { + Header = new FrameHeader(0, 0, 0, new Dictionary(), DateTimeOffset.Now) + }); + + Assert.That(scoreProcessor.TotalScore.Value, Is.Zero); + Assert.That(scoreProcessor.JudgedHits, Is.Zero); + } + private class TestJudgement : Judgement { public override HitResult MaxResult { get; } diff --git a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs index c3c4a2c949..f38155240e 100644 --- a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs +++ b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Scoring { @@ -107,6 +108,17 @@ namespace osu.Game.Rulesets.Scoring JudgedHits = 0; } + public virtual void ResetFromReplayFrame(Ruleset ruleset, ReplayFrame frame) + { + if (frame.Header == null) + return; + + JudgedHits = 0; + + foreach ((_, int count) in frame.Header.Statistics) + JudgedHits += count; + } + /// /// Creates the that represents the scoring result for a . /// diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index fc5e982521..37ee3771df 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -7,9 +7,11 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Utils; +using osu.Game.Extensions; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Replays; using osu.Game.Scoring; namespace osu.Game.Rulesets.Scoring @@ -18,6 +20,11 @@ namespace osu.Game.Rulesets.Scoring { private const double max_score = 1000000; + /// + /// Invoked when this was reset from a replay frame. + /// + public event Action OnResetFromReplayFrame; + /// /// The current total score. /// @@ -329,12 +336,6 @@ namespace osu.Game.Rulesets.Scoring HighestCombo.Value = 0; } - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - hitEvents.Clear(); - } - /// /// Retrieve a score populated with data for the current play this processor is responsible for. /// @@ -351,6 +352,70 @@ namespace osu.Game.Rulesets.Scoring score.HitEvents = hitEvents; } + + /// + /// Maximum for a normal hit (i.e. not tick/bonus) for this ruleset. Only populated via . + /// + private HitResult? maxNormalResult; + + public override void ResetFromReplayFrame(Ruleset ruleset, ReplayFrame frame) + { + base.ResetFromReplayFrame(ruleset, frame); + + if (frame.Header == null) + return; + + baseScore = 0; + rollingMaxBaseScore = 0; + HighestCombo.Value = frame.Header.MaxCombo; + + foreach ((HitResult result, int count) in frame.Header.Statistics) + { + // Bonus scores are counted separately directly from the statistics dictionary later on. + if (!result.IsScorable() || result.IsBonus()) + continue; + + // The maximum result of this judgement if it wasn't a miss. + // E.g. For a GOOD judgement, the max result is either GREAT/PERFECT depending on which one the ruleset uses (osu!: GREAT, osu!mania: PERFECT). + HitResult maxResult; + + switch (result) + { + case HitResult.LargeTickHit: + case HitResult.LargeTickMiss: + maxResult = HitResult.LargeTickHit; + break; + + case HitResult.SmallTickHit: + case HitResult.SmallTickMiss: + maxResult = HitResult.SmallTickHit; + break; + + default: + maxResult = maxNormalResult ??= ruleset.GetHitResults().OrderByDescending(kvp => Judgement.ToNumericResult(kvp.result)).First().result; + break; + } + + for (int i = 0; i < count; i++) + { + baseScore += Judgement.ToNumericResult(result); + rollingMaxBaseScore += Judgement.ToNumericResult(maxResult); + } + } + + scoreResultCounts.Clear(); + scoreResultCounts.AddRange(frame.Header.Statistics); + + updateScore(); + + OnResetFromReplayFrame?.Invoke(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + hitEvents.Clear(); + } } public enum ScoringMode diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 370c99ffaf..050ec82071 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -16,6 +16,7 @@ using osu.Game.Configuration; using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.Input.Handlers; +using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using static osu.Game.Input.Handlers.ReplayInputHandler; @@ -24,6 +25,11 @@ namespace osu.Game.Rulesets.UI public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler, IHasRecordingHandler where T : struct { + private readonly Ruleset ruleset; + + [Resolved(CanBeNull = true)] + private ScoreProcessor scoreProcessor { get; set; } + private ReplayRecorder recorder; public ReplayRecorder Recorder @@ -51,6 +57,8 @@ namespace osu.Game.Rulesets.UI protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) { + this.ruleset = ruleset.CreateInstance(); + InternalChild = KeyBindingContainer = CreateKeyBindingContainer(ruleset, variant, unique) .WithChild(content = new Container { RelativeSizeAxes = Axes.Both }); @@ -66,17 +74,23 @@ namespace osu.Game.Rulesets.UI public override void HandleInputStateChange(InputStateChangeEvent inputStateChange) { - if (inputStateChange is ReplayStateChangeEvent replayStateChanged) + switch (inputStateChange) { - foreach (var action in replayStateChanged.ReleasedActions) - KeyBindingContainer.TriggerReleased(action); + case ReplayStateChangeEvent stateChangeEvent: + foreach (var action in stateChangeEvent.ReleasedActions) + KeyBindingContainer.TriggerReleased(action); - foreach (var action in replayStateChanged.PressedActions) - KeyBindingContainer.TriggerPressed(action); - } - else - { - base.HandleInputStateChange(inputStateChange); + foreach (var action in stateChangeEvent.PressedActions) + KeyBindingContainer.TriggerPressed(action); + break; + + case ReplayStatisticsFrameEvent statisticsStateChangeEvent: + scoreProcessor?.ResetFromReplayFrame(ruleset, statisticsStateChangeEvent.Frame); + break; + + default: + base.HandleInputStateChange(inputStateChange); + break; } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index cfca2d0a3d..e0fcca4eb7 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -165,6 +165,7 @@ namespace osu.Game.Screens.Play PrepareReplay(); ScoreProcessor.NewJudgement += result => ScoreProcessor.PopulateScore(Score.ScoreInfo); + ScoreProcessor.OnResetFromReplayFrame += () => ScoreProcessor.PopulateScore(Score.ScoreInfo); gameActive.BindValueChanged(_ => updatePauseOnFocusLostState(), true); } From e5601772a99aba7374077f986f158a1a839f2264 Mon Sep 17 00:00:00 2001 From: Spooghetti420 Date: Mon, 31 Jan 2022 15:00:36 +0000 Subject: [PATCH 194/256] Make incompatible with --- osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs | 3 +++ osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index 42dc7fdc1f..1abca970d5 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Objects; @@ -25,6 +26,8 @@ namespace osu.Game.Rulesets.Mania.Mods public override ModType Type => ModType.Conversion; + public override Type[] IncompatibleMods => new[] { typeof(ManiaModInvert) }; + public const double END_NOTE_ALLOW_THRESHOLD = 0.5; public void ApplyToBeatmap(IBeatmap beatmap) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs index 1ea45c295c..4cbdaee323 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs @@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Mania.Mods public override ModType Type => ModType.Conversion; + public override Type[] IncompatibleMods => new[] { typeof(ManiaModHoldOff) }; + public void ApplyToBeatmap(IBeatmap beatmap) { var maniaBeatmap = (ManiaBeatmap)beatmap; From 2f88efd3c3c2f66f0bc8104f737cbc532527251f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 00:53:56 +0900 Subject: [PATCH 195/256] Pass column in rather than accessing parent --- osu.Game.Rulesets.Mania/UI/Column.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index a6c64e9b2d..dc3b86ac7f 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Mania.UI }, background, TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }, - new ColumnTouchInputArea() + new ColumnTouchInputArea(this) }; hitPolicy = new OrderedHitPolicy(HitObjectContainer); @@ -143,17 +143,19 @@ namespace osu.Game.Rulesets.Mania.UI public class ColumnTouchInputArea : Drawable { + private readonly Column column; + [Resolved(canBeNull: true)] private ManiaInputManager maniaInputManager { get; set; } private KeyBindingContainer keyBindingContainer; - public ColumnTouchInputArea() + public ColumnTouchInputArea(Column column) { RelativeSizeAxes = Axes.Both; - } - private Column column => (Column)Parent; + this.column = column; + } protected override void LoadComplete() { From 9227211a441eb2df90a2e0504e6bdaa08f94bfe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 31 Jan 2022 22:56:27 +0100 Subject: [PATCH 196/256] Privatise `shouldAlternate` --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index db8bd217f6..f68f2b5bee 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -25,7 +25,6 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) }; public override ModType Type => ModType.DifficultyIncrease; public override IconUsage? Icon => FontAwesome.Solid.Keyboard; - public bool ShouldAlternate => !isBreakTime.Value && introEnded; private bool introEnded; private double earliestStartTime; @@ -34,6 +33,8 @@ namespace osu.Game.Rulesets.Osu.Mods private OsuAction? lastActionPressed; private DrawableRuleset ruleset; + private bool shouldAlternate => !isBreakTime.Value && introEnded; + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { ruleset = drawableRuleset; @@ -82,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Mods } public bool OnPressed(KeyBindingPressEvent e) - => mod.ShouldAlternate && mod.onPressed(e.Action); + => mod.shouldAlternate && mod.onPressed(e.Action); public void OnReleased(KeyBindingReleaseEvent e) { From c50577e25ff3d20c307cbec120b8436558cb6aa3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 1 Feb 2022 10:48:41 +0900 Subject: [PATCH 197/256] Apply suggestion from review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 37ee3771df..79861c0ecc 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -396,11 +396,8 @@ namespace osu.Game.Rulesets.Scoring break; } - for (int i = 0; i < count; i++) - { - baseScore += Judgement.ToNumericResult(result); - rollingMaxBaseScore += Judgement.ToNumericResult(maxResult); - } + baseScore += count * Judgement.ToNumericResult(result); + rollingMaxBaseScore += count * Judgement.ToNumericResult(maxResult); } scoreResultCounts.Clear(); From 5a6d57efb77728df1d09dab9837a75e36311f611 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 12:43:35 +0900 Subject: [PATCH 198/256] Update fastlane and dependencies --- Gemfile.lock | 147 +++++++++++++++++++++++++++++---------------- fastlane/README.md | 2 +- 2 files changed, 95 insertions(+), 54 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8ac863c9a8..86c8baabe6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,53 +1,75 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.3) - addressable (2.7.0) + CFPropertyList (3.0.5) + rexml + addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) + artifactory (3.0.15) atomos (0.1.3) - aws-eventstream (1.1.0) - aws-partitions (1.413.0) - aws-sdk-core (3.110.0) + aws-eventstream (1.2.0) + aws-partitions (1.551.0) + aws-sdk-core (3.125.5) aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.239.0) + aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.40.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-kms (1.53.0) + aws-sdk-core (~> 3, >= 3.125.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.87.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-s3 (1.111.3) + aws-sdk-core (~> 3, >= 3.125.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.1) - aws-sigv4 (1.2.2) + aws-sigv4 (~> 1.4) + aws-sigv4 (1.4.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) - claide (1.0.3) + claide (1.1.0) colored (1.2) colored2 (3.1.2) commander-fastlane (4.4.6) highline (~> 1.7.2) declarative (0.0.20) - declarative-option (0.1.0) - digest-crc (0.6.3) + digest-crc (0.6.4) rake (>= 12.0.0, < 14.0.0) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) dotenv (2.7.6) - emoji_regex (3.2.1) - excon (0.78.1) - faraday (1.2.0) - multipart-post (>= 1.2, < 3) - ruby2_keywords + emoji_regex (3.2.3) + excon (0.90.0) + faraday (1.9.3) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) faraday-cookie_jar (0.0.7) faraday (>= 0.8.0) http-cookie (~> 1.0.0) - faraday_middleware (1.0.0) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.0.3) + multipart-post (>= 1.2, < 3) + faraday-net_http (1.0.1) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + faraday_middleware (1.2.0) faraday (~> 1.0) - fastimage (2.2.1) - fastlane (2.170.0) + fastimage (2.2.6) + fastlane (2.181.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) + artifactory (~> 3.0) aws-sdk-s3 (~> 1.0) babosa (>= 1.0.3, < 2.0.0) bundler (>= 1.12.0, < 3.0.0) @@ -68,6 +90,7 @@ GEM jwt (>= 2.1.0, < 3) mini_magick (>= 4.9.4, < 5.0.0) multipart-post (~> 2.0.0) + naturally (~> 2.2) plist (>= 3.1.0, < 4.0.0) rubyzip (>= 2.0.0, < 3.0.0) security (= 0.1.3) @@ -94,65 +117,80 @@ GEM representable (~> 3.0) retriable (>= 2.0, < 4.0) signet (~> 0.12) - google-cloud-core (1.5.0) + google-apis-core (0.4.2) + addressable (~> 2.5, >= 2.5.1) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.a) + rexml + webrick + google-apis-iamcredentials_v1 (0.10.0) + google-apis-core (>= 0.4, < 2.a) + google-apis-storage_v1 (0.11.0) + google-apis-core (>= 0.4, < 2.a) + google-cloud-core (1.6.0) google-cloud-env (~> 1.0) google-cloud-errors (~> 1.0) - google-cloud-env (1.4.0) + google-cloud-env (1.5.0) faraday (>= 0.17.3, < 2.0) - google-cloud-errors (1.0.1) - google-cloud-storage (1.29.2) - addressable (~> 2.5) + google-cloud-errors (1.2.0) + google-cloud-storage (1.36.0) + addressable (~> 2.8) digest-crc (~> 0.4) - google-api-client (~> 0.33) - google-cloud-core (~> 1.2) - googleauth (~> 0.9) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.1) + google-cloud-core (~> 1.6) + googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (0.14.0) + googleauth (0.17.1) faraday (>= 0.17.3, < 2.0) jwt (>= 1.4, < 3.0) memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) - signet (~> 0.14) + signet (~> 0.15) highline (1.7.10) - http-cookie (1.0.3) + http-cookie (1.0.4) domain_name (~> 0.5) httpclient (2.8.3) - jmespath (1.4.0) - json (2.5.1) - jwt (2.2.2) + jmespath (1.5.0) + json (2.6.1) + jwt (2.3.0) memoist (0.16.2) mini_magick (4.11.0) - mini_mime (1.0.2) + mini_mime (1.1.2) mini_portile2 (2.4.0) multi_json (1.15.0) multipart-post (2.0.0) nanaimo (0.3.0) - naturally (2.2.0) + naturally (2.2.1) nokogiri (1.10.10) mini_portile2 (~> 2.4.0) - os (1.1.1) - plist (3.5.0) + os (1.1.4) + plist (3.6.0) public_suffix (4.0.6) - rake (13.0.3) - representable (3.0.4) + rake (13.0.6) + representable (3.1.1) declarative (< 0.1.0) - declarative-option (< 0.2.0) + trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) + rexml (3.2.5) rouge (2.0.7) - ruby2_keywords (0.0.2) - rubyzip (2.3.0) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) security (0.1.3) - signet (0.14.0) - addressable (~> 2.3) + signet (0.16.0) + addressable (~> 2.8) faraday (>= 0.17.3, < 2.0) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) simctl (1.6.8) CFPropertyList naturally - slack-notifier (2.3.2) + slack-notifier (2.4.0) souyuz (0.9.1) fastlane (>= 1.103.0) highline (~> 1.7) @@ -160,6 +198,7 @@ GEM terminal-notifier (2.0.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) + trailblazer-option (0.1.2) tty-cursor (0.7.1) tty-screen (0.8.1) tty-spinner (0.9.3) @@ -167,18 +206,20 @@ GEM uber (0.1.0) unf (0.1.4) unf_ext - unf_ext (0.0.7.7) - unicode-display_width (1.7.0) + unf_ext (0.0.8) + unicode-display_width (1.8.0) + webrick (1.7.0) word_wrap (1.0.0) - xcodeproj (1.19.0) + xcodeproj (1.21.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.3.0) + rexml (~> 3.2.4) xcpretty (0.3.0) rouge (~> 2.0.7) - xcpretty-travis-formatter (1.0.0) + xcpretty-travis-formatter (1.0.1) xcpretty (~> 0.2, >= 0.0.7) PLATFORMS diff --git a/fastlane/README.md b/fastlane/README.md index a400ed9516..8273fdaa5d 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -12,7 +12,7 @@ Install _fastlane_ using ``` [sudo] gem install fastlane -NV ``` -or alternatively using `brew cask install fastlane` +or alternatively using `brew install fastlane` # Available Actions ## Android From aa492270dd9738bf9cf8c7dc0c4eb7543e155f8a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 13:30:26 +0900 Subject: [PATCH 199/256] Ignore `FodyWeavers.xml` files in subdirectories These are created when building specific projects rather than the main solution (typically iOS / android) and of no use to us. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index de6a3ac848..5b19270ab9 100644 --- a/.gitignore +++ b/.gitignore @@ -339,3 +339,4 @@ inspectcode # Fody (pulled in by Realm) - schema file FodyWeavers.xsd +**/FodyWeavers.xml From db973fb34876e1329c215f08dcee2dc7039ae422 Mon Sep 17 00:00:00 2001 From: dekrain Date: Tue, 1 Feb 2022 06:28:18 +0100 Subject: [PATCH 200/256] Add basic tooltip for leaderboard scores --- .../Online/Leaderboards/LeaderboardScore.cs | 5 +- .../Leaderboards/LeaderboardScoreTooltip.cs | 225 ++++++++++++++++++ 2 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 906e09b8c1..7779043ccd 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -32,7 +32,7 @@ using osu.Game.Utils; namespace osu.Game.Online.Leaderboards { - public class LeaderboardScore : OsuClickableContainer, IHasContextMenu + public class LeaderboardScore : OsuClickableContainer, IHasContextMenu, IHasCustomTooltip { public const float HEIGHT = 60; @@ -70,6 +70,9 @@ namespace osu.Game.Online.Leaderboards [Resolved] private Storage storage { get; set; } + public ITooltip GetCustomTooltip() => new LeaderboardScoreTooltip(); + public ScoreInfo TooltipContent => Score; + public LeaderboardScore(ScoreInfo score, int? rank, bool isOnlineScope = true) { Score = score; diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs new file mode 100644 index 0000000000..78064c33bc --- /dev/null +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -0,0 +1,225 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Game.Scoring; +using osuTK; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Online.Leaderboards +{ + public class LeaderboardScoreTooltip : VisibilityContainer, ITooltip + { + private OsuSpriteText timestampLabel; + private FillFlowContainer topScoreStatistics; + private FillFlowContainer bottomScoreStatistics; + private FillFlowContainer modStatistics; + + public LeaderboardScoreTooltip() + { + AutoSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 5; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.7f, + Colour = Colour4.Black, + }, + new GridContainer + { + Margin = new MarginPadding(5f), + AutoSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + // Info row + new Drawable[] + { + timestampLabel = new OsuSpriteText() + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + } + }, + // Mods row + new Drawable[] + { + modStatistics = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + } + }, + // Actual stats rows + new Drawable[] + { + topScoreStatistics = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + } + }, + new Drawable[] + { + bottomScoreStatistics = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + } + }, + } + } + }; + } + + private ScoreInfo currentScore; + + public void SetContent(ScoreInfo score) + { + if (currentScore == score) + return; + currentScore = score; + + timestampLabel.Text = $"Played on {score.Date.ToLocalTime():d MMMM yyyy HH:mm}"; + + modStatistics.Clear(); + topScoreStatistics.Clear(); + bottomScoreStatistics.Clear(); + + foreach (var mod in score.Mods) + { + modStatistics.Add(new ModCell(mod)); + } + + foreach (var result in score.GetStatisticsForDisplay()) + { + (result.Result > HitResult.Perfect + ? bottomScoreStatistics + : topScoreStatistics + ).Add(new HitResultCell(result)); + } + } + + protected override void PopIn() => this.FadeIn(20, Easing.OutQuint); + protected override void PopOut() => this.FadeOut(80, Easing.OutQuint); + + public void Move(Vector2 pos) => Position = pos; + + private class HitResultCell : CompositeDrawable + { + readonly private string DisplayName; + readonly private HitResult Result; + readonly private int Count; + + public HitResultCell(HitResultDisplayStatistic stat) + { + AutoSizeAxes = Axes.Both; + Padding = new MarginPadding{ Horizontal = 5f }; + + DisplayName = stat.DisplayName; + Result = stat.Result; + Count = stat.Count; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChild = new FillFlowContainer + { + Height = 12, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(2f, 0f), + Children = new Drawable[] + { + new CircularContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#222") + }, + new OsuSpriteText + { + Padding = new MarginPadding{ Horizontal = 2f }, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), + Text = DisplayName.ToUpperInvariant(), + Colour = colours.ForHitResult(Result), + } + } + }, + new OsuSpriteText + { + RelativeSizeAxes = Axes.Y, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + Text = Count.ToString(), + }, + } + }; + } + } + + private class ModCell : CompositeDrawable + { + readonly private Mod Mod; + + public ModCell(Mod mod) + { + AutoSizeAxes = Axes.Both; + Padding = new MarginPadding{ Horizontal = 5f }; + Mod = mod; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new FillFlowContainer + { + Height = 15, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(2f, 0f), + Children = new Drawable[] + { + new ModIcon(Mod, showTooltip: false).With(icon => + { + icon.Scale = new Vector2(15f / icon.Height); + }), + new OsuSpriteText + { + RelativeSizeAxes = Axes.Y, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + Text = Mod.SettingDescription, + } + } + }; + } + } + } +} From c2b775c0a39cebce5cf2d2494266484f17223094 Mon Sep 17 00:00:00 2001 From: dekrain Date: Tue, 1 Feb 2022 06:45:59 +0100 Subject: [PATCH 201/256] Minor alignment adjustments --- .../Leaderboards/LeaderboardScoreTooltip.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 78064c33bc..bea589e47f 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -199,7 +199,8 @@ namespace osu.Game.Online.Leaderboards [BackgroundDependencyLoader] private void load() { - InternalChild = new FillFlowContainer + FillFlowContainer container; + InternalChild = container = new FillFlowContainer { Height = 15, AutoSizeAxes = Axes.X, @@ -209,16 +210,25 @@ namespace osu.Game.Online.Leaderboards { new ModIcon(Mod, showTooltip: false).With(icon => { + icon.Origin = Anchor.CentreLeft; + icon.Anchor = Anchor.CentreLeft; icon.Scale = new Vector2(15f / icon.Height); }), - new OsuSpriteText - { - RelativeSizeAxes = Axes.Y, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), - Text = Mod.SettingDescription, - } } }; + + string description = Mod.SettingDescription; + if (!string.IsNullOrEmpty(description)) + { + container.Add(new OsuSpriteText + { + RelativeSizeAxes = Axes.Y, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + Text = Mod.SettingDescription, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + }); + } } } } From fd287e06f2bbe0f1115961184f837f0cd30927fe Mon Sep 17 00:00:00 2001 From: dekrain Date: Tue, 1 Feb 2022 06:51:00 +0100 Subject: [PATCH 202/256] Add missing license header --- osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index bea589e47f..ea02c238f3 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Containers; From d7b939277e81d99eb36aeb25f9aa71846b8b90c6 Mon Sep 17 00:00:00 2001 From: dekrain Date: Tue, 1 Feb 2022 07:10:00 +0100 Subject: [PATCH 203/256] Code quality improvements --- .../Leaderboards/LeaderboardScoreTooltip.cs | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index ea02c238f3..0b2de3e2c8 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -19,10 +19,10 @@ namespace osu.Game.Online.Leaderboards { public class LeaderboardScoreTooltip : VisibilityContainer, ITooltip { - private OsuSpriteText timestampLabel; - private FillFlowContainer topScoreStatistics; - private FillFlowContainer bottomScoreStatistics; - private FillFlowContainer modStatistics; + private readonly OsuSpriteText timestampLabel; + private readonly FillFlowContainer topScoreStatistics; + private readonly FillFlowContainer bottomScoreStatistics; + private readonly FillFlowContainer modStatistics; public LeaderboardScoreTooltip() { @@ -58,7 +58,7 @@ namespace osu.Game.Online.Leaderboards // Info row new Drawable[] { - timestampLabel = new OsuSpriteText() + timestampLabel = new OsuSpriteText { Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), } @@ -98,8 +98,9 @@ namespace osu.Game.Online.Leaderboards public void SetContent(ScoreInfo score) { - if (currentScore == score) + if (currentScore.Equals(score)) return; + currentScore = score; timestampLabel.Text = $"Played on {score.Date.ToLocalTime():d MMMM yyyy HH:mm}"; @@ -116,9 +117,9 @@ namespace osu.Game.Online.Leaderboards foreach (var result in score.GetStatisticsForDisplay()) { (result.Result > HitResult.Perfect - ? bottomScoreStatistics - : topScoreStatistics - ).Add(new HitResultCell(result)); + ? bottomScoreStatistics + : topScoreStatistics + ).Add(new HitResultCell(result)); } } @@ -129,9 +130,9 @@ namespace osu.Game.Online.Leaderboards private class HitResultCell : CompositeDrawable { - readonly private string DisplayName; - readonly private HitResult Result; - readonly private int Count; + private readonly string DisplayName; + private readonly HitResult Result; + private readonly int Count; public HitResultCell(HitResultDisplayStatistic stat) { @@ -190,7 +191,7 @@ namespace osu.Game.Online.Leaderboards private class ModCell : CompositeDrawable { - readonly private Mod Mod; + private readonly Mod Mod; public ModCell(Mod mod) { From e1b57c4bf61d97070021f154a25cbd802b3133dc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 16:07:57 +0900 Subject: [PATCH 204/256] Fix inspections --- .../Leaderboards/LeaderboardScoreTooltip.cs | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 0b2de3e2c8..7825a50a0b 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -119,7 +119,7 @@ namespace osu.Game.Online.Leaderboards (result.Result > HitResult.Perfect ? bottomScoreStatistics : topScoreStatistics - ).Add(new HitResultCell(result)); + ).Add(new HitResultCell(result)); } } @@ -130,18 +130,18 @@ namespace osu.Game.Online.Leaderboards private class HitResultCell : CompositeDrawable { - private readonly string DisplayName; - private readonly HitResult Result; - private readonly int Count; + private readonly string displayName; + private readonly HitResult result; + private readonly int count; public HitResultCell(HitResultDisplayStatistic stat) { AutoSizeAxes = Axes.Both; - Padding = new MarginPadding{ Horizontal = 5f }; + Padding = new MarginPadding { Horizontal = 5f }; - DisplayName = stat.DisplayName; - Result = stat.Result; - Count = stat.Count; + displayName = stat.DisplayName; + result = stat.Result; + count = stat.Count; } [BackgroundDependencyLoader] @@ -169,12 +169,12 @@ namespace osu.Game.Online.Leaderboards }, new OsuSpriteText { - Padding = new MarginPadding{ Horizontal = 2f }, + Padding = new MarginPadding { Horizontal = 2f }, Anchor = Anchor.Centre, Origin = Anchor.Centre, Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), - Text = DisplayName.ToUpperInvariant(), - Colour = colours.ForHitResult(Result), + Text = displayName.ToUpperInvariant(), + Colour = colours.ForHitResult(result), } } }, @@ -182,7 +182,7 @@ namespace osu.Game.Online.Leaderboards { RelativeSizeAxes = Axes.Y, Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), - Text = Count.ToString(), + Text = count.ToString(), }, } }; @@ -191,13 +191,13 @@ namespace osu.Game.Online.Leaderboards private class ModCell : CompositeDrawable { - private readonly Mod Mod; + private readonly Mod mod; public ModCell(Mod mod) { AutoSizeAxes = Axes.Both; - Padding = new MarginPadding{ Horizontal = 5f }; - Mod = mod; + Padding = new MarginPadding { Horizontal = 5f }; + this.mod = mod; } [BackgroundDependencyLoader] @@ -212,7 +212,7 @@ namespace osu.Game.Online.Leaderboards Spacing = new Vector2(2f, 0f), Children = new Drawable[] { - new ModIcon(Mod, showTooltip: false).With(icon => + new ModIcon(mod, showTooltip: false).With(icon => { icon.Origin = Anchor.CentreLeft; icon.Anchor = Anchor.CentreLeft; @@ -221,14 +221,15 @@ namespace osu.Game.Online.Leaderboards } }; - string description = Mod.SettingDescription; + string description = mod.SettingDescription; + if (!string.IsNullOrEmpty(description)) { container.Add(new OsuSpriteText { RelativeSizeAxes = Axes.Y, Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), - Text = Mod.SettingDescription, + Text = mod.SettingDescription, Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, }); From 855135c51e63f8a2095e7ae5dc8402a5df1b1920 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 16:13:27 +0900 Subject: [PATCH 205/256] Fix potential nullref during display due to incorrect equality check --- osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 7825a50a0b..85db59b3ea 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -15,6 +15,8 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; +#nullable enable + namespace osu.Game.Online.Leaderboards { public class LeaderboardScoreTooltip : VisibilityContainer, ITooltip @@ -94,14 +96,14 @@ namespace osu.Game.Online.Leaderboards }; } - private ScoreInfo currentScore; + private ScoreInfo? displayedScore; public void SetContent(ScoreInfo score) { - if (currentScore.Equals(score)) + if (displayedScore?.Equals(score) == true) return; - currentScore = score; + displayedScore = score; timestampLabel.Text = $"Played on {score.Date.ToLocalTime():d MMMM yyyy HH:mm}"; From fdb52a8fd70105516a2b4fb32cb6b3b4657d98ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 16:20:40 +0900 Subject: [PATCH 206/256] Remove gap in tooltip display between statistics --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 7779043ccd..b4aaebd4e4 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -186,7 +186,6 @@ namespace osu.Game.Online.Leaderboards Anchor = Anchor.BottomLeft, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Spacing = new Vector2(10f, 0f), Margin = new MarginPadding { Left = edge_margin }, Children = statisticsLabels }, @@ -231,7 +230,6 @@ namespace osu.Game.Online.Leaderboards Origin = Anchor.BottomRight, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Spacing = new Vector2(1), ChildrenEnumerable = Score.Mods.Select(mod => new ModIcon(mod) { Scale = new Vector2(0.375f) }) }, }, @@ -316,6 +314,7 @@ namespace osu.Game.Online.Leaderboards { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, + Padding = new MarginPadding { Right = 10 }, Children = new Drawable[] { new Container From 3ca2c906842209fe36e6d67ee7ace35f2cfad747 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 16:35:05 +0900 Subject: [PATCH 207/256] Add test scores in `BeatmapLeaderboard` test scene with more mods --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 48230ff9e9..7292ec96ed 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -197,7 +197,22 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] + { + new OsuModHidden(), + new OsuModHardRock(), + new OsuModFlashlight + { + FollowDelay = { Value = 200 } + }, + new OsuModDifficultyAdjust + { + CircleSize = { Value = 8 }, + ApproachRate = { Value = 7 }, + OverallDifficulty = { Value = 6 }, + DrainRate = { Value = 5 }, + } + }, Ruleset = new OsuRuleset().RulesetInfo, BeatmapInfo = beatmapInfo, User = new APIUser @@ -217,7 +232,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, User = new APIUser @@ -237,7 +252,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -258,7 +273,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -279,7 +294,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -300,7 +315,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.9826, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -321,7 +336,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.9654, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -342,7 +357,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.6025, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -363,7 +378,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.5140, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -384,7 +399,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.4222, MaxCombo = 244, TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, From 8eace12fe3b5b585657cfec9ad733c129dd4d73a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 16:35:22 +0900 Subject: [PATCH 208/256] Synchronise (roughly) backgrounds of all custom tooltips --- .../Drawables/DifficultyIconTooltip.cs | 1 + .../Leaderboards/LeaderboardScoreTooltip.cs | 29 ++++++++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index ec4bcbd65f..aba01a1294 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -30,6 +30,7 @@ namespace osu.Game.Beatmaps.Drawables { background = new Box { + Alpha = 0.9f, RelativeSizeAxes = Axes.Both }, new FillFlowContainer diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 85db59b3ea..7b88c0d534 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -21,24 +21,31 @@ namespace osu.Game.Online.Leaderboards { public class LeaderboardScoreTooltip : VisibilityContainer, ITooltip { - private readonly OsuSpriteText timestampLabel; - private readonly FillFlowContainer topScoreStatistics; - private readonly FillFlowContainer bottomScoreStatistics; - private readonly FillFlowContainer modStatistics; + private OsuSpriteText timestampLabel = null!; + private FillFlowContainer topScoreStatistics = null!; + private FillFlowContainer bottomScoreStatistics = null!; + private FillFlowContainer modStatistics = null!; public LeaderboardScoreTooltip() { AutoSizeAxes = Axes.Both; + AutoSizeDuration = 200; + AutoSizeEasing = Easing.OutQuint; + Masking = true; CornerRadius = 5; + } + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { InternalChildren = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Alpha = 0.7f, - Colour = Colour4.Black, + Alpha = 0.9f, + Colour = colours.Gray3, }, new GridContainer { @@ -118,10 +125,10 @@ namespace osu.Game.Online.Leaderboards foreach (var result in score.GetStatisticsForDisplay()) { - (result.Result > HitResult.Perfect - ? bottomScoreStatistics - : topScoreStatistics - ).Add(new HitResultCell(result)); + if (result.Result > HitResult.Perfect) + bottomScoreStatistics.Add(new HitResultCell(result)); + else + topScoreStatistics.Add(new HitResultCell(result)); } } @@ -171,7 +178,7 @@ namespace osu.Game.Online.Leaderboards }, new OsuSpriteText { - Padding = new MarginPadding { Horizontal = 2f }, + Padding = new MarginPadding(2), Anchor = Anchor.Centre, Origin = Anchor.Centre, Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), From f87920cd83d060542ac77784fdefef09f9521348 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 16:43:26 +0900 Subject: [PATCH 209/256] Remove unnecessary `GridContainer` and list mods verticall to give more space --- .../Leaderboards/LeaderboardScoreTooltip.cs | 61 ++++++++----------- 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 7b88c0d534..8c349d56e0 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -47,55 +47,42 @@ namespace osu.Game.Online.Leaderboards Alpha = 0.9f, Colour = colours.Gray3, }, - new GridContainer + new FillFlowContainer { Margin = new MarginPadding(5f), + Spacing = new Vector2(10), AutoSizeAxes = Axes.Both, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - }, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] + Direction = FillDirection.Vertical, + Children = new Drawable[] { // Info row - new Drawable[] + timestampLabel = new OsuSpriteText { - timestampLabel = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), - } + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), }, // Mods row - new Drawable[] + modStatistics = new FillFlowContainer { - modStatistics = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - } + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, }, - // Actual stats rows - new Drawable[] + new FillFlowContainer { - topScoreStatistics = new FillFlowContainer + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - } - }, - new Drawable[] - { - bottomScoreStatistics = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, + // Actual stats rows + topScoreStatistics = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + }, + bottomScoreStatistics = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + }, } }, } From 0f83f77d2b3fc8de0d615361e53130f5958c94c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 16:52:53 +0900 Subject: [PATCH 210/256] Add xmldoc for new `ResetFromReplayFrame` method --- osu.Game/Rulesets/Scoring/JudgementProcessor.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs index f38155240e..a643c31daa 100644 --- a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs +++ b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs @@ -108,6 +108,14 @@ namespace osu.Game.Rulesets.Scoring JudgedHits = 0; } + /// + /// Reset all statistics based on header information contained within a replay frame. + /// + /// + /// If the provided replay frame does not have any header information, this will be a noop. + /// + /// The ruleset to be used for retrieving statistics. + /// The replay frame to read header statistics from. public virtual void ResetFromReplayFrame(Ruleset ruleset, ReplayFrame frame) { if (frame.Header == null) From 15479ae046ebf96b22367565333622a573dd59ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Feb 2022 16:55:28 +0900 Subject: [PATCH 211/256] Add test coverage of no header doing nothing --- osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index bcaa70d062..70ba868de6 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -60,6 +60,12 @@ namespace osu.Game.Tests.Gameplay Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000)); Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1)); + // No header shouldn't cause any change + scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame()); + + Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000)); + Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1)); + // Reset with a miss instead. scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame { From b75c08c9ab07232d44b1bde38f68f2054547b91c Mon Sep 17 00:00:00 2001 From: Spooghetti420 <83249561+Spooghetti420@users.noreply.github.com> Date: Tue, 1 Feb 2022 13:36:36 +0000 Subject: [PATCH 212/256] Improve beat length logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index 1abca970d5..6e942f672b 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -65,8 +65,8 @@ namespace osu.Game.Rulesets.Mania.Mods public static double GetNoteDurationInBeatLength(HoldNote holdNote, ManiaBeatmap beatmap) { - double bpmAtNoteTime = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BPM; - return (60 * holdNote.Duration) / (1000 * bpmAtNoteTime); + double beatLength = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BeatLength; + return holdNote.Duration / beatLength; } } } From eee020f8e4a17db7a1af6be611cbc0b3c5554852 Mon Sep 17 00:00:00 2001 From: dekrain Date: Tue, 1 Feb 2022 20:26:52 +0100 Subject: [PATCH 213/256] Cleanup tooltip layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../Leaderboards/LeaderboardScoreTooltip.cs | 42 ++++++------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 8c349d56e0..a0eea94501 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -10,7 +10,6 @@ using osuTK; using osu.Game.Graphics.Sprites; using osu.Game.Graphics; using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; @@ -49,7 +48,7 @@ namespace osu.Game.Online.Leaderboards }, new FillFlowContainer { - Margin = new MarginPadding(5f), + Margin = new MarginPadding(5), Spacing = new Vector2(10), AutoSizeAxes = Axes.Both, Direction = FillDirection.Vertical, @@ -63,8 +62,10 @@ namespace osu.Game.Online.Leaderboards // Mods row modStatistics = new FillFlowContainer { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Full, + Spacing = new Vector2(5, 0), }, new FillFlowContainer { @@ -77,11 +78,13 @@ namespace osu.Game.Online.Leaderboards { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), }, bottomScoreStatistics = new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), }, } }, @@ -133,7 +136,6 @@ namespace osu.Game.Online.Leaderboards public HitResultCell(HitResultDisplayStatistic stat) { AutoSizeAxes = Axes.Both; - Padding = new MarginPadding { Horizontal = 5f }; displayName = stat.DisplayName; result = stat.Result; @@ -148,35 +150,17 @@ namespace osu.Game.Online.Leaderboards Height = 12, AutoSizeAxes = Axes.X, Direction = FillDirection.Horizontal, - Spacing = new Vector2(2f, 0f), + Spacing = new Vector2(5f, 0f), Children = new Drawable[] { - new CircularContainer + new OsuSpriteText { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("#222") - }, - new OsuSpriteText - { - Padding = new MarginPadding(2), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), - Text = displayName.ToUpperInvariant(), - Colour = colours.ForHitResult(result), - } - } + Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), + Text = displayName.ToUpperInvariant(), + Colour = colours.ForHitResult(result), }, new OsuSpriteText { - RelativeSizeAxes = Axes.Y, Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), Text = count.ToString(), }, @@ -192,7 +176,6 @@ namespace osu.Game.Online.Leaderboards public ModCell(Mod mod) { AutoSizeAxes = Axes.Both; - Padding = new MarginPadding { Horizontal = 5f }; this.mod = mod; } @@ -228,6 +211,7 @@ namespace osu.Game.Online.Leaderboards Text = mod.SettingDescription, Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, + Margin = new MarginPadding { Top = 1 }, }); } } From 3d7af805a3cd0905fef3bfc15544330d3bdcd912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:16:28 +0100 Subject: [PATCH 214/256] Fix `BeatmapMetadata` not using its user param correctly --- osu.Game/Beatmaps/BeatmapMetadata.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 1514d3af7a..f6666a6ea9 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -46,7 +46,7 @@ namespace osu.Game.Beatmaps public BeatmapMetadata(RealmUser? user = null) { - Author = new RealmUser(); + Author = user ?? new RealmUser(); } [UsedImplicitly] // Realm From a378e78ced3e0b5dfe6dafa3803060b1c9d118ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:17:27 +0100 Subject: [PATCH 215/256] Fix `RealmLive` unnecessarily passing ID around Appears to have never been needed. When the `retrieveFromID` method was created in 81b5717ae793e334fdcf8efd72d20debfb49e9ca, it didn't use the `id` parameter for anything either. --- osu.Game/Database/RealmLive.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index 186e801425..ecfececaa4 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -63,7 +63,7 @@ namespace osu.Game.Database return; } - perform(retrieveFromID(r, ID)); + perform(retrieveFromID(r)); RealmLiveStatistics.USAGE_ASYNC.Value++; }); } @@ -85,7 +85,7 @@ namespace osu.Game.Database return realm.Run(r => { - var returnData = perform(retrieveFromID(r, ID)); + var returnData = perform(retrieveFromID(r)); RealmLiveStatistics.USAGE_ASYNC.Value++; if (returnData is RealmObjectBase realmObject && realmObject.IsManaged) @@ -139,11 +139,11 @@ namespace osu.Game.Database } dataIsFromUpdateThread = true; - data = retrieveFromID(realm.Realm, ID); + data = retrieveFromID(realm.Realm); RealmLiveStatistics.USAGE_UPDATE_REFETCH.Value++; } - private T retrieveFromID(Realm realm, Guid id) + private T retrieveFromID(Realm realm) { var found = realm.Find(ID); From 16e0cc6a2b393312f90da6320d6d9e4d00685e54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:20:59 +0100 Subject: [PATCH 216/256] Remove `IIpcHost` param from `ScoreManager` No longer used since 3e3b9bc963583fa5f6ad3b822ad3e6e3818aa06c. --- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 8363c41437..1713e73905 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -247,7 +247,7 @@ namespace osu.Game var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, Host, () => difficultyCache, LocalConfig)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, () => difficultyCache, LocalConfig)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true)); dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API)); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 6f9cce2d3c..532c6b42a3 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -32,7 +32,7 @@ namespace osu.Game.Scoring private readonly ScoreModelManager scoreModelManager; public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, Scheduler scheduler, - IIpcHost importHost = null, Func difficulties = null, OsuConfigManager configManager = null) + Func difficulties = null, OsuConfigManager configManager = null) { this.realm = realm; this.scheduler = scheduler; From c6a65ccfeddc3dd190d38b0629fbb9efbd64fab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:22:22 +0100 Subject: [PATCH 217/256] Remove unused parameter from `createContent()` No longer used since 513e470b525c4948d793555beb0df0928b3b9019. --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 1e6899e05f..0c12eff503 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Edit.Timing } Columns = createHeaders(); - Content = value.Select((g, i) => createContent(i, g)).ToArray().ToRectangular(); + Content = value.Select(createContent).ToArray().ToRectangular(); } } @@ -76,7 +76,7 @@ namespace osu.Game.Screens.Edit.Timing return columns.ToArray(); } - private Drawable[] createContent(int index, ControlPointGroup group) + private Drawable[] createContent(ControlPointGroup group) { return new Drawable[] { From 1fa2bf5d69f0c92755f9191acb4ae686b160fa9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:27:14 +0100 Subject: [PATCH 218/256] Remove unused parameter from `createColourBars()` No longer used since b61aa660c63f0e126f32643ef17e7bb458d2a1ae. --- .../Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index a8141c57da..7903e54960 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osuTK; @@ -49,7 +48,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { InternalChild = new FillFlowContainer { @@ -127,7 +126,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters } }; - createColourBars(colours); + createColourBars(); } protected override void LoadComplete() @@ -150,7 +149,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters iconLate.Rotation = -Rotation; } - private void createColourBars(OsuColour colours) + private void createColourBars() { var windows = HitWindows.GetAllAvailableWindows().ToArray(); From a94702b3aeaac4b1dfe1e0adeb71140650bcaabd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:30:28 +0100 Subject: [PATCH 219/256] Remove unused parameters in `LegacyComboCounter` No longer used since 9bb8a43bcee61d11f26598fff2228af1f07a4d17. --- osu.Game/Screens/Play/HUD/LegacyComboCounter.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index 567e4386c6..9510453ba5 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -109,7 +109,7 @@ namespace osu.Game.Screens.Play.HUD return; if (isRolling) - onDisplayedCountRolling(displayedCount, value); + onDisplayedCountRolling(value); else if (displayedCount + 1 == value) onDisplayedCountIncrement(value); else @@ -151,7 +151,7 @@ namespace osu.Game.Screens.Play.HUD if (prev + 1 == Current.Value) onCountIncrement(prev, Current.Value); else - onCountChange(prev, Current.Value); + onCountChange(Current.Value); } else { @@ -226,7 +226,7 @@ namespace osu.Game.Screens.Play.HUD transformRoll(currentValue, newValue); } - private void onCountChange(int currentValue, int newValue) + private void onCountChange(int newValue) { scheduledPopOutCurrentId++; @@ -236,7 +236,7 @@ namespace osu.Game.Screens.Play.HUD DisplayedCount = newValue; } - private void onDisplayedCountRolling(int currentValue, int newValue) + private void onDisplayedCountRolling(int newValue) { if (newValue == 0) displayedCountSpriteText.FadeOut(fade_out_duration); From 07d09b3520210f1233cb024e8419548f342c4001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:32:58 +0100 Subject: [PATCH 220/256] Remove unused parameter from `createGameplayComponents()` No longer used since 136843c8e450507ad0527622af851d648afb1545. --- osu.Game/Screens/Play/Player.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 1556e752ab..240620b686 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -251,7 +251,7 @@ namespace osu.Game.Screens.Play { // underlay and gameplay should have access to the skinning sources. createUnderlayComponents(), - createGameplayComponents(Beatmap.Value, playableBeatmap) + createGameplayComponents(Beatmap.Value) } }, FailOverlay = new FailOverlay @@ -364,7 +364,7 @@ namespace osu.Game.Screens.Play private Drawable createUnderlayComponents() => DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }; - private Drawable createGameplayComponents(IWorkingBeatmap working, IBeatmap playableBeatmap) => new ScalingContainer(ScalingMode.Gameplay) + private Drawable createGameplayComponents(IWorkingBeatmap working) => new ScalingContainer(ScalingMode.Gameplay) { Children = new Drawable[] { From 994fb966b6ba27baf7c178a7483ccf353db46fc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:37:19 +0100 Subject: [PATCH 221/256] Remove `Host` ctor param from `SkinModelManager` No longer used since 29d074bdb8629ff480207498c0eeec61b010a530. --- osu.Game/Skinning/SkinManager.cs | 2 +- osu.Game/Skinning/SkinModelManager.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 06bd0abc9f..bad559d9fe 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -77,7 +77,7 @@ namespace osu.Game.Skinning userFiles = new StorageBackedResourceStore(storage.GetStorageForDirectory("files")); - skinModelManager = new SkinModelManager(storage, realm, host, this); + skinModelManager = new SkinModelManager(storage, realm, this); var defaultSkins = new[] { diff --git a/osu.Game/Skinning/SkinModelManager.cs b/osu.Game/Skinning/SkinModelManager.cs index 0af31100a9..33e49ce486 100644 --- a/osu.Game/Skinning/SkinModelManager.cs +++ b/osu.Game/Skinning/SkinModelManager.cs @@ -27,7 +27,7 @@ namespace osu.Game.Skinning private readonly IStorageResourceProvider skinResources; - public SkinModelManager(Storage storage, RealmAccess realm, GameHost host, IStorageResourceProvider skinResources) + public SkinModelManager(Storage storage, RealmAccess realm, IStorageResourceProvider skinResources) : base(storage, realm) { this.skinResources = skinResources; From 75101b11052c2f02471a8fed6129f9d0e4d5eb76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:39:52 +0100 Subject: [PATCH 222/256] Remove unused ruleset ctor params from test beatmap model managers No longer used since 00e3af3366e80214c7f0c956d144000a78cd0b56. --- .../Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 4 ++-- osu.Game/Tests/Visual/EditorTestScene.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index f9161816e7..79cb424803 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -173,14 +173,14 @@ namespace osu.Game.Tests.Online protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue) { - return new TestBeatmapModelManager(this, storage, realm, rulesets, onlineLookupQueue); + return new TestBeatmapModelManager(this, storage, realm, onlineLookupQueue); } internal class TestBeatmapModelManager : BeatmapModelManager { private readonly TestBeatmapManager testBeatmapManager; - public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) + public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) : base(databaseAccess, storage, beatmapOnlineLookupQueue) { this.testBeatmapManager = testBeatmapManager; diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index f7d62a8694..bcf169bb1e 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue) { - return new TestBeatmapModelManager(storage, realm, rulesets, onlineLookupQueue); + return new TestBeatmapModelManager(storage, realm, onlineLookupQueue); } protected override WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore resources, IResourceStore storage, WorkingBeatmap defaultBeatmap, GameHost host) @@ -150,7 +150,7 @@ namespace osu.Game.Tests.Visual internal class TestBeatmapModelManager : BeatmapModelManager { - public TestBeatmapModelManager(Storage storage, RealmAccess databaseAccess, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) + public TestBeatmapModelManager(Storage storage, RealmAccess databaseAccess, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) : base(databaseAccess, storage, beatmapOnlineLookupQueue) { } From e042f29ee3a12e60eb6d06165db93af1c37faf8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:41:18 +0100 Subject: [PATCH 223/256] Remove skin ctor param from `LegacyCatchComboCounter` No longer used since 004798d61d44d88114acfd7c2861378261eab107. --- .../Skinning/Legacy/CatchLegacySkinTransformer.cs | 2 +- .../Skinning/Legacy/LegacyCatchComboCounter.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index faad95e386..b2a555f89d 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy case CatchSkinComponents.CatchComboCounter: if (providesComboCounter) - return new LegacyCatchComboCounter(Skin); + return new LegacyCatchComboCounter(); return null; diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs index 33c3867f5a..b4d29988d9 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy private readonly LegacyRollingCounter explosion; - public LegacyCatchComboCounter(ISkin skin) + public LegacyCatchComboCounter() { AutoSizeAxes = Axes.Both; From e4028b8fc1b86df31a6882480797cf0094c0a027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:42:58 +0100 Subject: [PATCH 224/256] Remove index ctor param from `ColumnHitObjectArea` No longer used since 5692cecaa47e9e071fb6079d9f3847b8b1de0972. --- .../Skinning/TestSceneColumnHitObjectArea.cs | 4 ++-- osu.Game.Rulesets.Mania/UI/Column.cs | 2 +- osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs index 215f8fb1d5..8034341d15 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { RelativeSizeAxes = Axes.Both, Width = 0.5f, - Child = new ColumnHitObjectArea(0, new HitObjectContainer()) + Child = new ColumnHitObjectArea(new HitObjectContainer()) { RelativeSizeAxes = Axes.Both } @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { RelativeSizeAxes = Axes.Both, Width = 0.5f, - Child = new ColumnHitObjectArea(1, new HitObjectContainer()) + Child = new ColumnHitObjectArea(new HitObjectContainer()) { RelativeSizeAxes = Axes.Both } diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index dc3b86ac7f..a04f5ef98e 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Mania.UI sampleTriggerSource = new GameplaySampleTriggerSource(HitObjectContainer), // For input purposes, the background is added at the highest depth, but is then proxied back below all other elements background.CreateProxy(), - HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both }, + HitObjectArea = new ColumnHitObjectArea(HitObjectContainer) { RelativeSizeAxes = Axes.Both }, new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea()) { RelativeSizeAxes = Axes.Both diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs index f69d2aafdc..51c138f5e1 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components private readonly Drawable hitTarget; - public ColumnHitObjectArea(int columnIndex, HitObjectContainer hitObjectContainer) + public ColumnHitObjectArea(HitObjectContainer hitObjectContainer) : base(hitObjectContainer) { AddRangeInternal(new[] From 7cdf63c6543034a7517ec17949874d16bdd1b47f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:44:59 +0100 Subject: [PATCH 225/256] Remove unused `FindProvider()` methods No longer needed since 39f99bf785676c443f37248668eb9bf45006032e. --- osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs | 1 - osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs | 2 -- osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs | 1 - osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs | 4 ---- 4 files changed, 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index 1f01ba601b..a36f07ff7b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -118,7 +118,6 @@ namespace osu.Game.Rulesets.Osu.Tests public Drawable GetDrawableComponent(ISkinComponent component) => null; public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null; public ISample GetSample(ISampleInfo sampleInfo) => null; - public ISkin FindProvider(Func lookupFunction) => null; public IBindable GetConfig(TLookup lookup) { diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs index 34f70659e3..5553c67141 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs @@ -131,8 +131,6 @@ namespace osu.Game.Tests.Gameplay public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); - public ISkin FindProvider(Func lookupFunction) => null; - public IBindable GetConfig(TLookup lookup) { switch (lookup) diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs index 785f31386d..4209f188cc 100644 --- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs +++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs @@ -61,7 +61,6 @@ namespace osu.Game.Tests.NonVisual.Skinning public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotSupportedException(); public ISample GetSample(ISampleInfo sampleInfo) => throw new NotSupportedException(); public IBindable GetConfig(TLookup lookup) => throw new NotSupportedException(); - public ISkin FindProvider(Func lookupFunction) => null; } private class TestAnimationTimeReference : IAnimationTimeReference diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index 3e8ba69e01..35130f3109 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -301,8 +301,6 @@ namespace osu.Game.Tests.Visual.Gameplay public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); - - public ISkin FindProvider(Func lookupFunction) => throw new NotImplementedException(); } private class SecondarySource : ISkin @@ -314,8 +312,6 @@ namespace osu.Game.Tests.Visual.Gameplay public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); - - public ISkin FindProvider(Func lookupFunction) => throw new NotImplementedException(); } [Cached(typeof(ISkinSource))] From b978010b481fd8e46a7b0448b813a3e547d375ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:47:55 +0100 Subject: [PATCH 226/256] Remove unused `allowMissing` parameter in audio file check test No longer used since 7f95400f466beafe6fa94c002cfd6ed8dc3f4f94. --- .../Editing/Checks/CheckTooShortAudioFilesTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs b/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs index 242fec2f68..53c85defae 100644 --- a/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Editing.Checks beatmap.BeatmapInfo.BeatmapSet.Files.Add(CheckTestHelpers.CreateMockFile("jpg")); // Should fail to load, but not produce an error due to the extension not being expected to load. - Assert.IsEmpty(check.Run(getContext(null, allowMissing: true))); + Assert.IsEmpty(check.Run(getContext(null))); } [Test] @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Editing.Checks { using (var resourceStream = TestResources.OpenResource("Samples/missing.mp3")) { - Assert.IsEmpty(check.Run(getContext(resourceStream, allowMissing: true))); + Assert.IsEmpty(check.Run(getContext(resourceStream))); } } @@ -107,7 +107,7 @@ namespace osu.Game.Tests.Editing.Checks } } - private BeatmapVerifierContext getContext(Stream resourceStream, bool allowMissing = false) + private BeatmapVerifierContext getContext(Stream resourceStream) { var mockWorkingBeatmap = new Mock(beatmap, null, null); mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny())).Returns(resourceStream); From 3674ed15ce7cc4e5b0e50303e04db4af6eba3164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Feb 2022 21:52:16 +0100 Subject: [PATCH 227/256] Remove unused game host parameter No longer used since eeccf836ec04d2e3aac1fec93c15b48aa6672352. --- .../Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 79cb424803..94e61eaee0 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Online { Dependencies.Cache(rulesets = new RulesetStore(Realm)); Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); - Dependencies.CacheAs(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API, host)); + Dependencies.CacheAs(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API)); } [SetUp] @@ -196,7 +196,7 @@ namespace osu.Game.Tests.Online internal class TestBeatmapModelDownloader : BeatmapModelDownloader { - public TestBeatmapModelDownloader(IModelImporter importer, IAPIProvider apiProvider, GameHost gameHost) + public TestBeatmapModelDownloader(IModelImporter importer, IAPIProvider apiProvider) : base(importer, apiProvider) { } From 35b76532903254b3ea434f7fccf9ef95d07be72c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 09:13:19 +0900 Subject: [PATCH 228/256] Revert mod flow changes and add visual test coverage showing an overflow case --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 12 +++++++----- .../Online/Leaderboards/LeaderboardScoreTooltip.cs | 5 ++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 7292ec96ed..667fd08084 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -203,14 +203,16 @@ namespace osu.Game.Tests.Visual.SongSelect new OsuModHardRock(), new OsuModFlashlight { - FollowDelay = { Value = 200 } + FollowDelay = { Value = 200 }, + SizeMultiplier = { Value = 5 }, }, new OsuModDifficultyAdjust { - CircleSize = { Value = 8 }, - ApproachRate = { Value = 7 }, - OverallDifficulty = { Value = 6 }, - DrainRate = { Value = 5 }, + CircleSize = { Value = 11 }, + ApproachRate = { Value = 10 }, + OverallDifficulty = { Value = 10 }, + DrainRate = { Value = 10 }, + ExtendedLimits = { Value = true } } }, Ruleset = new OsuRuleset().RulesetInfo, diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index a0eea94501..c26e9e6802 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -62,9 +62,8 @@ namespace osu.Game.Online.Leaderboards // Mods row modStatistics = new FillFlowContainer { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Full, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, Spacing = new Vector2(5, 0), }, new FillFlowContainer From d065e32ca1f8c77b361593016bedf0487540fcff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 13:23:49 +0900 Subject: [PATCH 229/256] Fix crash due to `MatchLeaderboardScore`s not having populated rulesets --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 2 +- .../OnlinePlay/Match/Components/MatchLeaderboardScore.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index b4aaebd4e4..c2393a5de5 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -71,7 +71,7 @@ namespace osu.Game.Online.Leaderboards private Storage storage { get; set; } public ITooltip GetCustomTooltip() => new LeaderboardScoreTooltip(); - public ScoreInfo TooltipContent => Score; + public virtual ScoreInfo TooltipContent => Score; public LeaderboardScore(ScoreInfo score, int? rank, bool isOnlineScope = true) { diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboardScore.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboardScore.cs index 799c44cc28..cf7e33fd63 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboardScore.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboardScore.cs @@ -14,6 +14,8 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components { private readonly APIUserScoreAggregate score; + public override ScoreInfo TooltipContent => null; // match aggregate scores can't show statistics that the custom tooltip displays. + public MatchLeaderboardScore(APIUserScoreAggregate score, int? rank, bool isOnlineScope = true) : base(score.CreateScoreInfo(), rank, isOnlineScope) { From ddc8094a75babd5fad94ea8b692b8e12a899c461 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 13:34:23 +0900 Subject: [PATCH 230/256] Update description --- osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index 6e942f672b..a65938184c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mania.Mods public override double ScoreMultiplier => 1; - public override string Description => @"Turns all hold notes into normal notes. No coordination required."; + public override string Description => @"Replaces all hold notes with normal notes."; public override IconUsage? Icon => FontAwesome.Solid.DotCircle; From 0036d0e26db709f1661e3cff89364ccb7c6f94b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 13:58:13 +0900 Subject: [PATCH 231/256] Move alternate mod to "conversion" category --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index f68f2b5bee..936bb290b4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => @"Don't use the same key twice in a row!"; public override double ScoreMultiplier => 1.0; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) }; - public override ModType Type => ModType.DifficultyIncrease; + public override ModType Type => ModType.Conversion; public override IconUsage? Icon => FontAwesome.Solid.Keyboard; private bool introEnded; diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 7e8974b5ed..2faecbcda2 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -159,7 +159,6 @@ namespace osu.Game.Rulesets.Osu new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()), new OsuModHidden(), new MultiMod(new OsuModFlashlight(), new OsuModBlinds()), - new OsuModAlternate(), }; case ModType.Conversion: @@ -170,6 +169,7 @@ namespace osu.Game.Rulesets.Osu new OsuModClassic(), new OsuModRandom(), new OsuModMirror(), + new OsuModAlternate(), }; case ModType.Automation: From fed63abd83715cf1f904a53038999b7625e41f4f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 14:02:48 +0900 Subject: [PATCH 232/256] Sanitise interceptor logic to now require two separate check paths --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index 936bb290b4..9d389432e5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -33,8 +33,6 @@ namespace osu.Game.Rulesets.Osu.Mods private OsuAction? lastActionPressed; private DrawableRuleset ruleset; - private bool shouldAlternate => !isBreakTime.Value && introEnded; - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { ruleset = drawableRuleset; @@ -54,16 +52,22 @@ namespace osu.Game.Rulesets.Osu.Mods }; } - private bool onPressed(OsuAction key) + private bool checkCorrectAction(OsuAction action) { - if (lastActionPressed == key) + if (isBreakTime.Value) + return true; + + if (!introEnded) + return true; + + if (lastActionPressed != action) { - ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint); + // User alternated correctly + lastActionPressed = action; return true; } - lastActionPressed = key; - + ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint); return false; } @@ -83,7 +87,8 @@ namespace osu.Game.Rulesets.Osu.Mods } public bool OnPressed(KeyBindingPressEvent e) - => mod.shouldAlternate && mod.onPressed(e.Action); + // if the pressed action is incorrect, block it from reaching gameplay. + => !mod.checkCorrectAction(e.Action); public void OnReleased(KeyBindingReleaseEvent e) { From c7a192cc5fae75bca23b5a714d6ff2fde49b6bbf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 14:04:10 +0900 Subject: [PATCH 233/256] Only handle `LeftButton` and `RightButton` actions There are definitely going to be other actions used in the future, which would immediately cause this mod to fail. Limiting handling to left/right buttons only is the correct way forward. --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index 9d389432e5..b9f25bd1cf 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -60,9 +60,20 @@ namespace osu.Game.Rulesets.Osu.Mods if (!introEnded) return true; + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + break; + + // Any action which is not left or right button should be ignored. + default: + return true; + } + if (lastActionPressed != action) { - // User alternated correctly + // User alternated correctly. lastActionPressed = action; return true; } From a2affefb0aa8ca794ff329a1a7e65e3502450f35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 14:33:17 +0900 Subject: [PATCH 234/256] Avoid checking gameplay clock time in `Update` method --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index b9f25bd1cf..46b97dd23b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -16,7 +16,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModAlternate : Mod, IApplicableToDrawableRuleset, IApplicableToPlayer, IUpdatableByPlayfield + public class OsuModAlternate : Mod, IApplicableToDrawableRuleset, IApplicableToPlayer { public override string Name => @"Alternate"; public override string Acronym => @"AL"; @@ -26,20 +26,23 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Conversion; public override IconUsage? Icon => FontAwesome.Solid.Keyboard; - private bool introEnded; - private double earliestStartTime; + private double firstObjectValidJudgementTime; private IBindable isBreakTime; private const double flash_duration = 1000; private OsuAction? lastActionPressed; private DrawableRuleset ruleset; + private IFrameStableClock gameplayClock; + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { ruleset = drawableRuleset; drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this)); var firstHitObject = ruleset.Objects.FirstOrDefault(); - earliestStartTime = (firstHitObject?.StartTime ?? 0) - (firstHitObject?.HitWindows.WindowFor(HitResult.Meh) ?? 0); + firstObjectValidJudgementTime = (firstHitObject?.StartTime ?? 0) - (firstHitObject?.HitWindows.WindowFor(HitResult.Meh) ?? 0); + + gameplayClock = drawableRuleset.FrameStableClock; } public void ApplyToPlayer(Player player) @@ -57,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (isBreakTime.Value) return true; - if (!introEnded) + if (gameplayClock.CurrentTime < firstObjectValidJudgementTime) return true; switch (action) @@ -82,12 +85,6 @@ namespace osu.Game.Rulesets.Osu.Mods return false; } - public void Update(Playfield playfield) - { - if (!introEnded) - introEnded = playfield.Clock.CurrentTime > earliestStartTime; - } - private class InputInterceptor : Component, IKeyBindingHandler { private readonly OsuModAlternate mod; From 6e60e68b809be7e1d93f3a80371f1ba551d45c43 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 2 Feb 2022 14:44:06 +0900 Subject: [PATCH 235/256] Change from click to mousedown+mouseup and only play when cursor is visible --- osu.Game/Graphics/Cursor/MenuCursor.cs | 29 +++++++++++++++----------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index a89d8dac71..0cc751ea21 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -76,18 +76,6 @@ namespace osu.Game.Graphics.Cursor return base.OnMouseMove(e); } - protected override bool OnClick(ClickEvent e) - { - var channel = tapSample.GetChannel(); - - // scale to [-0.75, 0.75] so that the sample isn't fully panned left or right (sounds weird) - channel.Balance.Value = ((activeCursor.X / DrawWidth) * 2 - 1) * 0.75; - channel.Frequency.Value = 0.99 + RNG.NextDouble(0.02); - channel.Play(); - - return base.OnClick(e); - } - protected override bool OnMouseDown(MouseDownEvent e) { if (State.Value == Visibility.Visible) @@ -105,6 +93,8 @@ namespace osu.Game.Graphics.Cursor dragRotationState = DragRotationState.DragStarted; positionMouseDown = e.MousePosition; } + + playTapSample(); } return base.OnMouseDown(e); @@ -122,6 +112,9 @@ namespace osu.Game.Graphics.Cursor activeCursor.RotateTo(0, 600 * (1 + Math.Abs(activeCursor.Rotation / 720)), Easing.OutElasticHalf); dragRotationState = DragRotationState.NotDragging; } + + if (State.Value == Visibility.Visible) + playTapSample(0.8); } base.OnMouseUp(e); @@ -139,6 +132,18 @@ namespace osu.Game.Graphics.Cursor activeCursor.ScaleTo(0.6f, 250, Easing.In); } + private void playTapSample(double baseFrequency = 1f) + { + const float random_range = 0.02f; + SampleChannel channel = tapSample.GetChannel(); + + // Scale to [-0.75, 0.75] so that the sample isn't fully panned left or right (sounds weird) + channel.Balance.Value = ((activeCursor.X / DrawWidth) * 2 - 1) * 0.75; + channel.Frequency.Value = baseFrequency - (random_range / 2f) + RNG.NextDouble(random_range); + + channel.Play(); + } + public class Cursor : Container { private Container cursorContainer; From 0c5da9370a239b957f6d7a2fce929c0b7cff323c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 14:51:55 +0900 Subject: [PATCH 236/256] Fix rulesets potentially being marked `Available` even when methods are missing Came up when running the game after the recent breaking changes (https://github.com/ppy/osu/pull/16722), where two template rulesets I had loaded were erroring on startup but still being marked as available, allowing them to crash the game on attempting to initiate relpay logic. These cases are already handled for first-time ruleset loading via the `GetTypes()` enumeration in `RulesetStore.addRuleset`, but when consistency checking already present rulesets the only runtime validation being done was `ruleset.CreateInstance()`, which does not handle missing types or methods. --- osu.Game/Rulesets/RulesetStore.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index d017d54ed9..dd25005006 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -149,6 +149,10 @@ namespace osu.Game.Rulesets var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo ?? throw new RulesetLoadException(@"Instantiation failure"); + // If a ruleset isn't up-to-date with the API, it could cause a crash at an arbitrary point of execution. + // To eagerly handle cases of missing implementations, enumerate all types here and mark as non-available on throw. + resolvedType.Assembly.GetTypes(); + r.Name = instanceInfo.Name; r.ShortName = instanceInfo.ShortName; r.InstantiationInfo = instanceInfo.InstantiationInfo; From e7d72f1823a5a97db761b0712851128942668366 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 15:10:56 +0900 Subject: [PATCH 237/256] Revert recent changes --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 80 +++++++++++++++---- 1 file changed, 66 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index a383b533fd..cfe3312415 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -11,8 +11,8 @@ using osu.Game.Rulesets.Osu.UI; using osuTK; using System.Linq; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Configuration; using osu.Framework.Bindables; +using osu.Game.Configuration; namespace osu.Game.Rulesets.Osu.Mods { @@ -34,10 +34,16 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 1.0f, }; - private double? lastUpdate; + private const float spin_radius = 30; + + private Vector2? prevCursorPos; + private OsuInputManager inputManager; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { + // Grab the input manager for future use + inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; + // Hide judgment displays and follow points drawableRuleset.Playfield.DisplayJudgements.Value = false; (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); @@ -45,29 +51,75 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { + var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; double currentTime = playfield.Clock.CurrentTime; - if (currentTime - (lastUpdate ?? double.MinValue) < 100) - return; - - Vector2 cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; - + // Move all currently alive object to new destination foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { var h = drawable.HitObject; - if (currentTime < h.StartTime && (drawable is DrawableHitCircle || drawable is DrawableSlider)) + switch (drawable) { - double timeMoving = currentTime - (h.StartTime - h.TimePreempt); - float percentDoneMoving = (float)(timeMoving / h.TimePreempt); - float percentDistLeft = Math.Clamp(AssistStrength.Value - percentDoneMoving + 0.1f, 0, 1); + case DrawableHitCircle circle: - Vector2 targetPos = drawable.Position + percentDistLeft * (cursorPos - drawable.Position); - drawable.MoveTo(targetPos, h.StartTime - currentTime); + // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast + circle.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + // FIXME: some circles cause flash at original(?) position when clicked too early + + break; + + case DrawableSlider slider: + + // Move slider to cursor + if (currentTime < h.StartTime) + { + slider.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + } + // Move slider so that sliderball stays on the cursor + else + { + slider.HeadCircle.Hide(); // hide flash, triangles, ... so they don't move with slider + slider.MoveTo(cursorPos - slider.Ball.DrawPosition); + // FIXME: some sliders re-appearing at their original position for a single frame when they're done + } + + break; + + case DrawableSpinner spinner: + + // Move spinner _next_ to cursor + if (currentTime < h.StartTime) + { + spinner.MoveTo(cursorPos + new Vector2(0, -spin_radius), Math.Max(0, h.StartTime - currentTime - 10)); + } + else + { + // Move spinner visually + Vector2 delta = spin_radius * (spinner.Position - prevCursorPos ?? cursorPos).Normalized(); + const float angle = 3 * MathF.PI / 180; // radians per update, arbitrary value + + // Rotation matrix + var targetPos = new Vector2( + delta.X * MathF.Cos(angle) - delta.Y * MathF.Sin(angle) + cursorPos.X, + delta.X * MathF.Sin(angle) + delta.Y * MathF.Cos(angle) + cursorPos.Y + ); + + spinner.MoveTo(targetPos); + + // Move spinner logically + if (inputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false) + { + // Arbitrary value, might lead to some inconsistencies depending on clock rate, replay, ... + spinner.RotationTracker.AddRotation(2 * MathF.PI); + } + } + + break; } } - lastUpdate = currentTime; + prevCursorPos = cursorPos; } } } From 104256a054150963b6ffd5a4b68c14fedcfff834 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 15:06:04 +0900 Subject: [PATCH 238/256] Add test coverage --- .../Mods/TestSceneOsuModAimAssist.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs new file mode 100644 index 0000000000..8fa7e0cd09 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Rulesets.Osu.Mods; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public class TestSceneOsuModAimAssist : OsuModTestScene + { + [Test] + public void TestAimAssist() + { + var mod = new OsuModAimAssist(); + + CreateModTest(new ModTestData + { + Autoplay = false, + Mod = mod, + }); + } + } +} From 334ed2c9c488ec63e786d9197f756aa5bdaeb3b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 15:36:09 +0900 Subject: [PATCH 239/256] Fix sliders moving before they are actually hit --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index cfe3312415..a3c5638ea7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Mods case DrawableSlider slider: // Move slider to cursor - if (currentTime < h.StartTime) + if (!slider.HeadCircle.Result.HasResult) { slider.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); } From f07502ac5fadab49871be06c51bb29d95a7cb32a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 16:15:06 +0900 Subject: [PATCH 240/256] Use simple damp easing rather than transforms --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 69 ++++++++++--------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index a3c5638ea7..db83ee09af 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -2,17 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.UI; -using osu.Game.Rulesets.Osu.UI; -using osuTK; using System.Linq; -using osu.Game.Rulesets.Osu.Objects; using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Utils; using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; +using osuTK; namespace osu.Game.Rulesets.Osu.Mods { @@ -26,17 +27,18 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; + private IFrameStableClock gameplayClock; + [SettingSource("Assist strength", "Change the distance notes should travel towards you.", 0)] - public BindableFloat AssistStrength { get; } = new BindableFloat(0.3f) + public BindableFloat AssistStrength { get; } = new BindableFloat(0.5f) { Precision = 0.05f, - MinValue = 0.0f, + MinValue = 0.1f, MaxValue = 1.0f, }; - private const float spin_radius = 30; + private const float spin_radius = 50; - private Vector2? prevCursorPos; private OsuInputManager inputManager; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) @@ -44,6 +46,8 @@ namespace osu.Game.Rulesets.Osu.Mods // Grab the input manager for future use inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; + gameplayClock = drawableRuleset.FrameStableClock; + // Hide judgment displays and follow points drawableRuleset.Playfield.DisplayJudgements.Value = false; (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); @@ -62,10 +66,7 @@ namespace osu.Game.Rulesets.Osu.Mods switch (drawable) { case DrawableHitCircle circle: - - // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast - circle.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); - // FIXME: some circles cause flash at original(?) position when clicked too early + easeTo(circle, cursorPos); break; @@ -74,13 +75,13 @@ namespace osu.Game.Rulesets.Osu.Mods // Move slider to cursor if (!slider.HeadCircle.Result.HasResult) { - slider.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + easeTo(slider, cursorPos); } // Move slider so that sliderball stays on the cursor else { slider.HeadCircle.Hide(); // hide flash, triangles, ... so they don't move with slider - slider.MoveTo(cursorPos - slider.Ball.DrawPosition); + easeTo(slider, cursorPos - slider.Ball.DrawPosition); // FIXME: some sliders re-appearing at their original position for a single frame when they're done } @@ -91,35 +92,39 @@ namespace osu.Game.Rulesets.Osu.Mods // Move spinner _next_ to cursor if (currentTime < h.StartTime) { - spinner.MoveTo(cursorPos + new Vector2(0, -spin_radius), Math.Max(0, h.StartTime - currentTime - 10)); + easeTo(spinner, cursorPos + new Vector2(0, -spin_radius)); } else { // Move spinner visually - Vector2 delta = spin_radius * (spinner.Position - prevCursorPos ?? cursorPos).Normalized(); - const float angle = 3 * MathF.PI / 180; // radians per update, arbitrary value - - // Rotation matrix - var targetPos = new Vector2( - delta.X * MathF.Cos(angle) - delta.Y * MathF.Sin(angle) + cursorPos.X, - delta.X * MathF.Sin(angle) + delta.Y * MathF.Cos(angle) + cursorPos.Y - ); - - spinner.MoveTo(targetPos); + Vector2 delta = new Vector2(spin_radius); + float angle = (float)gameplayClock.CurrentTime * 10; // Move spinner logically if (inputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false) { - // Arbitrary value, might lead to some inconsistencies depending on clock rate, replay, ... - spinner.RotationTracker.AddRotation(2 * MathF.PI); + var targetPos = new Vector2( + delta.X * MathF.Cos(angle) - delta.Y * MathF.Sin(angle) + cursorPos.X, + delta.X * MathF.Sin(angle) + delta.Y * MathF.Cos(angle) + cursorPos.Y + ); + + easeTo(spinner, targetPos); } } break; } } + } - prevCursorPos = cursorPos; + private void easeTo(DrawableHitObject hitObject, Vector2 destination) + { + double dampLength = Interpolation.Lerp(500, 50, AssistStrength.Value); + + float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); + float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); + + hitObject.Position = new Vector2(x, y); } } } From 987aa5a21c043360d71f0155b52d70e77ddd3a5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 16:15:14 +0900 Subject: [PATCH 241/256] Add testing of different strengths --- .../Mods/TestSceneOsuModAimAssist.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs index 8fa7e0cd09..b8310bc4e7 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs @@ -8,15 +8,19 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { public class TestSceneOsuModAimAssist : OsuModTestScene { - [Test] - public void TestAimAssist() + [TestCase(0.1f)] + [TestCase(0.5f)] + [TestCase(1)] + public void TestAimAssist(float strength) { - var mod = new OsuModAimAssist(); - CreateModTest(new ModTestData { + Mod = new OsuModAimAssist + { + AssistStrength = { Value = strength }, + }, + PassCondition = () => true, Autoplay = false, - Mod = mod, }); } } From 2e46404fe5e10f5279730ad61f90be5a805830b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 16:15:49 +0900 Subject: [PATCH 242/256] Remove spinner support for now --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index db83ee09af..e3a68bcbcc 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -37,8 +37,6 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 1.0f, }; - private const float spin_radius = 50; - private OsuInputManager inputManager; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) @@ -82,34 +80,6 @@ namespace osu.Game.Rulesets.Osu.Mods { slider.HeadCircle.Hide(); // hide flash, triangles, ... so they don't move with slider easeTo(slider, cursorPos - slider.Ball.DrawPosition); - // FIXME: some sliders re-appearing at their original position for a single frame when they're done - } - - break; - - case DrawableSpinner spinner: - - // Move spinner _next_ to cursor - if (currentTime < h.StartTime) - { - easeTo(spinner, cursorPos + new Vector2(0, -spin_radius)); - } - else - { - // Move spinner visually - Vector2 delta = new Vector2(spin_radius); - float angle = (float)gameplayClock.CurrentTime * 10; - - // Move spinner logically - if (inputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false) - { - var targetPos = new Vector2( - delta.X * MathF.Cos(angle) - delta.Y * MathF.Sin(angle) + cursorPos.X, - delta.X * MathF.Sin(angle) + delta.Y * MathF.Cos(angle) + cursorPos.Y - ); - - easeTo(spinner, targetPos); - } } break; From 6e41a6e704512ea2a355ae23d5793435ce464ef7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 16:26:10 +0900 Subject: [PATCH 243/256] Tidy up code into a presentable state --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 31 +++++-------------- 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index e3a68bcbcc..ed4b139e00 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; @@ -23,30 +22,26 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Acronym => "AA"; public override IconUsage? Icon => FontAwesome.Solid.MousePointer; public override ModType Type => ModType.Fun; - public override string Description => "No need to chase the circle, the circle chases you"; + public override string Description => "No need to chase the circle – the circle chases you!"; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; private IFrameStableClock gameplayClock; - [SettingSource("Assist strength", "Change the distance notes should travel towards you.", 0)] + [SettingSource("Assist strength", "How much this mod will assist you.", 0)] public BindableFloat AssistStrength { get; } = new BindableFloat(0.5f) { Precision = 0.05f, - MinValue = 0.1f, + MinValue = 0.05f, MaxValue = 1.0f, }; - private OsuInputManager inputManager; - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - // Grab the input manager for future use - inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; - gameplayClock = drawableRuleset.FrameStableClock; - // Hide judgment displays and follow points + // Hide judgment displays and follow points as they won't make any sense. + // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. drawableRuleset.Playfield.DisplayJudgements.Value = false; (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); } @@ -54,33 +49,21 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; - double currentTime = playfield.Clock.CurrentTime; - // Move all currently alive object to new destination - foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) + foreach (var drawable in playfield.HitObjectContainer.AliveObjects) { - var h = drawable.HitObject; - switch (drawable) { case DrawableHitCircle circle: easeTo(circle, cursorPos); - break; case DrawableSlider slider: - // Move slider to cursor if (!slider.HeadCircle.Result.HasResult) - { easeTo(slider, cursorPos); - } - // Move slider so that sliderball stays on the cursor else - { - slider.HeadCircle.Hide(); // hide flash, triangles, ... so they don't move with slider easeTo(slider, cursorPos - slider.Ball.DrawPosition); - } break; } @@ -89,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Mods private void easeTo(DrawableHitObject hitObject, Vector2 destination) { - double dampLength = Interpolation.Lerp(500, 50, AssistStrength.Value); + double dampLength = Interpolation.Lerp(3000, 40, AssistStrength.Value); float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); From 4758de226beda4f592aecef6f2ec2d8d0c390f03 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 16:27:59 +0900 Subject: [PATCH 244/256] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index f85a96f819..963b61a2d3 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index aa6fb93aa0..cc68393eae 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index fbb4688588..846617c2f7 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + From b5fb3b7dae02bdbaeec37e535d447bb09fcfbaeb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 2 Feb 2022 18:42:20 +0900 Subject: [PATCH 245/256] Fix crash when selecting swap mod as freemod --- .../TestSceneMultiplayerMatchSubScreen.cs | 25 +++++++++++++++++++ .../Participants/ParticipantPanel.cs | 8 ++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 9d14d80d07..869fb17317 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -15,8 +15,12 @@ using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Taiko; +using osu.Game.Rulesets.Taiko.Mods; +using osu.Game.Rulesets.UI; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; +using osu.Game.Screens.OnlinePlay.Multiplayer.Participants; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; @@ -77,6 +81,27 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for join", () => RoomJoined); } + [Test] + public void TestTaikoOnlyMod() + { + AddStep("add playlist item", () => + { + SelectedRoom.Value.Playlist.Add(new PlaylistItem + { + Beatmap = { Value = new TestBeatmap(new TaikoRuleset().RulesetInfo).BeatmapInfo }, + Ruleset = { Value = new TaikoRuleset().RulesetInfo }, + AllowedMods = { new TaikoModSwap() } + }); + }); + + ClickButtonWhenEnabled(); + + AddUntilStep("wait for join", () => RoomJoined); + + AddStep("select swap mod", () => Client.ChangeUserMods(API.LocalUser.Value.OnlineID, new[] { new TaikoModSwap() })); + AddUntilStep("participant panel has mod", () => this.ChildrenOfType().Any(p => p.ChildrenOfType().Any(m => m.Mod is TaikoModSwap))); + } + [Test] public void TestSettingValidity() { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 8fbaebadfe..96a665f33d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -18,6 +19,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Screens.Play.HUD; using osu.Game.Users; @@ -184,8 +186,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants const double fade_time = 50; - // Todo: Should use the room's selected item to determine ruleset. - var ruleset = rulesets.GetRuleset(0)?.CreateInstance(); + var currentItem = Playlist.GetCurrentItem(); + Debug.Assert(currentItem != null); + + var ruleset = rulesets.GetRuleset(currentItem.RulesetID)?.CreateInstance(); int? currentModeRank = ruleset != null ? User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank : null; userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty; From 074a69163558c6ee6c2b177e97ed53126ab8b805 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 2 Feb 2022 17:16:35 +0300 Subject: [PATCH 246/256] Set keyboard step to `0.1` for difficulty adjust sliders --- osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index 45873a321a..c8e7284f5d 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -105,6 +105,7 @@ namespace osu.Game.Rulesets.Mods { ShowsDefaultIndicator = false, Current = currentNumber, + KeyboardStep = 0.1f, } }; From 74637444070838cc55bd0d7e04f9580f13552737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 2 Feb 2022 19:17:33 +0100 Subject: [PATCH 247/256] Fix osu! autoplay-like mods not declaring incompatibility with `AimAssist` --- osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs index 106edfb623..2668013321 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModAutoplay : ModAutoplay { - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAimAssist), typeof(OsuModAutopilot), typeof(OsuModSpunOut) }).ToArray(); public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs index f478790134..ff31cfcd18 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModCinema : ModCinema { - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAimAssist), typeof(OsuModAutopilot), typeof(OsuModSpunOut) }).ToArray(); public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { From 82f9ad63f514f1220c44bb087d58a154a1e1f2c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 2 Feb 2022 20:41:25 +0100 Subject: [PATCH 248/256] Fix flashlight size multiplier printing with too many decimal digits --- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 2 +- osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 2 +- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 2 +- osu.Game/Rulesets/Mods/ModFlashlight.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 2d92c925d7..d576ea3df8 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Mods public override double ScoreMultiplier => 1.12; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] - public override BindableNumber SizeMultiplier { get; } = new BindableNumber + public override BindableFloat SizeMultiplier { get; } = new BindableFloat { MinValue = 0.5f, MaxValue = 1.5f, diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 1ee4ea12e3..8ef5bfd94c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Mods public override Type[] IncompatibleMods => new[] { typeof(ModHidden) }; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] - public override BindableNumber SizeMultiplier { get; } = new BindableNumber + public override BindableFloat SizeMultiplier { get; } = new BindableFloat { MinValue = 0.5f, MaxValue = 3f, diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index b4eff57c55..38c84be295 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Mods }; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] - public override BindableNumber SizeMultiplier { get; } = new BindableNumber + public override BindableFloat SizeMultiplier { get; } = new BindableFloat { MinValue = 0.5f, MaxValue = 2f, diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index fb07c687bb..beec785fe8 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public override double ScoreMultiplier => 1.12; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] - public override BindableNumber SizeMultiplier { get; } = new BindableNumber + public override BindableFloat SizeMultiplier { get; } = new BindableFloat { MinValue = 0.5f, MaxValue = 1.5f, diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index e6487c6b29..b449f3f64d 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mods public override string Description => "Restricted view area."; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] - public abstract BindableNumber SizeMultiplier { get; } + public abstract BindableFloat SizeMultiplier { get; } [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] public abstract BindableBool ComboBasedSize { get; } From d9a43b4c4ce50d58ff4d12393c9b4f4c66bc602d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 3 Feb 2022 13:16:54 +0900 Subject: [PATCH 249/256] Fix API requests not completing when offline --- osu.Game/Online/API/APIAccess.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 8d91548149..1d2abe0c7f 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -399,7 +399,10 @@ namespace osu.Game.Online.API lock (queue) { if (state.Value == APIState.Offline) + { + request.Fail(new WebException("Disconnected from server")); return; + } queue.Enqueue(request); } From 62fa915193ba65a386d647ba63b0e5d422d18c64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 13:58:55 +0900 Subject: [PATCH 250/256] Standardise exception messages for local-user-logged-out flows --- osu.Game/Online/API/APIAccess.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 1d2abe0c7f..a1b8e5bee7 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -400,7 +400,7 @@ namespace osu.Game.Online.API { if (state.Value == APIState.Offline) { - request.Fail(new WebException("Disconnected from server")); + failFromAPIOffline(request); return; } @@ -419,7 +419,7 @@ namespace osu.Game.Online.API if (failOldRequests) { foreach (var req in oldQueueRequests) - req.Fail(new WebException(@"Disconnected from server")); + failFromAPIOffline(req); } } } @@ -440,6 +440,13 @@ namespace osu.Game.Online.API flushQueue(); } + private void failFromAPIOffline(APIRequest req) + { + Debug.Assert(state.Value == APIState.Offline); + + req.Fail(new WebException(@"User not logged in")); + } + private static APIUser createGuestUser() => new GuestUser(); protected override void Dispose(bool isDisposing) From a69c7a9de6f80b3b95b628abc1b5711c6338743e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 14:09:27 +0900 Subject: [PATCH 251/256] Split exceptions back out to give better messaging --- osu.Game/Online/API/APIAccess.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index a1b8e5bee7..c5302a393c 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -400,7 +400,7 @@ namespace osu.Game.Online.API { if (state.Value == APIState.Offline) { - failFromAPIOffline(request); + request.Fail(new WebException(@"User not logged in")); return; } @@ -419,7 +419,7 @@ namespace osu.Game.Online.API if (failOldRequests) { foreach (var req in oldQueueRequests) - failFromAPIOffline(req); + req.Fail(new WebException($@"Request failed from flush operation (state {state.Value})")); } } } @@ -440,13 +440,6 @@ namespace osu.Game.Online.API flushQueue(); } - private void failFromAPIOffline(APIRequest req) - { - Debug.Assert(state.Value == APIState.Offline); - - req.Fail(new WebException(@"User not logged in")); - } - private static APIUser createGuestUser() => new GuestUser(); protected override void Dispose(bool isDisposing) From c8ce00b26a5c9ca1049274cd7eee7e0c2f54e075 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 14:50:40 +0900 Subject: [PATCH 252/256] Trigger a re-layout of HUD components when scoring mode is changed This is a simple way of fixing the layout of scoring elements overlapping due to different score display width requirements of different scoring modes. It will only resolve the case where a user hasn't customsied the layout of the default skins, but as this is a very simple / low effort implementation for the most common scenario, I think it makes sense. Closes https://github.com/ppy/osu/issues/16067. --- osu.Game/Screens/Play/HUDOverlay.cs | 30 +++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index fdb5d418f3..628452fbc8 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -17,6 +17,7 @@ using osu.Game.Input.Bindings; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; @@ -83,10 +84,7 @@ namespace osu.Game.Screens.Play Children = new Drawable[] { CreateFailingLayer(), - mainComponents = new SkinnableTargetContainer(SkinnableTarget.MainHUDComponents) - { - RelativeSizeAxes = Axes.Both, - }, + mainComponents = new MainComponentsContainer(), topRightElements = new FillFlowContainer { Anchor = Anchor.TopRight, @@ -325,5 +323,29 @@ namespace osu.Game.Screens.Play break; } } + + private class MainComponentsContainer : SkinnableTargetContainer + { + private Bindable scoringMode; + + [Resolved] + private OsuConfigManager config { get; set; } + + public MainComponentsContainer() + : base(SkinnableTarget.MainHUDComponents) + { + RelativeSizeAxes = Axes.Both; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // When the scoring mode changes, relative positions of elements may change (see DefaultSkin.GetDrawableComponent). + // This is a best effort implementation for cases where users haven't customised layouts. + scoringMode = config.GetBindable(OsuSetting.ScoreDisplayMode); + scoringMode.BindValueChanged(val => Reload()); + } + } } } From 6355ac66634834124adfb30be16a3a3d138ada9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 15:10:06 +0900 Subject: [PATCH 253/256] Wait for `DialogOverlay` load in more tests Apparently the previous fix was not enough as this can still be seen failing (https://github.com/ppy/osu/runs/5046718623?check_suite_focus=true). This change is copying from what other tests use seemingly reliably, such as `TestScenePerformFromScreen`) --- osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index ed484e03f6..e31377b96e 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -128,6 +128,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("choose clear all scores", () => InputManager.Key(Key.Number4)); + AddUntilStep("wait for dialog display", () => Game.Dependencies.Get().IsLoaded); AddUntilStep("wait for dialog", () => Game.Dependencies.Get().CurrentDialog != null); AddStep("confirm deletion", () => InputManager.Key(Key.Number1)); AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get().CurrentDialog == null); @@ -172,6 +173,7 @@ namespace osu.Game.Tests.Visual.Navigation InputManager.Click(MouseButton.Left); }); + AddUntilStep("wait for dialog display", () => Game.Dependencies.Get().IsLoaded); AddUntilStep("wait for dialog", () => Game.Dependencies.Get().CurrentDialog != null); AddStep("confirm deletion", () => InputManager.Key(Key.Number1)); AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get().CurrentDialog == null); From 41aa4b8cca63be85c7a8434618c0dc3189f04f87 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 16:04:05 +0900 Subject: [PATCH 254/256] Fix `TestSelectingFilteredRuleset` failing under visual tests due to using local database --- .../SongSelect/TestSceneBeatmapCarousel.cs | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index a23bc620ec..4e46901e08 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -601,7 +601,7 @@ namespace osu.Game.Tests.Visual.SongSelect { BeatmapSetInfo testMixed = null; - createCarousel(); + createCarousel(new List()); AddStep("add mixed ruleset beatmapset", () => { @@ -765,22 +765,22 @@ namespace osu.Game.Tests.Visual.SongSelect { bool changed = false; - createCarousel(c => + if (beatmapSets == null) + { + beatmapSets = new List(); + + for (int i = 1; i <= (count ?? set_count); i++) + { + beatmapSets.Add(randomDifficulties + ? TestResources.CreateTestBeatmapSetInfo() + : TestResources.CreateTestBeatmapSetInfo(3)); + } + } + + createCarousel(beatmapSets, c => { carouselAdjust?.Invoke(c); - if (beatmapSets == null) - { - beatmapSets = new List(); - - for (int i = 1; i <= (count ?? set_count); i++) - { - beatmapSets.Add(randomDifficulties - ? TestResources.CreateTestBeatmapSetInfo() - : TestResources.CreateTestBeatmapSetInfo(3)); - } - } - carousel.Filter(initialCriteria?.Invoke() ?? new FilterCriteria()); carousel.BeatmapSetsChanged = () => changed = true; carousel.BeatmapSets = beatmapSets; @@ -789,7 +789,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("Wait for load", () => changed); } - private void createCarousel(Action carouselAdjust = null, Container target = null) + private void createCarousel(List beatmapSets, Action carouselAdjust = null, Container target = null) { AddStep("Create carousel", () => { @@ -803,6 +803,8 @@ namespace osu.Game.Tests.Visual.SongSelect carouselAdjust?.Invoke(carousel); + carousel.BeatmapSets = beatmapSets; + (target ?? this).Child = carousel; }); } From e65996efc31b5c17d7386baa1548d204889849ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 17:14:38 +0900 Subject: [PATCH 255/256] Rename variable to match purpose better --- osu.Game/Screens/Select/SongSelect.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 44af63a554..ee807762bf 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -110,7 +110,7 @@ namespace osu.Game.Screens.Select private double audioFeedbackLastPlaybackTime; - private bool randomClicked; + private bool randomSelectionPending; [Resolved] private MusicController music { get; set; } @@ -321,12 +321,12 @@ namespace osu.Game.Screens.Select { NextRandom = () => { - randomClicked = true; + randomSelectionPending = true; Carousel.SelectNextRandom(); }, PreviousRandom = () => { - randomClicked = true; + randomSelectionPending = true; Carousel.SelectPreviousRandom(); } }, null), @@ -498,7 +498,7 @@ namespace osu.Game.Screens.Select { if (beatmap != null && beatmapInfoPrevious != null && Time.Current - audioFeedbackLastPlaybackTime >= 50) { - if (randomClicked) + if (randomSelectionPending) sampleRandomBeatmap.Play(); else if (beatmap.BeatmapSet?.ID == beatmapInfoPrevious.BeatmapSet?.ID) sampleChangeDifficulty.Play(); @@ -508,7 +508,7 @@ namespace osu.Game.Screens.Select audioFeedbackLastPlaybackTime = Time.Current; } - randomClicked = false; + randomSelectionPending = false; beatmapInfoPrevious = beatmap; } From 6d6327d3da550900a717b166a8220f09ab1dde16 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Feb 2022 18:40:10 +0900 Subject: [PATCH 256/256] Fix test beatmap loading potentially performing selection before carousel itself is loaded --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 0f5c708ab8..f17daa8697 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -108,7 +108,7 @@ namespace osu.Game.Screens.Select set { loadedTestBeatmaps = true; - loadBeatmapSets(value); + Schedule(() => loadBeatmapSets(value)); } }