From b51a457e5a7231795708e1060c1557cc3809233a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Mar 2019 14:36:30 +0900 Subject: [PATCH 001/655] Implement sorcerer's diffcalc changes --- .../Difficulty/CatchDifficultyCalculator.cs | 2 +- .../Preprocessing/CatchDifficultyHitObject.cs | 6 +-- .../Difficulty/Skills/Movement.cs | 46 ++++++++++++++----- 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 8cfda5d532..2cb71428b9 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty { public class CatchDifficultyCalculator : DifficultyCalculator { - private const double star_scaling_factor = 0.145; + private const double star_scaling_factor = 0.15; protected override int SectionLength => 750; diff --git a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs index 24e526ed19..f0c68e4392 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing public readonly float LastNormalizedPosition; /// - /// Milliseconds elapsed since the start time of the previous , with a minimum of 25ms. + /// Milliseconds elapsed since the start time of the previous , with a minimum of 40ms. /// public readonly double StrainTime; @@ -34,8 +34,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing NormalizedPosition = BaseObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor; LastNormalizedPosition = LastObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor; - // Every strain interval is hard capped at the equivalent of 600 BPM streaming speed as a safety measure - StrainTime = Math.Max(25, DeltaTime); + // Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure + StrainTime = Math.Max(40, DeltaTime); } } } diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index d146153294..b1b5ba0312 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -14,7 +14,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills { private const float absolute_player_positioning_error = 16f; private const float normalized_hitobject_radius = 41.0f; - private const double direction_change_bonus = 12.5; + private const double direction_change_bonus = 9.5; + private const double antiflow_bonus = 25.0; protected override double SkillMultiplier => 850; protected override double StrainDecayBase => 0.2; @@ -23,6 +24,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills private float? lastPlayerPosition; private float lastDistanceMoved; + private double lastStrainTime; protected override double StrainValueOf(DifficultyHitObject current) { @@ -39,8 +41,11 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills float distanceMoved = playerPosition - lastPlayerPosition.Value; - double distanceAddition = Math.Pow(Math.Abs(distanceMoved), 1.3) / 500; - double sqrtStrain = Math.Sqrt(catchCurrent.StrainTime); + // Reduce speed scaling + double weightedStrainTime = catchCurrent.StrainTime + 20; + + double distanceAddition = Math.Pow(Math.Abs(distanceMoved), 1.3) / 600; + double sqrtStrain = Math.Sqrt(weightedStrainTime); double bonus = 0; @@ -53,33 +58,50 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills distanceAddition += direction_change_bonus / sqrtStrain * bonusFactor; - // Bonus for tougher direction switches and "almost" hyperdashes at this point - if (catchCurrent.LastObject.DistanceToHyperDash <= 10 / CatchPlayfield.BASE_WIDTH) - bonus = 0.3 * bonusFactor; + // Direction changes after jumps (antiflow) are harder + double antiflowBonusFactor = Math.Min(Math.Sqrt(Math.Abs(distanceMoved)) / 10, 1); + + distanceAddition += (antiflow_bonus / sqrtStrain) * (Math.Sqrt(Math.Abs(lastDistanceMoved)) / (lastStrainTime / 40 + 10.0)) * antiflowBonusFactor; } // Base bonus for every movement, giving some weight to streams. - distanceAddition += 7.5 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain; + distanceAddition += 10.0 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain; } - // Bonus for "almost" hyperdashes at corner points - if (catchCurrent.LastObject.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH) + // Big bonus for edge hyperdashes + if (catchCurrent.LastObject.DistanceToHyperDash <= 14.0f / CatchPlayfield.BASE_WIDTH) { if (!catchCurrent.LastObject.HyperDash) - bonus += 1.0; + bonus += 5.0; else { // After a hyperdash we ARE in the correct position. Always! playerPosition = catchCurrent.NormalizedPosition; } - distanceAddition *= 1.0 + bonus * ((10 - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10); + distanceAddition *= 1.0 + bonus * (14 - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 14 * (Math.Min(catchCurrent.StrainTime, 180) / 180); // Edge dashes are easier at lower ms values + } + + // Prevent wide, dense stacks of notes which fit on the catcher from greatly increasing SR + if (Math.Abs(distanceMoved) > 0.1) + { + if (Math.Abs(lastDistanceMoved) > 0.1 && Math.Sign(distanceMoved) != Math.Sign(lastDistanceMoved)) + { + if (Math.Abs(distanceMoved) <= (CatcherArea.CATCHER_SIZE) && Math.Abs(lastDistanceMoved) == Math.Abs(distanceMoved)) + { + if (catchCurrent.StrainTime <= 80 && lastStrainTime == catchCurrent.StrainTime) + { + distanceAddition *= Math.Max(((catchCurrent.StrainTime / 80) - 0.75) * 4, 0); + } + } + } } lastPlayerPosition = playerPosition; lastDistanceMoved = distanceMoved; + lastStrainTime = catchCurrent.StrainTime; - return distanceAddition / catchCurrent.StrainTime; + return distanceAddition / weightedStrainTime; } } } From 24fb25f1cdc770ecb7c016a8fba472c00507484d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 14 Mar 2019 23:39:45 +0900 Subject: [PATCH 002/655] Use fresh mods for each difficulty calculation --- .../Difficulty/CatchDifficultyCalculator.cs | 10 +++--- .../Difficulty/ManiaDifficultyCalculator.cs | 33 ++++++++++--------- .../Difficulty/OsuDifficultyCalculator.cs | 10 +++--- .../Difficulty/TaikoDifficultyCalculator.cs | 11 ++++--- ...DifficultyAdjustmentModCombinationsTest.cs | 14 ++++---- .../Difficulty/DifficultyCalculator.cs | 10 +++--- 6 files changed, 46 insertions(+), 42 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index f3b88bd928..d73ee19d41 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -90,12 +90,12 @@ namespace osu.Game.Rulesets.Catch.Difficulty new Movement(), }; - protected override Mod[] DifficultyAdjustmentMods => new Mod[] + protected override Type[] DifficultyAdjustmentMods => new[] { - new CatchModDoubleTime(), - new CatchModHalfTime(), - new CatchModHardRock(), - new CatchModEasy(), + typeof(CatchModDoubleTime), + typeof(CatchModHalfTime), + typeof(CatchModHardRock), + typeof(CatchModEasy), }; } } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 59fed1031f..bff3bfdb23 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; @@ -91,33 +92,33 @@ namespace osu.Game.Rulesets.Mania.Difficulty return skills.ToArray(); } - protected override Mod[] DifficultyAdjustmentMods + protected override Type[] DifficultyAdjustmentMods { get { - var mods = new Mod[] + var mods = new[] { - new ManiaModDoubleTime(), - new ManiaModHalfTime(), - new ManiaModEasy(), - new ManiaModHardRock(), + typeof(ManiaModDoubleTime), + typeof(ManiaModHalfTime), + typeof(ManiaModEasy), + typeof(ManiaModHardRock) }; if (isForCurrentRuleset) return mods; // if we are a convert, we can be played in any key mod. - return mods.Concat(new Mod[] + return mods.Concat(new[] { - new ManiaModKey1(), - new ManiaModKey2(), - new ManiaModKey3(), - new ManiaModKey4(), - new ManiaModKey5(), - new ManiaModKey6(), - new ManiaModKey7(), - new ManiaModKey8(), - new ManiaModKey9(), + typeof(ManiaModKey1), + typeof(ManiaModKey2), + typeof(ManiaModKey3), + typeof(ManiaModKey4), + typeof(ManiaModKey5), + typeof(ManiaModKey6), + typeof(ManiaModKey7), + typeof(ManiaModKey8), + typeof(ManiaModKey9), }).ToArray(); } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index e2a1542574..9c44eb6f97 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -74,12 +74,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty new Speed() }; - protected override Mod[] DifficultyAdjustmentMods => new Mod[] + protected override Type[] DifficultyAdjustmentMods => new[] { - new OsuModDoubleTime(), - new OsuModHalfTime(), - new OsuModEasy(), - new OsuModHardRock(), + typeof(OsuModDoubleTime), + typeof(OsuModHalfTime), + typeof(OsuModEasy), + typeof(OsuModHardRock), }; } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 685ad9949b..ad1fb4c2e5 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; @@ -47,12 +48,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] { new Strain() }; - protected override Mod[] DifficultyAdjustmentMods => new Mod[] + protected override Type[] DifficultyAdjustmentMods => new[] { - new TaikoModDoubleTime(), - new TaikoModHalfTime(), - new TaikoModEasy(), - new TaikoModHardRock(), + typeof(TaikoModDoubleTime), + typeof(TaikoModHalfTime), + typeof(TaikoModEasy), + typeof(TaikoModHardRock), }; } } diff --git a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs index 760a033aff..3bce6fedbb 100644 --- a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs +++ b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestSingleMod() { - var combinations = new TestLegacyDifficultyCalculator(new ModA()).CreateDifficultyAdjustmentModCombinations(); + var combinations = new TestLegacyDifficultyCalculator(typeof(ModA)).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(2, combinations.Length); Assert.IsTrue(combinations[0] is ModNoMod); @@ -37,7 +37,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestDoubleMod() { - var combinations = new TestLegacyDifficultyCalculator(new ModA(), new ModB()).CreateDifficultyAdjustmentModCombinations(); + var combinations = new TestLegacyDifficultyCalculator(typeof(ModA), typeof(ModB)).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(4, combinations.Length); Assert.IsTrue(combinations[0] is ModNoMod); @@ -52,7 +52,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestIncompatibleMods() { - var combinations = new TestLegacyDifficultyCalculator(new ModA(), new ModIncompatibleWithA()).CreateDifficultyAdjustmentModCombinations(); + var combinations = new TestLegacyDifficultyCalculator(typeof(ModA), typeof(ModIncompatibleWithA)).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(3, combinations.Length); Assert.IsTrue(combinations[0] is ModNoMod); @@ -63,7 +63,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestDoubleIncompatibleMods() { - var combinations = new TestLegacyDifficultyCalculator(new ModA(), new ModB(), new ModIncompatibleWithA(), new ModIncompatibleWithAAndB()).CreateDifficultyAdjustmentModCombinations(); + var combinations = new TestLegacyDifficultyCalculator(typeof(ModA), typeof(ModB), typeof(ModIncompatibleWithA), typeof(ModIncompatibleWithAAndB)).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(8, combinations.Length); Assert.IsTrue(combinations[0] is ModNoMod); @@ -86,7 +86,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestIncompatibleThroughBaseType() { - var combinations = new TestLegacyDifficultyCalculator(new ModAofA(), new ModIncompatibleWithAofA()).CreateDifficultyAdjustmentModCombinations(); + var combinations = new TestLegacyDifficultyCalculator(typeof(ModAofA), typeof(ModIncompatibleWithAofA)).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(3, combinations.Length); Assert.IsTrue(combinations[0] is ModNoMod); @@ -141,13 +141,13 @@ namespace osu.Game.Tests.NonVisual private class TestLegacyDifficultyCalculator : DifficultyCalculator { - public TestLegacyDifficultyCalculator(params Mod[] mods) + public TestLegacyDifficultyCalculator(params Type[] mods) : base(null, null) { DifficultyAdjustmentMods = mods; } - protected override Mod[] DifficultyAdjustmentMods { get; } + protected override Type[] DifficultyAdjustmentMods { get; } protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index db8bdde6bb..47ffa48b91 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -106,7 +106,7 @@ namespace osu.Game.Rulesets.Difficulty { return createDifficultyAdjustmentModCombinations(Enumerable.Empty(), DifficultyAdjustmentMods).ToArray(); - IEnumerable createDifficultyAdjustmentModCombinations(IEnumerable currentSet, Mod[] adjustmentSet, int currentSetCount = 0, int adjustmentSetStart = 0) + IEnumerable createDifficultyAdjustmentModCombinations(IEnumerable currentSet, Type[] adjustmentSet, int currentSetCount = 0, int adjustmentSetStart = 0) { switch (currentSetCount) { @@ -129,12 +129,14 @@ namespace osu.Game.Rulesets.Difficulty // combinations in further recursions, so a moving subset is used to eliminate this effect for (int i = adjustmentSetStart; i < adjustmentSet.Length; i++) { - var adjustmentMod = adjustmentSet[i]; + var adjustmentMod = createMod(); if (currentSet.Any(c => c.IncompatibleMods.Any(m => m.IsInstanceOfType(adjustmentMod)))) continue; - foreach (var combo in createDifficultyAdjustmentModCombinations(currentSet.Append(adjustmentMod), adjustmentSet, currentSetCount + 1, i + 1)) + foreach (var combo in createDifficultyAdjustmentModCombinations(currentSet.Append(createMod()), adjustmentSet, currentSetCount + 1, i + 1)) yield return combo; + + Mod createMod() => (Mod)Activator.CreateInstance(adjustmentSet[i]); } } } @@ -142,7 +144,7 @@ namespace osu.Game.Rulesets.Difficulty /// /// Retrieves all s which adjust the difficulty. /// - protected virtual Mod[] DifficultyAdjustmentMods => Array.Empty(); + protected virtual Type[] DifficultyAdjustmentMods => Array.Empty(); /// /// Creates to describe beatmap's calculated difficulty. From f959a2ee373f13a998bb8e752bc42d614ae12cd3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 16 Mar 2019 10:12:05 +0900 Subject: [PATCH 003/655] Update antiflow bonus --- osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index b1b5ba0312..d06813f160 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -59,9 +59,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills distanceAddition += direction_change_bonus / sqrtStrain * bonusFactor; // Direction changes after jumps (antiflow) are harder - double antiflowBonusFactor = Math.Min(Math.Sqrt(Math.Abs(distanceMoved)) / 10, 1); + double antiflowBonusFactor = Math.Min(Math.Abs(distanceMoved) / 70, 1); - distanceAddition += (antiflow_bonus / sqrtStrain) * (Math.Sqrt(Math.Abs(lastDistanceMoved)) / (lastStrainTime / 40 + 10.0)) * antiflowBonusFactor; + distanceAddition += (antiflow_bonus / (catchCurrent.StrainTime / 40 + 10)) * (Math.Sqrt(Math.Abs(lastDistanceMoved)) / Math.Sqrt(lastStrainTime + 20)) * antiflowBonusFactor; } // Base bonus for every movement, giving some weight to streams. From 9ae6cde837470d8e5708788bd224631f82af8243 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 Mar 2019 12:14:26 +0900 Subject: [PATCH 004/655] Nerf back-and-forth hyperdash chains --- .../Difficulty/Skills/Movement.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index d06813f160..d8e359bb48 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -15,9 +15,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills private const float absolute_player_positioning_error = 16f; private const float normalized_hitobject_radius = 41.0f; private const double direction_change_bonus = 9.5; - private const double antiflow_bonus = 25.0; + private const double antiflow_bonus = 26.0; - protected override double SkillMultiplier => 850; + protected override double SkillMultiplier => 860; protected override double StrainDecayBase => 0.2; protected override double DecayWeight => 0.94; @@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills private float? lastPlayerPosition; private float lastDistanceMoved; private double lastStrainTime; + private bool lastHyperdash; protected override double StrainValueOf(DifficultyHitObject current) { @@ -44,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills // Reduce speed scaling double weightedStrainTime = catchCurrent.StrainTime + 20; - double distanceAddition = Math.Pow(Math.Abs(distanceMoved), 1.3) / 600; + double distanceAddition = Math.Pow(Math.Abs(distanceMoved), 1.2) / 340; double sqrtStrain = Math.Sqrt(weightedStrainTime); double bonus = 0; @@ -61,7 +62,14 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills // Direction changes after jumps (antiflow) are harder double antiflowBonusFactor = Math.Min(Math.Abs(distanceMoved) / 70, 1); - distanceAddition += (antiflow_bonus / (catchCurrent.StrainTime / 40 + 10)) * (Math.Sqrt(Math.Abs(lastDistanceMoved)) / Math.Sqrt(lastStrainTime + 20)) * antiflowBonusFactor; + distanceAddition += (antiflow_bonus / (catchCurrent.StrainTime / 17.5 + 10)) * (Math.Sqrt(Math.Abs(lastDistanceMoved)) / Math.Sqrt(lastStrainTime + 25)) * antiflowBonusFactor; + + // Reduce strain slightly for Hyperdash chains + if (catchCurrent.LastObject.HyperDash && lastHyperdash) + distanceAddition *= 0.95; + + if (catchCurrent.LastObject.DistanceToHyperDash <= 14.0f / CatchPlayfield.BASE_WIDTH && !catchCurrent.LastObject.HyperDash) + bonus += 3.0; } // Base bonus for every movement, giving some weight to streams. @@ -100,6 +108,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills lastPlayerPosition = playerPosition; lastDistanceMoved = distanceMoved; lastStrainTime = catchCurrent.StrainTime; + lastHyperdash = catchCurrent.LastObject.HyperDash; return distanceAddition / weightedStrainTime; } From 9f12a36598f504539644674a89a33f370f9ef3c5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 Mar 2019 12:14:53 +0900 Subject: [PATCH 005/655] Buff slower edge dashes, nerf faster ones --- osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index d8e359bb48..d06ab2f928 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills { private const float absolute_player_positioning_error = 16f; private const float normalized_hitobject_radius = 41.0f; - private const double direction_change_bonus = 9.5; + private const double direction_change_bonus = 9.8; private const double antiflow_bonus = 26.0; protected override double SkillMultiplier => 860; @@ -76,18 +76,18 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills distanceAddition += 10.0 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain; } - // Big bonus for edge hyperdashes + // Big bonus for edge dashes if (catchCurrent.LastObject.DistanceToHyperDash <= 14.0f / CatchPlayfield.BASE_WIDTH) { if (!catchCurrent.LastObject.HyperDash) - bonus += 5.0; + bonus += 4.5; else { // After a hyperdash we ARE in the correct position. Always! playerPosition = catchCurrent.NormalizedPosition; } - distanceAddition *= 1.0 + bonus * (14 - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 14 * (Math.Min(catchCurrent.StrainTime, 180) / 180); // Edge dashes are easier at lower ms values + distanceAddition *= 1.0 + bonus * (14.0f - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 14.0f * (Math.Min(catchCurrent.StrainTime, 250) / 250); // Edge dashes are easier at lower ms values } // Prevent wide, dense stacks of notes which fit on the catcher from greatly increasing SR From 839dd7343f1cc6411077616ce0b2965e29130584 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 23 Mar 2019 15:57:22 +0900 Subject: [PATCH 006/655] Revert "Use fresh mods for each difficulty calculation" This reverts commit 24fb25f1cdc770ecb7c016a8fba472c00507484d. --- .../Difficulty/CatchDifficultyCalculator.cs | 10 +++--- .../Difficulty/ManiaDifficultyCalculator.cs | 33 +++++++++---------- .../Difficulty/OsuDifficultyCalculator.cs | 10 +++--- .../Difficulty/TaikoDifficultyCalculator.cs | 11 +++---- ...DifficultyAdjustmentModCombinationsTest.cs | 14 ++++---- .../Difficulty/DifficultyCalculator.cs | 10 +++--- 6 files changed, 42 insertions(+), 46 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 289deab8cd..47e2bc5259 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -91,12 +91,12 @@ namespace osu.Game.Rulesets.Catch.Difficulty new Movement(), }; - protected override Type[] DifficultyAdjustmentMods => new[] + protected override Mod[] DifficultyAdjustmentMods => new Mod[] { - typeof(CatchModDoubleTime), - typeof(CatchModHalfTime), - typeof(CatchModHardRock), - typeof(CatchModEasy), + new CatchModDoubleTime(), + new CatchModHalfTime(), + new CatchModHardRock(), + new CatchModEasy(), }; } } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index bff3bfdb23..59fed1031f 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; @@ -92,33 +91,33 @@ namespace osu.Game.Rulesets.Mania.Difficulty return skills.ToArray(); } - protected override Type[] DifficultyAdjustmentMods + protected override Mod[] DifficultyAdjustmentMods { get { - var mods = new[] + var mods = new Mod[] { - typeof(ManiaModDoubleTime), - typeof(ManiaModHalfTime), - typeof(ManiaModEasy), - typeof(ManiaModHardRock) + new ManiaModDoubleTime(), + new ManiaModHalfTime(), + new ManiaModEasy(), + new ManiaModHardRock(), }; if (isForCurrentRuleset) return mods; // if we are a convert, we can be played in any key mod. - return mods.Concat(new[] + return mods.Concat(new Mod[] { - typeof(ManiaModKey1), - typeof(ManiaModKey2), - typeof(ManiaModKey3), - typeof(ManiaModKey4), - typeof(ManiaModKey5), - typeof(ManiaModKey6), - typeof(ManiaModKey7), - typeof(ManiaModKey8), - typeof(ManiaModKey9), + new ManiaModKey1(), + new ManiaModKey2(), + new ManiaModKey3(), + new ManiaModKey4(), + new ManiaModKey5(), + new ManiaModKey6(), + new ManiaModKey7(), + new ManiaModKey8(), + new ManiaModKey9(), }).ToArray(); } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 9c44eb6f97..e2a1542574 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -74,12 +74,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty new Speed() }; - protected override Type[] DifficultyAdjustmentMods => new[] + protected override Mod[] DifficultyAdjustmentMods => new Mod[] { - typeof(OsuModDoubleTime), - typeof(OsuModHalfTime), - typeof(OsuModEasy), - typeof(OsuModHardRock), + new OsuModDoubleTime(), + new OsuModHalfTime(), + new OsuModEasy(), + new OsuModHardRock(), }; } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index ad1fb4c2e5..685ad9949b 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; @@ -48,12 +47,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] { new Strain() }; - protected override Type[] DifficultyAdjustmentMods => new[] + protected override Mod[] DifficultyAdjustmentMods => new Mod[] { - typeof(TaikoModDoubleTime), - typeof(TaikoModHalfTime), - typeof(TaikoModEasy), - typeof(TaikoModHardRock), + new TaikoModDoubleTime(), + new TaikoModHalfTime(), + new TaikoModEasy(), + new TaikoModHardRock(), }; } } diff --git a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs index 3bce6fedbb..760a033aff 100644 --- a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs +++ b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestSingleMod() { - var combinations = new TestLegacyDifficultyCalculator(typeof(ModA)).CreateDifficultyAdjustmentModCombinations(); + var combinations = new TestLegacyDifficultyCalculator(new ModA()).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(2, combinations.Length); Assert.IsTrue(combinations[0] is ModNoMod); @@ -37,7 +37,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestDoubleMod() { - var combinations = new TestLegacyDifficultyCalculator(typeof(ModA), typeof(ModB)).CreateDifficultyAdjustmentModCombinations(); + var combinations = new TestLegacyDifficultyCalculator(new ModA(), new ModB()).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(4, combinations.Length); Assert.IsTrue(combinations[0] is ModNoMod); @@ -52,7 +52,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestIncompatibleMods() { - var combinations = new TestLegacyDifficultyCalculator(typeof(ModA), typeof(ModIncompatibleWithA)).CreateDifficultyAdjustmentModCombinations(); + var combinations = new TestLegacyDifficultyCalculator(new ModA(), new ModIncompatibleWithA()).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(3, combinations.Length); Assert.IsTrue(combinations[0] is ModNoMod); @@ -63,7 +63,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestDoubleIncompatibleMods() { - var combinations = new TestLegacyDifficultyCalculator(typeof(ModA), typeof(ModB), typeof(ModIncompatibleWithA), typeof(ModIncompatibleWithAAndB)).CreateDifficultyAdjustmentModCombinations(); + var combinations = new TestLegacyDifficultyCalculator(new ModA(), new ModB(), new ModIncompatibleWithA(), new ModIncompatibleWithAAndB()).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(8, combinations.Length); Assert.IsTrue(combinations[0] is ModNoMod); @@ -86,7 +86,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestIncompatibleThroughBaseType() { - var combinations = new TestLegacyDifficultyCalculator(typeof(ModAofA), typeof(ModIncompatibleWithAofA)).CreateDifficultyAdjustmentModCombinations(); + var combinations = new TestLegacyDifficultyCalculator(new ModAofA(), new ModIncompatibleWithAofA()).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(3, combinations.Length); Assert.IsTrue(combinations[0] is ModNoMod); @@ -141,13 +141,13 @@ namespace osu.Game.Tests.NonVisual private class TestLegacyDifficultyCalculator : DifficultyCalculator { - public TestLegacyDifficultyCalculator(params Type[] mods) + public TestLegacyDifficultyCalculator(params Mod[] mods) : base(null, null) { DifficultyAdjustmentMods = mods; } - protected override Type[] DifficultyAdjustmentMods { get; } + protected override Mod[] DifficultyAdjustmentMods { get; } protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 9b630865c2..aad55f8a38 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Difficulty { return createDifficultyAdjustmentModCombinations(Enumerable.Empty(), DifficultyAdjustmentMods).ToArray(); - IEnumerable createDifficultyAdjustmentModCombinations(IEnumerable currentSet, Type[] adjustmentSet, int currentSetCount = 0, int adjustmentSetStart = 0) + IEnumerable createDifficultyAdjustmentModCombinations(IEnumerable currentSet, Mod[] adjustmentSet, int currentSetCount = 0, int adjustmentSetStart = 0) { switch (currentSetCount) { @@ -131,14 +131,12 @@ namespace osu.Game.Rulesets.Difficulty // combinations in further recursions, so a moving subset is used to eliminate this effect for (int i = adjustmentSetStart; i < adjustmentSet.Length; i++) { - var adjustmentMod = createMod(); + var adjustmentMod = adjustmentSet[i]; if (currentSet.Any(c => c.IncompatibleMods.Any(m => m.IsInstanceOfType(adjustmentMod)))) continue; - foreach (var combo in createDifficultyAdjustmentModCombinations(currentSet.Append(createMod()), adjustmentSet, currentSetCount + 1, i + 1)) + foreach (var combo in createDifficultyAdjustmentModCombinations(currentSet.Append(adjustmentMod), adjustmentSet, currentSetCount + 1, i + 1)) yield return combo; - - Mod createMod() => (Mod)Activator.CreateInstance(adjustmentSet[i]); } } } @@ -146,7 +144,7 @@ namespace osu.Game.Rulesets.Difficulty /// /// Retrieves all s which adjust the difficulty. /// - protected virtual Type[] DifficultyAdjustmentMods => Array.Empty(); + protected virtual Mod[] DifficultyAdjustmentMods => Array.Empty(); /// /// Creates to describe beatmap's calculated difficulty. From be5ffdbf22cd5a8dc5fca3c87306042741e2dafa Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 23 Mar 2019 16:01:14 +0900 Subject: [PATCH 007/655] Adjust edge bonuses to consider clock rate --- .../Preprocessing/CatchDifficultyHitObject.cs | 2 ++ .../Difficulty/Skills/Movement.cs | 21 +++++++------------ 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs index f0c68e4392..b2b4129c8a 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs @@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing /// Milliseconds elapsed since the start time of the previous , with a minimum of 40ms. /// public readonly double StrainTime; + public readonly double ClockRate; public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, float halfCatcherWidth) : base(hitObject, lastObject, clockRate) @@ -36,6 +37,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing // Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure StrainTime = Math.Max(40, DeltaTime); + ClockRate = clockRate; } } } diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index d06ab2f928..227fb2820c 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills private const double direction_change_bonus = 9.8; private const double antiflow_bonus = 26.0; - protected override double SkillMultiplier => 860; + protected override double SkillMultiplier => 850; protected override double StrainDecayBase => 0.2; protected override double DecayWeight => 0.94; @@ -25,7 +25,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills private float? lastPlayerPosition; private float lastDistanceMoved; private double lastStrainTime; - private bool lastHyperdash; protected override double StrainValueOf(DifficultyHitObject current) { @@ -62,35 +61,32 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills // Direction changes after jumps (antiflow) are harder double antiflowBonusFactor = Math.Min(Math.Abs(distanceMoved) / 70, 1); - distanceAddition += (antiflow_bonus / (catchCurrent.StrainTime / 17.5 + 10)) * (Math.Sqrt(Math.Abs(lastDistanceMoved)) / Math.Sqrt(lastStrainTime + 25)) * antiflowBonusFactor; - - // Reduce strain slightly for Hyperdash chains - if (catchCurrent.LastObject.HyperDash && lastHyperdash) - distanceAddition *= 0.95; + distanceAddition += (antiflow_bonus / (catchCurrent.StrainTime / 17.5 + 10)) * (Math.Sqrt(Math.Abs(lastDistanceMoved)) / Math.Sqrt(lastStrainTime + 20)) * antiflowBonusFactor; + // Bonus for edge dashes on direction change if (catchCurrent.LastObject.DistanceToHyperDash <= 14.0f / CatchPlayfield.BASE_WIDTH && !catchCurrent.LastObject.HyperDash) - bonus += 3.0; + bonus += 1.0; } // Base bonus for every movement, giving some weight to streams. distanceAddition += 10.0 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain; } - // Big bonus for edge dashes + // Bonus for edge dashes regardless of direction change if (catchCurrent.LastObject.DistanceToHyperDash <= 14.0f / CatchPlayfield.BASE_WIDTH) { if (!catchCurrent.LastObject.HyperDash) - bonus += 4.5; + bonus += 0.9; else { // After a hyperdash we ARE in the correct position. Always! playerPosition = catchCurrent.NormalizedPosition; } - distanceAddition *= 1.0 + bonus * (14.0f - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 14.0f * (Math.Min(catchCurrent.StrainTime, 250) / 250); // Edge dashes are easier at lower ms values + distanceAddition *= 1.0 + bonus * Math.Pow(14.0f - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH, 1.6f) / 14.0f * (Math.Min(catchCurrent.StrainTime * catchCurrent.ClockRate, 250) / 250); // Edge dashes are easier at lower ms values } - // Prevent wide, dense stacks of notes which fit on the catcher from greatly increasing SR + // Prevent wide dense stacks of notes which fit on the catcher from greatly increasing SR if (Math.Abs(distanceMoved) > 0.1) { if (Math.Abs(lastDistanceMoved) > 0.1 && Math.Sign(distanceMoved) != Math.Sign(lastDistanceMoved)) @@ -108,7 +104,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills lastPlayerPosition = playerPosition; lastDistanceMoved = distanceMoved; lastStrainTime = catchCurrent.StrainTime; - lastHyperdash = catchCurrent.LastObject.HyperDash; return distanceAddition / weightedStrainTime; } From 2705263145b1dcae03bc5b58304eebe9aebfa7f3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 26 Mar 2019 13:25:52 +0900 Subject: [PATCH 008/655] Scale edge dash threshold with clock rate --- .../Difficulty/Skills/Movement.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index 227fb2820c..27558838f4 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -62,28 +62,26 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills double antiflowBonusFactor = Math.Min(Math.Abs(distanceMoved) / 70, 1); distanceAddition += (antiflow_bonus / (catchCurrent.StrainTime / 17.5 + 10)) * (Math.Sqrt(Math.Abs(lastDistanceMoved)) / Math.Sqrt(lastStrainTime + 20)) * antiflowBonusFactor; - - // Bonus for edge dashes on direction change - if (catchCurrent.LastObject.DistanceToHyperDash <= 14.0f / CatchPlayfield.BASE_WIDTH && !catchCurrent.LastObject.HyperDash) - bonus += 1.0; } // Base bonus for every movement, giving some weight to streams. distanceAddition += 10.0 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain; } - // Bonus for edge dashes regardless of direction change - if (catchCurrent.LastObject.DistanceToHyperDash <= 14.0f / CatchPlayfield.BASE_WIDTH) + // Bonus for edge dashes + double edgeDashThreshold = 15.5f * ((Math.Min(catchCurrent.StrainTime * catchCurrent.ClockRate, 250) * 0.9 + 25) / 250); + + if (catchCurrent.LastObject.DistanceToHyperDash <= edgeDashThreshold / CatchPlayfield.BASE_WIDTH) { if (!catchCurrent.LastObject.HyperDash) - bonus += 0.9; + bonus += 2.3; else { // After a hyperdash we ARE in the correct position. Always! playerPosition = catchCurrent.NormalizedPosition; } - distanceAddition *= 1.0 + bonus * Math.Pow(14.0f - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH, 1.6f) / 14.0f * (Math.Min(catchCurrent.StrainTime * catchCurrent.ClockRate, 250) / 250); // Edge dashes are easier at lower ms values + distanceAddition *= 1.0 + bonus * Math.Pow(edgeDashThreshold - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH, 1.6f) / edgeDashThreshold * (Math.Min(catchCurrent.StrainTime * catchCurrent.ClockRate, 265) / 265); // Edge dashes are easier at lower ms values } // Prevent wide dense stacks of notes which fit on the catcher from greatly increasing SR From 9d0d402336e6b7123ee715105d953979797cc44c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Mar 2019 17:21:57 +0900 Subject: [PATCH 009/655] Apply pp calculator changes (Backported from https://github.com/ppy/osu-performance/compare/master...smoogipoo:sorcerer-catch-changes) --- .../Difficulty/CatchPerformanceCalculator.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index 5a640f6d1a..28da047187 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -55,8 +55,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty // Longer maps are worth more float lengthBonus = - 0.95f + 0.4f * Math.Min(1.0f, numTotalHits / 3000.0f) + - (numTotalHits > 3000 ? (float)Math.Log10(numTotalHits / 3000.0f) * 0.5f : 0.0f); + 0.95f + 0.3f * Math.Min(1.0f, numTotalHits / 2500.0f) + + (numTotalHits > 2500 ? (float)Math.Log10(numTotalHits / 2500.0f) * 0.475f : 0.0f); // Longer maps are worth more value *= lengthBonus; @@ -73,14 +73,22 @@ namespace osu.Game.Rulesets.Catch.Difficulty float approachRateFactor = 1.0f; if (approachRate > 9.0f) approachRateFactor += 0.1f * (approachRate - 9.0f); // 10% for each AR above 9 + if (approachRate > 10.0f) + approachRateFactor += 0.2f * (float)Math.Pow(approachRate - 10.0f, 1.5f); // Additional 20% at AR 11, 40% total else if (approachRate < 8.0f) approachRateFactor += 0.025f * (8.0f - approachRate); // 2.5% for each AR below 8 value *= approachRateFactor; if (mods.Any(m => m is ModHidden)) - // Hiddens gives nothing on max approach rate, and more the lower it is + { value *= 1.05f + 0.075f * (10.0f - Math.Min(10.0f, approachRate)); // 7.5% for each AR below 10 + // Hiddens gives almost nothing on max approach rate, and more the lower it is + if (approachRate <= 10.0f) + value *= 1.05f + 0.075f * (10.0f - approachRate); // 7.5% for each AR below 10 + else if (approachRate > 10.0f) + value *= 1.01f + 0.04f * (11.0f - Math.Min(11.0f, approachRate)); // 5% at AR 10, 1% at AR 11 + } if (mods.Any(m => m is ModFlashlight)) // Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps. From b402981fc644c85422d52449c90cd3120a3bd44d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Apr 2019 10:57:01 +0900 Subject: [PATCH 010/655] Buff CS > 5 --- .../Difficulty/CatchDifficultyCalculator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 47e2bc5259..a475aefd71 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -52,7 +52,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty using (var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty)) { halfCatchWidth = catcher.CatchWidth * 0.5f; - halfCatchWidth *= 0.8f; // We're only using 80% of the catcher's width to simulate imperfect gameplay. + // We're only using 80% of the catcher's width to simulate imperfect gameplay. + halfCatchWidth *= Math.Min(1.05f - (0.05f * beatmap.BeatmapInfo.BaseDifficulty.CircleSize), 0.8f); // Reduce the catcher's width further at circle sizes above 5. } CatchHitObject lastObject = null; From b2396b82a5bf218ac61786f9e38ae8a6976d91c8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Apr 2019 10:58:26 +0900 Subject: [PATCH 011/655] Change edge dashes to scale linearly once again --- .../Preprocessing/CatchDifficultyHitObject.cs | 3 +++ osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs | 10 ++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs index b2b4129c8a..9f6de35605 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs @@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing /// public readonly double StrainTime; public readonly double ClockRate; + public readonly double HalfCatcherWidth; public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, float halfCatcherWidth) : base(hitObject, lastObject, clockRate) @@ -38,6 +39,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing // Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure StrainTime = Math.Max(40, DeltaTime); ClockRate = clockRate; + HalfCatcherWidth = halfCatcherWidth; } } } + diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index 27558838f4..473b254d5b 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -68,20 +68,18 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills distanceAddition += 10.0 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain; } - // Bonus for edge dashes - double edgeDashThreshold = 15.5f * ((Math.Min(catchCurrent.StrainTime * catchCurrent.ClockRate, 250) * 0.9 + 25) / 250); - - if (catchCurrent.LastObject.DistanceToHyperDash <= edgeDashThreshold / CatchPlayfield.BASE_WIDTH) + // Bonus for "almost" hyperdashes at corner points + if (catchCurrent.LastObject.DistanceToHyperDash <= 20.0f / CatchPlayfield.BASE_WIDTH) { if (!catchCurrent.LastObject.HyperDash) - bonus += 2.3; + bonus += 5.7; else { // After a hyperdash we ARE in the correct position. Always! playerPosition = catchCurrent.NormalizedPosition; } - distanceAddition *= 1.0 + bonus * Math.Pow(edgeDashThreshold - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH, 1.6f) / edgeDashThreshold * (Math.Min(catchCurrent.StrainTime * catchCurrent.ClockRate, 265) / 265); // Edge dashes are easier at lower ms values + distanceAddition *= 1.0 + bonus * ((20 - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 20) * Math.Pow((Math.Min(catchCurrent.StrainTime, 265) / 265), 1.5); } // Prevent wide dense stacks of notes which fit on the catcher from greatly increasing SR From efee2fb283903f00b4039330117836c271016eb0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Apr 2019 11:00:26 +0900 Subject: [PATCH 012/655] Adjust antiflow calculations --- .../Difficulty/Skills/Movement.cs | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index 473b254d5b..e78e5c0a58 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -14,10 +14,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills { private const float absolute_player_positioning_error = 16f; private const float normalized_hitobject_radius = 41.0f; - private const double direction_change_bonus = 9.8; - private const double antiflow_bonus = 26.0; + private const double direction_change_bonus = 21.0; - protected override double SkillMultiplier => 850; + protected override double SkillMultiplier => 900; protected override double StrainDecayBase => 0.2; protected override double DecayWeight => 0.94; @@ -29,6 +28,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills protected override double StrainValueOf(DifficultyHitObject current) { var catchCurrent = (CatchDifficultyHitObject)current; + double halfCatcherWidth = catchCurrent.HalfCatcherWidth; if (lastPlayerPosition == null) lastPlayerPosition = catchCurrent.LastNormalizedPosition; @@ -41,10 +41,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills float distanceMoved = playerPosition - lastPlayerPosition.Value; - // Reduce speed scaling - double weightedStrainTime = catchCurrent.StrainTime + 20; + double weightedStrainTime = catchCurrent.StrainTime + 18; - double distanceAddition = Math.Pow(Math.Abs(distanceMoved), 1.2) / 340; + double distanceAddition = (Math.Pow(Math.Abs(distanceMoved), 1.3) / 510); double sqrtStrain = Math.Sqrt(weightedStrainTime); double bonus = 0; @@ -54,18 +53,14 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills { if (Math.Abs(lastDistanceMoved) > 0.1 && Math.Sign(distanceMoved) != Math.Sign(lastDistanceMoved)) { - double bonusFactor = Math.Min(absolute_player_positioning_error, Math.Abs(distanceMoved)) / absolute_player_positioning_error; + double bonusFactor = Math.Min(halfCatcherWidth, Math.Abs(distanceMoved)) / halfCatcherWidth; + double antiflowFactor = Math.Max(Math.Min(halfCatcherWidth, Math.Abs(lastDistanceMoved)) / halfCatcherWidth, 0.3); - distanceAddition += direction_change_bonus / sqrtStrain * bonusFactor; - - // Direction changes after jumps (antiflow) are harder - double antiflowBonusFactor = Math.Min(Math.Abs(distanceMoved) / 70, 1); - - distanceAddition += (antiflow_bonus / (catchCurrent.StrainTime / 17.5 + 10)) * (Math.Sqrt(Math.Abs(lastDistanceMoved)) / Math.Sqrt(lastStrainTime + 20)) * antiflowBonusFactor; + distanceAddition += direction_change_bonus / Math.Sqrt(lastStrainTime + 18) * bonusFactor * antiflowFactor * Math.Max(1 - Math.Pow(weightedStrainTime / 1000, 2), 0); } // Base bonus for every movement, giving some weight to streams. - distanceAddition += 10.0 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain; + distanceAddition += 12.5 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain; } // Bonus for "almost" hyperdashes at corner points From 21e62c37d8d2fcacb49a1f475ae65d73f04b4abf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Apr 2019 07:28:04 +0900 Subject: [PATCH 013/655] General fixes --- .../Difficulty/CatchDifficultyCalculator.cs | 2 +- .../Preprocessing/CatchDifficultyHitObject.cs | 3 --- .../Difficulty/Skills/Movement.cs | 13 ++++++------- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index a475aefd71..4cd297478a 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty { public class CatchDifficultyCalculator : DifficultyCalculator { - private const double star_scaling_factor = 0.15; + private const double star_scaling_factor = 0.153; protected override int SectionLength => 750; diff --git a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs index 9f6de35605..b2b4129c8a 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs @@ -25,7 +25,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing /// public readonly double StrainTime; public readonly double ClockRate; - public readonly double HalfCatcherWidth; public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, float halfCatcherWidth) : base(hitObject, lastObject, clockRate) @@ -39,8 +38,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing // Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure StrainTime = Math.Max(40, DeltaTime); ClockRate = clockRate; - HalfCatcherWidth = halfCatcherWidth; } } } - diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index e78e5c0a58..4ea5135c4f 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -28,7 +28,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills protected override double StrainValueOf(DifficultyHitObject current) { var catchCurrent = (CatchDifficultyHitObject)current; - double halfCatcherWidth = catchCurrent.HalfCatcherWidth; if (lastPlayerPosition == null) lastPlayerPosition = catchCurrent.LastNormalizedPosition; @@ -46,17 +45,17 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills double distanceAddition = (Math.Pow(Math.Abs(distanceMoved), 1.3) / 510); double sqrtStrain = Math.Sqrt(weightedStrainTime); - double bonus = 0; + double edgeDashBonus = 0; // Direction changes give an extra point! if (Math.Abs(distanceMoved) > 0.1) { if (Math.Abs(lastDistanceMoved) > 0.1 && Math.Sign(distanceMoved) != Math.Sign(lastDistanceMoved)) { - double bonusFactor = Math.Min(halfCatcherWidth, Math.Abs(distanceMoved)) / halfCatcherWidth; - double antiflowFactor = Math.Max(Math.Min(halfCatcherWidth, Math.Abs(lastDistanceMoved)) / halfCatcherWidth, 0.3); + double bonusFactor = Math.Min(50, Math.Abs(distanceMoved)) / 50; + double antiflowFactor = Math.Max(Math.Min(70, Math.Abs(lastDistanceMoved)) / 70, 0.3); - distanceAddition += direction_change_bonus / Math.Sqrt(lastStrainTime + 18) * bonusFactor * antiflowFactor * Math.Max(1 - Math.Pow(weightedStrainTime / 1000, 2), 0); + distanceAddition += direction_change_bonus / Math.Sqrt(lastStrainTime + 18) * bonusFactor * antiflowFactor * Math.Max(1 - Math.Pow(weightedStrainTime / 1000, 3), 0); } // Base bonus for every movement, giving some weight to streams. @@ -67,14 +66,14 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills if (catchCurrent.LastObject.DistanceToHyperDash <= 20.0f / CatchPlayfield.BASE_WIDTH) { if (!catchCurrent.LastObject.HyperDash) - bonus += 5.7; + edgeDashBonus += 5.7; else { // After a hyperdash we ARE in the correct position. Always! playerPosition = catchCurrent.NormalizedPosition; } - distanceAddition *= 1.0 + bonus * ((20 - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 20) * Math.Pow((Math.Min(catchCurrent.StrainTime, 265) / 265), 1.5); + distanceAddition *= 1.0 + edgeDashBonus * ((20 - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 20) * Math.Pow((Math.Min(catchCurrent.StrainTime * catchCurrent.ClockRate, 265) / 265), 1.5); // Edge Dashes are easier at lower ms values } // Prevent wide dense stacks of notes which fit on the catcher from greatly increasing SR From 5566c4881a25af18e2b34407260966d148f51ff0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Apr 2019 11:38:48 +0900 Subject: [PATCH 014/655] Buff DT --- osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index 4ea5135c4f..7d5a7b4007 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills float distanceMoved = playerPosition - lastPlayerPosition.Value; - double weightedStrainTime = catchCurrent.StrainTime + 18; + double weightedStrainTime = catchCurrent.StrainTime + 10 + (8 / catchCurrent.ClockRate); double distanceAddition = (Math.Pow(Math.Abs(distanceMoved), 1.3) / 510); double sqrtStrain = Math.Sqrt(weightedStrainTime); From 2824a32db60b99222ac2f862e1f54c980af0aab7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Apr 2019 11:39:13 +0900 Subject: [PATCH 015/655] Adjust circle-size bonus point --- .../Difficulty/CatchDifficultyCalculator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 4cd297478a..c56881ba51 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -52,8 +52,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty using (var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty)) { halfCatchWidth = catcher.CatchWidth * 0.5f; - // We're only using 80% of the catcher's width to simulate imperfect gameplay. - halfCatchWidth *= Math.Min(1.05f - (0.05f * beatmap.BeatmapInfo.BaseDifficulty.CircleSize), 0.8f); // Reduce the catcher's width further at circle sizes above 5. + // We're only using 80% of the catcher's width to simulate imperfect gameplay, reduced further at circle sizes above 5.5 + halfCatchWidth *= Math.Min(1.075f - (0.05f * beatmap.BeatmapInfo.BaseDifficulty.CircleSize), 0.8f); } CatchHitObject lastObject = null; From 9d116efdbd8731443e46195f666aa1c21752aec5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Apr 2019 10:57:27 +0900 Subject: [PATCH 016/655] Limit to 10000 tiny ticks per slider --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 2adc156efd..9baa6a8531 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -55,6 +55,8 @@ namespace osu.Game.Rulesets.Catch.Objects SliderEventDescriptor? lastEvent = null; + int ticksGenerated = 0; + foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset)) { // generate tiny droplets since the last point @@ -70,6 +72,9 @@ namespace osu.Game.Rulesets.Catch.Objects for (double t = timeBetweenTiny; t < sinceLastTick; t += timeBetweenTiny) { + if (ticksGenerated++ >= 10000) + break; + AddNested(new TinyDroplet { Samples = tickSamples, From 5a0b93bdb2f2000cc7360319982cf652d0bdbbe1 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 20 Feb 2020 17:02:22 +0300 Subject: [PATCH 017/655] Add ShowTag method --- .../Online/TestSceneBeatmapListingOverlay.cs | 6 ++++++ osu.Game/Overlays/BeatmapListingOverlay.cs | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 7c05d99c59..4aea29faa0 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -24,6 +24,12 @@ namespace osu.Game.Tests.Visual.Online Add(overlay = new BeatmapListingOverlay()); } + [Test] + public void TestShowTag() + { + AddStep("Show Rem tag", () => overlay.ShowTag("Rem")); + } + [Test] public void TestShow() { diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 213e9a4244..e212d05442 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -158,6 +158,26 @@ namespace osu.Game.Overlays sortDirection.BindValueChanged(_ => queueUpdateSearch()); } + public void ShowTag(string tag) + { + var currentQuery = searchSection.Query.Value; + + if (currentQuery != tag) + { + setDefaultSearchValues(); + searchSection.Query.Value = tag; + } + + Show(); + } + + private void setDefaultSearchValues() + { + searchSection.Query.Value = string.Empty; + searchSection.Ruleset.Value = new RulesetInfo { Name = @"Any" }; + searchSection.Category.Value = BeatmapSearchCategory.Leaderboard; + } + private ScheduledDelegate queryChangedDebounce; private void queueUpdateSearch(bool queryTextChanged = false) From 6b2ae67eafda3bef8a2d720865f1ec2851e4fa86 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 20 Feb 2020 17:40:45 +0300 Subject: [PATCH 018/655] Implement Genre filter --- .../Online/TestSceneBeatmapListingOverlay.cs | 7 +++++ .../API/Requests/SearchBeatmapSetsRequest.cs | 26 ++++++++++++++++++- .../BeatmapListingSearchSection.cs | 4 +++ osu.Game/Overlays/BeatmapListingOverlay.cs | 18 ++++++++++++- 4 files changed, 53 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 4aea29faa0..9dec965818 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using osu.Game.Overlays; using NUnit.Framework; +using osu.Game.Online.API.Requests; namespace osu.Game.Tests.Visual.Online { @@ -30,6 +31,12 @@ namespace osu.Game.Tests.Visual.Online AddStep("Show Rem tag", () => overlay.ShowTag("Rem")); } + [Test] + public void TestShowGenre() + { + AddStep("Show Anime genre", () => overlay.ShowGenre(BeatmapSearchGenre.Anime)); + } + [Test] public void TestShow() { diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 930ca8fdf1..797a0a1015 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -16,15 +16,17 @@ namespace osu.Game.Online.API.Requests private readonly BeatmapSearchCategory searchCategory; private readonly DirectSortCriteria sortCriteria; private readonly SortDirection direction; + private readonly BeatmapSearchGenre genre; private string directionString => direction == SortDirection.Descending ? @"desc" : @"asc"; - public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset, BeatmapSearchCategory searchCategory = BeatmapSearchCategory.Any, DirectSortCriteria sortCriteria = DirectSortCriteria.Ranked, SortDirection direction = SortDirection.Descending) + public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset, BeatmapSearchCategory searchCategory = BeatmapSearchCategory.Any, DirectSortCriteria sortCriteria = DirectSortCriteria.Ranked, SortDirection direction = SortDirection.Descending, BeatmapSearchGenre genre = BeatmapSearchGenre.Any) { this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); this.ruleset = ruleset; this.searchCategory = searchCategory; this.sortCriteria = sortCriteria; this.direction = direction; + this.genre = genre; } protected override WebRequest CreateWebRequest() @@ -36,6 +38,10 @@ namespace osu.Game.Online.API.Requests req.AddParameter("m", ruleset.ID.Value.ToString()); req.AddParameter("s", searchCategory.ToString().ToLowerInvariant()); + + if (genre != BeatmapSearchGenre.Any) + req.AddParameter("g", ((int)genre).ToString()); + req.AddParameter("sort", $"{sortCriteria.ToString().ToLowerInvariant()}_{directionString}"); return req; @@ -62,4 +68,22 @@ namespace osu.Game.Online.API.Requests [Description("My Maps")] Mine, } + + public enum BeatmapSearchGenre + { + Any, + Unspecified, + + [Description("Video Game")] + Game, + Anime, + Rock, + Pop, + Other, + Novelty, + + [Description("Hip Hop")] + Hiphop = 9, + Electronic + } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs index f9799d8a6b..1e97720705 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs @@ -25,6 +25,8 @@ namespace osu.Game.Overlays.BeatmapListing public Bindable Category => categoryFilter.Current; + public Bindable Genre => genreFilter.Current; + public BeatmapSetInfo BeatmapSet { set @@ -43,6 +45,7 @@ namespace osu.Game.Overlays.BeatmapListing private readonly BeatmapSearchTextBox textBox; private readonly BeatmapSearchRulesetFilterRow modeFilter; private readonly BeatmapSearchFilterRow categoryFilter; + private readonly BeatmapSearchSmallFilterRow genreFilter; private readonly Box background; private readonly UpdateableBeatmapSetCover beatmapCover; @@ -98,6 +101,7 @@ namespace osu.Game.Overlays.BeatmapListing { modeFilter = new BeatmapSearchRulesetFilterRow(), categoryFilter = new BeatmapSearchFilterRow(@"Categories"), + genreFilter = new BeatmapSearchSmallFilterRow(@"Genre"), } } } diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index e212d05442..604af971f3 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -154,6 +154,7 @@ namespace osu.Game.Overlays searchSection.Ruleset.BindValueChanged(_ => queueUpdateSearch()); searchSection.Category.BindValueChanged(_ => queueUpdateSearch()); + searchSection.Genre.BindValueChanged(_ => queueUpdateSearch()); sortCriteria.BindValueChanged(_ => queueUpdateSearch()); sortDirection.BindValueChanged(_ => queueUpdateSearch()); } @@ -171,11 +172,25 @@ namespace osu.Game.Overlays Show(); } + public void ShowGenre(BeatmapSearchGenre genre) + { + var currentGenre = searchSection.Genre.Value; + + if (currentGenre != genre) + { + setDefaultSearchValues(); + searchSection.Genre.Value = genre; + } + + Show(); + } + private void setDefaultSearchValues() { searchSection.Query.Value = string.Empty; searchSection.Ruleset.Value = new RulesetInfo { Name = @"Any" }; searchSection.Category.Value = BeatmapSearchCategory.Leaderboard; + searchSection.Genre.Value = BeatmapSearchGenre.Any; } private ScheduledDelegate queryChangedDebounce; @@ -208,7 +223,8 @@ namespace osu.Game.Overlays searchSection.Ruleset.Value, searchSection.Category.Value, sortControl.Current.Value, - sortControl.SortDirection.Value); + sortControl.SortDirection.Value, + searchSection.Genre.Value); getSetsRequest.Success += response => Schedule(() => recreatePanels(response)); From 063a53017e82a4b883dd088bfb4ca36d72a9a6f5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 20 Feb 2020 17:56:49 +0300 Subject: [PATCH 019/655] Implement Language filter --- .../Online/TestSceneBeatmapListingOverlay.cs | 6 +++++ .../API/Requests/SearchBeatmapSetsRequest.cs | 23 ++++++++++++++++++- .../BeatmapListingSearchSection.cs | 4 ++++ osu.Game/Overlays/BeatmapListingOverlay.cs | 18 ++++++++++++++- 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 9dec965818..4dceb57129 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -37,6 +37,12 @@ namespace osu.Game.Tests.Visual.Online AddStep("Show Anime genre", () => overlay.ShowGenre(BeatmapSearchGenre.Anime)); } + [Test] + public void TestShowLanguage() + { + AddStep("Show Japanese language", () => overlay.ShowLanguage(BeatmapSearchLanguage.Japanese)); + } + [Test] public void TestShow() { diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 797a0a1015..c2679fcd5f 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -17,9 +17,10 @@ namespace osu.Game.Online.API.Requests private readonly DirectSortCriteria sortCriteria; private readonly SortDirection direction; private readonly BeatmapSearchGenre genre; + private readonly BeatmapSearchLanguage language; private string directionString => direction == SortDirection.Descending ? @"desc" : @"asc"; - public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset, BeatmapSearchCategory searchCategory = BeatmapSearchCategory.Any, DirectSortCriteria sortCriteria = DirectSortCriteria.Ranked, SortDirection direction = SortDirection.Descending, BeatmapSearchGenre genre = BeatmapSearchGenre.Any) + public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset, BeatmapSearchCategory searchCategory = BeatmapSearchCategory.Any, DirectSortCriteria sortCriteria = DirectSortCriteria.Ranked, SortDirection direction = SortDirection.Descending, BeatmapSearchGenre genre = BeatmapSearchGenre.Any, BeatmapSearchLanguage language = BeatmapSearchLanguage.Any) { this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); this.ruleset = ruleset; @@ -27,6 +28,7 @@ namespace osu.Game.Online.API.Requests this.sortCriteria = sortCriteria; this.direction = direction; this.genre = genre; + this.language = language; } protected override WebRequest CreateWebRequest() @@ -42,6 +44,9 @@ namespace osu.Game.Online.API.Requests if (genre != BeatmapSearchGenre.Any) req.AddParameter("g", ((int)genre).ToString()); + if (language != BeatmapSearchLanguage.Any) + req.AddParameter("l", ((int)language).ToString()); + req.AddParameter("sort", $"{sortCriteria.ToString().ToLowerInvariant()}_{directionString}"); return req; @@ -86,4 +91,20 @@ namespace osu.Game.Online.API.Requests Hiphop = 9, Electronic } + + public enum BeatmapSearchLanguage + { + Any, + English = 2, + Chilnese = 4, + French = 7, + German, + Italian = 11, + Japanese = 3, + Korean = 6, + Spanish = 10, + Swedish = 9, + Instrumantal = 5, + Other = 1 + } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs index 1e97720705..28619ea6fe 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs @@ -27,6 +27,8 @@ namespace osu.Game.Overlays.BeatmapListing public Bindable Genre => genreFilter.Current; + public Bindable Language => languageFilter.Current; + public BeatmapSetInfo BeatmapSet { set @@ -46,6 +48,7 @@ namespace osu.Game.Overlays.BeatmapListing private readonly BeatmapSearchRulesetFilterRow modeFilter; private readonly BeatmapSearchFilterRow categoryFilter; private readonly BeatmapSearchSmallFilterRow genreFilter; + private readonly BeatmapSearchSmallFilterRow languageFilter; private readonly Box background; private readonly UpdateableBeatmapSetCover beatmapCover; @@ -102,6 +105,7 @@ namespace osu.Game.Overlays.BeatmapListing modeFilter = new BeatmapSearchRulesetFilterRow(), categoryFilter = new BeatmapSearchFilterRow(@"Categories"), genreFilter = new BeatmapSearchSmallFilterRow(@"Genre"), + languageFilter = new BeatmapSearchSmallFilterRow(@"Language"), } } } diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 604af971f3..414dabd7c1 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -155,6 +155,7 @@ namespace osu.Game.Overlays searchSection.Ruleset.BindValueChanged(_ => queueUpdateSearch()); searchSection.Category.BindValueChanged(_ => queueUpdateSearch()); searchSection.Genre.BindValueChanged(_ => queueUpdateSearch()); + searchSection.Language.BindValueChanged(_ => queueUpdateSearch()); sortCriteria.BindValueChanged(_ => queueUpdateSearch()); sortDirection.BindValueChanged(_ => queueUpdateSearch()); } @@ -185,12 +186,26 @@ namespace osu.Game.Overlays Show(); } + public void ShowLanguage(BeatmapSearchLanguage language) + { + var currentLanguage = searchSection.Language.Value; + + if (currentLanguage != language) + { + setDefaultSearchValues(); + searchSection.Language.Value = language; + } + + Show(); + } + private void setDefaultSearchValues() { searchSection.Query.Value = string.Empty; searchSection.Ruleset.Value = new RulesetInfo { Name = @"Any" }; searchSection.Category.Value = BeatmapSearchCategory.Leaderboard; searchSection.Genre.Value = BeatmapSearchGenre.Any; + searchSection.Language.Value = BeatmapSearchLanguage.Any; } private ScheduledDelegate queryChangedDebounce; @@ -224,7 +239,8 @@ namespace osu.Game.Overlays searchSection.Category.Value, sortControl.Current.Value, sortControl.SortDirection.Value, - searchSection.Genre.Value); + searchSection.Genre.Value, + searchSection.Language.Value); getSetsRequest.Success += response => Schedule(() => recreatePanels(response)); From eeae0a57746a4ed70199439499561b55f8311f2e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 21 Feb 2020 00:56:33 +0300 Subject: [PATCH 020/655] Fix typos --- osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index c2679fcd5f..1c1da33d8a 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -80,7 +80,7 @@ namespace osu.Game.Online.API.Requests Unspecified, [Description("Video Game")] - Game, + VideoGame, Anime, Rock, Pop, @@ -88,7 +88,7 @@ namespace osu.Game.Online.API.Requests Novelty, [Description("Hip Hop")] - Hiphop = 9, + HipHop = 9, Electronic } @@ -96,7 +96,7 @@ namespace osu.Game.Online.API.Requests { Any, English = 2, - Chilnese = 4, + Chinese = 4, French = 7, German, Italian = 11, @@ -104,7 +104,7 @@ namespace osu.Game.Online.API.Requests Korean = 6, Spanish = 10, Swedish = 9, - Instrumantal = 5, + Instrumental = 5, Other = 1 } } From d50cca626405266f86e7eae733b2af247cb2243d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 21 Feb 2020 01:05:20 +0300 Subject: [PATCH 021/655] Minor enum adjustments for consistency --- .../API/Requests/SearchBeatmapSetsRequest.cs | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 1c1da33d8a..e329015f67 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -76,35 +76,35 @@ namespace osu.Game.Online.API.Requests public enum BeatmapSearchGenre { - Any, - Unspecified, + Any = 0, + Unspecified = 1, [Description("Video Game")] - VideoGame, - Anime, - Rock, - Pop, - Other, - Novelty, + VideoGame = 2, + Anime = 3, + Rock = 4, + Pop = 5, + Other = 6, + Novelty = 7, [Description("Hip Hop")] HipHop = 9, - Electronic + Electronic = 10 } public enum BeatmapSearchLanguage { Any, - English = 2, - Chinese = 4, - French = 7, + Other, + English, + Japanese, + Chinese, + Instrumental, + Korean, + French, German, - Italian = 11, - Japanese = 3, - Korean = 6, - Spanish = 10, - Swedish = 9, - Instrumental = 5, - Other = 1 + Swedish, + Spanish, + Italian } } From 58903759f13f307df17fd781780952a5d92f102e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 21 Feb 2020 01:37:36 +0300 Subject: [PATCH 022/655] Implement enum attributes to set display order --- .../API/Requests/SearchBeatmapSetsRequest.cs | 41 +++++++++++++++++++ .../BeatmapListing/BeatmapSearchFilterRow.cs | 27 +++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index e329015f67..58a41b6e08 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.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.ComponentModel; using osu.Framework.IO.Network; using osu.Game.Overlays; @@ -92,19 +93,59 @@ namespace osu.Game.Online.API.Requests Electronic = 10 } + [HasOrderedElements] public enum BeatmapSearchLanguage { + [Order(0)] Any, + + [Order(11)] Other, + + [Order(1)] English, + + [Order(6)] Japanese, + + [Order(2)] Chinese, + + [Order(10)] Instrumental, + + [Order(7)] Korean, + + [Order(3)] French, + + [Order(4)] German, + + [Order(9)] Swedish, + + [Order(8)] Spanish, + + [Order(5)] Italian } + + [AttributeUsage(AttributeTargets.Field)] + public class OrderAttribute : Attribute + { + public readonly int Order; + + public OrderAttribute(int order) + { + Order = order; + } + } + + [AttributeUsage(AttributeTargets.Enum)] + public class HasOrderedElementsAttribute : Attribute + { + } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs index 2c046a2bbf..467399dd20 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -13,6 +14,7 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API.Requests; using osuTK; using osuTK.Graphics; @@ -79,9 +81,30 @@ namespace osu.Game.Overlays.BeatmapListing TabContainer.Spacing = new Vector2(10, 0); - if (typeof(T).IsEnum) + var type = typeof(T); + + if (type.IsEnum) { - foreach (var val in (T[])Enum.GetValues(typeof(T))) + if (Attribute.GetCustomAttribute(type, typeof(HasOrderedElementsAttribute)) != null) + { + var enumValues = Enum.GetValues(type).Cast().ToArray(); + var enumNames = Enum.GetNames(type); + + int[] enumPositions = Array.ConvertAll(enumNames, n => + { + var orderAttr = (OrderAttribute)type.GetField(n).GetCustomAttributes(typeof(OrderAttribute), false)[0]; + return orderAttr.Order; + }); + + Array.Sort(enumPositions, enumValues); + + foreach (var val in enumValues) + AddItem(val); + + return; + } + + foreach (var val in (T[])Enum.GetValues(type)) AddItem(val); } } From 20b49bea4b516180bfcddfd40cbc57635c38c84a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 21 Feb 2020 01:49:03 +0300 Subject: [PATCH 023/655] Refactor SearchBeatmapSetsRequest --- .../API/Requests/SearchBeatmapSetsRequest.cs | 43 +++++++++++-------- osu.Game/Overlays/BeatmapListingOverlay.cs | 16 +++---- osu.Game/Overlays/DirectOverlay.cs | 10 ++--- 3 files changed, 38 insertions(+), 31 deletions(-) diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 58a41b6e08..aef0788b49 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -12,24 +12,31 @@ namespace osu.Game.Online.API.Requests { public class SearchBeatmapSetsRequest : APIRequest { + public BeatmapSearchCategory SearchCategory { get; set; } + + public DirectSortCriteria SortCriteria { get; set; } + + public SortDirection SortDirection { get; set; } + + public BeatmapSearchGenre Genre { get; set; } + + public BeatmapSearchLanguage Language { get; set; } + private readonly string query; private readonly RulesetInfo ruleset; - private readonly BeatmapSearchCategory searchCategory; - private readonly DirectSortCriteria sortCriteria; - private readonly SortDirection direction; - private readonly BeatmapSearchGenre genre; - private readonly BeatmapSearchLanguage language; - private string directionString => direction == SortDirection.Descending ? @"desc" : @"asc"; - public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset, BeatmapSearchCategory searchCategory = BeatmapSearchCategory.Any, DirectSortCriteria sortCriteria = DirectSortCriteria.Ranked, SortDirection direction = SortDirection.Descending, BeatmapSearchGenre genre = BeatmapSearchGenre.Any, BeatmapSearchLanguage language = BeatmapSearchLanguage.Any) + private string directionString => SortDirection == SortDirection.Descending ? @"desc" : @"asc"; + + public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset) { this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); this.ruleset = ruleset; - this.searchCategory = searchCategory; - this.sortCriteria = sortCriteria; - this.direction = direction; - this.genre = genre; - this.language = language; + + SearchCategory = BeatmapSearchCategory.Any; + SortCriteria = DirectSortCriteria.Ranked; + SortDirection = SortDirection.Descending; + Genre = BeatmapSearchGenre.Any; + Language = BeatmapSearchLanguage.Any; } protected override WebRequest CreateWebRequest() @@ -40,15 +47,15 @@ namespace osu.Game.Online.API.Requests if (ruleset.ID.HasValue) req.AddParameter("m", ruleset.ID.Value.ToString()); - req.AddParameter("s", searchCategory.ToString().ToLowerInvariant()); + req.AddParameter("s", SearchCategory.ToString().ToLowerInvariant()); - if (genre != BeatmapSearchGenre.Any) - req.AddParameter("g", ((int)genre).ToString()); + if (Genre != BeatmapSearchGenre.Any) + req.AddParameter("g", ((int)Genre).ToString()); - if (language != BeatmapSearchLanguage.Any) - req.AddParameter("l", ((int)language).ToString()); + if (Language != BeatmapSearchLanguage.Any) + req.AddParameter("l", ((int)Language).ToString()); - req.AddParameter("sort", $"{sortCriteria.ToString().ToLowerInvariant()}_{directionString}"); + req.AddParameter("sort", $"{SortCriteria.ToString().ToLowerInvariant()}_{directionString}"); return req; } diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 414dabd7c1..5b7466df0d 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -233,14 +233,14 @@ namespace osu.Game.Overlays currentContent?.FadeColour(Color4.DimGray, 400, Easing.OutQuint); - getSetsRequest = new SearchBeatmapSetsRequest( - searchSection.Query.Value, - searchSection.Ruleset.Value, - searchSection.Category.Value, - sortControl.Current.Value, - sortControl.SortDirection.Value, - searchSection.Genre.Value, - searchSection.Language.Value); + getSetsRequest = new SearchBeatmapSetsRequest(searchSection.Query.Value, searchSection.Ruleset.Value) + { + SearchCategory = searchSection.Category.Value, + SortCriteria = sortControl.Current.Value, + SortDirection = sortControl.SortDirection.Value, + Genre = searchSection.Genre.Value, + Language = searchSection.Language.Value, + }; getSetsRequest.Success += response => Schedule(() => recreatePanels(response)); diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index a6f8b65a0d..0620e687e5 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -254,11 +254,11 @@ namespace osu.Game.Overlays previewTrackManager.StopAnyPlaying(this); - getSetsRequest = new SearchBeatmapSetsRequest( - currentQuery.Value, - ((FilterControl)Filter).Ruleset.Value, - Filter.DisplayStyleControl.Dropdown.Current.Value, - Filter.Tabs.Current.Value); //todo: sort direction (?) + getSetsRequest = new SearchBeatmapSetsRequest(currentQuery.Value, ((FilterControl)Filter).Ruleset.Value) + { + SearchCategory = Filter.DisplayStyleControl.Dropdown.Current.Value, + SortCriteria = Filter.Tabs.Current.Value + }; getSetsRequest.Success += response => { From 3c56118f45f184f8a0a274db99e346530f0893ad Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 21 Feb 2020 02:28:33 +0300 Subject: [PATCH 024/655] Implement BeatmapSearchParameters and refactor all the components --- .../TestSceneBeatmapListingSearchSection.cs | 27 +++++++- .../BeatmapListingSearchSection.cs | 35 +++++++---- .../BeatmapListing/BeatmapSearchParameters.cs | 30 +++++++++ osu.Game/Overlays/BeatmapListingOverlay.cs | 63 ++++++------------- 4 files changed, 96 insertions(+), 59 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapListing/BeatmapSearchParameters.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchSection.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchSection.cs index 1d8db71527..f809c780f1 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchSection.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchSection.cs @@ -11,6 +11,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapListing; +using osu.Game.Online.API.Requests; using osuTK; namespace osu.Game.Tests.Visual.UserInterface @@ -32,6 +33,8 @@ namespace osu.Game.Tests.Visual.UserInterface OsuSpriteText query; OsuSpriteText ruleset; OsuSpriteText category; + OsuSpriteText genre; + OsuSpriteText language; Add(section = new BeatmapListingSearchSection { @@ -49,12 +52,19 @@ namespace osu.Game.Tests.Visual.UserInterface query = new OsuSpriteText(), ruleset = new OsuSpriteText(), category = new OsuSpriteText(), + genre = new OsuSpriteText(), + language = new OsuSpriteText(), } }); - section.Query.BindValueChanged(q => query.Text = $"Query: {q.NewValue}", true); - section.Ruleset.BindValueChanged(r => ruleset.Text = $"Ruleset: {r.NewValue}", true); - section.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true); + section.SearchParameters.BindValueChanged(parameters => + { + query.Text = $"Query: {parameters.NewValue.Query}"; + ruleset.Text = $"Ruleset: {parameters.NewValue.Ruleset}"; + category.Text = $"Category: {parameters.NewValue.Category}"; + genre.Text = $"Genre: {parameters.NewValue.Genre}"; + language.Text = $"Language: {parameters.NewValue.Language}"; + }, true); } [Test] @@ -65,6 +75,17 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("Set null beatmap", () => section.BeatmapSet = null); } + [Test] + public void TestParametersSet() + { + AddStep("Set big black tag", () => section.SetTag("big black")); + AddAssert("Check query is big black", () => section.SearchParameters.Value.Query == "big black"); + AddStep("Set anime genre", () => section.SetGenre(BeatmapSearchGenre.Anime)); + AddAssert("Check genre is anime", () => section.SearchParameters.Value.Genre == BeatmapSearchGenre.Anime); + AddStep("Set japanese language", () => section.SetLanguage(BeatmapSearchLanguage.Japanese)); + AddAssert("Check language is japanese", () => section.SearchParameters.Value.Language == BeatmapSearchLanguage.Japanese); + } + private static readonly BeatmapSetInfo beatmap_set = new BeatmapSetInfo { OnlineInfo = new BeatmapSetOnlineInfo diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs index 28619ea6fe..121b101861 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Online.API.Requests; -using osu.Game.Rulesets; using osuTK; using osu.Framework.Bindables; using osu.Game.Beatmaps.Drawables; @@ -19,15 +18,7 @@ namespace osu.Game.Overlays.BeatmapListing { public class BeatmapListingSearchSection : CompositeDrawable { - public Bindable Query => textBox.Current; - - public Bindable Ruleset => modeFilter.Current; - - public Bindable Category => categoryFilter.Current; - - public Bindable Genre => genreFilter.Current; - - public Bindable Language => languageFilter.Current; + public Bindable SearchParameters = new Bindable(); public BeatmapSetInfo BeatmapSet { @@ -113,7 +104,13 @@ namespace osu.Game.Overlays.BeatmapListing } }); - Category.Value = BeatmapSearchCategory.Leaderboard; + categoryFilter.Current.Value = BeatmapSearchCategory.Leaderboard; + + textBox.Current.BindValueChanged(_ => changeSearchParameters()); + modeFilter.Current.BindValueChanged(_ => changeSearchParameters()); + categoryFilter.Current.BindValueChanged(_ => changeSearchParameters()); + genreFilter.Current.BindValueChanged(_ => changeSearchParameters()); + languageFilter.Current.BindValueChanged(_ => changeSearchParameters(), true); } [BackgroundDependencyLoader] @@ -122,6 +119,22 @@ namespace osu.Game.Overlays.BeatmapListing background.Colour = colourProvider.Dark6; } + public void SetTag(string tag) => textBox.Current.Value = tag; + + public void SetGenre(BeatmapSearchGenre genre) => genreFilter.Current.Value = genre; + + public void SetLanguage(BeatmapSearchLanguage language) => languageFilter.Current.Value = language; + + private void changeSearchParameters() + { + SearchParameters.Value = new BeatmapSearchParameters( + textBox.Current.Value, + modeFilter.Current.Value, + categoryFilter.Current.Value, + genreFilter.Current.Value, + languageFilter.Current.Value); + } + private class BeatmapSearchTextBox : SearchTextBox { protected override Color4 SelectionColour => Color4.Gray; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchParameters.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchParameters.cs new file mode 100644 index 0000000000..6a681503f5 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchParameters.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Online.API.Requests; +using osu.Game.Rulesets; + +namespace osu.Game.Overlays.BeatmapListing +{ + public class BeatmapSearchParameters + { + public readonly string Query; + + public readonly RulesetInfo Ruleset; + + public readonly BeatmapSearchCategory Category; + + public readonly BeatmapSearchGenre Genre; + + public readonly BeatmapSearchLanguage Language; + + public BeatmapSearchParameters(string query, RulesetInfo ruleset, BeatmapSearchCategory category, BeatmapSearchGenre genre, BeatmapSearchLanguage language) + { + Query = query; + Ruleset = ruleset; + Category = category; + Genre = genre; + Language = language; + } + } +} diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 5b7466df0d..2449f561c1 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -144,70 +144,43 @@ namespace osu.Game.Overlays var sortCriteria = sortControl.Current; var sortDirection = sortControl.SortDirection; - searchSection.Query.BindValueChanged(query => + searchSection.SearchParameters.BindValueChanged(parameters => { - sortCriteria.Value = string.IsNullOrEmpty(query.NewValue) ? DirectSortCriteria.Ranked : DirectSortCriteria.Relevance; - sortDirection.Value = SortDirection.Descending; + if (parameters.OldValue.Query != parameters.NewValue.Query) + { + sortCriteria.Value = string.IsNullOrEmpty(parameters.NewValue.Query) ? DirectSortCriteria.Ranked : DirectSortCriteria.Relevance; + sortDirection.Value = SortDirection.Descending; - queueUpdateSearch(true); + queueUpdateSearch(true); + } + else + { + queueUpdateSearch(); + } }); - searchSection.Ruleset.BindValueChanged(_ => queueUpdateSearch()); - searchSection.Category.BindValueChanged(_ => queueUpdateSearch()); - searchSection.Genre.BindValueChanged(_ => queueUpdateSearch()); - searchSection.Language.BindValueChanged(_ => queueUpdateSearch()); sortCriteria.BindValueChanged(_ => queueUpdateSearch()); sortDirection.BindValueChanged(_ => queueUpdateSearch()); } public void ShowTag(string tag) { - var currentQuery = searchSection.Query.Value; - - if (currentQuery != tag) - { - setDefaultSearchValues(); - searchSection.Query.Value = tag; - } - + searchSection.SetTag(tag); Show(); } public void ShowGenre(BeatmapSearchGenre genre) { - var currentGenre = searchSection.Genre.Value; - - if (currentGenre != genre) - { - setDefaultSearchValues(); - searchSection.Genre.Value = genre; - } - + searchSection.SetGenre(genre); Show(); } public void ShowLanguage(BeatmapSearchLanguage language) { - var currentLanguage = searchSection.Language.Value; - - if (currentLanguage != language) - { - setDefaultSearchValues(); - searchSection.Language.Value = language; - } - + searchSection.SetLanguage(language); Show(); } - private void setDefaultSearchValues() - { - searchSection.Query.Value = string.Empty; - searchSection.Ruleset.Value = new RulesetInfo { Name = @"Any" }; - searchSection.Category.Value = BeatmapSearchCategory.Leaderboard; - searchSection.Genre.Value = BeatmapSearchGenre.Any; - searchSection.Language.Value = BeatmapSearchLanguage.Any; - } - private ScheduledDelegate queryChangedDebounce; private void queueUpdateSearch(bool queryTextChanged = false) @@ -233,13 +206,13 @@ namespace osu.Game.Overlays currentContent?.FadeColour(Color4.DimGray, 400, Easing.OutQuint); - getSetsRequest = new SearchBeatmapSetsRequest(searchSection.Query.Value, searchSection.Ruleset.Value) + getSetsRequest = new SearchBeatmapSetsRequest(searchSection.SearchParameters.Value.Query, searchSection.SearchParameters.Value.Ruleset) { - SearchCategory = searchSection.Category.Value, + SearchCategory = searchSection.SearchParameters.Value.Category, SortCriteria = sortControl.Current.Value, SortDirection = sortControl.SortDirection.Value, - Genre = searchSection.Genre.Value, - Language = searchSection.Language.Value, + Genre = searchSection.SearchParameters.Value.Genre, + Language = searchSection.SearchParameters.Value.Language }; getSetsRequest.Success += response => Schedule(() => recreatePanels(response)); From 1318f242c1cdd710b52ace6de9ce881ec24cb1fb Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 6 Mar 2020 02:12:30 +0300 Subject: [PATCH 025/655] Revert changes to basic implementation and remove redundant stuff --- .../Online/TestSceneBeatmapListingOverlay.cs | 19 -------- .../TestSceneBeatmapListingSearchSection.cs | 25 ++-------- .../BeatmapListingSearchSection.cs | 33 ++++--------- .../BeatmapListing/BeatmapSearchParameters.cs | 30 ------------ osu.Game/Overlays/BeatmapListingOverlay.cs | 47 +++++-------------- 5 files changed, 28 insertions(+), 126 deletions(-) delete mode 100644 osu.Game/Overlays/BeatmapListing/BeatmapSearchParameters.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 4dceb57129..7c05d99c59 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using osu.Game.Overlays; using NUnit.Framework; -using osu.Game.Online.API.Requests; namespace osu.Game.Tests.Visual.Online { @@ -25,24 +24,6 @@ namespace osu.Game.Tests.Visual.Online Add(overlay = new BeatmapListingOverlay()); } - [Test] - public void TestShowTag() - { - AddStep("Show Rem tag", () => overlay.ShowTag("Rem")); - } - - [Test] - public void TestShowGenre() - { - AddStep("Show Anime genre", () => overlay.ShowGenre(BeatmapSearchGenre.Anime)); - } - - [Test] - public void TestShowLanguage() - { - AddStep("Show Japanese language", () => overlay.ShowLanguage(BeatmapSearchLanguage.Japanese)); - } - [Test] public void TestShow() { diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchSection.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchSection.cs index f809c780f1..69e3fbd75f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchSection.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchSection.cs @@ -11,7 +11,6 @@ using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapListing; -using osu.Game.Online.API.Requests; using osuTK; namespace osu.Game.Tests.Visual.UserInterface @@ -57,14 +56,11 @@ namespace osu.Game.Tests.Visual.UserInterface } }); - section.SearchParameters.BindValueChanged(parameters => - { - query.Text = $"Query: {parameters.NewValue.Query}"; - ruleset.Text = $"Ruleset: {parameters.NewValue.Ruleset}"; - category.Text = $"Category: {parameters.NewValue.Category}"; - genre.Text = $"Genre: {parameters.NewValue.Genre}"; - language.Text = $"Language: {parameters.NewValue.Language}"; - }, true); + section.Query.BindValueChanged(q => query.Text = $"Query: {q.NewValue}", true); + section.Ruleset.BindValueChanged(r => ruleset.Text = $"Ruleset: {r.NewValue}", true); + section.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true); + section.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); + section.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true); } [Test] @@ -75,17 +71,6 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("Set null beatmap", () => section.BeatmapSet = null); } - [Test] - public void TestParametersSet() - { - AddStep("Set big black tag", () => section.SetTag("big black")); - AddAssert("Check query is big black", () => section.SearchParameters.Value.Query == "big black"); - AddStep("Set anime genre", () => section.SetGenre(BeatmapSearchGenre.Anime)); - AddAssert("Check genre is anime", () => section.SearchParameters.Value.Genre == BeatmapSearchGenre.Anime); - AddStep("Set japanese language", () => section.SetLanguage(BeatmapSearchLanguage.Japanese)); - AddAssert("Check language is japanese", () => section.SearchParameters.Value.Language == BeatmapSearchLanguage.Japanese); - } - private static readonly BeatmapSetInfo beatmap_set = new BeatmapSetInfo { OnlineInfo = new BeatmapSetOnlineInfo diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs index 121b101861..501abbf2c8 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs @@ -13,12 +13,21 @@ using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osuTK.Graphics; +using osu.Game.Rulesets; namespace osu.Game.Overlays.BeatmapListing { public class BeatmapListingSearchSection : CompositeDrawable { - public Bindable SearchParameters = new Bindable(); + public Bindable Query => textBox.Current; + + public Bindable Ruleset => modeFilter.Current; + + public Bindable Category => categoryFilter.Current; + + public Bindable Genre => genreFilter.Current; + + public Bindable Language => languageFilter.Current; public BeatmapSetInfo BeatmapSet { @@ -105,12 +114,6 @@ namespace osu.Game.Overlays.BeatmapListing }); categoryFilter.Current.Value = BeatmapSearchCategory.Leaderboard; - - textBox.Current.BindValueChanged(_ => changeSearchParameters()); - modeFilter.Current.BindValueChanged(_ => changeSearchParameters()); - categoryFilter.Current.BindValueChanged(_ => changeSearchParameters()); - genreFilter.Current.BindValueChanged(_ => changeSearchParameters()); - languageFilter.Current.BindValueChanged(_ => changeSearchParameters(), true); } [BackgroundDependencyLoader] @@ -119,22 +122,6 @@ namespace osu.Game.Overlays.BeatmapListing background.Colour = colourProvider.Dark6; } - public void SetTag(string tag) => textBox.Current.Value = tag; - - public void SetGenre(BeatmapSearchGenre genre) => genreFilter.Current.Value = genre; - - public void SetLanguage(BeatmapSearchLanguage language) => languageFilter.Current.Value = language; - - private void changeSearchParameters() - { - SearchParameters.Value = new BeatmapSearchParameters( - textBox.Current.Value, - modeFilter.Current.Value, - categoryFilter.Current.Value, - genreFilter.Current.Value, - languageFilter.Current.Value); - } - private class BeatmapSearchTextBox : SearchTextBox { protected override Color4 SelectionColour => Color4.Gray; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchParameters.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchParameters.cs deleted file mode 100644 index 6a681503f5..0000000000 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchParameters.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Online.API.Requests; -using osu.Game.Rulesets; - -namespace osu.Game.Overlays.BeatmapListing -{ - public class BeatmapSearchParameters - { - public readonly string Query; - - public readonly RulesetInfo Ruleset; - - public readonly BeatmapSearchCategory Category; - - public readonly BeatmapSearchGenre Genre; - - public readonly BeatmapSearchLanguage Language; - - public BeatmapSearchParameters(string query, RulesetInfo ruleset, BeatmapSearchCategory category, BeatmapSearchGenre genre, BeatmapSearchLanguage language) - { - Query = query; - Ruleset = ruleset; - Category = category; - Genre = genre; - Language = language; - } - } -} diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index ebe4b7fe61..1a5257457f 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -153,43 +153,22 @@ namespace osu.Game.Overlays var sortCriteria = sortControl.Current; var sortDirection = sortControl.SortDirection; - searchSection.SearchParameters.BindValueChanged(parameters => + searchSection.Query.BindValueChanged(query => { - if (parameters.OldValue.Query != parameters.NewValue.Query) - { - sortCriteria.Value = string.IsNullOrEmpty(parameters.NewValue.Query) ? DirectSortCriteria.Ranked : DirectSortCriteria.Relevance; - sortDirection.Value = SortDirection.Descending; - - queueUpdateSearch(true); - } - else - { - queueUpdateSearch(); - } + sortCriteria.Value = string.IsNullOrEmpty(query.NewValue) ? DirectSortCriteria.Ranked : DirectSortCriteria.Relevance; + sortDirection.Value = SortDirection.Descending; + queueUpdateSearch(true); }); + searchSection.Ruleset.BindValueChanged(_ => queueUpdateSearch()); + searchSection.Category.BindValueChanged(_ => queueUpdateSearch()); + searchSection.Genre.BindValueChanged(_ => queueUpdateSearch()); + searchSection.Language.BindValueChanged(_ => queueUpdateSearch()); + sortCriteria.BindValueChanged(_ => queueUpdateSearch()); sortDirection.BindValueChanged(_ => queueUpdateSearch()); } - public void ShowTag(string tag) - { - searchSection.SetTag(tag); - Show(); - } - - public void ShowGenre(BeatmapSearchGenre genre) - { - searchSection.SetGenre(genre); - Show(); - } - - public void ShowLanguage(BeatmapSearchLanguage language) - { - searchSection.SetLanguage(language); - Show(); - } - private ScheduledDelegate queryChangedDebounce; private LoadingLayer loadingLayer; @@ -218,13 +197,13 @@ namespace osu.Game.Overlays loadingLayer.Show(); - getSetsRequest = new SearchBeatmapSetsRequest(searchSection.SearchParameters.Value.Query, searchSection.SearchParameters.Value.Ruleset) + getSetsRequest = new SearchBeatmapSetsRequest(searchSection.Query.Value, searchSection.Ruleset.Value) { - SearchCategory = searchSection.SearchParameters.Value.Category, + SearchCategory = searchSection.Category.Value, SortCriteria = sortControl.Current.Value, SortDirection = sortControl.SortDirection.Value, - Genre = searchSection.SearchParameters.Value.Genre, - Language = searchSection.SearchParameters.Value.Language + Genre = searchSection.Genre.Value, + Language = searchSection.Language.Value }; getSetsRequest.Success += response => Schedule(() => recreatePanels(response)); From c7384b9717a7cf9d063a502a8b219c04f98cc991 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 6 Mar 2020 03:09:43 +0300 Subject: [PATCH 026/655] Implement BeatmapListingSearchHandler component --- .../Online/TestSceneBeatmapListingOverlay.cs | 2 + .../BeatmapListingSearchHandler.cs | 163 +++++++++++++++++ osu.Game/Overlays/BeatmapListingOverlay.cs | 172 +++--------------- 3 files changed, 191 insertions(+), 146 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapListing/BeatmapListingSearchHandler.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 7c05d99c59..f80687e142 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using osu.Game.Overlays; using NUnit.Framework; +using osu.Game.Overlays.BeatmapListing; namespace osu.Game.Tests.Visual.Online { @@ -13,6 +14,7 @@ namespace osu.Game.Tests.Visual.Online public override IReadOnlyList RequiredTypes => new[] { typeof(BeatmapListingOverlay), + typeof(BeatmapListingSearchHandler) }; protected override bool UseOnlineAPI => true; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchHandler.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchHandler.cs new file mode 100644 index 0000000000..ce3d37fb98 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchHandler.cs @@ -0,0 +1,163 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Threading; +using osu.Game.Beatmaps; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Direct; +using osu.Game.Rulesets; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.BeatmapListing +{ + public class BeatmapListingSearchHandler : CompositeDrawable + { + public Action> SearchFinished; + public Action SearchStarted; + + [Resolved] + private IAPIProvider api { get; set; } + + [Resolved] + private RulesetStore rulesets { get; set; } + + private readonly BeatmapListingSearchSection searchSection; + private readonly BeatmapListingSortTabControl sortControl; + private readonly Box sortControlBackground; + + private SearchBeatmapSetsRequest getSetsRequest; + + public BeatmapListingSearchHandler() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.25f), + Type = EdgeEffectType.Shadow, + Radius = 3, + Offset = new Vector2(0f, 1f), + }, + Child = searchSection = new BeatmapListingSearchSection(), + }, + new Container + { + RelativeSizeAxes = Axes.X, + Height = 40, + Children = new Drawable[] + { + sortControlBackground = new Box + { + RelativeSizeAxes = Axes.Both + }, + sortControl = new BeatmapListingSortTabControl + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Left = 20 } + } + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + sortControlBackground.Colour = colourProvider.Background5; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + var sortCriteria = sortControl.Current; + var sortDirection = sortControl.SortDirection; + + searchSection.Query.BindValueChanged(query => + { + sortCriteria.Value = string.IsNullOrEmpty(query.NewValue) ? DirectSortCriteria.Ranked : DirectSortCriteria.Relevance; + sortDirection.Value = SortDirection.Descending; + queueUpdateSearch(true); + }); + + searchSection.Ruleset.BindValueChanged(_ => queueUpdateSearch()); + searchSection.Category.BindValueChanged(_ => queueUpdateSearch()); + searchSection.Genre.BindValueChanged(_ => queueUpdateSearch()); + searchSection.Language.BindValueChanged(_ => queueUpdateSearch()); + + sortCriteria.BindValueChanged(_ => queueUpdateSearch()); + sortDirection.BindValueChanged(_ => queueUpdateSearch()); + } + + private ScheduledDelegate queryChangedDebounce; + + private void queueUpdateSearch(bool queryTextChanged = false) + { + SearchStarted?.Invoke(); + + getSetsRequest?.Cancel(); + + queryChangedDebounce?.Cancel(); + queryChangedDebounce = Scheduler.AddDelayed(updateSearch, queryTextChanged ? 500 : 100); + } + + private void updateSearch() + { + getSetsRequest = new SearchBeatmapSetsRequest(searchSection.Query.Value, searchSection.Ruleset.Value) + { + SearchCategory = searchSection.Category.Value, + SortCriteria = sortControl.Current.Value, + SortDirection = sortControl.SortDirection.Value, + Genre = searchSection.Genre.Value, + Language = searchSection.Language.Value + }; + + getSetsRequest.Success += response => Schedule(() => onSearchFinished(response)); + + api.Queue(getSetsRequest); + } + + private void onSearchFinished(SearchBeatmapSetsResponse response) + { + var beatmaps = response.BeatmapSets.Select(r => r.ToBeatmapSet(rulesets)).ToList(); + + searchSection.BeatmapSet = response.Total == 0 ? null : beatmaps.First(); + + SearchFinished?.Invoke(beatmaps); + } + + protected override void Dispose(bool isDisposing) + { + getSetsRequest?.Cancel(); + queryChangedDebounce?.Cancel(); + + base.Dispose(isDisposing); + } + } +} diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 1a5257457f..dd8dc4a79d 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -1,27 +1,23 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Linq; +using System.Threading; using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Threading; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API.Requests; using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.Direct; -using osu.Game.Rulesets; using osuTK; -using osuTK.Graphics; namespace osu.Game.Overlays { @@ -30,14 +26,9 @@ namespace osu.Game.Overlays [Resolved] private PreviewTrackManager previewTrackManager { get; set; } - [Resolved] - private RulesetStore rulesets { get; set; } - - private SearchBeatmapSetsRequest getSetsRequest; - private Drawable currentContent; - private BeatmapListingSearchSection searchSection; - private BeatmapListingSortTabControl sortControl; + private LoadingLayer loadingLayer; + private Container panelTarget; public BeatmapListingOverlay() : base(OverlayColourScheme.Blue) @@ -63,27 +54,13 @@ namespace osu.Game.Overlays AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), Children = new Drawable[] { - new FillFlowContainer + new BeatmapListingHeader(), + new BeatmapListingSearchHandler { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Masking = true, - EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.Black.Opacity(0.25f), - Type = EdgeEffectType.Shadow, - Radius = 3, - Offset = new Vector2(0f, 1f), - }, - Children = new Drawable[] - { - new BeatmapListingHeader(), - searchSection = new BeatmapListingSearchSection(), - } + SearchStarted = onSearchStarted, + SearchFinished = onSearchFinished, }, new Container { @@ -96,132 +73,41 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both, Colour = ColourProvider.Background4, }, - new FillFlowContainer + panelTarget = new Container { - RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.X, - Height = 40, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourProvider.Background5 - }, - sortControl = new BeatmapListingSortTabControl - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Margin = new MarginPadding { Left = 20 } - } - } - }, - new Container - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Padding = new MarginPadding { Horizontal = 20 }, - Children = new Drawable[] - { - panelTarget = new Container - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - }, - loadingLayer = new LoadingLayer(panelTarget), - } - }, - } - } + RelativeSizeAxes = Axes.X, + Padding = new MarginPadding { Horizontal = 20 } + }, + loadingLayer = new LoadingLayer(panelTarget) } - } + }, } } } }; } - protected override void LoadComplete() + private CancellationTokenSource cancellationToken; + + private void onSearchStarted() { - base.LoadComplete(); - - var sortCriteria = sortControl.Current; - var sortDirection = sortControl.SortDirection; - - searchSection.Query.BindValueChanged(query => - { - sortCriteria.Value = string.IsNullOrEmpty(query.NewValue) ? DirectSortCriteria.Ranked : DirectSortCriteria.Relevance; - sortDirection.Value = SortDirection.Descending; - queueUpdateSearch(true); - }); - - searchSection.Ruleset.BindValueChanged(_ => queueUpdateSearch()); - searchSection.Category.BindValueChanged(_ => queueUpdateSearch()); - searchSection.Genre.BindValueChanged(_ => queueUpdateSearch()); - searchSection.Language.BindValueChanged(_ => queueUpdateSearch()); - - sortCriteria.BindValueChanged(_ => queueUpdateSearch()); - sortDirection.BindValueChanged(_ => queueUpdateSearch()); - } - - private ScheduledDelegate queryChangedDebounce; - - private LoadingLayer loadingLayer; - private Container panelTarget; - - private void queueUpdateSearch(bool queryTextChanged = false) - { - getSetsRequest?.Cancel(); - - queryChangedDebounce?.Cancel(); - queryChangedDebounce = Scheduler.AddDelayed(updateSearch, queryTextChanged ? 500 : 100); - } - - private void updateSearch() - { - if (!IsLoaded) - return; - - if (State.Value == Visibility.Hidden) - return; - - if (API == null) - return; + cancellationToken?.Cancel(); previewTrackManager.StopAnyPlaying(this); - loadingLayer.Show(); - - getSetsRequest = new SearchBeatmapSetsRequest(searchSection.Query.Value, searchSection.Ruleset.Value) - { - SearchCategory = searchSection.Category.Value, - SortCriteria = sortControl.Current.Value, - SortDirection = sortControl.SortDirection.Value, - Genre = searchSection.Genre.Value, - Language = searchSection.Language.Value - }; - - getSetsRequest.Success += response => Schedule(() => recreatePanels(response)); - - API.Queue(getSetsRequest); + if (panelTarget.Any()) + loadingLayer.Show(); } - private void recreatePanels(SearchBeatmapSetsResponse response) + private void onSearchFinished(List beatmaps) { - if (response.Total == 0) + if (!beatmaps.Any()) { - searchSection.BeatmapSet = null; - LoadComponentAsync(new NotFoundDrawable(), addContentToPlaceholder); + LoadComponentAsync(new NotFoundDrawable(), addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); return; } - var beatmaps = response.BeatmapSets.Select(r => r.ToBeatmapSet(rulesets)).ToList(); - var newPanels = new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -236,18 +122,14 @@ namespace osu.Game.Overlays }) }; - LoadComponentAsync(newPanels, loaded => - { - addContentToPlaceholder(loaded); - searchSection.BeatmapSet = beatmaps.First(); - }); + LoadComponentAsync(newPanels, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); } private void addContentToPlaceholder(Drawable content) { loadingLayer.Hide(); - Drawable lastContent = currentContent; + var lastContent = currentContent; if (lastContent != null) { @@ -266,9 +148,7 @@ namespace osu.Game.Overlays protected override void Dispose(bool isDisposing) { - getSetsRequest?.Cancel(); - queryChangedDebounce?.Cancel(); - + cancellationToken?.Cancel(); base.Dispose(isDisposing); } From 742698acab9ba2034ebae716bcd8cc1634d5a13e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 10 Mar 2020 15:30:24 +0900 Subject: [PATCH 027/655] Add notelock implementation --- .../Objects/Drawables/DrawableHitCircle.cs | 2 +- .../Objects/Drawables/DrawableOsuHitObject.cs | 7 +++++ osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 28 +++++++++++++++++-- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index da1e666aba..3ca2714511 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables var result = HitObject.HitWindows.ResultFor(timeOffset); - if (result == HitResult.None) + if (result == HitResult.None || CheckHittable?.Invoke(this) == false) { Shake(Math.Abs(timeOffset) - HitObject.HitWindows.WindowFor(HitResult.Miss)); return; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index a677cb6a72..82a81040e4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; @@ -16,6 +17,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // Must be set to update IsHovered as it's used in relax mdo to detect osu hit objects. public override bool HandlePositionalInput => true; + /// + /// Whether this can be hit. + /// If not-null, this will not receive a judgement until this function returns true. + /// + public Func CheckHittable; + protected DrawableOsuHitObject(OsuHitObject hitObject) : base(hitObject) { diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 6d1ea4bbfc..9eb2786951 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Extensions.IEnumerableExtensions; using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -64,7 +65,10 @@ namespace osu.Game.Rulesets.Osu.UI base.Add(h); - followPoints.AddFollowPoints((DrawableOsuHitObject)h); + DrawableOsuHitObject osuHitObject = (DrawableOsuHitObject)h; + osuHitObject.CheckHittable = checkHittable; + + followPoints.AddFollowPoints(osuHitObject); } public override bool Remove(DrawableHitObject h) @@ -72,11 +76,31 @@ namespace osu.Game.Rulesets.Osu.UI bool result = base.Remove(h); if (result) - followPoints.RemoveFollowPoints((DrawableOsuHitObject)h); + { + DrawableOsuHitObject osuHitObject = (DrawableOsuHitObject)h; + osuHitObject.CheckHittable = null; + + followPoints.RemoveFollowPoints(osuHitObject); + } return result; } + private bool checkHittable(DrawableOsuHitObject osuHitObject) + { + var lastObject = HitObjectContainer.AliveObjects.GetPrevious(osuHitObject); + + // Ensure the last object is not alive anymore, in which case always allow the hit. + if (lastObject == null) + return true; + + // Ensure that either the last object has received a judgement or the hit time occurs after the last object's start time. + if (lastObject.Judged || Time.Current > lastObject.HitObject.StartTime) + return true; + + return false; + } + private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) { if (!judgedObject.DisplayResult || !DisplayJudgements.Value) From 1aacd1aaa2568eecb1f00ecc1b77cb0ae406082f Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 16 Mar 2020 20:43:02 +0100 Subject: [PATCH 028/655] Initial implementation of LowHealthLayer --- osu.Game/Configuration/OsuConfigManager.cs | 2 + .../Sections/Gameplay/GeneralSettings.cs | 6 +++ osu.Game/Screens/Play/HUD/LowHealthLayer.cs | 47 +++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 osu.Game/Screens/Play/HUD/LowHealthLayer.cs diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 21de654670..895bacafc4 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -88,6 +88,7 @@ namespace osu.Game.Configuration Set(OsuSetting.ShowInterface, true); Set(OsuSetting.ShowProgressGraph, true); Set(OsuSetting.ShowHealthDisplayWhenCantFail, true); + Set(OsuSetting.FadePlayfieldWhenLowHealth, true); Set(OsuSetting.KeyOverlay, false); Set(OsuSetting.ScoreMeter, ScoreMeterType.HitErrorBoth); @@ -183,6 +184,7 @@ namespace osu.Game.Configuration ShowInterface, ShowProgressGraph, ShowHealthDisplayWhenCantFail, + FadePlayfieldWhenLowHealth, MouseDisableButtons, MouseDisableWheel, AudioOffset, diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 2d2cd42213..6b6b3e8fa4 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -53,6 +53,12 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay Keywords = new[] { "hp", "bar" } }, new SettingsCheckbox + { + LabelText = "Fade playfield to red when health is low", + Bindable = config.GetBindable(OsuSetting.FadePlayfieldWhenLowHealth), + Keywords = new[] { "hp", "playfield", "health" } + }, + new SettingsCheckbox { LabelText = "Always show key overlay", Bindable = config.GetBindable(OsuSetting.KeyOverlay) diff --git a/osu.Game/Screens/Play/HUD/LowHealthLayer.cs b/osu.Game/Screens/Play/HUD/LowHealthLayer.cs new file mode 100644 index 0000000000..8f03a95877 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/LowHealthLayer.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Configuration; +using osu.Game.Graphics; + +namespace osu.Game.Screens.Play.HUD +{ + public class LowHealthLayer : HealthDisplay + { + private const float max_alpha = 0.4f; + + private const double fade_time = 300; + + private readonly Box box; + + private Bindable configFadeRedWhenLowHealth; + + public LowHealthLayer() + { + Child = box = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0 + }; + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config, OsuColour color) + { + configFadeRedWhenLowHealth = config.GetBindable(OsuSetting.FadePlayfieldWhenLowHealth); + box.Colour = color.Red; + + configFadeRedWhenLowHealth.BindValueChanged(value => + { + if (value.NewValue) + this.FadeIn(fade_time, Easing.OutQuint); + else + this.FadeOut(fade_time, Easing.OutQuint); + }, true); + } + } +} From 8c611a981f0fc35ab89fb8012157dc7c62cecb00 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 16 Mar 2020 21:48:28 +0100 Subject: [PATCH 029/655] Update visual tests --- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index fc03dc6ed3..579f6ff9b6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -103,6 +103,38 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("return value", () => config.Set(OsuSetting.KeyOverlay, keyCounterVisibleValue)); } + [Test] + public void TestChangeHealthValue() + { + void applyToHealthDisplays(double value) + { + if (hudOverlay == null) return; + + hudOverlay.LowHealthDisplay.Current.Value = value; + hudOverlay.HealthDisplay.Current.Value = value; + } + + createNew(); + AddSliderStep("health value", 0, 1, 0.5, applyToHealthDisplays); + + AddStep("enable low health display", () => + { + config.Set(OsuSetting.FadePlayfieldWhenLowHealth, true); + hudOverlay.LowHealthDisplay.FinishTransforms(true); + }); + AddAssert("low health display is visible", () => hudOverlay.LowHealthDisplay.IsPresent); + AddStep("set health to 30%", () => applyToHealthDisplays(0.3)); + AddAssert("hud is not faded to red", () => !hudOverlay.LowHealthDisplay.Child.IsPresent); + AddStep("set health to < 10%", () => applyToHealthDisplays(0.1f)); + AddAssert("hud is faded to red", () => hudOverlay.LowHealthDisplay.Child.IsPresent); + AddStep("disable low health display", () => + { + config.Set(OsuSetting.FadePlayfieldWhenLowHealth, false); + hudOverlay.LowHealthDisplay.FinishTransforms(true); + }); + AddAssert("low health display is not visible", () => !hudOverlay.LowHealthDisplay.IsPresent); + } + private void createNew(Action action = null) { AddStep("create overlay", () => From 6b0c5bc65d1f6aa80fd98abd2261613ca971fbbc Mon Sep 17 00:00:00 2001 From: Lucas A Date: Tue, 17 Mar 2020 22:32:07 +0100 Subject: [PATCH 030/655] Rename to LowHealthLayer to FaillingLayer. --- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 32 ------------- osu.Game/Screens/Play/HUD/FaillingLayer.cs | 47 +++++++++++++++++++ osu.Game/Screens/Play/HUD/LowHealthLayer.cs | 47 ------------------- 3 files changed, 47 insertions(+), 79 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/FaillingLayer.cs delete mode 100644 osu.Game/Screens/Play/HUD/LowHealthLayer.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 579f6ff9b6..fc03dc6ed3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -103,38 +103,6 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("return value", () => config.Set(OsuSetting.KeyOverlay, keyCounterVisibleValue)); } - [Test] - public void TestChangeHealthValue() - { - void applyToHealthDisplays(double value) - { - if (hudOverlay == null) return; - - hudOverlay.LowHealthDisplay.Current.Value = value; - hudOverlay.HealthDisplay.Current.Value = value; - } - - createNew(); - AddSliderStep("health value", 0, 1, 0.5, applyToHealthDisplays); - - AddStep("enable low health display", () => - { - config.Set(OsuSetting.FadePlayfieldWhenLowHealth, true); - hudOverlay.LowHealthDisplay.FinishTransforms(true); - }); - AddAssert("low health display is visible", () => hudOverlay.LowHealthDisplay.IsPresent); - AddStep("set health to 30%", () => applyToHealthDisplays(0.3)); - AddAssert("hud is not faded to red", () => !hudOverlay.LowHealthDisplay.Child.IsPresent); - AddStep("set health to < 10%", () => applyToHealthDisplays(0.1f)); - AddAssert("hud is faded to red", () => hudOverlay.LowHealthDisplay.Child.IsPresent); - AddStep("disable low health display", () => - { - config.Set(OsuSetting.FadePlayfieldWhenLowHealth, false); - hudOverlay.LowHealthDisplay.FinishTransforms(true); - }); - AddAssert("low health display is not visible", () => !hudOverlay.LowHealthDisplay.IsPresent); - } - private void createNew(Action action = null) { AddStep("create overlay", () => diff --git a/osu.Game/Screens/Play/HUD/FaillingLayer.cs b/osu.Game/Screens/Play/HUD/FaillingLayer.cs new file mode 100644 index 0000000000..3dc18cefec --- /dev/null +++ b/osu.Game/Screens/Play/HUD/FaillingLayer.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; + +namespace osu.Game.Screens.Play.HUD +{ + /// + /// An overlay layer on top of the player HUD which fades to red when the current player health falls a certain threshold defined by . + /// + public class FaillingLayer : HealthDisplay + { + private const float max_alpha = 0.4f; + + private readonly Box box; + + /// + /// The threshold under which the current player life should be considered low and the layer should start fading in. + /// + protected virtual double LowHealthThreshold => 0.20f; + + public FaillingLayer() + { + Child = box = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0 + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour color) + { + box.Colour = color.Red; + } + + protected override void Update() + { + box.Alpha = (float)Math.Clamp(max_alpha * (1 - Current.Value / LowHealthThreshold), 0, max_alpha); + base.Update(); + } + } +} diff --git a/osu.Game/Screens/Play/HUD/LowHealthLayer.cs b/osu.Game/Screens/Play/HUD/LowHealthLayer.cs deleted file mode 100644 index 8f03a95877..0000000000 --- a/osu.Game/Screens/Play/HUD/LowHealthLayer.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Game.Configuration; -using osu.Game.Graphics; - -namespace osu.Game.Screens.Play.HUD -{ - public class LowHealthLayer : HealthDisplay - { - private const float max_alpha = 0.4f; - - private const double fade_time = 300; - - private readonly Box box; - - private Bindable configFadeRedWhenLowHealth; - - public LowHealthLayer() - { - Child = box = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0 - }; - } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config, OsuColour color) - { - configFadeRedWhenLowHealth = config.GetBindable(OsuSetting.FadePlayfieldWhenLowHealth); - box.Colour = color.Red; - - configFadeRedWhenLowHealth.BindValueChanged(value => - { - if (value.NewValue) - this.FadeIn(fade_time, Easing.OutQuint); - else - this.FadeOut(fade_time, Easing.OutQuint); - }, true); - } - } -} From ed4f9f8ba9959c142dc1282ef37e735a4a162b7e Mon Sep 17 00:00:00 2001 From: Lucas A Date: Tue, 17 Mar 2020 22:57:47 +0100 Subject: [PATCH 031/655] Bind every HealthDisplay on Player load --- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 8 ++++++++ osu.Game/Screens/Play/HUD/FaillingLayer.cs | 1 + osu.Game/Screens/Play/HUD/HealthDisplay.cs | 6 ++++++ osu.Game/Screens/Play/Player.cs | 4 ++++ 4 files changed, 19 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index a37ef8d9a0..50bff4fe3a 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Input.Handlers; @@ -16,6 +17,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; using osuTK; namespace osu.Game.Rulesets.Osu.UI @@ -29,6 +31,12 @@ namespace osu.Game.Rulesets.Osu.UI { } + [BackgroundDependencyLoader] + private void load() + { + Overlays.Add(new FaillingLayer()); + } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // always show the gameplay cursor protected override Playfield CreatePlayfield() => new OsuPlayfield(); diff --git a/osu.Game/Screens/Play/HUD/FaillingLayer.cs b/osu.Game/Screens/Play/HUD/FaillingLayer.cs index 3dc18cefec..6651ad6c88 100644 --- a/osu.Game/Screens/Play/HUD/FaillingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FaillingLayer.cs @@ -25,6 +25,7 @@ namespace osu.Game.Screens.Play.HUD public FaillingLayer() { + RelativeSizeAxes = Axes.Both; Child = box = new Box { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index 37038ad58c..6a5b77a64b 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -3,6 +3,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Play.HUD { @@ -13,5 +14,10 @@ namespace osu.Game.Screens.Play.HUD MinValue = 0, MaxValue = 1 }; + + public virtual void BindHealthProcessor(HealthProcessor processor) + { + Current.BindTo(processor.Health); + } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index bcadba14af..0df4aacb7a 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -24,6 +24,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; +using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Users; @@ -184,6 +185,9 @@ namespace osu.Game.Screens.Play foreach (var mod in Mods.Value.OfType()) mod.ApplyToHealthProcessor(HealthProcessor); + foreach (var overlay in DrawableRuleset.Overlays.OfType()) + overlay.BindHealthProcessor(HealthProcessor); + BreakOverlay.IsBreakTime.BindValueChanged(onBreakTimeChanged, true); } From 44c13b081c4167685ace193a5a6fadae95072fcf Mon Sep 17 00:00:00 2001 From: Lucas A Date: Tue, 17 Mar 2020 22:58:20 +0100 Subject: [PATCH 032/655] Remove old configuration variants. --- osu.Game/Configuration/OsuConfigManager.cs | 2 -- .../Overlays/Settings/Sections/Gameplay/GeneralSettings.cs | 6 ------ 2 files changed, 8 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 895bacafc4..21de654670 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -88,7 +88,6 @@ namespace osu.Game.Configuration Set(OsuSetting.ShowInterface, true); Set(OsuSetting.ShowProgressGraph, true); Set(OsuSetting.ShowHealthDisplayWhenCantFail, true); - Set(OsuSetting.FadePlayfieldWhenLowHealth, true); Set(OsuSetting.KeyOverlay, false); Set(OsuSetting.ScoreMeter, ScoreMeterType.HitErrorBoth); @@ -184,7 +183,6 @@ namespace osu.Game.Configuration ShowInterface, ShowProgressGraph, ShowHealthDisplayWhenCantFail, - FadePlayfieldWhenLowHealth, MouseDisableButtons, MouseDisableWheel, AudioOffset, diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 6b6b3e8fa4..2d2cd42213 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -53,12 +53,6 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay Keywords = new[] { "hp", "bar" } }, new SettingsCheckbox - { - LabelText = "Fade playfield to red when health is low", - Bindable = config.GetBindable(OsuSetting.FadePlayfieldWhenLowHealth), - Keywords = new[] { "hp", "playfield", "health" } - }, - new SettingsCheckbox { LabelText = "Always show key overlay", Bindable = config.GetBindable(OsuSetting.KeyOverlay) From 17bae532bd91e782ac9be727843b4e8f57456df9 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Tue, 17 Mar 2020 23:09:50 +0100 Subject: [PATCH 033/655] Add failling layer to others rulesets. --- osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs | 8 ++++++++ osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 3 +++ 2 files changed, 11 insertions(+) diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index fd8a1d175d..705c2d756c 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Allocation; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -14,6 +15,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Screens.Play.HUD; namespace osu.Game.Rulesets.Catch.UI { @@ -30,6 +32,12 @@ namespace osu.Game.Rulesets.Catch.UI TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450); } + [BackgroundDependencyLoader] + private void load() + { + Overlays.Add(new FaillingLayer()); + } + protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, CreateDrawableRepresentation); diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 2c497541a8..b8b6ff3c3c 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -19,6 +19,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Screens.Play.HUD; using osuTK; namespace osu.Game.Rulesets.Mania.UI @@ -52,6 +53,8 @@ namespace osu.Game.Rulesets.Mania.UI configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true); Config.BindWith(ManiaRulesetSetting.ScrollTime, TimeRange); + + Overlays.Add(new FaillingLayer()); } /// From a1274a9eb0ca927c4d07cb94aaba0fa101745a4a Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 18 Mar 2020 08:17:41 +0100 Subject: [PATCH 034/655] Fix and add missing XMLDoc --- osu.Game/Screens/Play/HUD/FaillingLayer.cs | 2 +- osu.Game/Screens/Play/HUD/HealthDisplay.cs | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/FaillingLayer.cs b/osu.Game/Screens/Play/HUD/FaillingLayer.cs index 6651ad6c88..55cc4476b0 100644 --- a/osu.Game/Screens/Play/HUD/FaillingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FaillingLayer.cs @@ -10,7 +10,7 @@ using osu.Game.Graphics; namespace osu.Game.Screens.Play.HUD { /// - /// An overlay layer on top of the player HUD which fades to red when the current player health falls a certain threshold defined by . + /// An overlay layer on top of the playfield which fades to red when the current player health falls a certain threshold defined by . /// public class FaillingLayer : HealthDisplay { diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index 6a5b77a64b..4094b3de69 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -4,9 +4,14 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Play.HUD { + /// + /// A container for components displaying the current player health. + /// Gets bound automatically to the when inserted to hierarchy. + /// public abstract class HealthDisplay : Container { public readonly BindableDouble Current = new BindableDouble @@ -14,7 +19,11 @@ namespace osu.Game.Screens.Play.HUD MinValue = 0, MaxValue = 1 }; - + + /// + /// Bind the tracked fields of to this health display. + /// + /// public virtual void BindHealthProcessor(HealthProcessor processor) { Current.BindTo(processor.Health); From 80a86102b65b5a2421ef75e1899ff609ae463cb8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 18 Mar 2020 17:00:48 +0900 Subject: [PATCH 035/655] Add test --- .../TestSceneNoteLock.cs | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs new file mode 100644 index 0000000000..a7416671f6 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs @@ -0,0 +1,180 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Screens; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Replays; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneNoteLock : RateAdjustedBeatmapTestScene + { + private const double time_first_circle = 1500; + private const double time_second_circle = 1600; + + private static readonly Vector2 position_first_circle = Vector2.Zero; + private static readonly Vector2 position_second_circle = new Vector2(80); + + /// + /// Tests clicking the second circle before the first hitobject's start time, while the first hitobject HAS NOT been judged. + /// + [Test] + public void TestClickSecondCircleBeforeFirstCircleTime() + { + performTest(new List + { + new OsuReplayFrame { Time = time_first_circle - 100, Position = position_second_circle, Actions = { OsuAction.LeftButton } } + }); + + addJudgementAssert(HitResult.Miss, HitResult.Miss); + } + + /// + /// Tests clicking the second circle at the first hitobject's start time, while the first hitobject HAS NOT been judged. + /// + [Test] + public void TestClickSecondCircleAtFirstCircleTime() + { + performTest(new List + { + new OsuReplayFrame { Time = time_first_circle, Position = position_second_circle, Actions = { OsuAction.LeftButton } } + }); + + addJudgementAssert(HitResult.Miss, HitResult.Miss); + } + + /// + /// Tests clicking the second circle after the first hitobject's start time, while the first hitobject HAS NOT been judged. + /// + [Test] + public void TestClickSecondCircleAfterFirstCircleTime() + { + performTest(new List + { + new OsuReplayFrame { Time = time_first_circle + 100, Position = position_second_circle, Actions = { OsuAction.LeftButton } } + }); + + addJudgementAssert(HitResult.Miss, HitResult.Great); + } + + /// + /// Tests clicking the second circle before the first hitobject's start time, while the first hitobject HAS been judged. + /// + [Test] + public void TestClickSecondCircleBeforeFirstCircleTimeWithFirstCircleJudged() + { + performTest(new List + { + new OsuReplayFrame { Time = time_first_circle - 200, Position = position_first_circle, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_first_circle - 100, Position = position_second_circle, Actions = { OsuAction.RightButton } } + }); + + addJudgementAssert(HitResult.Great, HitResult.Great); + } + + private void addJudgementAssert(HitResult firstCircle, HitResult secondCircle) + { + AddAssert($"first circle judgement is {firstCircle}", () => judgementResults.Single(r => r.HitObject.StartTime == time_first_circle).Type == firstCircle); + AddAssert($"second circle judgement is {secondCircle}", () => judgementResults.Single(r => r.HitObject.StartTime == time_second_circle).Type == secondCircle); + } + + private ScoreAccessibleReplayPlayer currentPlayer; + private List judgementResults; + private bool allJudgedFired; + + private void performTest(List frames) + { + AddStep("load player", () => + { + Beatmap.Value = CreateWorkingBeatmap(new Beatmap + { + HitObjects = + { + new TestHitCircle + { + StartTime = time_first_circle, + Position = position_first_circle + }, + new TestHitCircle + { + StartTime = time_second_circle, + Position = position_second_circle + } + }, + BeatmapInfo = + { + BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 }, + Ruleset = new OsuRuleset().RulesetInfo + }, + }); + + Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f }); + + var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); + + p.OnLoadComplete += _ => + { + p.ScoreProcessor.NewJudgement += result => + { + if (currentPlayer == p) judgementResults.Add(result); + }; + p.ScoreProcessor.AllJudged += () => + { + if (currentPlayer == p) allJudgedFired = true; + }; + }; + + LoadScreen(currentPlayer = p); + allJudgedFired = false; + judgementResults = new List(); + }); + + AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); + AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); + AddUntilStep("Wait for all judged", () => allJudgedFired); + } + + private class TestHitCircle : HitCircle + { + protected override HitWindows CreateHitWindows() => new TestHitWindows(); + } + + private class TestHitWindows : HitWindows + { + private static readonly DifficultyRange[] ranges = + { + new DifficultyRange(HitResult.Great, 500, 500, 500), + new DifficultyRange(HitResult.Miss, 1000, 1000, 1000), + }; + + public override bool IsHitResultAllowed(HitResult result) => result == HitResult.Great || result == HitResult.Miss; + + protected override DifficultyRange[] GetRanges() => ranges; + } + + private class ScoreAccessibleReplayPlayer : ReplayPlayer + { + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + protected override bool PauseOnFocusLost => false; + + public ScoreAccessibleReplayPlayer(Score score) + : base(score, false, false) + { + } + } + } +} From 1d680b7a0073b783cee638e64c31d90c966f9deb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 18 Mar 2020 19:13:25 +0900 Subject: [PATCH 036/655] Better english Co-Authored-By: Dean Herbert --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs | 2 +- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 82a81040e4..3e66549ca0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// /// Whether this can be hit. - /// If not-null, this will not receive a judgement until this function returns true. + /// If non-null, judgements will be ignored (resulting in a shake) whilst the function returns false. /// public Func CheckHittable; diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 9eb2786951..643253b1af 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.UI { var lastObject = HitObjectContainer.AliveObjects.GetPrevious(osuHitObject); - // Ensure the last object is not alive anymore, in which case always allow the hit. + // If there is no previous object alive, allow the hit. if (lastObject == null) return true; From e9f224b5e8c3ae93098848ee3ee2146d47e7146e Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 18 Mar 2020 21:16:54 +0100 Subject: [PATCH 037/655] Apply review suggestions --- osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs | 2 +- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 2 +- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 2 +- .../Play/HUD/{FaillingLayer.cs => FailingLayer.cs} | 8 ++++---- osu.Game/Screens/Play/HUD/HealthDisplay.cs | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) rename osu.Game/Screens/Play/HUD/{FaillingLayer.cs => FailingLayer.cs} (82%) diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index 705c2d756c..50c4154c61 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Catch.UI [BackgroundDependencyLoader] private void load() { - Overlays.Add(new FaillingLayer()); + Overlays.Add(new FailingLayer()); } protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index b8b6ff3c3c..8e56144752 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.UI Config.BindWith(ManiaRulesetSetting.ScrollTime, TimeRange); - Overlays.Add(new FaillingLayer()); + Overlays.Add(new FailingLayer()); } /// diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index 50bff4fe3a..ed75d47bbe 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.UI [BackgroundDependencyLoader] private void load() { - Overlays.Add(new FaillingLayer()); + Overlays.Add(new FailingLayer()); } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // always show the gameplay cursor diff --git a/osu.Game/Screens/Play/HUD/FaillingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs similarity index 82% rename from osu.Game/Screens/Play/HUD/FaillingLayer.cs rename to osu.Game/Screens/Play/HUD/FailingLayer.cs index 55cc4476b0..5f7dc77928 100644 --- a/osu.Game/Screens/Play/HUD/FaillingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -10,9 +10,9 @@ using osu.Game.Graphics; namespace osu.Game.Screens.Play.HUD { /// - /// An overlay layer on top of the playfield which fades to red when the current player health falls a certain threshold defined by . + /// An overlay layer on top of the playfield which fades to red when the current player health falls below a certain threshold defined by . /// - public class FaillingLayer : HealthDisplay + public class FailingLayer : HealthDisplay { private const float max_alpha = 0.4f; @@ -21,9 +21,9 @@ namespace osu.Game.Screens.Play.HUD /// /// The threshold under which the current player life should be considered low and the layer should start fading in. /// - protected virtual double LowHealthThreshold => 0.20f; + protected double LowHealthThreshold { get; set; } = 0.20f; - public FaillingLayer() + public FailingLayer() { RelativeSizeAxes = Axes.Both; Child = box = new Box diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index 4094b3de69..4ea08626ad 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Play.HUD /// Bind the tracked fields of to this health display. /// /// - public virtual void BindHealthProcessor(HealthProcessor processor) + public void BindHealthProcessor(HealthProcessor processor) { Current.BindTo(processor.Health); } From a4171253a38f2d09eedea9f38eb7d3eca0afebff Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 18 Mar 2020 21:41:43 +0100 Subject: [PATCH 038/655] Make LowHealthThreshold a field. --- osu.Game/Screens/Play/HUD/FailingLayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index 5f7dc77928..5f4037c14d 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Play.HUD /// /// The threshold under which the current player life should be considered low and the layer should start fading in. /// - protected double LowHealthThreshold { get; set; } = 0.20f; + public double LowHealthThreshold = 0.20f; public FailingLayer() { From f285b43a74afd66c6c2ec1dcbe63e4f66f007314 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Mar 2020 17:44:32 +0900 Subject: [PATCH 039/655] Allow simultaneous hitobjects --- osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs | 2 +- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs index a7416671f6..59d8727ae1 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Tests new OsuReplayFrame { Time = time_first_circle, Position = position_second_circle, Actions = { OsuAction.LeftButton } } }); - addJudgementAssert(HitResult.Miss, HitResult.Miss); + addJudgementAssert(HitResult.Miss, HitResult.Great); } /// diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 643253b1af..bf91504b00 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -94,8 +94,9 @@ namespace osu.Game.Rulesets.Osu.UI if (lastObject == null) return true; - // Ensure that either the last object has received a judgement or the hit time occurs after the last object's start time. - if (lastObject.Judged || Time.Current > lastObject.HitObject.StartTime) + // Ensure that either the last object has received a judgement or the hit time occurs at or after the last object's start time. + // Simultaneous hitobjects are allowed to be hit at the same time value to account for edge-cases such as Centipede. + if (lastObject.Judged || Time.Current >= lastObject.HitObject.StartTime) return true; return false; From 12a48d2774dd0e4aa19cdd989b34c7022343ff1e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Mar 2020 19:16:24 +0900 Subject: [PATCH 040/655] Cause all earlier hitobjects to get missed --- .../TestSceneNoteLock.cs | 13 ++++- .../Objects/Drawables/DrawableOsuHitObject.cs | 6 +++ osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 52 +++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs index 59d8727ae1..e2b8364f3e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Screens; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Replays; @@ -24,6 +25,8 @@ namespace osu.Game.Rulesets.Osu.Tests { private const double time_first_circle = 1500; private const double time_second_circle = 1600; + private const double early_miss_window = 1000; // time after -1000 to -500 is considered a miss + private const double late_miss_window = 500; // time after +500 is considered a miss private static readonly Vector2 position_first_circle = Vector2.Zero; private static readonly Vector2 position_second_circle = new Vector2(80); @@ -40,6 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests }); addJudgementAssert(HitResult.Miss, HitResult.Miss); + addJudgementOffsetAssert(late_miss_window); } /// @@ -54,6 +58,7 @@ namespace osu.Game.Rulesets.Osu.Tests }); addJudgementAssert(HitResult.Miss, HitResult.Great); + addJudgementOffsetAssert(0); } /// @@ -68,6 +73,7 @@ namespace osu.Game.Rulesets.Osu.Tests }); addJudgementAssert(HitResult.Miss, HitResult.Great); + addJudgementOffsetAssert(100); } /// @@ -91,6 +97,11 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert($"second circle judgement is {secondCircle}", () => judgementResults.Single(r => r.HitObject.StartTime == time_second_circle).Type == secondCircle); } + private void addJudgementOffsetAssert(double offset) + { + AddAssert($"first circle judged at {offset}", () => Precision.AlmostEquals(judgementResults.Single(r => r.HitObject.StartTime == time_first_circle).TimeOffset, offset, 100)); + } + private ScoreAccessibleReplayPlayer currentPlayer; private List judgementResults; private bool allJudgedFired; @@ -157,7 +168,7 @@ namespace osu.Game.Rulesets.Osu.Tests private static readonly DifficultyRange[] ranges = { new DifficultyRange(HitResult.Great, 500, 500, 500), - new DifficultyRange(HitResult.Miss, 1000, 1000, 1000), + new DifficultyRange(HitResult.Miss, early_miss_window, early_miss_window, early_miss_window), }; public override bool IsHitResultAllowed(HitResult result) => result == HitResult.Great || result == HitResult.Miss; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 3e66549ca0..13829dc2f7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Graphics.Containers; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -61,6 +62,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } + /// + /// Causes this to get missed, disregarding all conditions in implementations of . + /// + public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss); + protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(HitObject, judgement); } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index bf91504b00..e36d32d01a 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Extensions.IEnumerableExtensions; using osuTK; using osu.Framework.Graphics; @@ -104,6 +105,8 @@ namespace osu.Game.Rulesets.Osu.UI private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) { + missAllEarlier(result); + if (!judgedObject.DisplayResult || !DisplayJudgements.Value) return; @@ -117,6 +120,55 @@ namespace osu.Game.Rulesets.Osu.UI judgementLayer.Add(explosion); } + /// + /// Misses all s occurring earlier than the start time of a judged . + /// + /// The of the judged . + private void missAllEarlier(JudgementResult result) + { + // Hitobjects that count as bonus should not cause other hitobjects to get missed. + // E.g. For the sequence slider-head -> circle -> slider-tick, hitting the tick before the circle should not cause the circle to be missed. + // E.g. For the sequence spinner -> circle -> spinner-bonus, hitting the bonus before the circle should not cause the circle to be missed. + if (result.Judgement.IsBonus) + return; + + // The minimum start time required for hitobjects so that they aren't missed. + double minimumTime = result.HitObject.StartTime; + + foreach (var obj in HitObjectContainer.AliveObjects) + { + if (obj.HitObject.StartTime >= minimumTime) + break; + + attemptMiss(obj); + + foreach (var n in obj.NestedHitObjects) + { + if (n.HitObject.StartTime >= minimumTime) + break; + + attemptMiss(n); + } + } + + static void attemptMiss(DrawableHitObject obj) + { + if (!(obj is DrawableOsuHitObject osuObject)) + throw new InvalidOperationException($"{obj.GetType()} is not a {nameof(DrawableOsuHitObject)}."); + + // Hitobjects that have already been judged cannot be missed. + if (osuObject.Judged) + return; + + // Hitobjects that count as bonus should not be missed. + // For the sequence slider-head -> slider-tick -> circle, hitting the circle before the tick should not cause the tick to be missed. + if (osuObject.Result.Judgement.IsBonus) + return; + + osuObject.MissForcefully(); + } + } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos); private class ApproachCircleProxyContainer : LifetimeManagementContainer From e85f45f91125ed1e428772df542294992586601e Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Tue, 24 Mar 2020 22:03:16 +0100 Subject: [PATCH 041/655] Move old ScreenTitle to MultiHeaderTitle --- .../Graphics/UserInterface/ScreenTitle.cs | 102 ------------------ .../UserInterface/ScreenTitleTextureIcon.cs | 40 ------- osu.Game/Screens/Multi/Header.cs | 77 ++++++++++++- 3 files changed, 73 insertions(+), 146 deletions(-) delete mode 100644 osu.Game/Graphics/UserInterface/ScreenTitle.cs delete mode 100644 osu.Game/Graphics/UserInterface/ScreenTitleTextureIcon.cs diff --git a/osu.Game/Graphics/UserInterface/ScreenTitle.cs b/osu.Game/Graphics/UserInterface/ScreenTitle.cs deleted file mode 100644 index ecd0508258..0000000000 --- a/osu.Game/Graphics/UserInterface/ScreenTitle.cs +++ /dev/null @@ -1,102 +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.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics.Sprites; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Graphics.UserInterface -{ - public abstract class ScreenTitle : CompositeDrawable, IHasAccentColour - { - public const float ICON_WIDTH = ICON_SIZE + spacing; - - public const float ICON_SIZE = 25; - private const float spacing = 6; - private const int text_offset = 2; - - private SpriteIcon iconSprite; - private readonly OsuSpriteText titleText, pageText; - - protected IconUsage Icon - { - set - { - if (iconSprite == null) - throw new InvalidOperationException($"Cannot use {nameof(Icon)} with a custom {nameof(CreateIcon)} function."); - - iconSprite.Icon = value; - } - } - - protected string Title - { - set => titleText.Text = value; - } - - protected string Section - { - set => pageText.Text = value; - } - - public Color4 AccentColour - { - get => pageText.Colour; - set => pageText.Colour = value; - } - - protected virtual Drawable CreateIcon() => iconSprite = new SpriteIcon - { - Size = new Vector2(ICON_SIZE), - }; - - protected ScreenTitle() - { - AutoSizeAxes = Axes.Both; - - InternalChildren = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(spacing, 0), - Direction = FillDirection.Horizontal, - Children = new[] - { - CreateIcon().With(t => - { - t.Anchor = Anchor.Centre; - t.Origin = Anchor.Centre; - }), - titleText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold), - Margin = new MarginPadding { Bottom = text_offset } - }, - new Circle - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(4), - Colour = Color4.Gray, - }, - pageText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 20), - Margin = new MarginPadding { Bottom = text_offset } - } - } - }, - }; - } - } -} diff --git a/osu.Game/Graphics/UserInterface/ScreenTitleTextureIcon.cs b/osu.Game/Graphics/UserInterface/ScreenTitleTextureIcon.cs deleted file mode 100644 index c2a13970de..0000000000 --- a/osu.Game/Graphics/UserInterface/ScreenTitleTextureIcon.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osuTK; - -namespace osu.Game.Graphics.UserInterface -{ - /// - /// A custom icon class for use with based off a texture resource. - /// - public class ScreenTitleTextureIcon : CompositeDrawable - { - private readonly string textureName; - - public ScreenTitleTextureIcon(string textureName) - { - this.textureName = textureName; - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - Size = new Vector2(ScreenTitle.ICON_SIZE); - - InternalChild = new Sprite - { - RelativeSizeAxes = Axes.Both, - Texture = textures.Get(textureName), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fit - }; - } - } -} diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs index 0a05472ba3..6f790d703e 100644 --- a/osu.Game/Screens/Multi/Header.cs +++ b/osu.Game/Screens/Multi/Header.cs @@ -6,10 +6,13 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.SearchableList; +using osu.Game.Graphics.Sprites; +using osuTK; using osuTK.Graphics; namespace osu.Game.Screens.Multi @@ -43,7 +46,7 @@ namespace osu.Game.Screens.Multi { Anchor = Anchor.CentreLeft, Origin = Anchor.BottomLeft, - X = -ScreenTitle.ICON_WIDTH, + X = -MultiHeaderTitle.ICON_WIDTH, }, breadcrumbs = new HeaderBreadcrumbControl(stack) { @@ -70,18 +73,84 @@ namespace osu.Game.Screens.Multi breadcrumbs.StripColour = colours.Green; } - private class MultiHeaderTitle : ScreenTitle + private class MultiHeaderTitle : CompositeDrawable, IHasAccentColour { + public const float ICON_WIDTH = ICON_SIZE + spacing; + + public const float ICON_SIZE = 25; + private const float spacing = 6; + private const int text_offset = 2; + + private SpriteIcon iconSprite; + private readonly OsuSpriteText titleText, pageText; + public IMultiplayerSubScreen Screen { - set => Section = value.ShortTitle.ToLowerInvariant(); + set => pageText.Text = value.ShortTitle.ToLowerInvariant(); + } + + protected string Title + { + set => titleText.Text = value; + } + + public Color4 AccentColour + { + get => pageText.Colour; + set => pageText.Colour = value; + } + + public MultiHeaderTitle() + : base() + { + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(spacing, 0), + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + iconSprite = new SpriteIcon + { + Size = new Vector2(ICON_SIZE), + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }, + titleText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold), + Margin = new MarginPadding { Bottom = text_offset } + }, + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(4), + Colour = Color4.Gray, + }, + pageText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 20), + Margin = new MarginPadding { Bottom = text_offset } + } + } + }, + }; } [BackgroundDependencyLoader] private void load(OsuColour colours) { Title = "multi"; - Icon = OsuIcon.Multi; + iconSprite.Icon = OsuIcon.Multi; AccentColour = colours.Yellow; } } From 127c16fccdfc0b836b4221e1692a83e232965257 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Tue, 24 Mar 2020 22:03:38 +0100 Subject: [PATCH 042/655] Implement OverlayTitle component --- osu.Game/Overlays/OverlayTitle.cs | 80 +++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 osu.Game/Overlays/OverlayTitle.cs diff --git a/osu.Game/Overlays/OverlayTitle.cs b/osu.Game/Overlays/OverlayTitle.cs new file mode 100644 index 0000000000..9fafee41b6 --- /dev/null +++ b/osu.Game/Overlays/OverlayTitle.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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Overlays +{ + public abstract class OverlayTitle : CompositeDrawable + { + private readonly OsuSpriteText title; + private readonly Container icon; + + protected string Title + { + set => title.Text = value; + } + + protected string IconTexture + { + set => icon.Child = new OverlayTitleIcon(value); + } + + protected OverlayTitle() + { + AutoSizeAxes = Axes.Both; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(10, 0), + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + icon = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Horizontal = 5 }, // compensates for osu-web sprites having around 5px of whitespace on each side + Size = new Vector2(30) + }, + title = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Regular), + Margin = new MarginPadding { Vertical = 17.5f } // 15px padding + 2.5px line-height difference compensation + } + } + }; + } + + private class OverlayTitleIcon : Sprite + { + private readonly string textureName; + + public OverlayTitleIcon(string textureName) + { + this.textureName = textureName; + + RelativeSizeAxes = Axes.Both; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + FillMode = FillMode.Fit; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + Texture = textures.Get(textureName); + } + } + } +} \ No newline at end of file From a5781d7fc595f6c017da739567233aa1dca63adc Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Tue, 24 Mar 2020 22:08:20 +0100 Subject: [PATCH 043/655] Replace ScreenTitle with OverlayTitle and update titles to match new design --- .../UserInterface/TestSceneOverlayHeader.cs | 16 +++++++------- .../BeatmapListing/BeatmapListingHeader.cs | 13 ++++-------- .../Overlays/BeatmapSet/BeatmapSetHeader.cs | 11 ++++------ .../Overlays/Changelog/ChangelogHeader.cs | 19 +++-------------- osu.Game/Overlays/News/NewsHeader.cs | 21 +++---------------- osu.Game/Overlays/OverlayHeader.cs | 8 +++---- osu.Game/Overlays/OverlayTitle.cs | 4 ++-- osu.Game/Overlays/Profile/ProfileHeader.cs | 11 ++++------ .../Rankings/RankingsOverlayHeader.cs | 21 ++++--------------- 9 files changed, 34 insertions(+), 90 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs index 1cd68d1fdd..9dc71c7e74 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs @@ -100,21 +100,21 @@ namespace osu.Game.Tests.Visual.UserInterface private class TestNoBackgroundHeader : OverlayHeader { - protected override ScreenTitle CreateTitle() => new TestTitle(); + protected override OverlayTitle CreateTitle() => new TestTitle(); } private class TestNoControlHeader : OverlayHeader { protected override Drawable CreateBackground() => new OverlayHeaderBackground(@"Headers/changelog"); - protected override ScreenTitle CreateTitle() => new TestTitle(); + protected override OverlayTitle CreateTitle() => new TestTitle(); } private class TestStringTabControlHeader : TabControlOverlayHeader { protected override Drawable CreateBackground() => new OverlayHeaderBackground(@"Headers/news"); - protected override ScreenTitle CreateTitle() => new TestTitle(); + protected override OverlayTitle CreateTitle() => new TestTitle(); protected override Drawable CreateTitleContent() => new OverlayRulesetSelector(); @@ -129,7 +129,7 @@ namespace osu.Game.Tests.Visual.UserInterface { protected override Drawable CreateBackground() => new OverlayHeaderBackground(@"Headers/rankings"); - protected override ScreenTitle CreateTitle() => new TestTitle(); + protected override OverlayTitle CreateTitle() => new TestTitle(); } private enum TestEnum @@ -141,7 +141,7 @@ namespace osu.Game.Tests.Visual.UserInterface private class TestBreadcrumbControlHeader : BreadcrumbControlOverlayHeader { - protected override ScreenTitle CreateTitle() => new TestTitle(); + protected override OverlayTitle CreateTitle() => new TestTitle(); public TestBreadcrumbControlHeader() { @@ -151,15 +151,13 @@ namespace osu.Game.Tests.Visual.UserInterface } } - private class TestTitle : ScreenTitle + private class TestTitle : OverlayTitle { public TestTitle() { Title = "title"; - Section = "section"; + IconTexture = "Icons/changelog"; } - - protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/changelog"); } } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs index 5af92914de..1bab200fec 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs @@ -1,24 +1,19 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; - namespace osu.Game.Overlays.BeatmapListing { public class BeatmapListingHeader : OverlayHeader { - protected override ScreenTitle CreateTitle() => new BeatmapListingTitle(); + protected override OverlayTitle CreateTitle() => new BeatmapListingTitle(); - private class BeatmapListingTitle : ScreenTitle + private class BeatmapListingTitle : OverlayTitle { public BeatmapListingTitle() { - Title = @"beatmap"; - Section = @"listing"; + Title = "beatmap listing"; + IconTexture = "Icons/changelog"; } - - protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/changelog"); } } } diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs index e5e3e276d5..4626589d81 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs @@ -3,7 +3,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; namespace osu.Game.Overlays.BeatmapSet @@ -14,22 +13,20 @@ namespace osu.Game.Overlays.BeatmapSet public BeatmapRulesetSelector RulesetSelector { get; private set; } - protected override ScreenTitle CreateTitle() => new BeatmapHeaderTitle(); + protected override OverlayTitle CreateTitle() => new BeatmapHeaderTitle(); protected override Drawable CreateTitleContent() => RulesetSelector = new BeatmapRulesetSelector { Current = Ruleset }; - private class BeatmapHeaderTitle : ScreenTitle + private class BeatmapHeaderTitle : OverlayTitle { public BeatmapHeaderTitle() { - Title = @"beatmap"; - Section = @"info"; + Title = "beatmap info"; + IconTexture = "Icons/changelog"; } - - protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/changelog"); } } } diff --git a/osu.Game/Overlays/Changelog/ChangelogHeader.cs b/osu.Game/Overlays/Changelog/ChangelogHeader.cs index 532efeb4bd..050bdea03a 100644 --- a/osu.Game/Overlays/Changelog/ChangelogHeader.cs +++ b/osu.Game/Overlays/Changelog/ChangelogHeader.cs @@ -9,7 +9,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.Changelog @@ -50,8 +49,6 @@ namespace osu.Game.Overlays.Changelog streamsBackground.Colour = colourProvider.Background5; } - private ChangelogHeaderTitle title; - private void showBuild(ValueChangedEvent e) { if (e.OldValue != null) @@ -63,14 +60,11 @@ namespace osu.Game.Overlays.Changelog Current.Value = e.NewValue.ToString(); updateCurrentStream(); - - title.Version = e.NewValue.UpdateStream.DisplayName; } else { Current.Value = listing_string; Streams.Current.Value = null; - title.Version = null; } } @@ -100,7 +94,7 @@ namespace osu.Game.Overlays.Changelog } }; - protected override ScreenTitle CreateTitle() => title = new ChangelogHeaderTitle(); + protected override OverlayTitle CreateTitle() => new ChangelogHeaderTitle(); public void Populate(List streams) { @@ -116,20 +110,13 @@ namespace osu.Game.Overlays.Changelog Streams.Current.Value = Streams.Items.FirstOrDefault(s => s.Name == Build.Value.UpdateStream.Name); } - private class ChangelogHeaderTitle : ScreenTitle + private class ChangelogHeaderTitle : OverlayTitle { - public string Version - { - set => Section = value ?? listing_string; - } - public ChangelogHeaderTitle() { Title = "changelog"; - Version = null; + IconTexture = "Icons/changelog"; } - - protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/changelog"); } } } diff --git a/osu.Game/Overlays/News/NewsHeader.cs b/osu.Game/Overlays/News/NewsHeader.cs index b55e3ffba0..8214c71b3a 100644 --- a/osu.Game/Overlays/News/NewsHeader.cs +++ b/osu.Game/Overlays/News/NewsHeader.cs @@ -3,7 +3,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; using System; namespace osu.Game.Overlays.News @@ -12,8 +11,6 @@ namespace osu.Game.Overlays.News { private const string front_page_string = "frontpage"; - private NewsHeaderTitle title; - public readonly Bindable Post = new Bindable(null); public Action ShowFrontPage; @@ -40,36 +37,24 @@ namespace osu.Game.Overlays.News { TabControl.AddItem(e.NewValue); Current.Value = e.NewValue; - - title.IsReadingPost = true; } else { Current.Value = front_page_string; - title.IsReadingPost = false; } } protected override Drawable CreateBackground() => new OverlayHeaderBackground(@"Headers/news"); - protected override ScreenTitle CreateTitle() => title = new NewsHeaderTitle(); + protected override OverlayTitle CreateTitle() => new NewsHeaderTitle(); - private class NewsHeaderTitle : ScreenTitle + private class NewsHeaderTitle : OverlayTitle { - private const string post_string = "post"; - - public bool IsReadingPost - { - set => Section = value ? post_string : front_page_string; - } - public NewsHeaderTitle() { Title = "news"; - IsReadingPost = false; + IconTexture = "Icons/news"; } - - protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/news"); } } } diff --git a/osu.Game/Overlays/OverlayHeader.cs b/osu.Game/Overlays/OverlayHeader.cs index bedf8e5435..f017d66485 100644 --- a/osu.Game/Overlays/OverlayHeader.cs +++ b/osu.Game/Overlays/OverlayHeader.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays public abstract class OverlayHeader : Container { private readonly Box titleBackground; - private readonly ScreenTitle title; + private readonly OverlayTitle title; protected readonly FillFlowContainer HeaderInfo; @@ -57,7 +57,6 @@ namespace osu.Game.Overlays Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, - Vertical = 10, }, Children = new[] { @@ -86,7 +85,6 @@ namespace osu.Game.Overlays private void load(OverlayColourProvider colourProvider) { titleBackground.Colour = colourProvider.Dark5; - title.AccentColour = colourProvider.Highlight1; } [NotNull] @@ -96,11 +94,11 @@ namespace osu.Game.Overlays protected virtual Drawable CreateBackground() => Empty(); /// - /// Creates a on the opposite side of the . Used mostly to create . + /// Creates a on the opposite side of the . Used mostly to create . /// [NotNull] protected virtual Drawable CreateTitleContent() => Empty(); - protected abstract ScreenTitle CreateTitle(); + protected abstract OverlayTitle CreateTitle(); } } diff --git a/osu.Game/Overlays/OverlayTitle.cs b/osu.Game/Overlays/OverlayTitle.cs index 9fafee41b6..1c9567428c 100644 --- a/osu.Game/Overlays/OverlayTitle.cs +++ b/osu.Game/Overlays/OverlayTitle.cs @@ -63,7 +63,7 @@ namespace osu.Game.Overlays public OverlayTitleIcon(string textureName) { this.textureName = textureName; - + RelativeSizeAxes = Axes.Both; Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -77,4 +77,4 @@ namespace osu.Game.Overlays } } } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index f7c09e33c1..0161d91daa 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Profile.Header; using osu.Game.Users; @@ -87,19 +86,17 @@ namespace osu.Game.Overlays.Profile } }; - protected override ScreenTitle CreateTitle() => new ProfileHeaderTitle(); + protected override OverlayTitle CreateTitle() => new ProfileHeaderTitle(); private void updateDisplay(User user) => coverContainer.User = user; - private class ProfileHeaderTitle : ScreenTitle + private class ProfileHeaderTitle : OverlayTitle { public ProfileHeaderTitle() { - Title = "player"; - Section = "info"; + Title = "player info"; + IconTexture = "Icons/profile"; } - - protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/profile"); } } } diff --git a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs index 99325aa1da..e30c6f07a8 100644 --- a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs @@ -3,7 +3,6 @@ using osu.Framework.Graphics; using osu.Framework.Bindables; -using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Users; @@ -18,33 +17,21 @@ namespace osu.Game.Overlays.Rankings private OverlayRulesetSelector rulesetSelector; private CountryFilter countryFilter; - protected override ScreenTitle CreateTitle() => new RankingsTitle - { - Scope = { BindTarget = Current } - }; + protected override OverlayTitle CreateTitle() => new RankingsTitle(); protected override Drawable CreateTitleContent() => rulesetSelector = new OverlayRulesetSelector(); protected override Drawable CreateContent() => countryFilter = new CountryFilter(); - protected override Drawable CreateBackground() => new OverlayHeaderBackground(@"Headers/rankings"); + protected override Drawable CreateBackground() => new OverlayHeaderBackground("Headers/rankings"); - private class RankingsTitle : ScreenTitle + private class RankingsTitle : OverlayTitle { - public readonly Bindable Scope = new Bindable(); - public RankingsTitle() { Title = "ranking"; + IconTexture = "Icons/rankings"; } - - protected override void LoadComplete() - { - base.LoadComplete(); - Scope.BindValueChanged(scope => Section = scope.NewValue.ToString().ToLowerInvariant(), true); - } - - protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/rankings"); } } From 05de65937b950e7e35b3b55dbc6cf58528a54d8b Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Tue, 24 Mar 2020 22:14:15 +0100 Subject: [PATCH 044/655] Update ruleset selector design --- osu.Game/Overlays/OverlayRulesetSelector.cs | 2 +- osu.Game/Overlays/OverlayRulesetTabItem.cs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OverlayRulesetSelector.cs b/osu.Game/Overlays/OverlayRulesetSelector.cs index b73d38eeb3..8c44157f78 100644 --- a/osu.Game/Overlays/OverlayRulesetSelector.cs +++ b/osu.Game/Overlays/OverlayRulesetSelector.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Spacing = new Vector2(25, 0), + Spacing = new Vector2(20, 0), }; } } diff --git a/osu.Game/Overlays/OverlayRulesetTabItem.cs b/osu.Game/Overlays/OverlayRulesetTabItem.cs index 9b4dd5ba1e..9d4afc94d1 100644 --- a/osu.Game/Overlays/OverlayRulesetTabItem.cs +++ b/osu.Game/Overlays/OverlayRulesetTabItem.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets; using osuTK.Graphics; using osuTK; using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; namespace osu.Game.Overlays { @@ -53,6 +54,8 @@ namespace osu.Game.Overlays Origin = Anchor.Centre, Anchor = Anchor.Centre, Text = value.Name, + Font = OsuFont.GetFont(size: 14), + ShadowColour = Color4.Black.Opacity(0.75f) } }, new HoverClickSounds() From 2f5dc93d6119428654b0fa40e4e5e9439a074d64 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Thu, 26 Mar 2020 00:19:54 +0200 Subject: [PATCH 045/655] Select recommended difficulty --- osu.Game/Screens/Select/BeatmapCarousel.cs | 10 +++++-- .../Select/Carousel/CarouselBeatmapSet.cs | 28 ++++++++++++++++++- .../Carousel/CarouselGroupEagerSelect.cs | 10 +++++-- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index fa8974f55a..2c45b3642d 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -23,6 +23,8 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Input.Bindings; using osu.Game.Screens.Select.Carousel; +using osu.Game.Online.API; +using osu.Game.Users; namespace osu.Game.Screens.Select { @@ -31,6 +33,8 @@ namespace osu.Game.Screens.Select private const float bleed_top = FilterControl.HEIGHT; private const float bleed_bottom = Footer.HEIGHT; + private readonly Bindable localUser = new Bindable(); + /// /// Triggered when the loaded change and are completely loaded. /// @@ -140,7 +144,7 @@ namespace osu.Game.Screens.Select private BeatmapManager beatmaps { get; set; } [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, IAPIProvider api) { config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm); config.BindWith(OsuSetting.SongSelectRightMouseScroll, RightClickScrollingEnabled); @@ -154,6 +158,8 @@ namespace osu.Game.Screens.Select beatmaps.BeatmapRestored += beatmapRestored; loadBeatmapSets(GetLoadableBeatmaps()); + + localUser.BindTo(api.LocalUser); } protected virtual IEnumerable GetLoadableBeatmaps() => beatmaps.GetAllUsableBeatmapSetsEnumerable(); @@ -588,7 +594,7 @@ namespace osu.Game.Screens.Select b.Metadata = beatmapSet.Metadata; } - var set = new CarouselBeatmapSet(beatmapSet); + var set = new CarouselBeatmapSet(beatmapSet, localUser); foreach (var c in set.Beatmaps) { diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 8e323c66e2..9f1c39c578 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -4,19 +4,23 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Beatmaps; using osu.Game.Screens.Select.Filter; +using osu.Game.Users; namespace osu.Game.Screens.Select.Carousel { public class CarouselBeatmapSet : CarouselGroupEagerSelect { + private readonly Bindable localUser; + public IEnumerable Beatmaps => InternalChildren.OfType(); public BeatmapSetInfo BeatmapSet; - public CarouselBeatmapSet(BeatmapSetInfo beatmapSet) + public CarouselBeatmapSet(BeatmapSetInfo beatmapSet, Bindable localUser) { BeatmapSet = beatmapSet ?? throw new ArgumentNullException(nameof(beatmapSet)); @@ -24,10 +28,32 @@ namespace osu.Game.Screens.Select.Carousel .Where(b => !b.Hidden) .Select(b => new CarouselBeatmap(b)) .ForEach(AddChild); + + this.localUser = localUser; } protected override DrawableCarouselItem CreateDrawableRepresentation() => new DrawableCarouselBeatmapSet(this); + protected override CarouselItem GetNextToSelect() + { + if (LastSelected == null) + { + decimal? pp = localUser.Value?.Statistics?.PP ?? 60; // TODO: This needs to get ruleset specific statistics + + var recommendedDifficulty = Math.Pow((double)pp, 0.4) * 0.195; + return Children.OfType() + .Where(b => !b.Filtered.Value) + .OrderBy(b => + { + var difference = b.Beatmap.StarDifficulty - recommendedDifficulty; + return difference >= 0 ? difference * 2 : difference * -1; // prefer easier over harder + }) + .FirstOrDefault(); + } + + return base.GetNextToSelect(); + } + public override int CompareTo(FilterCriteria criteria, CarouselItem other) { if (!(other is CarouselBeatmapSet otherSet)) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index 6ce12f7b89..262bea9c71 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -90,11 +90,15 @@ namespace osu.Game.Screens.Select.Carousel PerformSelection(); } + protected virtual CarouselItem GetNextToSelect() + { + return Children.Skip(lastSelectedIndex).FirstOrDefault(i => !i.Filtered.Value) ?? + Children.Reverse().Skip(InternalChildren.Count - lastSelectedIndex).FirstOrDefault(i => !i.Filtered.Value); + } + protected virtual void PerformSelection() { - CarouselItem nextToSelect = - Children.Skip(lastSelectedIndex).FirstOrDefault(i => !i.Filtered.Value) ?? - Children.Reverse().Skip(InternalChildren.Count - lastSelectedIndex).FirstOrDefault(i => !i.Filtered.Value); + CarouselItem nextToSelect = GetNextToSelect(); if (nextToSelect != null) nextToSelect.State.Value = CarouselItemState.Selected; From e6b2e3b0ed1f4059a9fef74053b7ed5d6ec39d9d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 26 Mar 2020 05:18:12 +0300 Subject: [PATCH 046/655] Add osu!catch skin configurations --- .../Skinning/CatchSkinConfiguration.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs b/osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs new file mode 100644 index 0000000000..aea5beaa6b --- /dev/null +++ b/osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.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. + +namespace osu.Game.Rulesets.Catch.Skinning +{ + public enum CatchSkinConfiguration + { + /// + /// The colour to be used for the catcher while on hyper-dashing state. + /// + HyperDash, + + /// + /// The colour to be used for hyper-dash fruits. + /// + HyperDashFruit, + + /// + /// The colour to be used for the "exploding" catcher sprite on beginning of hyper-dashing. + /// + HyperDashAfterImage, + } +} From aa162b1033caff83366debb191f58366561b6555 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 26 Mar 2020 05:30:59 +0300 Subject: [PATCH 047/655] Setup hyper-dash colouring test scene --- .../TestSceneHyperDashColouring.cs | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs new file mode 100644 index 0000000000..2041e365ea --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -0,0 +1,112 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Testing; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Catch.Skinning; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Skinning; +using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Catch.Tests +{ + public class TestSceneHyperDashColouring : OsuTestScene + { + private Drawable setupSkinHierarchy(Func getChild, bool customHyperDashCatcherColour = false, bool customHyperDashFruitColour = false, bool customHyperDashAfterColour = false) + { + var testSkinProvider = new SkinProvidingContainer(new TestLegacySkin(customHyperDashCatcherColour, customHyperDashFruitColour, customHyperDashAfterColour)); + + var legacySkinTransformer = new SkinProvidingContainer(new CatchLegacySkinTransformer(testSkinProvider)); + + return testSkinProvider + .WithChild(legacySkinTransformer + .WithChild(getChild.Invoke())); + } + + private bool checkFruitHyperDashColour(DrawableFruit fruit, Color4 expectedColour, bool isLegacyFruit) => + isLegacyFruit + ? fruit.ChildrenOfType().First().Drawable.ChildrenOfType().Any(c => c.Colour == expectedColour) + : fruit.ChildrenOfType().First().Drawable.ChildrenOfType().Single(c => c.BorderColour == expectedColour).Any(d => d.Colour == expectedColour); + + private class TestLegacySkin : ISkin + { + public static Color4 CustomHyperDashColour { get; } = Color4.Goldenrod; + public static Color4 CustomHyperDashFruitColour { get; } = Color4.Cyan; + public static Color4 CustomHyperDashAfterColour { get; } = Color4.Lime; + + private readonly bool customHyperDashCatcherColour; + private readonly bool customHyperDashFruitColour; + private readonly bool customHyperDashAfterColour; + + public TestLegacySkin(bool customHyperDashCatcherColour = false, bool customHyperDashFruitColour = false, bool customHyperDashAfterColour = false) + { + this.customHyperDashCatcherColour = customHyperDashCatcherColour; + this.customHyperDashFruitColour = customHyperDashFruitColour; + this.customHyperDashAfterColour = customHyperDashAfterColour; + } + + public Drawable GetDrawableComponent(ISkinComponent component) => null; + + public Texture GetTexture(string componentName) + { + if (componentName == "fruit-pear") + { + // convince CatchLegacySkinTransformer to use the LegacyFruitPiece for pear fruit. + var texture = new Texture(Texture.WhitePixel.TextureGL) + { + Width = 1, + Height = 1, + ScaleAdjust = 1 / 96f + }; + return texture; + } + + return null; + } + + public SampleChannel GetSample(ISampleInfo sampleInfo) => null; + + public IBindable GetConfig(TLookup lookup) + { + switch (lookup) + { + case CatchSkinConfiguration config when config == CatchSkinConfiguration.HyperDash: + if (customHyperDashCatcherColour) + return SkinUtils.As(new Bindable(CustomHyperDashColour)); + + return null; + + case CatchSkinConfiguration config when config == CatchSkinConfiguration.HyperDashFruit: + if (customHyperDashFruitColour) + return SkinUtils.As(new Bindable(CustomHyperDashFruitColour)); + + return null; + + case CatchSkinConfiguration config when config == CatchSkinConfiguration.HyperDashAfterImage: + if (customHyperDashAfterColour) + return SkinUtils.As(new Bindable(CustomHyperDashAfterColour)); + + return null; + } + + return null; + } + } + } +} From 0a368f13d99421d17c34fa48a75db4001115c95a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 26 Mar 2020 05:37:26 +0300 Subject: [PATCH 048/655] Add default hyper-dash colour constant on Catcher --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index e361b29a9d..f53e14a8c7 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -21,6 +21,8 @@ namespace osu.Game.Rulesets.Catch.UI { public class Catcher : Container, IKeyBindingHandler { + public static Color4 DefaultHyperDashColour { get; } = Color4.Red; + /// /// Whether we are hyper-dashing or not. /// From 6f2cc5471adabc4392fcf1f63a5de32266016c10 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 26 Mar 2020 05:38:41 +0300 Subject: [PATCH 049/655] Add support for custom hyper-dash fruit colouring --- .../Objects/Drawables/FruitPiece.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs index 5797588ded..c8f7c4912e 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs @@ -7,7 +7,10 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Catch.Skinning; +using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Skinning; using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Objects.Drawables @@ -31,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables } [BackgroundDependencyLoader] - private void load(DrawableHitObject drawableObject) + private void load(DrawableHitObject drawableObject, ISkinSource skin) { DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject; hitObject = drawableCatchObject.HitObject; @@ -60,6 +63,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables }, }); + var hyperDashColour = + skin.GetConfig(CatchSkinConfiguration.HyperDashFruit)?.Value ?? + skin.GetConfig(CatchSkinConfiguration.HyperDash)?.Value ?? + Catcher.DefaultHyperDashColour; + if (hitObject.HyperDash) { AddInternal(new Circle @@ -67,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - BorderColour = Color4.Red, + BorderColour = hyperDashColour, BorderThickness = 12f * RADIUS_ADJUST, Children = new Drawable[] { @@ -77,7 +85,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables Alpha = 0.3f, Blending = BlendingParameters.Additive, RelativeSizeAxes = Axes.Both, - Colour = Color4.Red, + Colour = hyperDashColour, } } }); From d995f3e1cc7eff1604d4fa06ed4f85de7152f020 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 26 Mar 2020 05:39:32 +0300 Subject: [PATCH 050/655] Add support for custom hyper-dash legacy fruit colouring --- osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs index 25ee0811d0..99ecf12fd3 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Skinning; using osuTK; @@ -53,10 +54,15 @@ namespace osu.Game.Rulesets.Catch.Skinning if (drawableCatchObject.HitObject.HyperDash) { + var hyperDashColour = + skin.GetConfig(CatchSkinConfiguration.HyperDashFruit)?.Value ?? + skin.GetConfig(CatchSkinConfiguration.HyperDash)?.Value ?? + Catcher.DefaultHyperDashColour; + var hyperDash = new Sprite { Texture = skin.GetTexture(lookupName), - Colour = Color4.Red, + Colour = hyperDashColour, Anchor = Anchor.Centre, Origin = Anchor.Centre, Blending = BlendingParameters.Additive, From 29274b004cfa1141d3a4c85ec97e8960ccdeca48 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 26 Mar 2020 05:40:38 +0300 Subject: [PATCH 051/655] Add hyper-dash fruit colouring test cases --- .../TestSceneHyperDashColouring.cs | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index 2041e365ea..7fab961aa7 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -28,6 +28,77 @@ namespace osu.Game.Rulesets.Catch.Tests { public class TestSceneHyperDashColouring : OsuTestScene { + [TestCase(false)] + [TestCase(true)] + public void TestHyperDashFruitColour(bool legacyFruit) + { + DrawableFruit drawableFruit = null; + + AddStep("setup fruit", () => + { + var fruit = new Fruit { IndexInBeatmap = legacyFruit ? 0 : 1, HyperDashTarget = new Banana() }; + fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + Child = setupSkinHierarchy(() => + drawableFruit = new DrawableFruit(fruit) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(4f), + }, false, false); + }); + + AddAssert("fruit colour default-hyperdash", () => checkFruitHyperDashColour(drawableFruit, Catcher.DefaultHyperDashColour, legacyFruit)); + } + + [TestCase(false, true)] + [TestCase(false, false)] + [TestCase(true, true)] + [TestCase(true, false)] + public void TestCustomHyperDashFruitColour(bool legacyFruit, bool customCatcherHyperDashColour) + { + DrawableFruit drawableFruit = null; + + AddStep("setup fruit", () => + { + var fruit = new Fruit { IndexInBeatmap = legacyFruit ? 0 : 1, HyperDashTarget = new Banana() }; + fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + Child = setupSkinHierarchy(() => + drawableFruit = new DrawableFruit(fruit) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(4f), + }, customCatcherHyperDashColour, true); + }); + + AddAssert("fruit colour custom-hyperdash", () => checkFruitHyperDashColour(drawableFruit, TestLegacySkin.CustomHyperDashFruitColour, legacyFruit)); + } + + [TestCase(false)] + [TestCase(true)] + public void TestCustomHyperDashFruitColourFallback(bool legacyFruit) + { + DrawableFruit drawableFruit = null; + + AddStep("setup fruit", () => + { + var fruit = new Fruit { IndexInBeatmap = legacyFruit ? 0 : 1, HyperDashTarget = new Banana() }; + fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + Child = setupSkinHierarchy(() => + drawableFruit = new DrawableFruit(fruit) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(4f), + }, true, false); + }); + + AddAssert("fruit colour catcher-custom-hyperdash", () => checkFruitHyperDashColour(drawableFruit, TestLegacySkin.CustomHyperDashColour, legacyFruit)); + } + private Drawable setupSkinHierarchy(Func getChild, bool customHyperDashCatcherColour = false, bool customHyperDashFruitColour = false, bool customHyperDashAfterColour = false) { var testSkinProvider = new SkinProvidingContainer(new TestLegacySkin(customHyperDashCatcherColour, customHyperDashFruitColour, customHyperDashAfterColour)); From c1ac57e70fc05e11e6d085f2829eef31d524328e Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 26 Mar 2020 12:14:44 +0100 Subject: [PATCH 052/655] Add back visual tests and add easing to alpha fade. --- .../Visual/Gameplay/TestSceneFailingLayer.cs | 31 +++++++++++++++++++ osu.Game/Screens/Play/HUD/FailingLayer.cs | 7 ++++- 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs new file mode 100644 index 0000000000..3016890ade --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs @@ -0,0 +1,31 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Screens.Play.HUD; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneFailingLayer : OsuTestScene + { + private readonly FailingLayer layer; + + public TestSceneFailingLayer() + { + Child = layer = new FailingLayer(); + } + + [Test] + public void TestLayerFading() + { + AddSliderStep("current health", 0.0, 1.0, 1.0, val => + { + layer.Current.Value = val; + }); + + AddStep("set health to 0.10", () => layer.Current.Value = 0.10); + AddWaitStep("wait for fade to finish", 5); + AddStep("set health to 1", () => layer.Current.Value = 1f); + } + } +} diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index 5f4037c14d..97d2458674 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; using osu.Game.Graphics; namespace osu.Game.Screens.Play.HUD @@ -16,6 +17,8 @@ namespace osu.Game.Screens.Play.HUD { private const float max_alpha = 0.4f; + private const int fade_time = 400; + private readonly Box box; /// @@ -41,7 +44,9 @@ namespace osu.Game.Screens.Play.HUD protected override void Update() { - box.Alpha = (float)Math.Clamp(max_alpha * (1 - Current.Value / LowHealthThreshold), 0, max_alpha); + box.Alpha = (float)Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, fade_time), box.Alpha, + Math.Clamp(max_alpha * (1 - Current.Value / LowHealthThreshold), 0, max_alpha), 0, fade_time, Easing.Out); + base.Update(); } } From e33055e2c455eae6c030d9740c08560149e9cbd1 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Thu, 26 Mar 2020 14:19:36 +0100 Subject: [PATCH 053/655] Simplify active tab font changes and expose necessary fields in OsuTabItem --- .../Graphics/UserInterface/OsuTabControl.cs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index ca9f1330f9..c2feca171b 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -113,13 +113,13 @@ namespace osu.Game.Graphics.UserInterface private const float transition_length = 500; - private void fadeActive() + protected void FadeHovered() { Bar.FadeIn(transition_length, Easing.OutQuint); Text.FadeColour(Color4.White, transition_length, Easing.OutQuint); } - private void fadeInactive() + protected void FadeUnhovered() { Bar.FadeOut(transition_length, Easing.OutQuint); Text.FadeColour(AccentColour, transition_length, Easing.OutQuint); @@ -128,14 +128,14 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnHover(HoverEvent e) { if (!Active.Value) - fadeActive(); + FadeHovered(); return true; } protected override void OnHoverLost(HoverLostEvent e) { if (!Active.Value) - fadeInactive(); + FadeUnhovered(); } [BackgroundDependencyLoader] @@ -172,13 +172,19 @@ namespace osu.Game.Graphics.UserInterface }, new HoverClickSounds() }; - - Active.BindValueChanged(active => Text.Font = Text.Font.With(Typeface.Torus, weight: active.NewValue ? FontWeight.Bold : FontWeight.Medium), true); } - protected override void OnActivated() => fadeActive(); + protected override void OnActivated() + { + Text.Font = Text.Font.With(weight: FontWeight.Bold); + FadeHovered(); + } - protected override void OnDeactivated() => fadeInactive(); + protected override void OnDeactivated() + { + Text.Font = Text.Font.With(weight: FontWeight.Medium); + FadeUnhovered(); + } } } } From 816418742ea4641b1a6c18b3ec35a33aae73d6b7 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Thu, 26 Mar 2020 15:43:48 +0100 Subject: [PATCH 054/655] Update header tab control --- osu.Game/Overlays/OverlayTabControl.cs | 21 +++++++++++--------- osu.Game/Overlays/TabControlOverlayHeader.cs | 14 ++++++------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/OverlayTabControl.cs b/osu.Game/Overlays/OverlayTabControl.cs index aa96f0e19b..a1cbf2c1e7 100644 --- a/osu.Game/Overlays/OverlayTabControl.cs +++ b/osu.Game/Overlays/OverlayTabControl.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; @@ -35,17 +36,22 @@ namespace osu.Game.Overlays protected OverlayTabControl() { TabContainer.Masking = false; - TabContainer.Spacing = new Vector2(15, 0); + TabContainer.Spacing = new Vector2(20, 0); AddInternal(bar = new Box { RelativeSizeAxes = Axes.X, - Height = 2, Anchor = Anchor.BottomLeft, - Origin = Anchor.CentreLeft + Origin = Anchor.BottomLeft }); } + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + AccentColour = colourProvider.Highlight1; + } + protected override Dropdown CreateDropdown() => null; protected override TabItem CreateTabItem(T value) => new OverlayTabItem(value); @@ -90,7 +96,7 @@ namespace osu.Game.Overlays Bar = new ExpandingBar { Anchor = Anchor.BottomCentre, - ExpandedSize = 7.5f, + ExpandedSize = 5f, CollapsedSize = 0 }, new HoverClickSounds() @@ -119,6 +125,7 @@ namespace osu.Game.Overlays { HoverAction(); Text.Font = Text.Font.With(weight: FontWeight.Bold); + Text.FadeColour(Color4.White, 120, Easing.InQuad); } protected override void OnDeactivated() @@ -135,11 +142,7 @@ namespace osu.Game.Overlays OnDeactivated(); } - protected virtual void HoverAction() - { - Bar.Expand(); - Text.FadeColour(Color4.White, 120, Easing.InQuad); - } + protected virtual void HoverAction() => Bar.Expand(); protected virtual void UnhoverAction() { diff --git a/osu.Game/Overlays/TabControlOverlayHeader.cs b/osu.Game/Overlays/TabControlOverlayHeader.cs index b199a2a0cf..d6d53eec58 100644 --- a/osu.Game/Overlays/TabControlOverlayHeader.cs +++ b/osu.Game/Overlays/TabControlOverlayHeader.cs @@ -22,6 +22,7 @@ namespace osu.Game.Overlays { protected OsuTabControl TabControl; + private readonly Box controlBackground; private readonly BindableWithCurrent current = new BindableWithCurrent(); public Bindable Current @@ -30,8 +31,6 @@ namespace osu.Game.Overlays set => current.Current = value; } - private readonly Box controlBackground; - protected TabControlOverlayHeader() { HeaderInfo.Add(new Container @@ -56,7 +55,6 @@ namespace osu.Game.Overlays [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - TabControl.AccentColour = colourProvider.Highlight1; controlBackground.Colour = colourProvider.Dark4; } @@ -65,14 +63,16 @@ namespace osu.Game.Overlays public class OverlayHeaderTabControl : OverlayTabControl { + private const float bar_height = 1; + public OverlayHeaderTabControl() { - BarHeight = 1; RelativeSizeAxes = Axes.None; AutoSizeAxes = Axes.X; Anchor = Anchor.BottomLeft; Origin = Anchor.BottomLeft; - Height = 35; + Height = 47; + BarHeight = bar_height; } protected override TabItem CreateTabItem(T value) => new OverlayHeaderTabItem(value); @@ -82,7 +82,6 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Direction = FillDirection.Horizontal, - Spacing = new Vector2(5, 0), }; private class OverlayHeaderTabItem : OverlayTabItem @@ -92,7 +91,8 @@ namespace osu.Game.Overlays { Text.Text = value.ToString().ToLower(); Text.Font = OsuFont.GetFont(size: 14); - Bar.ExpandedSize = 5; + Text.Margin = new MarginPadding { Vertical = 16.5f }; // 15px padding + 1.5px line-height difference compensation + Bar.Margin = new MarginPadding { Bottom = bar_height }; } } } From 46ebf6ef7827f633485ac4c8b5cbee2ed66b191b Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Thu, 26 Mar 2020 15:44:22 +0100 Subject: [PATCH 055/655] Update user profile section tabs and rename classes for better readibility --- osu.Game/Overlays/UserProfileOverlay.cs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 045a52a0c7..44f3acb564 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays private GetUserRequest userReq; protected ProfileHeader Header; private ProfileSectionsContainer sectionsContainer; - private ProfileTabControl tabs; + private ProfileSectionTabControl tabs; public const float CONTENT_X_MARGIN = 70; @@ -62,7 +62,7 @@ namespace osu.Game.Overlays } : Array.Empty(); - tabs = new ProfileTabControl + tabs = new ProfileSectionTabControl { RelativeSizeAxes = Axes.X, Anchor = Anchor.TopCentre, @@ -149,19 +149,23 @@ namespace osu.Game.Overlays } } - private class ProfileTabControl : OverlayTabControl + private class ProfileSectionTabControl : OverlayTabControl { - public ProfileTabControl() + private const float bar_height = 2; + + public ProfileSectionTabControl() { TabContainer.RelativeSizeAxes &= ~Axes.X; TabContainer.AutoSizeAxes |= Axes.X; TabContainer.Anchor |= Anchor.x1; TabContainer.Origin |= Anchor.x1; + + BarHeight = bar_height; } - protected override TabItem CreateTabItem(ProfileSection value) => new ProfileTabItem(value) + protected override TabItem CreateTabItem(ProfileSection value) => new ProfileSectionTabItem(value) { - AccentColour = AccentColour + AccentColour = AccentColour, }; [BackgroundDependencyLoader] @@ -170,12 +174,14 @@ namespace osu.Game.Overlays AccentColour = colourProvider.Highlight1; } - private class ProfileTabItem : OverlayTabItem + private class ProfileSectionTabItem : OverlayTabItem { - public ProfileTabItem(ProfileSection value) + public ProfileSectionTabItem(ProfileSection value) : base(value) { Text.Text = value.Title; + Bar.ExpandedSize = 10; + Bar.Margin = new MarginPadding { Bottom = bar_height }; } } } From da996ffe748d2d30821284f1ccf48ad0fa0d193d Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Thu, 26 Mar 2020 15:44:53 +0100 Subject: [PATCH 056/655] Update header breadcrumb tab control --- .../Overlays/BreadcrumbControlOverlayHeader.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs b/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs index 1d8411dfcc..81315f9638 100644 --- a/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs +++ b/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; @@ -16,6 +17,13 @@ namespace osu.Game.Overlays public OverlayHeaderBreadcrumbControl() { RelativeSizeAxes = Axes.X; + Height = 47; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + AccentColour = colourProvider.Light2; } protected override TabItem CreateTabItem(string value) => new ControlTabItem(value); @@ -27,10 +35,18 @@ namespace osu.Game.Overlays public ControlTabItem(string value) : base(value) { + RelativeSizeAxes = Axes.Y; Text.Font = Text.Font.With(size: 14); - Chevron.Y = 3; + Text.Anchor = Anchor.CentreLeft; + Text.Origin = Anchor.CentreLeft; + Chevron.Y = 1; Bar.Height = 0; } + + // base OsuTabItem makes font bold on activation, we don't want that here + protected override void OnActivated() => FadeHovered(); + + protected override void OnDeactivated() => FadeUnhovered(); } } } From 9a30ff5a00c91b3e0b5b606b11efeb0402c58f8a Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Thu, 26 Mar 2020 16:11:58 +0100 Subject: [PATCH 057/655] Fix code quality issues --- osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs | 1 - osu.Game/Overlays/OverlayHeader.cs | 4 +--- osu.Game/Overlays/TabControlOverlayHeader.cs | 1 - osu.Game/Screens/Multi/Header.cs | 3 +-- 4 files changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs index 9dc71c7e74..c81ec9f663 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs @@ -8,7 +8,6 @@ using System.Collections.Generic; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; using osu.Framework.Allocation; -using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Shapes; using osuTK.Graphics; diff --git a/osu.Game/Overlays/OverlayHeader.cs b/osu.Game/Overlays/OverlayHeader.cs index f017d66485..4ac0f697c3 100644 --- a/osu.Game/Overlays/OverlayHeader.cs +++ b/osu.Game/Overlays/OverlayHeader.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.UserInterface; using osuTK.Graphics; namespace osu.Game.Overlays @@ -14,7 +13,6 @@ namespace osu.Game.Overlays public abstract class OverlayHeader : Container { private readonly Box titleBackground; - private readonly OverlayTitle title; protected readonly FillFlowContainer HeaderInfo; @@ -60,7 +58,7 @@ namespace osu.Game.Overlays }, Children = new[] { - title = CreateTitle().With(title => + CreateTitle().With(title => { title.Anchor = Anchor.CentreLeft; title.Origin = Anchor.CentreLeft; diff --git a/osu.Game/Overlays/TabControlOverlayHeader.cs b/osu.Game/Overlays/TabControlOverlayHeader.cs index d6d53eec58..ab1a6aff78 100644 --- a/osu.Game/Overlays/TabControlOverlayHeader.cs +++ b/osu.Game/Overlays/TabControlOverlayHeader.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; -using osuTK; namespace osu.Game.Overlays { diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs index 6f790d703e..7a2d3a6239 100644 --- a/osu.Game/Screens/Multi/Header.cs +++ b/osu.Game/Screens/Multi/Header.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Multi private const float spacing = 6; private const int text_offset = 2; - private SpriteIcon iconSprite; + private readonly SpriteIcon iconSprite; private readonly OsuSpriteText titleText, pageText; public IMultiplayerSubScreen Screen @@ -101,7 +101,6 @@ namespace osu.Game.Screens.Multi } public MultiHeaderTitle() - : base() { AutoSizeAxes = Axes.Both; From 543f584595be39f4b429a45d5f0131bd452a5d0c Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Thu, 26 Mar 2020 16:44:46 +0100 Subject: [PATCH 058/655] Adjust user profile tabs --- osu.Game/Overlays/UserProfileOverlay.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 44f3acb564..6ec30f7707 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -67,7 +67,6 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.X, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Height = 34 }; Add(new Box @@ -160,6 +159,7 @@ namespace osu.Game.Overlays TabContainer.Anchor |= Anchor.x1; TabContainer.Origin |= Anchor.x1; + Height = 36 + bar_height; BarHeight = bar_height; } @@ -180,6 +180,8 @@ namespace osu.Game.Overlays : base(value) { Text.Text = value.Title; + Text.Font = Text.Font.With(size: 16); + Text.Margin = new MarginPadding { Bottom = 10 + bar_height }; Bar.ExpandedSize = 10; Bar.Margin = new MarginPadding { Bottom = bar_height }; } From 83410315c64a5362325ee9fe02293af2fd5247b8 Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Thu, 26 Mar 2020 17:18:01 +0100 Subject: [PATCH 059/655] Make fields private --- osu.Game/Screens/Multi/Header.cs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs index 7a2d3a6239..5b8e8a7fd9 100644 --- a/osu.Game/Screens/Multi/Header.cs +++ b/osu.Game/Screens/Multi/Header.cs @@ -75,25 +75,20 @@ namespace osu.Game.Screens.Multi private class MultiHeaderTitle : CompositeDrawable, IHasAccentColour { - public const float ICON_WIDTH = ICON_SIZE + spacing; + public const float ICON_WIDTH = icon_size + spacing; - public const float ICON_SIZE = 25; + private const float icon_size = 25; private const float spacing = 6; private const int text_offset = 2; private readonly SpriteIcon iconSprite; - private readonly OsuSpriteText titleText, pageText; + private readonly OsuSpriteText title, pageText; public IMultiplayerSubScreen Screen { set => pageText.Text = value.ShortTitle.ToLowerInvariant(); } - protected string Title - { - set => titleText.Text = value; - } - public Color4 AccentColour { get => pageText.Colour; @@ -115,11 +110,11 @@ namespace osu.Game.Screens.Multi { iconSprite = new SpriteIcon { - Size = new Vector2(ICON_SIZE), + Size = new Vector2(icon_size), Anchor = Anchor.Centre, Origin = Anchor.Centre }, - titleText = new OsuSpriteText + title = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -148,7 +143,7 @@ namespace osu.Game.Screens.Multi [BackgroundDependencyLoader] private void load(OsuColour colours) { - Title = "multi"; + title.Text = "multi"; iconSprite.Icon = OsuIcon.Multi; AccentColour = colours.Yellow; } From ee112c6f507e295a414721e4049f679583b9ab24 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Thu, 26 Mar 2020 18:42:08 +0200 Subject: [PATCH 060/655] Move and change logic --- osu.Game/Screens/Select/BeatmapCarousel.cs | 33 ++++++++++++++++--- .../Select/Carousel/CarouselBeatmapSet.cs | 12 +++---- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 2c45b3642d..65472f8a0e 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -24,7 +24,8 @@ using osu.Game.Graphics.Cursor; using osu.Game.Input.Bindings; using osu.Game.Screens.Select.Carousel; using osu.Game.Online.API; -using osu.Game.Users; +using osu.Game.Rulesets; +using osu.Game.Online.API.Requests; namespace osu.Game.Screens.Select { @@ -33,7 +34,7 @@ namespace osu.Game.Screens.Select private const float bleed_top = FilterControl.HEIGHT; private const float bleed_bottom = Footer.HEIGHT; - private readonly Bindable localUser = new Bindable(); + private readonly Bindable recommendedStarDifficulty = new Bindable(); /// /// Triggered when the loaded change and are completely loaded. @@ -143,8 +144,11 @@ namespace osu.Game.Screens.Select [Resolved] private BeatmapManager beatmaps { get; set; } + [Resolved] + private IAPIProvider api { get; set; } + [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuConfigManager config, IAPIProvider api) + private void load(OsuConfigManager config, Bindable decoupledRuleset) { config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm); config.BindWith(OsuSetting.SongSelectRightMouseScroll, RightClickScrollingEnabled); @@ -159,7 +163,26 @@ namespace osu.Game.Screens.Select loadBeatmapSets(GetLoadableBeatmaps()); - localUser.BindTo(api.LocalUser); + decoupledRuleset.BindValueChanged(UpdateRecommendedStarDifficulty, true); + } + + protected void UpdateRecommendedStarDifficulty(ValueChangedEvent ruleset) + { + if (api.LocalUser.Value is GuestUser) + { + recommendedStarDifficulty.Value = 0; + return; + } + + var req = new GetUserRequest(api.LocalUser.Value.Id, ruleset.NewValue); + + req.Success += result => + { + // algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505 + recommendedStarDifficulty.Value = Math.Pow((double)(result.Statistics.PP ?? 0), 0.4) * 0.195; + }; + + api.PerformAsync(req); } protected virtual IEnumerable GetLoadableBeatmaps() => beatmaps.GetAllUsableBeatmapSetsEnumerable(); @@ -594,7 +617,7 @@ namespace osu.Game.Screens.Select b.Metadata = beatmapSet.Metadata; } - var set = new CarouselBeatmapSet(beatmapSet, localUser); + var set = new CarouselBeatmapSet(beatmapSet, recommendedStarDifficulty); foreach (var c in set.Beatmaps) { diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 9f1c39c578..064840d99a 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -8,19 +8,18 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Beatmaps; using osu.Game.Screens.Select.Filter; -using osu.Game.Users; namespace osu.Game.Screens.Select.Carousel { public class CarouselBeatmapSet : CarouselGroupEagerSelect { - private readonly Bindable localUser; + private readonly Bindable recommendedStarDifficulty = new Bindable(); public IEnumerable Beatmaps => InternalChildren.OfType(); public BeatmapSetInfo BeatmapSet; - public CarouselBeatmapSet(BeatmapSetInfo beatmapSet, Bindable localUser) + public CarouselBeatmapSet(BeatmapSetInfo beatmapSet, Bindable recommendedStarDifficulty) { BeatmapSet = beatmapSet ?? throw new ArgumentNullException(nameof(beatmapSet)); @@ -29,7 +28,7 @@ namespace osu.Game.Screens.Select.Carousel .Select(b => new CarouselBeatmap(b)) .ForEach(AddChild); - this.localUser = localUser; + this.recommendedStarDifficulty.BindTo(recommendedStarDifficulty); } protected override DrawableCarouselItem CreateDrawableRepresentation() => new DrawableCarouselBeatmapSet(this); @@ -38,14 +37,11 @@ namespace osu.Game.Screens.Select.Carousel { if (LastSelected == null) { - decimal? pp = localUser.Value?.Statistics?.PP ?? 60; // TODO: This needs to get ruleset specific statistics - - var recommendedDifficulty = Math.Pow((double)pp, 0.4) * 0.195; return Children.OfType() .Where(b => !b.Filtered.Value) .OrderBy(b => { - var difference = b.Beatmap.StarDifficulty - recommendedDifficulty; + var difference = b.Beatmap.StarDifficulty - recommendedStarDifficulty.Value; return difference >= 0 ? difference * 2 : difference * -1; // prefer easier over harder }) .FirstOrDefault(); From bbbaaae3ee8bbf6d48498deef378ca1974b2ff17 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Thu, 26 Mar 2020 19:18:16 +0200 Subject: [PATCH 061/655] Write tests --- .../SongSelect/TestSceneBeatmapCarousel.cs | 31 +++++++++++++++++++ osu.Game/Screens/Select/BeatmapCarousel.cs | 8 ++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 0cc37bbd57..b9b52a28cb 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Text; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -579,6 +580,34 @@ namespace osu.Game.Tests.Visual.SongSelect checkVisibleItemCount(true, 15); } + [Test] + public void TestSelectRecommendedDifficulty() + { + void setRecommendedAndExpect(double recommended, int expectedSet, int expectedDiff) + { + AddStep($"Recommend SR {recommended}", () => carousel.RecommendedStarDifficulty.Value = recommended); + advanceSelection(direction: 1, diff: false); + waitForSelection(expectedSet, expectedDiff); + } + + createCarousel(); + AddStep("Add beatmaps", () => + { + for (int i = 1; i <= 7; i++) + { + var set = createTestBeatmapSet(i); + carousel.UpdateBeatmapSet(set); + } + }); + waitForSelection(1, 1); + setRecommendedAndExpect(1, 2, 1); + setRecommendedAndExpect(3.9, 3, 1); + setRecommendedAndExpect(4.1, 4, 2); + setRecommendedAndExpect(5.6, 5, 2); + setRecommendedAndExpect(5.7, 6, 3); + setRecommendedAndExpect(10, 7, 3); + } + private void loadBeatmaps(List beatmapSets = null, Func initialCriteria = null) { createCarousel(); @@ -781,6 +810,8 @@ namespace osu.Game.Tests.Visual.SongSelect { public new List Items => base.Items; + public new Bindable RecommendedStarDifficulty => base.RecommendedStarDifficulty; + public bool PendingFilterTask => PendingFilter != null; protected override IEnumerable GetLoadableBeatmaps() => Enumerable.Empty(); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 65472f8a0e..9aa4938886 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Select private const float bleed_top = FilterControl.HEIGHT; private const float bleed_bottom = Footer.HEIGHT; - private readonly Bindable recommendedStarDifficulty = new Bindable(); + protected readonly Bindable RecommendedStarDifficulty = new Bindable(); /// /// Triggered when the loaded change and are completely loaded. @@ -170,7 +170,7 @@ namespace osu.Game.Screens.Select { if (api.LocalUser.Value is GuestUser) { - recommendedStarDifficulty.Value = 0; + RecommendedStarDifficulty.Value = 0; return; } @@ -179,7 +179,7 @@ namespace osu.Game.Screens.Select req.Success += result => { // algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505 - recommendedStarDifficulty.Value = Math.Pow((double)(result.Statistics.PP ?? 0), 0.4) * 0.195; + RecommendedStarDifficulty.Value = Math.Pow((double)(result.Statistics.PP ?? 0), 0.4) * 0.195; }; api.PerformAsync(req); @@ -617,7 +617,7 @@ namespace osu.Game.Screens.Select b.Metadata = beatmapSet.Metadata; } - var set = new CarouselBeatmapSet(beatmapSet, recommendedStarDifficulty); + var set = new CarouselBeatmapSet(beatmapSet, RecommendedStarDifficulty); foreach (var c in set.Beatmaps) { From 7b24cc325f0ed61490766307ac0a681d5a9bb766 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 27 Mar 2020 20:57:57 +0300 Subject: [PATCH 062/655] Implement OverlayScrollContainer component --- .../TestSceneOverlayScrollContainer.cs | 90 ++++++++++ osu.Game/Overlays/OverlayScrollContainer.cs | 169 ++++++++++++++++++ 2 files changed, 259 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs create mode 100644 osu.Game/Overlays/OverlayScrollContainer.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs new file mode 100644 index 0000000000..1fc85c3c04 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs @@ -0,0 +1,90 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; +using System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Shapes; +using osuTK.Graphics; +using NUnit.Framework; +using osu.Framework.Utils; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneOverlayScrollContainer : OsuManualInputManagerTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OverlayScrollContainer) + }; + + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + + private OverlayScrollContainer scroll; + + private int invocationCount; + + [SetUp] + public void SetUp() => Schedule(() => + { + Add(scroll = new OverlayScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = new Container + { + Height = 3000, + RelativeSizeAxes = Axes.X, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Gray + } + } + }); + + invocationCount = 0; + + scroll.Button.Action += () => invocationCount++; + }); + + [Test] + public void TestButtonVisibility() + { + AddAssert("button is hidden", () => scroll.Button.State.Value == Visibility.Hidden); + + AddStep("scroll to end", () => scroll.ScrollToEnd(false)); + AddAssert("button is visible", () => scroll.Button.State.Value == Visibility.Visible); + + AddStep("scroll to start", () => scroll.ScrollToStart(false)); + AddAssert("button is hidden", () => scroll.Button.State.Value == Visibility.Hidden); + } + + [Test] + public void TestButtonAction() + { + AddStep("scroll to end", () => scroll.ScrollToEnd(false)); + + AddStep("invoke action", () => scroll.Button.Action.Invoke()); + + AddUntilStep("scrolled back to start", () => Precision.AlmostEquals(scroll.Current, 0, 0.1f)); + } + + [Test] + public void TestMultipleClicks() + { + AddStep("scroll to end", () => scroll.ScrollToEnd(false)); + + AddAssert("invocation count is 0", () => invocationCount == 0); + + AddStep("hover button", () => InputManager.MoveMouseTo(scroll.Button)); + AddRepeatStep("click button", () => InputManager.Click(MouseButton.Left), 3); + + AddAssert("invocation count is 1", () => invocationCount == 1); + } + } +} diff --git a/osu.Game/Overlays/OverlayScrollContainer.cs b/osu.Game/Overlays/OverlayScrollContainer.cs new file mode 100644 index 0000000000..1a875ded95 --- /dev/null +++ b/osu.Game/Overlays/OverlayScrollContainer.cs @@ -0,0 +1,169 @@ +// 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.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays +{ + /// + /// which provides . Mostly used in . + /// + public class OverlayScrollContainer : OsuScrollContainer + { + /// + /// Scroll position at which the will be shown. + /// + private const int button_scroll_position = 200; + + public ScrollToTopButton Button { get; } + + private float currentTarget; + + public OverlayScrollContainer() + { + AddInternal(Button = new ScrollToTopButton + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Margin = new MarginPadding(20), + Action = () => + { + ScrollToStart(); + currentTarget = Target; + Button.State.Value = Visibility.Hidden; + } + }); + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + if (ScrollContent.DrawHeight + button_scroll_position < DrawHeight) + { + Button.State.Value = Visibility.Hidden; + return; + } + + if (Target == currentTarget) + return; + + currentTarget = Target; + Button.State.Value = Current > button_scroll_position ? Visibility.Visible : Visibility.Hidden; + } + + public class ScrollToTopButton : VisibilityContainer + { + private const int fade_duration = 500; + + public Action Action + { + get => button.Action; + set => button.Action = value; + } + + public override bool PropagatePositionalInputSubTree => true; + + protected override bool StartHidden => true; + + private readonly Button button; + + public ScrollToTopButton() + { + Size = new Vector2(50); + Child = button = new Button(); + } + + protected override bool OnMouseDown(MouseDownEvent e) => true; + + protected override void PopIn() => button.FadeIn(fade_duration, Easing.OutQuint); + + protected override void PopOut() => button.FadeOut(fade_duration, Easing.OutQuint); + + private class Button : OsuHoverContainer + { + public override bool PropagatePositionalInputSubTree => Alpha == 1; + + protected override IEnumerable EffectTargets => new[] { background }; + + private Color4 flashColour; + + private readonly Container content; + private readonly Box background; + + public Button() + { + RelativeSizeAxes = Axes.Both; + Add(content = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0f, 1f), + Radius = 3f, + Colour = Color4.Black.Opacity(0.25f), + }, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(15), + Icon = FontAwesome.Solid.ChevronUp + } + } + }); + + TooltipText = "Scroll to top"; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + IdleColour = colourProvider.Background6; + HoverColour = colourProvider.Background5; + flashColour = colourProvider.Light1; + } + + protected override bool OnClick(ClickEvent e) + { + background.FlashColour(flashColour, 800, Easing.OutQuint); + return base.OnClick(e); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + content.ScaleTo(0.75f, 2000, Easing.OutQuint); + return true; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + content.ScaleTo(1, 1000, Easing.OutElastic); + base.OnMouseUp(e); + } + } + } + } +} From 45eb03bfe2adc868729915defc057b09e5fb7f90 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 Mar 2020 07:43:47 +0300 Subject: [PATCH 063/655] Apply review suggestions --- .../TestSceneHyperDashColouring.cs | 32 +++++++------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index 7fab961aa7..9ab8cf9113 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Catch.Tests }, false, false); }); - AddAssert("fruit colour default-hyperdash", () => checkFruitHyperDashColour(drawableFruit, Catcher.DefaultHyperDashColour, legacyFruit)); + AddAssert("default colour", () => checkFruitHyperDashColour(drawableFruit, Catcher.DefaultHyperDashColour, legacyFruit)); } [TestCase(false, true)] @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Catch.Tests }, customCatcherHyperDashColour, true); }); - AddAssert("fruit colour custom-hyperdash", () => checkFruitHyperDashColour(drawableFruit, TestLegacySkin.CustomHyperDashFruitColour, legacyFruit)); + AddAssert("custom colour", () => checkFruitHyperDashColour(drawableFruit, TestLegacySkin.CustomHyperDashFruitColour, legacyFruit)); } [TestCase(false)] @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Catch.Tests }, true, false); }); - AddAssert("fruit colour catcher-custom-hyperdash", () => checkFruitHyperDashColour(drawableFruit, TestLegacySkin.CustomHyperDashColour, legacyFruit)); + AddAssert("catcher custom colour", () => checkFruitHyperDashColour(drawableFruit, TestLegacySkin.CustomHyperDashColour, legacyFruit)); } private Drawable setupSkinHierarchy(Func getChild, bool customHyperDashCatcherColour = false, bool customHyperDashFruitColour = false, bool customHyperDashAfterColour = false) @@ -139,13 +139,12 @@ namespace osu.Game.Rulesets.Catch.Tests if (componentName == "fruit-pear") { // convince CatchLegacySkinTransformer to use the LegacyFruitPiece for pear fruit. - var texture = new Texture(Texture.WhitePixel.TextureGL) + return new Texture(Texture.WhitePixel.TextureGL) { Width = 1, Height = 1, ScaleAdjust = 1 / 96f }; - return texture; } return null; @@ -155,25 +154,16 @@ namespace osu.Game.Rulesets.Catch.Tests public IBindable GetConfig(TLookup lookup) { - switch (lookup) + if (lookup is CatchSkinConfiguration config) { - case CatchSkinConfiguration config when config == CatchSkinConfiguration.HyperDash: - if (customHyperDashCatcherColour) - return SkinUtils.As(new Bindable(CustomHyperDashColour)); + if (config == CatchSkinConfiguration.HyperDash && customHyperDashCatcherColour) + return SkinUtils.As(new Bindable(CustomHyperDashColour)); - return null; + if (config == CatchSkinConfiguration.HyperDashFruit && customHyperDashFruitColour) + return SkinUtils.As(new Bindable(CustomHyperDashFruitColour)); - case CatchSkinConfiguration config when config == CatchSkinConfiguration.HyperDashFruit: - if (customHyperDashFruitColour) - return SkinUtils.As(new Bindable(CustomHyperDashFruitColour)); - - return null; - - case CatchSkinConfiguration config when config == CatchSkinConfiguration.HyperDashAfterImage: - if (customHyperDashAfterColour) - return SkinUtils.As(new Bindable(CustomHyperDashAfterColour)); - - return null; + if (config == CatchSkinConfiguration.HyperDashAfterImage && customHyperDashAfterColour) + return SkinUtils.As(new Bindable(CustomHyperDashAfterColour)); } return null; From 1c711147f37c289f1519088563fab122c05785cc Mon Sep 17 00:00:00 2001 From: Santeri Nogelainen Date: Sat, 28 Mar 2020 17:22:01 +0200 Subject: [PATCH 064/655] Move all carousel rank logic into separate classes (TopLocalRank and CarouselBeatmapRank) --- osu.Game/Online/Leaderboards/TopLocalRank.cs | 78 +++++++++++++++++++ .../Online/Leaderboards/UpdateableRank.cs | 22 ++++-- .../Select/Carousel/CarouselBeatmapRank.cs | 67 ++++++++++++++++ .../Carousel/DrawableCarouselBeatmap.cs | 20 ++++- 4 files changed, 177 insertions(+), 10 deletions(-) create mode 100644 osu.Game/Online/Leaderboards/TopLocalRank.cs create mode 100644 osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs diff --git a/osu.Game/Online/Leaderboards/TopLocalRank.cs b/osu.Game/Online/Leaderboards/TopLocalRank.cs new file mode 100644 index 0000000000..40855e6cf8 --- /dev/null +++ b/osu.Game/Online/Leaderboards/TopLocalRank.cs @@ -0,0 +1,78 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Online.API; +using osu.Game.Rulesets; +using osu.Game.Scoring; + +namespace osu.Game.Online.Leaderboards +{ + public class TopLocalRank : Container + { + private readonly BeatmapInfo beatmap; + + private ScoreManager scores; + private IBindable ruleset; + private IAPIProvider api; + private UpdateableRank rank; + + /// + /// Raised when the top score is loaded + /// + public Action ScoreLoaded; + + public TopLocalRank(BeatmapInfo beatmap) + { + this.beatmap = beatmap; + + RelativeSizeAxes = Axes.Both; + + InternalChild = rank = new UpdateableRank(null) + { + RelativeSizeAxes = Axes.Both + }; + } + + [BackgroundDependencyLoader] + private void load(ScoreManager scores, IBindable ruleset, IAPIProvider api) + { + this.scores = scores; + this.ruleset = ruleset; + this.api = api; + + FetchAndLoadTopScore(); + } + + public void FetchAndLoadTopScore() + { + var score = fetchTopScore(); + + loadTopScore(score); + } + + private void loadTopScore(ScoreInfo score) + { + Schedule(() => rank.Rank = score?.Rank); + + ScoreLoaded?.Invoke(score); + } + + private ScoreInfo fetchTopScore() + { + if (scores == null || beatmap == null || ruleset?.Value == null || api?.LocalUser.Value == null) + return null; + + return scores.GetAllUsableScores() + .Where(s => s.UserID == api.LocalUser.Value.Id && s.BeatmapInfoID == beatmap.ID && s.RulesetID == ruleset.Value.ID) + .OrderByDescending(s => s.TotalScore) + .FirstOrDefault(); + } + } +} diff --git a/osu.Game/Online/Leaderboards/UpdateableRank.cs b/osu.Game/Online/Leaderboards/UpdateableRank.cs index d9e8957281..8f74fd84fe 100644 --- a/osu.Game/Online/Leaderboards/UpdateableRank.cs +++ b/osu.Game/Online/Leaderboards/UpdateableRank.cs @@ -7,23 +7,31 @@ using osu.Game.Scoring; namespace osu.Game.Online.Leaderboards { - public class UpdateableRank : ModelBackedDrawable + public class UpdateableRank : ModelBackedDrawable { - public ScoreRank Rank + public ScoreRank? Rank { get => Model; set => Model = value; } - public UpdateableRank(ScoreRank rank) + public UpdateableRank(ScoreRank? rank) { Rank = rank; } - protected override Drawable CreateDrawable(ScoreRank rank) => new DrawableRank(rank) + protected override Drawable CreateDrawable(ScoreRank? rank) { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; + if (rank.HasValue) + { + return new DrawableRank(rank.Value) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + } + + return null; + } } } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs new file mode 100644 index 0000000000..9ad0dc946e --- /dev/null +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs @@ -0,0 +1,67 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Scoring; + +namespace osu.Game.Online.Leaderboards +{ + public class CarouselBeatmapRank : Container + { + private const int rank_size = 20; + private readonly BeatmapInfo beatmap; + + private TopLocalRank rank; + + public CarouselBeatmapRank(BeatmapInfo beatmap) + { + this.beatmap = beatmap; + + Height = rank_size; + } + + [BackgroundDependencyLoader] + private void load(ScoreManager scores, IBindable ruleset) + { + scores.ItemAdded += scoreChanged; + scores.ItemRemoved += scoreChanged; + ruleset.ValueChanged += _ => rulesetChanged(); + + rank = new TopLocalRank(beatmap) + { + ScoreLoaded = scaleDisplay + }; + + InternalChild = new DelayedLoadWrapper(rank) + { + RelativeSizeAxes = Axes.Both + }; + } + + private void rulesetChanged() + { + rank.FetchAndLoadTopScore(); + } + + private void scoreChanged(ScoreInfo score) + { + if (score.BeatmapInfoID == beatmap.ID) + { + rank.FetchAndLoadTopScore(); + } + } + + private void scaleDisplay(ScoreInfo score) + { + if (score != null) + Width = rank_size * 2; + else + Width = 0; + } + } +} diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 841bbf415c..a58d706003 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -19,6 +19,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Leaderboards; using osu.Game.Overlays; using osuTK; using osuTK.Graphics; @@ -122,10 +123,23 @@ namespace osu.Game.Screens.Select.Carousel }, } }, - starCounter = new StarCounter + new FillFlowContainer { - Current = (float)beatmap.StarDifficulty, - Scale = new Vector2(0.8f), + Direction = FillDirection.Horizontal, + Spacing = new Vector2(4, 0), + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new CarouselBeatmapRank(beatmap) + { + Scale = new Vector2(0.8f) + }, + starCounter = new StarCounter + { + Current = (float)beatmap.StarDifficulty, + Scale = new Vector2(0.8f), + } + } } } } From faa2b49be41032f14dacc829ce90de8ae38b6783 Mon Sep 17 00:00:00 2001 From: Santeri Nogelainen Date: Sat, 28 Mar 2020 18:13:39 +0200 Subject: [PATCH 065/655] Fix namespace for CarouselBeatmapRank, make UpdateableRank in TopLocalRank readonly --- osu.Game/Online/Leaderboards/TopLocalRank.cs | 2 +- osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs | 3 ++- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Leaderboards/TopLocalRank.cs b/osu.Game/Online/Leaderboards/TopLocalRank.cs index 40855e6cf8..83d92f8ffa 100644 --- a/osu.Game/Online/Leaderboards/TopLocalRank.cs +++ b/osu.Game/Online/Leaderboards/TopLocalRank.cs @@ -17,11 +17,11 @@ namespace osu.Game.Online.Leaderboards public class TopLocalRank : Container { private readonly BeatmapInfo beatmap; + private readonly UpdateableRank rank; private ScoreManager scores; private IBindable ruleset; private IAPIProvider api; - private UpdateableRank rank; /// /// Raised when the top score is loaded diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs index 9ad0dc946e..fbd4292138 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs @@ -6,10 +6,11 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; +using osu.Game.Online.Leaderboards; using osu.Game.Rulesets; using osu.Game.Scoring; -namespace osu.Game.Online.Leaderboards +namespace osu.Game.Screens.Select.Carousel { public class CarouselBeatmapRank : Container { diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index a58d706003..4b42d818f5 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -19,7 +19,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Online.Leaderboards; using osu.Game.Overlays; using osuTK; using osuTK.Graphics; From 11826800fb834ccd9b04b98db23c713fdfc6b4e9 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 29 Mar 2020 17:00:26 +0300 Subject: [PATCH 066/655] Test slider snaking --- .../TestSceneSliderSnaking.cs | 294 ++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs new file mode 100644 index 0000000000..3e40713f52 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -0,0 +1,294 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Configuration; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using osu.Game.Storyboards; +using osuTK; +using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestSceneSliderSnaking : TestSceneOsuPlayer + { + [Resolved] + private AudioManager audioManager { get; set; } + + private TrackVirtualManual track; + + protected override bool Autoplay => true; + + private readonly Bindable snakingIn = new Bindable(); + private readonly Bindable snakingOut = new Bindable(); + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) + { + var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); + track = (TrackVirtualManual)working.Track; + return working; + } + + [BackgroundDependencyLoader] + private void load(RulesetConfigCache configCache) + { + var config = (OsuRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(OsuRulesetSetting.SnakingInSliders, snakingIn); + config.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut); + } + + private DrawableSlider slider; + private DrawableSliderRepeat repeat; + private Vector2 vector; + + [SetUpSteps] + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddUntilStep("wait for track to start running", () => track.IsRunning); + } + + [Test] + public void TestSnaking() + { + AddStep("retrieve 1st slider", () => slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.First()); + testLinear(true); + testLinear(false); + AddStep("retrieve 2nd slider", () => slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.Skip(1).First()); + testRepeating(true); + testRepeating(false); + AddStep("retrieve 3rd slider", () => slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.Skip(2).First()); + testDoubleRepeating(true); + testDoubleRepeating(false); + + // Test arrow stays in place + setSnaking(true); + addSeekStep(13500); + AddStep("retrieve 2nd slider repeat", () => + { + var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.Skip(1).First(); + repeat = drawable.ChildrenOfType>().First().Children.First(); + }); + AddStep("Save repeat vector", () => vector = repeat.Position); + addSeekStep(13700); + AddAssert("Repeat vector is same", () => Precision.AlmostEquals(vector.X, repeat.Position.X, 1) && Precision.AlmostEquals(vector.Y, repeat.Position.Y, 1)); + } + + private void testLinear(bool snaking) + { + var increased = snaking ? "increased" : "is same"; + + setSnaking(snaking); + addSeekStep(1800); + AddStep("Save end vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.Last(); + }); + addSeekStep(1900); + AddAssert($"End vector {increased}", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var last = body.CurrentCurve.Last(); + return snaking ? last.X > vector.X && last.Y > vector.Y : last == vector; + }); + addSeekStep(3100); + AddStep("Save start vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.First(); + }); + addSeekStep(3200); + AddAssert($"Start vector {increased}", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var first = body.CurrentCurve.First(); + return snaking ? first.X > vector.X && first.Y > vector.Y : first == vector; + }); + } + + private void testRepeating(bool snaking) + { + var increased = snaking ? "increased" : "is same"; + var decreased = snaking ? "decreased" : "is same"; + + setSnaking(snaking); + addSeekStep(8800); + AddStep("Save end vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.Last(); + }); + addSeekStep(8900); + AddAssert($"End vector {increased}", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var last = body.CurrentCurve.Last(); + return snaking ? last.X > vector.X && last.Y > vector.Y : last == vector; + }); + addSeekStep(10100); + AddStep("Save start vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.First(); + }); + addSeekStep(10200); + AddAssert("Start vector is same", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var first = body.CurrentCurve.First(); + return first == vector; + }); + addSeekStep(13700); + AddStep("Save end vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.Last(); + }); + addSeekStep(13800); + AddAssert($"End vector {decreased}", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var last = body.CurrentCurve.Last(); + return snaking ? last.X < vector.X && last.Y < vector.Y : last == vector; + }); + } + + private void testDoubleRepeating(bool snaking) + { + var increased = snaking ? "increased" : "is same"; + + setSnaking(snaking); + addSeekStep(18800); + AddStep("Save end vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.Last(); + }); + addSeekStep(18900); + AddAssert($"End vector {increased}", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var last = body.CurrentCurve.Last(); + return snaking ? last.X > vector.X && last.Y > vector.Y : last == vector; + }); + addSeekStep(20100); + AddStep("Save start vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.First(); + }); + addSeekStep(20200); + AddAssert("Start vector is same", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var first = body.CurrentCurve.First(); + return first == vector; + }); + addSeekStep(23700); + AddStep("Save end vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.Last(); + }); + addSeekStep(23800); + AddAssert("End vector is same", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var last = body.CurrentCurve.Last(); + return last == vector; + }); + addSeekStep(27300); + AddStep("Save start vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.First(); + }); + addSeekStep(27400); + AddAssert($"Start vector {increased}", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var first = body.CurrentCurve.First(); + return snaking ? first.X > vector.X && first.Y > vector.Y : first == vector; + }); + } + + private void setSnaking(bool value) + { + var text = value ? "Enable" : "Disable"; + AddStep($"{text} snaking", () => + { + snakingIn.Value = value; + snakingOut.Value = value; + }); + } + + private void addSeekStep(double time) + { + AddStep($"seek to {time}", () => track.Seek(time)); + + AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap + { + HitObjects = new List + { + new Slider + { + StartTime = 3000, + Position = new Vector2(100, 100), + Path = new SliderPath(PathType.PerfectCurve, new[] + { + Vector2.Zero, + new Vector2(300, 200) + }), + }, + new Slider + { + StartTime = 10000, + Position = new Vector2(100, 100), + Path = new SliderPath(PathType.PerfectCurve, new[] + { + Vector2.Zero, + new Vector2(300, 200) + }), + RepeatCount = 1, + }, + + new Slider + { + StartTime = 20000, + Position = new Vector2(100, 100), + Path = new SliderPath(PathType.PerfectCurve, new[] + { + Vector2.Zero, + new Vector2(300, 200) + }), + RepeatCount = 2, + }, + + new HitCircle + { + StartTime = 99999, + } + } + }; + } +} From ce2fa23baf1c55e83ba051033975a606d1b100ba Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 29 Mar 2020 17:43:18 +0300 Subject: [PATCH 067/655] Include a test for miss --- .../TestSceneSliderSnaking.cs | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 3e40713f52..a53e06dc0f 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -32,7 +32,8 @@ namespace osu.Game.Rulesets.Osu.Tests private TrackVirtualManual track; - protected override bool Autoplay => true; + protected override bool Autoplay => autoplay; + private bool autoplay; private readonly Bindable snakingIn = new Bindable(); private readonly Bindable snakingOut = new Bindable(); @@ -57,16 +58,15 @@ namespace osu.Game.Rulesets.Osu.Tests private Vector2 vector; [SetUpSteps] - public override void SetUpSteps() - { - base.SetUpSteps(); - - AddUntilStep("wait for track to start running", () => track.IsRunning); - } + public override void SetUpSteps() { } [Test] public void TestSnaking() { + AddStep("have autoplay", () => autoplay = true); + base.SetUpSteps(); + AddUntilStep("wait for track to start running", () => track.IsRunning); + AddStep("retrieve 1st slider", () => slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.First()); testLinear(true); testLinear(false); @@ -76,9 +76,19 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("retrieve 3rd slider", () => slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.Skip(2).First()); testDoubleRepeating(true); testDoubleRepeating(false); + } - // Test arrow stays in place + [TestCase(true)] + [TestCase(false)] + public void TestArrowStays(bool isHit) + { + var isSame = isHit ? "is same" : "decreased"; + var enable = isHit ? "enable" : "disable"; + + AddStep($"{enable} autoplay", () => autoplay = isHit); setSnaking(true); + base.SetUpSteps(); + addSeekStep(13500); AddStep("retrieve 2nd slider repeat", () => { @@ -87,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.Tests }); AddStep("Save repeat vector", () => vector = repeat.Position); addSeekStep(13700); - AddAssert("Repeat vector is same", () => Precision.AlmostEquals(vector.X, repeat.Position.X, 1) && Precision.AlmostEquals(vector.Y, repeat.Position.Y, 1)); + AddAssert($"Repeat vector {isSame}", () => isHit ? Precision.AlmostEquals(vector.X, repeat.X, 1) && Precision.AlmostEquals(vector.Y, repeat.Y, 1) : repeat.X < vector.X && repeat.Y < vector.Y); } private void testLinear(bool snaking) From b9277165f788361acb43b3c57da49ce605d44155 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Mar 2020 15:11:15 +0900 Subject: [PATCH 068/655] Refactor test to support custom hitobjects --- .../TestSceneNoteLock.cs | 126 +++++++++++++----- 1 file changed, 94 insertions(+), 32 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs index e2b8364f3e..af82a05c4f 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Screens; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -23,8 +24,6 @@ namespace osu.Game.Rulesets.Osu.Tests { public class TestSceneNoteLock : RateAdjustedBeatmapTestScene { - private const double time_first_circle = 1500; - private const double time_second_circle = 1600; private const double early_miss_window = 1000; // time after -1000 to -500 is considered a miss private const double late_miss_window = 500; // time after +500 is considered a miss @@ -37,13 +36,31 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestClickSecondCircleBeforeFirstCircleTime() { - performTest(new List + const double time_first_circle = 1500; + const double time_second_circle = 1600; + + var hitObjects = new List + { + new TestHitCircle + { + StartTime = time_first_circle, + Position = position_first_circle + }, + new TestHitCircle + { + StartTime = time_second_circle, + Position = position_second_circle + } + }; + + performTest(hitObjects, new List { new OsuReplayFrame { Time = time_first_circle - 100, Position = position_second_circle, Actions = { OsuAction.LeftButton } } }); - addJudgementAssert(HitResult.Miss, HitResult.Miss); - addJudgementOffsetAssert(late_miss_window); + addJudgementAssert(hitObjects[0], HitResult.Miss); + addJudgementAssert(hitObjects[1], HitResult.Miss); + addJudgementOffsetAssert(hitObjects[0], late_miss_window); } /// @@ -52,13 +69,31 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestClickSecondCircleAtFirstCircleTime() { - performTest(new List + const double time_first_circle = 1500; + const double time_second_circle = 1600; + + var hitObjects = new List + { + new TestHitCircle + { + StartTime = time_first_circle, + Position = position_first_circle + }, + new TestHitCircle + { + StartTime = time_second_circle, + Position = position_second_circle + } + }; + + performTest(hitObjects, new List { new OsuReplayFrame { Time = time_first_circle, Position = position_second_circle, Actions = { OsuAction.LeftButton } } }); - addJudgementAssert(HitResult.Miss, HitResult.Great); - addJudgementOffsetAssert(0); + addJudgementAssert(hitObjects[0], HitResult.Miss); + addJudgementAssert(hitObjects[1], HitResult.Great); + addJudgementOffsetAssert(hitObjects[0], 0); } /// @@ -67,13 +102,31 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestClickSecondCircleAfterFirstCircleTime() { - performTest(new List + const double time_first_circle = 1500; + const double time_second_circle = 1600; + + var hitObjects = new List + { + new TestHitCircle + { + StartTime = time_first_circle, + Position = position_first_circle + }, + new TestHitCircle + { + StartTime = time_second_circle, + Position = position_second_circle + } + }; + + performTest(hitObjects, new List { new OsuReplayFrame { Time = time_first_circle + 100, Position = position_second_circle, Actions = { OsuAction.LeftButton } } }); - addJudgementAssert(HitResult.Miss, HitResult.Great); - addJudgementOffsetAssert(100); + addJudgementAssert(hitObjects[0], HitResult.Miss); + addJudgementAssert(hitObjects[1], HitResult.Great); + addJudgementOffsetAssert(hitObjects[0], 100); } /// @@ -82,49 +135,58 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestClickSecondCircleBeforeFirstCircleTimeWithFirstCircleJudged() { - performTest(new List + const double time_first_circle = 1500; + const double time_second_circle = 1600; + + var hitObjects = new List + { + new TestHitCircle + { + StartTime = time_first_circle, + Position = position_first_circle + }, + new TestHitCircle + { + StartTime = time_second_circle, + Position = position_second_circle + } + }; + + performTest(hitObjects, new List { new OsuReplayFrame { Time = time_first_circle - 200, Position = position_first_circle, Actions = { OsuAction.LeftButton } }, new OsuReplayFrame { Time = time_first_circle - 100, Position = position_second_circle, Actions = { OsuAction.RightButton } } }); - addJudgementAssert(HitResult.Great, HitResult.Great); + addJudgementAssert(hitObjects[0], HitResult.Great); + addJudgementAssert(hitObjects[1], HitResult.Great); + addJudgementOffsetAssert(hitObjects[0], -200); // time_first_circle - 200 + addJudgementOffsetAssert(hitObjects[0], -200); // time_second_circle - first_circle_time - 100 } - private void addJudgementAssert(HitResult firstCircle, HitResult secondCircle) + private void addJudgementAssert(OsuHitObject hitObject, HitResult result) { - AddAssert($"first circle judgement is {firstCircle}", () => judgementResults.Single(r => r.HitObject.StartTime == time_first_circle).Type == firstCircle); - AddAssert($"second circle judgement is {secondCircle}", () => judgementResults.Single(r => r.HitObject.StartTime == time_second_circle).Type == secondCircle); + AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}", + () => judgementResults.Single(r => r.HitObject == hitObject).Type == result); } - private void addJudgementOffsetAssert(double offset) + private void addJudgementOffsetAssert(OsuHitObject hitObject, double offset) { - AddAssert($"first circle judged at {offset}", () => Precision.AlmostEquals(judgementResults.Single(r => r.HitObject.StartTime == time_first_circle).TimeOffset, offset, 100)); + AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judged at {offset}", + () => Precision.AlmostEquals(judgementResults.Single(r => r.HitObject == hitObject).TimeOffset, offset, 100)); } private ScoreAccessibleReplayPlayer currentPlayer; private List judgementResults; private bool allJudgedFired; - private void performTest(List frames) + private void performTest(List hitObjects, List frames) { AddStep("load player", () => { Beatmap.Value = CreateWorkingBeatmap(new Beatmap { - HitObjects = - { - new TestHitCircle - { - StartTime = time_first_circle, - Position = position_first_circle - }, - new TestHitCircle - { - StartTime = time_second_circle, - Position = position_second_circle - } - }, + HitObjects = hitObjects, BeatmapInfo = { BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 }, From e51097da9e7ee6f6128fd5e9b19e23117a85904b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 30 Mar 2020 09:29:00 +0300 Subject: [PATCH 069/655] Add a legacy skin provider above the test skin --- .../TestSceneHyperDashColouring.cs | 122 +++++++++--------- 1 file changed, 63 insertions(+), 59 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index 9ab8cf9113..fea2939eae 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -1,9 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -28,6 +28,9 @@ namespace osu.Game.Rulesets.Catch.Tests { public class TestSceneHyperDashColouring : OsuTestScene { + [Resolved] + private SkinManager skins { get; set; } + [TestCase(false)] [TestCase(true)] public void TestHyperDashFruitColour(bool legacyFruit) @@ -36,19 +39,21 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("setup fruit", () => { - var fruit = new Fruit { IndexInBeatmap = legacyFruit ? 0 : 1, HyperDashTarget = new Banana() }; + var fruit = new Fruit { HyperDashTarget = new Banana() }; fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - Child = setupSkinHierarchy(() => - drawableFruit = new DrawableFruit(fruit) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(4f), - }, false, false); + Child = setupSkinHierarchy(drawableFruit = new DrawableFruit(fruit) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(4f), + }, false, false, false, legacyFruit); }); - AddAssert("default colour", () => checkFruitHyperDashColour(drawableFruit, Catcher.DefaultHyperDashColour, legacyFruit)); + AddAssert("default colour", () => + legacyFruit + ? checkLegacyFruitHyperDashColour(drawableFruit, Catcher.DefaultHyperDashColour) + : checkFruitHyperDashColour(drawableFruit, Catcher.DefaultHyperDashColour)); } [TestCase(false, true)] @@ -61,19 +66,21 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("setup fruit", () => { - var fruit = new Fruit { IndexInBeatmap = legacyFruit ? 0 : 1, HyperDashTarget = new Banana() }; + var fruit = new Fruit { HyperDashTarget = new Banana() }; fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - Child = setupSkinHierarchy(() => - drawableFruit = new DrawableFruit(fruit) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(4f), - }, customCatcherHyperDashColour, true); + Child = setupSkinHierarchy(drawableFruit = new DrawableFruit(fruit) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(4f), + }, customCatcherHyperDashColour, false, true, legacyFruit); }); - AddAssert("custom colour", () => checkFruitHyperDashColour(drawableFruit, TestLegacySkin.CustomHyperDashFruitColour, legacyFruit)); + AddAssert("custom colour", () => + legacyFruit + ? checkLegacyFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashFruitColour) + : checkFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashFruitColour)); } [TestCase(false)] @@ -84,71 +91,68 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("setup fruit", () => { - var fruit = new Fruit { IndexInBeatmap = legacyFruit ? 0 : 1, HyperDashTarget = new Banana() }; + var fruit = new Fruit { HyperDashTarget = new Banana() }; fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - Child = setupSkinHierarchy(() => + Child = setupSkinHierarchy( drawableFruit = new DrawableFruit(fruit) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Scale = new Vector2(4f), - }, true, false); + }, true, false, false, legacyFruit); }); - AddAssert("catcher custom colour", () => checkFruitHyperDashColour(drawableFruit, TestLegacySkin.CustomHyperDashColour, legacyFruit)); + AddAssert("catcher custom colour", () => + legacyFruit + ? checkLegacyFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashColour) + : checkFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashColour)); } - private Drawable setupSkinHierarchy(Func getChild, bool customHyperDashCatcherColour = false, bool customHyperDashFruitColour = false, bool customHyperDashAfterColour = false) + private Drawable setupSkinHierarchy(Drawable child, bool customCatcherColour = false, bool customAfterColour = false, bool customFruitColour = false, bool legacySkin = true) { - var testSkinProvider = new SkinProvidingContainer(new TestLegacySkin(customHyperDashCatcherColour, customHyperDashFruitColour, customHyperDashAfterColour)); + var testSkinProvider = new SkinProvidingContainer(new TestSkin(customCatcherColour, customAfterColour, customFruitColour)); - var legacySkinTransformer = new SkinProvidingContainer(new CatchLegacySkinTransformer(testSkinProvider)); + if (legacySkin) + { + var legacySkinProvider = new SkinProvidingContainer(skins.GetSkin(DefaultLegacySkin.Info)); + var legacySkinTransformer = new SkinProvidingContainer(new CatchLegacySkinTransformer(testSkinProvider)); - return testSkinProvider - .WithChild(legacySkinTransformer - .WithChild(getChild.Invoke())); + return legacySkinProvider + .WithChild(testSkinProvider + .WithChild(legacySkinTransformer + .WithChild(child))); + } + + return testSkinProvider.WithChild(child); } - private bool checkFruitHyperDashColour(DrawableFruit fruit, Color4 expectedColour, bool isLegacyFruit) => - isLegacyFruit - ? fruit.ChildrenOfType().First().Drawable.ChildrenOfType().Any(c => c.Colour == expectedColour) - : fruit.ChildrenOfType().First().Drawable.ChildrenOfType().Single(c => c.BorderColour == expectedColour).Any(d => d.Colour == expectedColour); + private bool checkFruitHyperDashColour(DrawableFruit fruit, Color4 expectedColour) => + fruit.ChildrenOfType().First().Drawable.ChildrenOfType().Single(c => c.BorderColour == expectedColour).Any(d => d.Colour == expectedColour); - private class TestLegacySkin : ISkin + private bool checkLegacyFruitHyperDashColour(DrawableFruit fruit, Color4 expectedColour) => + fruit.ChildrenOfType().First().Drawable.ChildrenOfType().Any(c => c.Colour == expectedColour); + + private class TestSkin : ISkin { public static Color4 CustomHyperDashColour { get; } = Color4.Goldenrod; public static Color4 CustomHyperDashFruitColour { get; } = Color4.Cyan; public static Color4 CustomHyperDashAfterColour { get; } = Color4.Lime; - private readonly bool customHyperDashCatcherColour; - private readonly bool customHyperDashFruitColour; - private readonly bool customHyperDashAfterColour; + private readonly bool customCatcherColour; + private readonly bool customAfterColour; + private readonly bool customFruitColour; - public TestLegacySkin(bool customHyperDashCatcherColour = false, bool customHyperDashFruitColour = false, bool customHyperDashAfterColour = false) + public TestSkin(bool customCatcherColour = false, bool customAfterColour = false, bool customFruitColour = false) { - this.customHyperDashCatcherColour = customHyperDashCatcherColour; - this.customHyperDashFruitColour = customHyperDashFruitColour; - this.customHyperDashAfterColour = customHyperDashAfterColour; + this.customCatcherColour = customCatcherColour; + this.customAfterColour = customAfterColour; + this.customFruitColour = customFruitColour; } public Drawable GetDrawableComponent(ISkinComponent component) => null; - public Texture GetTexture(string componentName) - { - if (componentName == "fruit-pear") - { - // convince CatchLegacySkinTransformer to use the LegacyFruitPiece for pear fruit. - return new Texture(Texture.WhitePixel.TextureGL) - { - Width = 1, - Height = 1, - ScaleAdjust = 1 / 96f - }; - } - - return null; - } + public Texture GetTexture(string componentName) => null; public SampleChannel GetSample(ISampleInfo sampleInfo) => null; @@ -156,13 +160,13 @@ namespace osu.Game.Rulesets.Catch.Tests { if (lookup is CatchSkinConfiguration config) { - if (config == CatchSkinConfiguration.HyperDash && customHyperDashCatcherColour) + if (config == CatchSkinConfiguration.HyperDash && customCatcherColour) return SkinUtils.As(new Bindable(CustomHyperDashColour)); - if (config == CatchSkinConfiguration.HyperDashFruit && customHyperDashFruitColour) + if (config == CatchSkinConfiguration.HyperDashFruit && customFruitColour) return SkinUtils.As(new Bindable(CustomHyperDashFruitColour)); - if (config == CatchSkinConfiguration.HyperDashAfterImage && customHyperDashAfterColour) + if (config == CatchSkinConfiguration.HyperDashAfterImage && customAfterColour) return SkinUtils.As(new Bindable(CustomHyperDashAfterColour)); } From 16a4525a9cdbe35aecc28579937a0606e919f5de Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 30 Mar 2020 09:33:47 +0300 Subject: [PATCH 070/655] CatchSkinConfiguration -> CatchSkinColour --- .../TestSceneHyperDashColouring.cs | 8 ++++---- osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs | 4 ++-- .../{CatchSkinConfiguration.cs => CatchSkinColour.cs} | 2 +- osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) rename osu.Game.Rulesets.Catch/Skinning/{CatchSkinConfiguration.cs => CatchSkinColour.cs} (94%) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index fea2939eae..ebc3d3bff1 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -158,15 +158,15 @@ namespace osu.Game.Rulesets.Catch.Tests public IBindable GetConfig(TLookup lookup) { - if (lookup is CatchSkinConfiguration config) + if (lookup is CatchSkinColour config) { - if (config == CatchSkinConfiguration.HyperDash && customCatcherColour) + if (config == CatchSkinColour.HyperDash && customCatcherColour) return SkinUtils.As(new Bindable(CustomHyperDashColour)); - if (config == CatchSkinConfiguration.HyperDashFruit && customFruitColour) + if (config == CatchSkinColour.HyperDashFruit && customFruitColour) return SkinUtils.As(new Bindable(CustomHyperDashFruitColour)); - if (config == CatchSkinConfiguration.HyperDashAfterImage && customAfterColour) + if (config == CatchSkinColour.HyperDashAfterImage && customAfterColour) return SkinUtils.As(new Bindable(CustomHyperDashAfterColour)); } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs index c8f7c4912e..16818746b5 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs @@ -64,8 +64,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables }); var hyperDashColour = - skin.GetConfig(CatchSkinConfiguration.HyperDashFruit)?.Value ?? - skin.GetConfig(CatchSkinConfiguration.HyperDash)?.Value ?? + skin.GetConfig(CatchSkinColour.HyperDashFruit)?.Value ?? + skin.GetConfig(CatchSkinColour.HyperDash)?.Value ?? Catcher.DefaultHyperDashColour; if (hitObject.HyperDash) diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs b/osu.Game.Rulesets.Catch/Skinning/CatchSkinColour.cs similarity index 94% rename from osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs rename to osu.Game.Rulesets.Catch/Skinning/CatchSkinColour.cs index aea5beaa6b..2ad8f89739 100644 --- a/osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs +++ b/osu.Game.Rulesets.Catch/Skinning/CatchSkinColour.cs @@ -3,7 +3,7 @@ namespace osu.Game.Rulesets.Catch.Skinning { - public enum CatchSkinConfiguration + public enum CatchSkinColour { /// /// The colour to be used for the catcher while on hyper-dashing state. diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs index 99ecf12fd3..5235058c52 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs @@ -55,8 +55,8 @@ namespace osu.Game.Rulesets.Catch.Skinning if (drawableCatchObject.HitObject.HyperDash) { var hyperDashColour = - skin.GetConfig(CatchSkinConfiguration.HyperDashFruit)?.Value ?? - skin.GetConfig(CatchSkinConfiguration.HyperDash)?.Value ?? + skin.GetConfig(CatchSkinColour.HyperDashFruit)?.Value ?? + skin.GetConfig(CatchSkinColour.HyperDash)?.Value ?? Catcher.DefaultHyperDashColour; var hyperDash = new Sprite From 0d202929921e26afb043ea637bb7c9a722b0f7d6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Mar 2020 16:14:56 +0900 Subject: [PATCH 071/655] Fix ticks/spinners contributing to notelock --- .../Objects/Drawables/DrawableSlider.cs | 2 +- .../Objects/Drawables/DrawableSliderHead.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- .../Objects/SliderHeadCircle.cs | 9 ++++ osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 53 +++++++++++-------- 5 files changed, 42 insertions(+), 26 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 5c7f4a42b3..b017eacf70 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -124,7 +124,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case SliderTailCircle tail: return new DrawableSliderTail(slider, tail); - case HitCircle head: + case SliderHeadCircle head: return new DrawableSliderHead(slider, head) { OnShake = Shake }; case SliderTick tick: diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index c5609b01e0..563282e18f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly Slider slider; - public DrawableSliderHead(Slider slider, HitCircle h) + public DrawableSliderHead(Slider slider, SliderHeadCircle h) : base(h) { this.slider = slider; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index db1f46d8e2..e5d6c20738 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -155,7 +155,7 @@ namespace osu.Game.Rulesets.Osu.Objects break; case SliderEventType.Head: - AddNested(HeadCircle = new SliderCircle + AddNested(HeadCircle = new SliderHeadCircle { StartTime = e.Time, Position = Position, diff --git a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs new file mode 100644 index 0000000000..f6d46aeef5 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs @@ -0,0 +1,9 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Osu.Objects +{ + public class SliderHeadCircle : HitCircle + { + } +} diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index e36d32d01a..97e002edd0 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Skinning; @@ -126,10 +127,7 @@ namespace osu.Game.Rulesets.Osu.UI /// The of the judged . private void missAllEarlier(JudgementResult result) { - // Hitobjects that count as bonus should not cause other hitobjects to get missed. - // E.g. For the sequence slider-head -> circle -> slider-tick, hitting the tick before the circle should not cause the circle to be missed. - // E.g. For the sequence spinner -> circle -> spinner-bonus, hitting the bonus before the circle should not cause the circle to be missed. - if (result.Judgement.IsBonus) + if (!contributesToNoteLock(result.HitObject)) return; // The minimum start time required for hitobjects so that they aren't missed. @@ -140,35 +138,44 @@ namespace osu.Game.Rulesets.Osu.UI if (obj.HitObject.StartTime >= minimumTime) break; - attemptMiss(obj); + performMiss(obj); foreach (var n in obj.NestedHitObjects) { if (n.HitObject.StartTime >= minimumTime) break; - attemptMiss(n); + performMiss(n); } } - - static void attemptMiss(DrawableHitObject obj) - { - if (!(obj is DrawableOsuHitObject osuObject)) - throw new InvalidOperationException($"{obj.GetType()} is not a {nameof(DrawableOsuHitObject)}."); - - // Hitobjects that have already been judged cannot be missed. - if (osuObject.Judged) - return; - - // Hitobjects that count as bonus should not be missed. - // For the sequence slider-head -> slider-tick -> circle, hitting the circle before the tick should not cause the tick to be missed. - if (osuObject.Result.Judgement.IsBonus) - return; - - osuObject.MissForcefully(); - } } + private void performMiss(DrawableHitObject obj) + { + if (!(obj is DrawableOsuHitObject osuObject)) + throw new InvalidOperationException($"{obj.GetType()} is not a {nameof(DrawableOsuHitObject)}."); + + // Hitobjects that have already been judged cannot be missed. + if (osuObject.Judged) + return; + + // Hitobjects that count as bonus should not be missed. + // For the sequence slider-head -> slider-tick -> circle, hitting the circle before the tick should not cause the tick to be missed. + if (!contributesToNoteLock(obj.HitObject)) + return; + + osuObject.MissForcefully(); + } + + /// + /// Whether a hitobject contributes to notelock. + /// Only hit circles and slider start circles contribute to notelock. + /// + /// The hitobject to test. + /// Whether contributes to notelock. + private bool contributesToNoteLock(HitObject hitObject) + => hitObject is HitCircle && !(hitObject is SliderTailCircle); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos); private class ApproachCircleProxyContainer : LifetimeManagementContainer From e074c3e5e99f69349471cf21e8a72323f390417b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Mar 2020 16:15:07 +0900 Subject: [PATCH 072/655] Add additional tests --- .../TestSceneNoteLock.cs | 182 ++++++++++++++++-- 1 file changed, 166 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs index af82a05c4f..a33fb54ff6 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -11,6 +12,8 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Replays; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Replays; @@ -27,9 +30,6 @@ namespace osu.Game.Rulesets.Osu.Tests private const double early_miss_window = 1000; // time after -1000 to -500 is considered a miss private const double late_miss_window = 500; // time after +500 is considered a miss - private static readonly Vector2 position_first_circle = Vector2.Zero; - private static readonly Vector2 position_second_circle = new Vector2(80); - /// /// Tests clicking the second circle before the first hitobject's start time, while the first hitobject HAS NOT been judged. /// @@ -38,24 +38,26 @@ namespace osu.Game.Rulesets.Osu.Tests { const double time_first_circle = 1500; const double time_second_circle = 1600; + Vector2 positionFirstCircle = Vector2.Zero; + Vector2 positionSecondCircle = new Vector2(80); var hitObjects = new List { new TestHitCircle { StartTime = time_first_circle, - Position = position_first_circle + Position = positionFirstCircle }, new TestHitCircle { StartTime = time_second_circle, - Position = position_second_circle + Position = positionSecondCircle } }; performTest(hitObjects, new List { - new OsuReplayFrame { Time = time_first_circle - 100, Position = position_second_circle, Actions = { OsuAction.LeftButton } } + new OsuReplayFrame { Time = time_first_circle - 100, Position = positionSecondCircle, Actions = { OsuAction.LeftButton } } }); addJudgementAssert(hitObjects[0], HitResult.Miss); @@ -71,24 +73,26 @@ namespace osu.Game.Rulesets.Osu.Tests { const double time_first_circle = 1500; const double time_second_circle = 1600; + Vector2 positionFirstCircle = Vector2.Zero; + Vector2 positionSecondCircle = new Vector2(80); var hitObjects = new List { new TestHitCircle { StartTime = time_first_circle, - Position = position_first_circle + Position = positionFirstCircle }, new TestHitCircle { StartTime = time_second_circle, - Position = position_second_circle + Position = positionSecondCircle } }; performTest(hitObjects, new List { - new OsuReplayFrame { Time = time_first_circle, Position = position_second_circle, Actions = { OsuAction.LeftButton } } + new OsuReplayFrame { Time = time_first_circle, Position = positionSecondCircle, Actions = { OsuAction.LeftButton } } }); addJudgementAssert(hitObjects[0], HitResult.Miss); @@ -104,24 +108,26 @@ namespace osu.Game.Rulesets.Osu.Tests { const double time_first_circle = 1500; const double time_second_circle = 1600; + Vector2 positionFirstCircle = Vector2.Zero; + Vector2 positionSecondCircle = new Vector2(80); var hitObjects = new List { new TestHitCircle { StartTime = time_first_circle, - Position = position_first_circle + Position = positionFirstCircle }, new TestHitCircle { StartTime = time_second_circle, - Position = position_second_circle + Position = positionSecondCircle } }; performTest(hitObjects, new List { - new OsuReplayFrame { Time = time_first_circle + 100, Position = position_second_circle, Actions = { OsuAction.LeftButton } } + new OsuReplayFrame { Time = time_first_circle + 100, Position = positionSecondCircle, Actions = { OsuAction.LeftButton } } }); addJudgementAssert(hitObjects[0], HitResult.Miss); @@ -137,25 +143,27 @@ namespace osu.Game.Rulesets.Osu.Tests { const double time_first_circle = 1500; const double time_second_circle = 1600; + Vector2 positionFirstCircle = Vector2.Zero; + Vector2 positionSecondCircle = new Vector2(80); var hitObjects = new List { new TestHitCircle { StartTime = time_first_circle, - Position = position_first_circle + Position = positionFirstCircle }, new TestHitCircle { StartTime = time_second_circle, - Position = position_second_circle + Position = positionSecondCircle } }; performTest(hitObjects, new List { - new OsuReplayFrame { Time = time_first_circle - 200, Position = position_first_circle, Actions = { OsuAction.LeftButton } }, - new OsuReplayFrame { Time = time_first_circle - 100, Position = position_second_circle, Actions = { OsuAction.RightButton } } + new OsuReplayFrame { Time = time_first_circle - 200, Position = positionFirstCircle, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_first_circle - 100, Position = positionSecondCircle, Actions = { OsuAction.RightButton } } }); addJudgementAssert(hitObjects[0], HitResult.Great); @@ -164,12 +172,133 @@ namespace osu.Game.Rulesets.Osu.Tests addJudgementOffsetAssert(hitObjects[0], -200); // time_second_circle - first_circle_time - 100 } + [Test] + public void TestMissSliderHeadAndHitAllSliderTicks() + { + const double time_slider = 1500; + const double time_circle = 1510; + Vector2 positionCircle = Vector2.Zero; + Vector2 positionSlider = new Vector2(80); + + var hitObjects = new List + { + new TestHitCircle + { + StartTime = time_circle, + Position = positionCircle + }, + new TestSlider + { + StartTime = time_slider, + Position = positionSlider, + Path = new SliderPath(PathType.Linear, new[] + { + Vector2.Zero, + new Vector2(25, 0), + }) + } + }; + + performTest(hitObjects, new List + { + new OsuReplayFrame { Time = time_slider, Position = positionCircle, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_slider + 10, Position = positionSlider, Actions = { OsuAction.RightButton } } + }); + + addJudgementAssert(hitObjects[0], HitResult.Great); + addJudgementAssert(hitObjects[1], HitResult.Great); + addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.Miss); + addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.Great); + } + + [Test] + public void TestHitSliderTicksBeforeCircle() + { + const double time_slider = 1500; + const double time_circle = 1510; + Vector2 positionCircle = Vector2.Zero; + Vector2 positionSlider = new Vector2(80); + + var hitObjects = new List + { + new TestHitCircle + { + StartTime = time_circle, + Position = positionCircle + }, + new TestSlider + { + StartTime = time_slider, + Position = positionSlider, + Path = new SliderPath(PathType.Linear, new[] + { + Vector2.Zero, + new Vector2(25, 0), + }) + } + }; + + performTest(hitObjects, new List + { + new OsuReplayFrame { Time = time_slider, Position = positionSlider, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_circle + late_miss_window - 100, Position = positionCircle, Actions = { OsuAction.RightButton } }, + new OsuReplayFrame { Time = time_circle + late_miss_window - 90, Position = positionSlider, Actions = { OsuAction.LeftButton } }, + }); + + addJudgementAssert(hitObjects[0], HitResult.Great); + addJudgementAssert(hitObjects[1], HitResult.Great); + addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.Great); + addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.Great); + } + + [Test] + public void TestHitCircleBeforeSpinner() + { + const double time_spinner = 1500; + const double time_circle = 1510; + Vector2 positionCircle = Vector2.Zero; + + var hitObjects = new List + { + new TestSpinner + { + StartTime = time_spinner, + Position = new Vector2(256, 192), + EndTime = time_spinner + 1000, + }, + new TestHitCircle + { + StartTime = time_circle, + Position = positionCircle + }, + }; + + performTest(hitObjects, new List + { + new OsuReplayFrame { Time = time_spinner, Position = positionCircle, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_spinner + 10, Position = new Vector2(236, 192), Actions = { OsuAction.RightButton } }, + new OsuReplayFrame { Time = time_spinner + 20, Position = new Vector2(256, 172), Actions = { OsuAction.RightButton } }, + new OsuReplayFrame { Time = time_spinner + 30, Position = new Vector2(276, 192), Actions = { OsuAction.RightButton } }, + new OsuReplayFrame { Time = time_spinner + 40, Position = new Vector2(256, 212), Actions = { OsuAction.RightButton } }, + new OsuReplayFrame { Time = time_spinner + 50, Position = new Vector2(236, 192), Actions = { OsuAction.RightButton } }, + }); + + addJudgementAssert(hitObjects[0], HitResult.Great); + addJudgementAssert(hitObjects[1], HitResult.Great); + } + private void addJudgementAssert(OsuHitObject hitObject, HitResult result) { AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}", () => judgementResults.Single(r => r.HitObject == hitObject).Type == result); } + private void addJudgementAssert(string name, Func hitObject, HitResult result) + { + AddAssert($"{name} judgement is {result}", + () => judgementResults.Single(r => r.HitObject == hitObject()).Type == result); + } + private void addJudgementOffsetAssert(OsuHitObject hitObject, double offset) { AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judged at {offset}", @@ -225,6 +354,27 @@ namespace osu.Game.Rulesets.Osu.Tests protected override HitWindows CreateHitWindows() => new TestHitWindows(); } + private class TestSlider : Slider + { + public TestSlider() + { + DefaultsApplied += () => + { + HeadCircle.HitWindows = new TestHitWindows(); + TailCircle.HitWindows = new TestHitWindows(); + }; + } + } + + private class TestSpinner : Spinner + { + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); + SpinsRequired = 1; + } + } + private class TestHitWindows : HitWindows { private static readonly DifficultyRange[] ranges = From 744f6c3ca7be99511c5732c0fa4c8688a3acbd5e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Mar 2020 16:33:46 +0900 Subject: [PATCH 073/655] Rename method + adjust comments --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 97e002edd0..994b3d9718 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Osu.UI /// The of the judged . private void missAllEarlier(JudgementResult result) { - if (!contributesToNoteLock(result.HitObject)) + if (!causesNoteLockMisses(result.HitObject)) return; // The minimum start time required for hitobjects so that they aren't missed. @@ -159,21 +159,18 @@ namespace osu.Game.Rulesets.Osu.UI if (osuObject.Judged) return; - // Hitobjects that count as bonus should not be missed. - // For the sequence slider-head -> slider-tick -> circle, hitting the circle before the tick should not cause the tick to be missed. - if (!contributesToNoteLock(obj.HitObject)) + if (!causesNoteLockMisses(obj.HitObject)) return; osuObject.MissForcefully(); } /// - /// Whether a hitobject contributes to notelock. - /// Only hit circles and slider start circles contribute to notelock. + /// Whether a can be missed and causes other hitobjects to be missed during notelock. /// - /// The hitobject to test. - /// Whether contributes to notelock. - private bool contributesToNoteLock(HitObject hitObject) + /// The to test. + /// Whether contributes to notelock misses. + private bool causesNoteLockMisses(HitObject hitObject) => hitObject is HitCircle && !(hitObject is SliderTailCircle); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos); From 796976db3c046f967e0403c9d15e4d305cbe3435 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Mar 2020 17:00:53 +0900 Subject: [PATCH 074/655] Completely ignore spinners from note lock --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 994b3d9718..db8a47e4a2 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -90,7 +90,14 @@ namespace osu.Game.Rulesets.Osu.UI private bool checkHittable(DrawableOsuHitObject osuHitObject) { - var lastObject = HitObjectContainer.AliveObjects.GetPrevious(osuHitObject); + DrawableHitObject lastObject = osuHitObject; + + // Get the last hitobject that contributes to note lock + while ((lastObject = HitObjectContainer.AliveObjects.GetPrevious(lastObject)) != null) + { + if (contributesToNoteLock(lastObject.HitObject)) + break; + } // If there is no previous object alive, allow the hit. if (lastObject == null) @@ -166,10 +173,19 @@ namespace osu.Game.Rulesets.Osu.UI } /// - /// Whether a can be missed and causes other hitobjects to be missed during notelock. + /// Whether a is contributes to note lock. + /// Future contributing s will not be hittable until the start time of the last contributing is reached. /// /// The to test. - /// Whether contributes to notelock misses. + /// Whether causes note lock. + private bool contributesToNoteLock(HitObject hitObject) + => hitObject is HitCircle || hitObject is Slider; + + /// + /// Whether a can be missed and causes other s to be missed when hit out-of-order during note lock. + /// + /// The to test. + /// Whether contributes to note lock misses. private bool causesNoteLockMisses(HitObject hitObject) => hitObject is HitCircle && !(hitObject is SliderTailCircle); From 1ff60b73d70deb25d9133f42b17b813711759bbc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Mar 2020 17:01:29 +0900 Subject: [PATCH 075/655] Refactor tests a bit --- .../TestSceneNoteLock.cs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs index a33fb54ff6..2c69540951 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Tests private const double late_miss_window = 500; // time after +500 is considered a miss /// - /// Tests clicking the second circle before the first hitobject's start time, while the first hitobject HAS NOT been judged. + /// Tests clicking a future circle before the first circle's start time, while the first circle HAS NOT been judged. /// [Test] public void TestClickSecondCircleBeforeFirstCircleTime() @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Tests } /// - /// Tests clicking the second circle at the first hitobject's start time, while the first hitobject HAS NOT been judged. + /// Tests clicking a future circle at the first circle's start time, while the first circle HAS NOT been judged. /// [Test] public void TestClickSecondCircleAtFirstCircleTime() @@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Tests } /// - /// Tests clicking the second circle after the first hitobject's start time, while the first hitobject HAS NOT been judged. + /// Tests clicking a future circle after the first circle's start time, while the first circle HAS NOT been judged. /// [Test] public void TestClickSecondCircleAfterFirstCircleTime() @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Tests } /// - /// Tests clicking the second circle before the first hitobject's start time, while the first hitobject HAS been judged. + /// Tests clicking a future circle before the first circle's start time, while the first circle HAS been judged. /// [Test] public void TestClickSecondCircleBeforeFirstCircleTimeWithFirstCircleJudged() @@ -172,6 +172,9 @@ namespace osu.Game.Rulesets.Osu.Tests addJudgementOffsetAssert(hitObjects[0], -200); // time_second_circle - first_circle_time - 100 } + /// + /// Tests clicking a future circle after a slider's start time, but hitting all slider ticks. + /// [Test] public void TestMissSliderHeadAndHitAllSliderTicks() { @@ -211,6 +214,9 @@ namespace osu.Game.Rulesets.Osu.Tests addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.Great); } + /// + /// Tests clicking hitting future slider ticks before a circle. + /// [Test] public void TestHitSliderTicksBeforeCircle() { @@ -251,11 +257,14 @@ namespace osu.Game.Rulesets.Osu.Tests addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.Great); } + /// + /// Tests clicking a future circle before a spinner. + /// [Test] public void TestHitCircleBeforeSpinner() { const double time_spinner = 1500; - const double time_circle = 1510; + const double time_circle = 1800; Vector2 positionCircle = Vector2.Zero; var hitObjects = new List @@ -275,7 +284,7 @@ namespace osu.Game.Rulesets.Osu.Tests performTest(hitObjects, new List { - new OsuReplayFrame { Time = time_spinner, Position = positionCircle, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_spinner - 100, Position = positionCircle, Actions = { OsuAction.LeftButton } }, new OsuReplayFrame { Time = time_spinner + 10, Position = new Vector2(236, 192), Actions = { OsuAction.RightButton } }, new OsuReplayFrame { Time = time_spinner + 20, Position = new Vector2(256, 172), Actions = { OsuAction.RightButton } }, new OsuReplayFrame { Time = time_spinner + 30, Position = new Vector2(276, 192), Actions = { OsuAction.RightButton } }, From 35647d59a6f46e9f9284995edcd43af377954158 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Mar 2020 19:09:05 +0900 Subject: [PATCH 076/655] Add failing test --- .../TestSceneOverlayScrollContainer.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs index 1fc85c3c04..684436459f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs @@ -74,6 +74,20 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("scrolled back to start", () => Precision.AlmostEquals(scroll.Current, 0, 0.1f)); } + [Test] + public void TestClick() + { + AddStep("scroll to end", () => scroll.ScrollToEnd(false)); + + AddStep("click button", () => + { + InputManager.MoveMouseTo(scroll.Button); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("scrolled back to start", () => Precision.AlmostEquals(scroll.Current, 0, 0.1f)); + } + [Test] public void TestMultipleClicks() { From 179bd1ce7ebe2384fc819bdac20865f5176fd7d7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 30 Mar 2020 13:38:04 +0300 Subject: [PATCH 077/655] Fix failing test --- osu.Game/Overlays/OverlayScrollContainer.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/OverlayScrollContainer.cs b/osu.Game/Overlays/OverlayScrollContainer.cs index 1a875ded95..a9524b9d32 100644 --- a/osu.Game/Overlays/OverlayScrollContainer.cs +++ b/osu.Game/Overlays/OverlayScrollContainer.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -83,7 +84,10 @@ namespace osu.Game.Overlays public ScrollToTopButton() { Size = new Vector2(50); - Child = button = new Button(); + Child = button = new Button + { + AreaState = { BindTarget = State } + }; } protected override bool OnMouseDown(MouseDownEvent e) => true; @@ -94,7 +98,9 @@ namespace osu.Game.Overlays private class Button : OsuHoverContainer { - public override bool PropagatePositionalInputSubTree => Alpha == 1; + public readonly Bindable AreaState = new Bindable(); + + public override bool HandlePositionalInput => AreaState.Value == Visibility.Visible; protected override IEnumerable EffectTargets => new[] { background }; From 3cae0cedeea80e1dc6206f26e5526a5b7e22662a Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 30 Mar 2020 12:59:39 +0200 Subject: [PATCH 078/655] Add a game setting to disable the layer --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ .../Settings/Sections/Gameplay/GeneralSettings.cs | 6 ++++++ osu.Game/Screens/Play/HUD/FailingLayer.cs | 8 +++++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 41f6747b74..6fed5ea5a2 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -87,6 +87,7 @@ namespace osu.Game.Configuration Set(OsuSetting.ShowInterface, true); Set(OsuSetting.ShowProgressGraph, true); Set(OsuSetting.ShowHealthDisplayWhenCantFail, true); + Set(OsuSetting.FadePlayfieldWhenHealthLow, true); Set(OsuSetting.KeyOverlay, false); Set(OsuSetting.ScoreMeter, ScoreMeterType.HitErrorBoth); @@ -181,6 +182,7 @@ namespace osu.Game.Configuration ShowInterface, ShowProgressGraph, ShowHealthDisplayWhenCantFail, + FadePlayfieldWhenHealthLow, MouseDisableButtons, MouseDisableWheel, AudioOffset, diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 2d2cd42213..4b75910454 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -53,6 +53,12 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay Keywords = new[] { "hp", "bar" } }, new SettingsCheckbox + { + LabelText = "Fade playfield to red when health is low", + Bindable = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow), + Keywords = new[] { "hp", "low", "playfield", "red" } + }, + new SettingsCheckbox { LabelText = "Always show key overlay", Bindable = config.GetBindable(OsuSetting.KeyOverlay) diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index 97d2458674..761178b93d 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -3,9 +3,11 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Utils; +using osu.Game.Configuration; using osu.Game.Graphics; namespace osu.Game.Screens.Play.HUD @@ -21,6 +23,8 @@ namespace osu.Game.Screens.Play.HUD private readonly Box box; + private Bindable enabled; + /// /// The threshold under which the current player life should be considered low and the layer should start fading in. /// @@ -37,9 +41,11 @@ namespace osu.Game.Screens.Play.HUD } [BackgroundDependencyLoader] - private void load(OsuColour color) + private void load(OsuColour color, OsuConfigManager config) { box.Colour = color.Red; + enabled = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow); + enabled.BindValueChanged(e => this.FadeTo(e.NewValue ? 1 : 0, fade_time, Easing.OutQuint), true); } protected override void Update() From e26fbd5ed87681d8577fc5d9dced39cfd199b9cf Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 31 Mar 2020 13:45:59 +0300 Subject: [PATCH 079/655] Remove overcomplicated stuff --- .../TestSceneOverlayScrollContainer.cs | 6 +- osu.Game/Overlays/OverlayScrollContainer.cs | 159 ++++++++---------- 2 files changed, 75 insertions(+), 90 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs index 684436459f..0eccc907a1 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs @@ -55,13 +55,13 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestButtonVisibility() { - AddAssert("button is hidden", () => scroll.Button.State.Value == Visibility.Hidden); + AddAssert("button is hidden", () => scroll.Button.Current.Value == Visibility.Hidden); AddStep("scroll to end", () => scroll.ScrollToEnd(false)); - AddAssert("button is visible", () => scroll.Button.State.Value == Visibility.Visible); + AddAssert("button is visible", () => scroll.Button.Current.Value == Visibility.Visible); AddStep("scroll to start", () => scroll.ScrollToStart(false)); - AddAssert("button is hidden", () => scroll.Button.State.Value == Visibility.Hidden); + AddAssert("button is hidden", () => scroll.Button.Current.Value == Visibility.Hidden); } [Test] diff --git a/osu.Game/Overlays/OverlayScrollContainer.cs b/osu.Game/Overlays/OverlayScrollContainer.cs index a9524b9d32..f96d9e3a31 100644 --- a/osu.Game/Overlays/OverlayScrollContainer.cs +++ b/osu.Game/Overlays/OverlayScrollContainer.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -11,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osuTK; @@ -43,7 +43,7 @@ namespace osu.Game.Overlays { ScrollToStart(); currentTarget = Target; - Button.State.Value = Visibility.Hidden; + Button.Current.Value = Visibility.Hidden; } }); } @@ -54,7 +54,7 @@ namespace osu.Game.Overlays if (ScrollContent.DrawHeight + button_scroll_position < DrawHeight) { - Button.State.Value = Visibility.Hidden; + Button.Current.Value = Visibility.Hidden; return; } @@ -62,113 +62,98 @@ namespace osu.Game.Overlays return; currentTarget = Target; - Button.State.Value = Current > button_scroll_position ? Visibility.Visible : Visibility.Hidden; + Button.Current.Value = Current > button_scroll_position ? Visibility.Visible : Visibility.Hidden; } - public class ScrollToTopButton : VisibilityContainer + public class ScrollToTopButton : OsuHoverContainer, IHasCurrentValue { private const int fade_duration = 500; - public Action Action + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current { - get => button.Action; - set => button.Action = value; + get => current.Current; + set => current.Current = value; } - public override bool PropagatePositionalInputSubTree => true; + protected override IEnumerable EffectTargets => new[] { background }; - protected override bool StartHidden => true; + private Color4 flashColour; - private readonly Button button; + private readonly Container content; + private readonly Box background; public ScrollToTopButton() { Size = new Vector2(50); - Child = button = new Button + Alpha = 0; + Add(content = new CircularContainer { - AreaState = { BindTarget = State } - }; + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0f, 1f), + Radius = 3f, + Colour = Color4.Black.Opacity(0.25f), + }, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(15), + Icon = FontAwesome.Solid.ChevronUp + } + } + }); + + TooltipText = "Scroll to top"; } - protected override bool OnMouseDown(MouseDownEvent e) => true; - - protected override void PopIn() => button.FadeIn(fade_duration, Easing.OutQuint); - - protected override void PopOut() => button.FadeOut(fade_duration, Easing.OutQuint); - - private class Button : OsuHoverContainer + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) { - public readonly Bindable AreaState = new Bindable(); + IdleColour = colourProvider.Background6; + HoverColour = colourProvider.Background5; + flashColour = colourProvider.Light1; + } - public override bool HandlePositionalInput => AreaState.Value == Visibility.Visible; - - protected override IEnumerable EffectTargets => new[] { background }; - - private Color4 flashColour; - - private readonly Container content; - private readonly Box background; - - public Button() + protected override void LoadComplete() + { + base.LoadComplete(); + Current.BindValueChanged(visibility => { - RelativeSizeAxes = Axes.Both; - Add(content = new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Masking = true, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Offset = new Vector2(0f, 1f), - Radius = 3f, - Colour = Color4.Black.Opacity(0.25f), - }, - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both - }, - new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(15), - Icon = FontAwesome.Solid.ChevronUp - } - } - }); + Enabled.Value = visibility.NewValue == Visibility.Visible; + this.FadeTo(visibility.NewValue == Visibility.Visible ? 1 : 0, fade_duration, Easing.OutQuint); + }, true); + } - TooltipText = "Scroll to top"; - } + protected override bool OnClick(ClickEvent e) + { + background.FlashColour(flashColour, 800, Easing.OutQuint); + return base.OnClick(e); + } - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - IdleColour = colourProvider.Background6; - HoverColour = colourProvider.Background5; - flashColour = colourProvider.Light1; - } + protected override bool OnMouseDown(MouseDownEvent e) + { + content.ScaleTo(0.75f, 2000, Easing.OutQuint); + return true; + } - protected override bool OnClick(ClickEvent e) - { - background.FlashColour(flashColour, 800, Easing.OutQuint); - return base.OnClick(e); - } - - protected override bool OnMouseDown(MouseDownEvent e) - { - content.ScaleTo(0.75f, 2000, Easing.OutQuint); - return true; - } - - protected override void OnMouseUp(MouseUpEvent e) - { - content.ScaleTo(1, 1000, Easing.OutElastic); - base.OnMouseUp(e); - } + protected override void OnMouseUp(MouseUpEvent e) + { + content.ScaleTo(1, 1000, Easing.OutElastic); + base.OnMouseUp(e); } } } From 1562612f41681da4ead59c0d220068f933ef5faf Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 1 Apr 2020 15:12:31 +0200 Subject: [PATCH 080/655] Update visual tests and remove unessecary XMLDoc tag --- .../Visual/Gameplay/TestSceneFailingLayer.cs | 30 ++++++++++++++++--- osu.Game/Screens/Play/HUD/HealthDisplay.cs | 1 - 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs index 3016890ade..97fe0ac769 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs @@ -1,7 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; +using osu.Game.Configuration; using osu.Game.Screens.Play.HUD; namespace osu.Game.Tests.Visual.Gameplay @@ -10,22 +15,39 @@ namespace osu.Game.Tests.Visual.Gameplay { private readonly FailingLayer layer; + [Resolved] + private OsuConfigManager config { get; set; } + public TestSceneFailingLayer() { Child = layer = new FailingLayer(); } + [Test] + public void TestLayerConfig() + { + AddStep("enable layer", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true)); + AddWaitStep("wait for transition to finish", 5); + AddAssert("layer is enabled", () => layer.IsPresent); + + AddStep("disable layer", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false)); + AddWaitStep("wait for transition to finish", 5); + AddAssert("layer is disabled", () => !layer.IsPresent); + AddStep("restore layer enabling", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true)); + } + [Test] public void TestLayerFading() { - AddSliderStep("current health", 0.0, 1.0, 1.0, val => - { - layer.Current.Value = val; - }); + AddSliderStep("current health", 0.0, 1.0, 1.0, val => layer.Current.Value = val); + var box = layer.ChildrenOfType().First(); AddStep("set health to 0.10", () => layer.Current.Value = 0.10); AddWaitStep("wait for fade to finish", 5); + AddAssert("layer fade is visible", () => box.IsPresent); AddStep("set health to 1", () => layer.Current.Value = 1f); + AddWaitStep("wait for fade to finish", 10); + AddAssert("layer fade is invisible", () => !box.IsPresent); } } } diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index 4ea08626ad..01cb64a88c 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -23,7 +23,6 @@ namespace osu.Game.Screens.Play.HUD /// /// Bind the tracked fields of to this health display. /// - /// public void BindHealthProcessor(HealthProcessor processor) { Current.BindTo(processor.Health); From 7e82f5740b0668e1f21321cd257be9928026ad54 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 3 Apr 2020 19:35:50 +0300 Subject: [PATCH 081/655] Add a skin extension for simplifying falling back on hyper-dash colours --- .../Objects/Drawables/FruitPiece.cs | 3 +-- .../Skinning/CatchSkinExtensions.cs | 16 ++++++++++++++++ .../Skinning/LegacyFruitPiece.cs | 7 +------ 3 files changed, 18 insertions(+), 8 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/Skinning/CatchSkinExtensions.cs diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs index 16818746b5..2437958916 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs @@ -64,8 +64,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables }); var hyperDashColour = - skin.GetConfig(CatchSkinColour.HyperDashFruit)?.Value ?? - skin.GetConfig(CatchSkinColour.HyperDash)?.Value ?? + skin.GetHyperDashFruitColour()?.Value ?? Catcher.DefaultHyperDashColour; if (hitObject.HyperDash) diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchSkinExtensions.cs b/osu.Game.Rulesets.Catch/Skinning/CatchSkinExtensions.cs new file mode 100644 index 0000000000..8fc0831918 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Skinning/CatchSkinExtensions.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Game.Skinning; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Catch.Skinning +{ + internal static class CatchSkinExtensions + { + public static IBindable GetHyperDashFruitColour(this ISkin skin) + => skin.GetConfig(CatchSkinColour.HyperDashFruit) ?? + skin.GetConfig(CatchSkinColour.HyperDash); + } +} diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs index 5235058c52..d8489399d2 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs @@ -54,15 +54,10 @@ namespace osu.Game.Rulesets.Catch.Skinning if (drawableCatchObject.HitObject.HyperDash) { - var hyperDashColour = - skin.GetConfig(CatchSkinColour.HyperDashFruit)?.Value ?? - skin.GetConfig(CatchSkinColour.HyperDash)?.Value ?? - Catcher.DefaultHyperDashColour; - var hyperDash = new Sprite { Texture = skin.GetTexture(lookupName), - Colour = hyperDashColour, + Colour = skin.GetHyperDashFruitColour()?.Value ?? Catcher.DefaultHyperDashColour, Anchor = Anchor.Centre, Origin = Anchor.Centre, Blending = BlendingParameters.Additive, From 0340b6db51f88120511ac243cc0872bc8527eb32 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 3 Apr 2020 19:50:32 +0300 Subject: [PATCH 082/655] Describe step names more --- .../TestSceneHyperDashColouring.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index ebc3d3bff1..066b399f13 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Catch.Tests { DrawableFruit drawableFruit = null; - AddStep("setup fruit", () => + AddStep("setup hyper-dash fruit", () => { var fruit = new Fruit { HyperDashTarget = new Banana() }; fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.Tests }, false, false, false, legacyFruit); }); - AddAssert("default colour", () => + AddAssert("hyper-dash fruit has default colour", () => legacyFruit ? checkLegacyFruitHyperDashColour(drawableFruit, Catcher.DefaultHyperDashColour) : checkFruitHyperDashColour(drawableFruit, Catcher.DefaultHyperDashColour)); @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Catch.Tests { DrawableFruit drawableFruit = null; - AddStep("setup fruit", () => + AddStep("setup hyper-dash fruit", () => { var fruit = new Fruit { HyperDashTarget = new Banana() }; fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Catch.Tests }, customCatcherHyperDashColour, false, true, legacyFruit); }); - AddAssert("custom colour", () => + AddAssert("hyper-dash fruit use fruit colour from skin", () => legacyFruit ? checkLegacyFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashFruitColour) : checkFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashFruitColour)); @@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Catch.Tests { DrawableFruit drawableFruit = null; - AddStep("setup fruit", () => + AddStep("setup hyper-dash fruit", () => { var fruit = new Fruit { HyperDashTarget = new Banana() }; fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Catch.Tests }, true, false, false, legacyFruit); }); - AddAssert("catcher custom colour", () => + AddAssert("hyper-dash fruit colour falls back to catcher colour from skin", () => legacyFruit ? checkLegacyFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashColour) : checkFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashColour)); From dd684b68d9cef47cc6e5a61a730343536428dc3b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 3 Apr 2020 19:53:38 +0300 Subject: [PATCH 083/655] Make parameters required --- osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index 066b399f13..2009099a61 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -109,7 +109,7 @@ namespace osu.Game.Rulesets.Catch.Tests : checkFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashColour)); } - private Drawable setupSkinHierarchy(Drawable child, bool customCatcherColour = false, bool customAfterColour = false, bool customFruitColour = false, bool legacySkin = true) + private Drawable setupSkinHierarchy(Drawable child, bool customCatcherColour, bool customAfterColour, bool customFruitColour, bool legacySkin = true) { var testSkinProvider = new SkinProvidingContainer(new TestSkin(customCatcherColour, customAfterColour, customFruitColour)); @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Catch.Tests private readonly bool customAfterColour; private readonly bool customFruitColour; - public TestSkin(bool customCatcherColour = false, bool customAfterColour = false, bool customFruitColour = false) + public TestSkin(bool customCatcherColour, bool customAfterColour, bool customFruitColour) { this.customCatcherColour = customCatcherColour; this.customAfterColour = customAfterColour; From 493b6540116687b1d031963d30a30c5b7b90f06a Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 3 Apr 2020 11:30:02 -0700 Subject: [PATCH 084/655] Remove horizontal margin from mod display Can skew center alignment on fill flow containers. Fixes affected areas. Vector2(5, 0) is similar to MarginPadding { Left = 10 }. --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 2 +- osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs | 2 +- osu.Game/Screens/Play/HUD/ModDisplay.cs | 1 - osu.Game/Screens/Play/HUDOverlay.cs | 2 +- osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs | 1 + osu.Game/Screens/Select/FooterButtonMods.cs | 1 - 6 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 03a19b6690..2294cd6966 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.UserInterface Anchor = Anchor.TopRight, Origin = Anchor.TopRight, AutoSizeAxes = Axes.Both, - Position = new Vector2(0, 25), + Position = new Vector2(-5, 25), Current = { BindTarget = modSelect.SelectedMods } } }; diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs index 6cd1aa912f..ed3f9af8e2 100644 --- a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs @@ -161,7 +161,7 @@ namespace osu.Game.Screens.Multi { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), + Spacing = new Vector2(15, 0), Children = new Drawable[] { authorText = new LinkFlowContainer { AutoSizeAxes = Axes.Both }, diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 336b03544f..cd15886c0b 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -56,7 +56,6 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.TopCentre, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Margin = new MarginPadding { Left = 10, Right = 10 }, }, unrankedText = new OsuSpriteText { diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index a5f8051557..e06f6d19c2 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -285,7 +285,7 @@ namespace osu.Game.Screens.Play Anchor = Anchor.TopRight, Origin = Anchor.TopRight, AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = 20, Right = 10 }, + Margin = new MarginPadding { Top = 20, Right = 20 }, }; protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset?.FirstAvailableHitWindows); diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index df7eed9a02..8ef0920d19 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -124,6 +124,7 @@ namespace osu.Game.Screens.Ranking.Expanded Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, AutoSizeAxes = Axes.Both, + Spacing = new Vector2(5, 0), Children = new Drawable[] { new StarRatingDisplay(beatmap) diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 2411cf26f9..b18301c082 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -92,7 +92,6 @@ namespace osu.Game.Screens.Select public FooterModDisplay() { ExpansionMode = ExpansionMode.AlwaysContracted; - IconsContainer.Margin = new MarginPadding(); } } } From 88cc552534043ec8c715a45635fe7bf581a9ba11 Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 3 Apr 2020 11:30:22 -0700 Subject: [PATCH 085/655] Fix results star rating display not being centered when no mods are present Needed or the spacing will apply to the fill flow container, causing alignment issues. --- .../Expanded/ExpandedPanelMiddleContent.cs | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 8ef0920d19..b058cc142b 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -30,6 +30,9 @@ namespace osu.Game.Screens.Ranking.Expanded private readonly ScoreInfo score; private readonly List statisticDisplays = new List(); + + private FillFlowContainer starAndModDisplay; + private RollingCounter scoreCounter; /// @@ -119,7 +122,7 @@ namespace osu.Game.Screens.Ranking.Expanded Alpha = 0, AlwaysPresent = true }, - new FillFlowContainer + starAndModDisplay = new FillFlowContainer { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -132,15 +135,6 @@ namespace osu.Game.Screens.Ranking.Expanded Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft }, - new ModDisplay - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - DisplayUnrankedText = false, - ExpansionMode = ExpansionMode.AlwaysExpanded, - Scale = new Vector2(0.5f), - Current = { Value = score.Mods } - } } }, new FillFlowContainer @@ -215,6 +209,19 @@ namespace osu.Game.Screens.Ranking.Expanded } } }; + + if (score.Mods.Any()) + { + starAndModDisplay.Add(new ModDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + DisplayUnrankedText = false, + ExpansionMode = ExpansionMode.AlwaysExpanded, + Scale = new Vector2(0.5f), + Current = { Value = score.Mods } + }); + } } protected override void LoadComplete() From 8cdae790c3b0fc90996ae473ffe998206f9af51a Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 3 Apr 2020 17:32:37 +0200 Subject: [PATCH 086/655] Load user rulesets from the game data directory --- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Rulesets/RulesetStore.cs | 40 +++++++++++++++++++++++++++---- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 5487bd9320..609b6ce98e 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -168,7 +168,7 @@ namespace osu.Game var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures); - dependencies.Cache(RulesetStore = new RulesetStore(contextFactory)); + dependencies.Cache(RulesetStore = new RulesetStore(contextFactory, Storage)); dependencies.Cache(FileStore = new FileStore(contextFactory, Storage)); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index a389d4ff75..c3c7b653da 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Reflection; using osu.Framework.Logging; +using osu.Framework.Platform; using osu.Game.Database; namespace osu.Game.Rulesets @@ -17,16 +18,20 @@ namespace osu.Game.Rulesets private readonly Dictionary loadedAssemblies = new Dictionary(); - public RulesetStore(IDatabaseContextFactory factory) + private readonly Storage rulesetStorage; + + public RulesetStore(IDatabaseContextFactory factory, Storage storage = null) : base(factory) { + rulesetStorage = storage?.GetStorageForDirectory("rulesets"); + AppDomain.CurrentDomain.AssemblyResolve += resolveRulesetDependencyAssembly; + // On android in release configuration assemblies are loaded from the apk directly into memory. // We cannot read assemblies from cwd, so should check loaded assemblies instead. loadFromAppDomain(); loadFromDisk(); + loadUserRulesets(); addMissingRulesets(); - - AppDomain.CurrentDomain.AssemblyResolve += resolveRulesetAssembly; } /// @@ -48,7 +53,17 @@ namespace osu.Game.Rulesets /// public IEnumerable AvailableRulesets { get; private set; } - private Assembly resolveRulesetAssembly(object sender, ResolveEventArgs args) => loadedAssemblies.Keys.FirstOrDefault(a => a.FullName == args.Name); + private Assembly resolveRulesetDependencyAssembly(object sender, ResolveEventArgs args) + { + var asm = new AssemblyName(args.Name); + + // this assumes the only explicit dependency of the ruleset is the game core assembly. + // the ruleset dependency on the game core assembly requires manual resolving, transient dependencies should be resolved automatically + if (asm.Name.Equals(typeof(OsuGame).Assembly.GetName().Name, StringComparison.Ordinal)) + return Assembly.GetExecutingAssembly(); + + return null; + } private void addMissingRulesets() { @@ -120,6 +135,21 @@ namespace osu.Game.Rulesets } } + private void loadUserRulesets() + { + try + { + var rulesets = rulesetStorage?.GetFiles(".", $"{ruleset_library_prefix}.*.dll"); + + foreach (var ruleset in rulesets.Where(f => !f.Contains("Tests"))) + loadRulesetFromFile(rulesetStorage?.GetFullPath(ruleset)); + } + catch (Exception e) + { + Logger.Error(e, "Couldn't load user rulesets"); + } + } + private void loadFromDisk() { try @@ -175,7 +205,7 @@ namespace osu.Game.Rulesets protected virtual void Dispose(bool disposing) { - AppDomain.CurrentDomain.AssemblyResolve -= resolveRulesetAssembly; + AppDomain.CurrentDomain.AssemblyResolve -= resolveRulesetDependencyAssembly; } } } From 6700ef910f3a7d218a0e1b91f1692e4089d518ac Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 4 Apr 2020 11:35:15 +0300 Subject: [PATCH 087/655] use startAtCurrentTime --- osu.Game/Skinning/LegacySkinExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index 9bfde4fdcb..476e53bdaa 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -14,7 +14,7 @@ namespace osu.Game.Skinning public static class LegacySkinExtensions { public static Drawable GetAnimation(this ISkin source, string componentName, bool animatable, bool looping, bool applyConfigFrameRate = false, string animationSeparator = "-", - bool startAtCurrentTime = false, double? frameLength = null) + bool startAtCurrentTime = true, double? frameLength = null) { Texture texture; From c3f0ef1bd4a13888c51e48ff4f409e7c05aafbe0 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 4 Apr 2020 15:10:54 +0300 Subject: [PATCH 088/655] Major DRYing of code --- .../TestSceneSliderSnaking.cs | 201 +++++------------- 1 file changed, 56 insertions(+), 145 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index a53e06dc0f..04f00122dc 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -1,8 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; +using Humanizer; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -38,6 +40,9 @@ namespace osu.Game.Rulesets.Osu.Tests private readonly Bindable snakingIn = new Bindable(); private readonly Bindable snakingOut = new Bindable(); + private const double duration_of_span = 3605; + private const double fade_in_modifier = -1200; + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) { var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); @@ -55,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Tests private DrawableSlider slider; private DrawableSliderRepeat repeat; - private Vector2 vector; + private Vector2 savedVector; [SetUpSteps] public override void SetUpSteps() { } @@ -67,25 +72,18 @@ namespace osu.Game.Rulesets.Osu.Tests base.SetUpSteps(); AddUntilStep("wait for track to start running", () => track.IsRunning); - AddStep("retrieve 1st slider", () => slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.First()); - testLinear(true); - testLinear(false); - AddStep("retrieve 2nd slider", () => slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.Skip(1).First()); - testRepeating(true); - testRepeating(false); - AddStep("retrieve 3rd slider", () => slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.Skip(2).First()); - testDoubleRepeating(true); - testDoubleRepeating(false); + for (int i = 0; i < 3; i++) + { + testSlider(i, true); + testSlider(i, false); + } } [TestCase(true)] [TestCase(false)] public void TestArrowStays(bool isHit) { - var isSame = isHit ? "is same" : "decreased"; - var enable = isHit ? "enable" : "disable"; - - AddStep($"{enable} autoplay", () => autoplay = isHit); + AddStep($"{(isHit ? "enable" : "disable")} autoplay", () => autoplay = isHit); setSnaking(true); base.SetUpSteps(); @@ -95,154 +93,67 @@ namespace osu.Game.Rulesets.Osu.Tests var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.Skip(1).First(); repeat = drawable.ChildrenOfType>().First().Children.First(); }); - AddStep("Save repeat vector", () => vector = repeat.Position); + AddStep("Save repeat vector", () => savedVector = repeat.Position); addSeekStep(13700); - AddAssert($"Repeat vector {isSame}", () => isHit ? Precision.AlmostEquals(vector.X, repeat.X, 1) && Precision.AlmostEquals(vector.Y, repeat.Y, 1) : repeat.X < vector.X && repeat.Y < vector.Y); + // Precision.AlmostEquals is used because repeat might have a chance to update its position depending on where in the frame its hit + AddAssert($"Repeat vector {(isHit ? "is same" : "decreased")}", () => isHit ? Precision.AlmostEquals(savedVector.X, repeat.X, 1) && Precision.AlmostEquals(savedVector.Y, repeat.Y, 1) : repeat.X < savedVector.X && repeat.Y < savedVector.Y); } - private void testLinear(bool snaking) + private void testSlider(int index, bool snaking) { - var increased = snaking ? "increased" : "is same"; - + double startTime = index * 10000 + 3000; + int repeats = index; + AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => + { + slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.Skip(index).First(); + }); setSnaking(snaking); - addSeekStep(1800); - AddStep("Save end vector", () => + testSnakingIn(startTime + fade_in_modifier, snaking); + for (int i = 0; i < repeats + 1; i++) { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.Last(); - }); - addSeekStep(1900); - AddAssert($"End vector {increased}", () => + testSnakingOut(startTime + 100 + duration_of_span * i, snaking && i == repeats, i%2 == 1); + } + } + + private void testSnakingIn(double startTime, bool isSnakingExpected) + { + addSeekStep(startTime); + AddStep("Save end vector", () => savedVector = getCurrentSliderVector(true)); + addSeekStep(startTime + 100); + AddAssert($"End vector increased", () => { - var body = (PlaySliderBody)slider.Body.Drawable; - var last = body.CurrentCurve.Last(); - return snaking ? last.X > vector.X && last.Y > vector.Y : last == vector; - }); - addSeekStep(3100); - AddStep("Save start vector", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.First(); - }); - addSeekStep(3200); - AddAssert($"Start vector {increased}", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - var first = body.CurrentCurve.First(); - return snaking ? first.X > vector.X && first.Y > vector.Y : first == vector; + var currentVector = getCurrentSliderVector(true); + return isSnakingExpected ? currentVector.X > savedVector.X && currentVector.Y > savedVector.Y : currentVector == savedVector; }); } - private void testRepeating(bool snaking) + private void testSnakingOut(double startTime, bool isSnakingExpected, bool testSliderEnd) { - var increased = snaking ? "increased" : "is same"; - var decreased = snaking ? "decreased" : "is same"; - - setSnaking(snaking); - addSeekStep(8800); - AddStep("Save end vector", () => + addSeekStep(startTime); + AddStep($"Save {(testSliderEnd ? "end" : "start")} vector", () => savedVector = getCurrentSliderVector(testSliderEnd)); + addSeekStep(startTime + 100); + AddAssert($"{(testSliderEnd ? "End" : "Start")} vector {(isSnakingExpected ? (testSliderEnd ? "decreased" : "increased") : "is same")}", () => { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.Last(); - }); - addSeekStep(8900); - AddAssert($"End vector {increased}", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - var last = body.CurrentCurve.Last(); - return snaking ? last.X > vector.X && last.Y > vector.Y : last == vector; - }); - addSeekStep(10100); - AddStep("Save start vector", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.First(); - }); - addSeekStep(10200); - AddAssert("Start vector is same", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - var first = body.CurrentCurve.First(); - return first == vector; - }); - addSeekStep(13700); - AddStep("Save end vector", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.Last(); - }); - addSeekStep(13800); - AddAssert($"End vector {decreased}", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - var last = body.CurrentCurve.Last(); - return snaking ? last.X < vector.X && last.Y < vector.Y : last == vector; + var currentVector = getCurrentSliderVector(testSliderEnd); + bool check(Vector2 a, Vector2 b) + { + if (testSliderEnd) + return a.X < b.X && a.Y < b.Y; + return a.X > b.X && a.Y > b.Y; + } + return isSnakingExpected ? check(currentVector, savedVector) : currentVector == savedVector; }); } - private void testDoubleRepeating(bool snaking) + private Vector2 getCurrentSliderVector(bool getEndOne) { - var increased = snaking ? "increased" : "is same"; - - setSnaking(snaking); - addSeekStep(18800); - AddStep("Save end vector", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.Last(); - }); - addSeekStep(18900); - AddAssert($"End vector {increased}", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - var last = body.CurrentCurve.Last(); - return snaking ? last.X > vector.X && last.Y > vector.Y : last == vector; - }); - addSeekStep(20100); - AddStep("Save start vector", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.First(); - }); - addSeekStep(20200); - AddAssert("Start vector is same", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - var first = body.CurrentCurve.First(); - return first == vector; - }); - addSeekStep(23700); - AddStep("Save end vector", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.Last(); - }); - addSeekStep(23800); - AddAssert("End vector is same", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - var last = body.CurrentCurve.Last(); - return last == vector; - }); - addSeekStep(27300); - AddStep("Save start vector", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.First(); - }); - addSeekStep(27400); - AddAssert($"Start vector {increased}", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - var first = body.CurrentCurve.First(); - return snaking ? first.X > vector.X && first.Y > vector.Y : first == vector; - }); + var body = (PlaySliderBody)slider.Body.Drawable; + return getEndOne ? body.CurrentCurve.Last() : body.CurrentCurve.First(); } private void setSnaking(bool value) { - var text = value ? "Enable" : "Disable"; - AddStep($"{text} snaking", () => + AddStep($"{(value ? "Enable" : "Disable")} snaking", () => { snakingIn.Value = value; snakingOut.Value = value; @@ -272,7 +183,7 @@ namespace osu.Game.Rulesets.Osu.Tests }, new Slider { - StartTime = 10000, + StartTime = 13000, Position = new Vector2(100, 100), Path = new SliderPath(PathType.PerfectCurve, new[] { @@ -284,7 +195,7 @@ namespace osu.Game.Rulesets.Osu.Tests new Slider { - StartTime = 20000, + StartTime = 23000, Position = new Vector2(100, 100), Path = new SliderPath(PathType.PerfectCurve, new[] { @@ -296,7 +207,7 @@ namespace osu.Game.Rulesets.Osu.Tests new HitCircle { - StartTime = 99999, + StartTime = 199999, } } }; From a8a52e506dfd9aaba5b19f9c9294718ff12ee39e Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 4 Apr 2020 15:35:35 +0300 Subject: [PATCH 089/655] Review and style changes --- .../TestSceneSliderSnaking.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 04f00122dc..99b2f7d46e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using Humanizer; @@ -37,8 +36,8 @@ namespace osu.Game.Rulesets.Osu.Tests protected override bool Autoplay => autoplay; private bool autoplay; - private readonly Bindable snakingIn = new Bindable(); - private readonly Bindable snakingOut = new Bindable(); + private readonly BindableBool snakingIn = new BindableBool(); + private readonly BindableBool snakingOut = new BindableBool(); private const double duration_of_span = 3605; private const double fade_in_modifier = -1200; @@ -95,8 +94,15 @@ namespace osu.Game.Rulesets.Osu.Tests }); AddStep("Save repeat vector", () => savedVector = repeat.Position); addSeekStep(13700); - // Precision.AlmostEquals is used because repeat might have a chance to update its position depending on where in the frame its hit - AddAssert($"Repeat vector {(isHit ? "is same" : "decreased")}", () => isHit ? Precision.AlmostEquals(savedVector.X, repeat.X, 1) && Precision.AlmostEquals(savedVector.Y, repeat.Y, 1) : repeat.X < savedVector.X && repeat.Y < savedVector.Y); + + AddAssert($"Repeat vector {(isHit ? "is same" : "decreased")}", () => + { + if (isHit) + // Precision.AlmostEquals is used because repeat might have a chance to update its position depending on where in the frame its hit + return Precision.AlmostEquals(savedVector, repeat.Position, 1); + + return repeat.X < savedVector.X && repeat.Y < savedVector.Y; + }); } private void testSlider(int index, bool snaking) @@ -109,9 +115,10 @@ namespace osu.Game.Rulesets.Osu.Tests }); setSnaking(snaking); testSnakingIn(startTime + fade_in_modifier, snaking); + for (int i = 0; i < repeats + 1; i++) { - testSnakingOut(startTime + 100 + duration_of_span * i, snaking && i == repeats, i%2 == 1); + testSnakingOut(startTime + 100 + duration_of_span * i, snaking && i == repeats, i % 2 == 1); } } @@ -120,7 +127,7 @@ namespace osu.Game.Rulesets.Osu.Tests addSeekStep(startTime); AddStep("Save end vector", () => savedVector = getCurrentSliderVector(true)); addSeekStep(startTime + 100); - AddAssert($"End vector increased", () => + AddAssert($"End vector {(isSnakingExpected ? "increased" : "is same")}", () => { var currentVector = getCurrentSliderVector(true); return isSnakingExpected ? currentVector.X > savedVector.X && currentVector.Y > savedVector.Y : currentVector == savedVector; @@ -135,12 +142,15 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert($"{(testSliderEnd ? "End" : "Start")} vector {(isSnakingExpected ? (testSliderEnd ? "decreased" : "increased") : "is same")}", () => { var currentVector = getCurrentSliderVector(testSliderEnd); + bool check(Vector2 a, Vector2 b) { if (testSliderEnd) return a.X < b.X && a.Y < b.Y; + return a.X > b.X && a.Y > b.Y; } + return isSnakingExpected ? check(currentVector, savedVector) : currentVector == savedVector; }); } From 0ebb5a81f937601a9008a4f9cb94779f6d9b4861 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 4 Apr 2020 15:59:39 +0300 Subject: [PATCH 090/655] Fix oversight in testing --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 99b2f7d46e..51d4d1c008 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -86,14 +86,14 @@ namespace osu.Game.Rulesets.Osu.Tests setSnaking(true); base.SetUpSteps(); - addSeekStep(13500); + addSeekStep(16500); AddStep("retrieve 2nd slider repeat", () => { var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.Skip(1).First(); repeat = drawable.ChildrenOfType>().First().Children.First(); }); AddStep("Save repeat vector", () => savedVector = repeat.Position); - addSeekStep(13700); + addSeekStep(16700); AddAssert($"Repeat vector {(isHit ? "is same" : "decreased")}", () => { From 0e45a4d54e1dd372f120dbfb926234065f4158af Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 4 Apr 2020 20:13:46 +0200 Subject: [PATCH 091/655] Add back cached ruleset assembly lookup --- osu.Game/Rulesets/RulesetStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index c3c7b653da..7b4c0302aa 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets if (asm.Name.Equals(typeof(OsuGame).Assembly.GetName().Name, StringComparison.Ordinal)) return Assembly.GetExecutingAssembly(); - return null; + return loadedAssemblies.Keys.FirstOrDefault(a => a.FullName == asm.FullName); } private void addMissingRulesets() From e340d2628b36a9ce935e8d75931f414416d683e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 5 Apr 2020 03:17:11 +0900 Subject: [PATCH 092/655] Fix sliderball accent colour not being set correctly --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 5c7f4a42b3..9b6f39d91d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -186,7 +186,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.ApplySkin(skin, allowFallback); bool allowBallTint = skin.GetConfig(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false; - Ball.Colour = allowBallTint ? AccentColour.Value : Color4.White; + Ball.AccentColour = allowBallTint ? AccentColour.Value : Color4.White; } protected override void CheckForResult(bool userTriggered, double timeOffset) From 1e8badb14a9ebdb4733af916d847fc10e0505f7c Mon Sep 17 00:00:00 2001 From: Santeri Nogelainen Date: Sat, 4 Apr 2020 22:28:36 +0300 Subject: [PATCH 093/655] Move all logic to TopLocalRank and remove CarouselBeatmapRank --- osu.Game/Online/Leaderboards/TopLocalRank.cs | 52 +++++++------- .../Select/Carousel/CarouselBeatmapRank.cs | 68 ------------------- .../Carousel/DrawableCarouselBeatmap.cs | 6 +- 3 files changed, 32 insertions(+), 94 deletions(-) delete mode 100644 osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs diff --git a/osu.Game/Online/Leaderboards/TopLocalRank.cs b/osu.Game/Online/Leaderboards/TopLocalRank.cs index 83d92f8ffa..51c171a176 100644 --- a/osu.Game/Online/Leaderboards/TopLocalRank.cs +++ b/osu.Game/Online/Leaderboards/TopLocalRank.cs @@ -1,12 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Rulesets; @@ -14,43 +11,44 @@ using osu.Game.Scoring; namespace osu.Game.Online.Leaderboards { - public class TopLocalRank : Container + public class TopLocalRank : UpdateableRank { private readonly BeatmapInfo beatmap; - private readonly UpdateableRank rank; private ScoreManager scores; private IBindable ruleset; private IAPIProvider api; - /// - /// Raised when the top score is loaded - /// - public Action ScoreLoaded; + protected override double LoadDelay => 250; - public TopLocalRank(BeatmapInfo beatmap) + public TopLocalRank(BeatmapInfo beatmap) : base(null) { this.beatmap = beatmap; - - RelativeSizeAxes = Axes.Both; - - InternalChild = rank = new UpdateableRank(null) - { - RelativeSizeAxes = Axes.Both - }; } [BackgroundDependencyLoader] private void load(ScoreManager scores, IBindable ruleset, IAPIProvider api) { + scores.ItemAdded += scoreChanged; + scores.ItemRemoved += scoreChanged; + ruleset.ValueChanged += _ => fetchAndLoadTopScore(); + + this.ruleset = ruleset.GetBoundCopy(); this.scores = scores; - this.ruleset = ruleset; this.api = api; - FetchAndLoadTopScore(); + fetchAndLoadTopScore(); } - public void FetchAndLoadTopScore() + private void scoreChanged(ScoreInfo score) + { + if (score.BeatmapInfoID == beatmap.ID) + { + fetchAndLoadTopScore(); + } + } + + private void fetchAndLoadTopScore() { var score = fetchTopScore(); @@ -59,9 +57,16 @@ namespace osu.Game.Online.Leaderboards private void loadTopScore(ScoreInfo score) { - Schedule(() => rank.Rank = score?.Rank); + var rank = score?.Rank; - ScoreLoaded?.Invoke(score); + // toggle the display of this drawable + // we do not want empty space if there is no rank to be displayed + if (rank.HasValue) + Show(); + else + Hide(); + + Schedule(() => Rank = rank); } private ScoreInfo fetchTopScore() @@ -69,8 +74,7 @@ namespace osu.Game.Online.Leaderboards if (scores == null || beatmap == null || ruleset?.Value == null || api?.LocalUser.Value == null) return null; - return scores.GetAllUsableScores() - .Where(s => s.UserID == api.LocalUser.Value.Id && s.BeatmapInfoID == beatmap.ID && s.RulesetID == ruleset.Value.ID) + return scores.QueryScores(s => s.UserID == api.LocalUser.Value.Id && s.BeatmapInfoID == beatmap.ID && s.RulesetID == ruleset.Value.ID && !s.DeletePending) .OrderByDescending(s => s.TotalScore) .FirstOrDefault(); } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs deleted file mode 100644 index fbd4292138..0000000000 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; -using osu.Game.Online.Leaderboards; -using osu.Game.Rulesets; -using osu.Game.Scoring; - -namespace osu.Game.Screens.Select.Carousel -{ - public class CarouselBeatmapRank : Container - { - private const int rank_size = 20; - private readonly BeatmapInfo beatmap; - - private TopLocalRank rank; - - public CarouselBeatmapRank(BeatmapInfo beatmap) - { - this.beatmap = beatmap; - - Height = rank_size; - } - - [BackgroundDependencyLoader] - private void load(ScoreManager scores, IBindable ruleset) - { - scores.ItemAdded += scoreChanged; - scores.ItemRemoved += scoreChanged; - ruleset.ValueChanged += _ => rulesetChanged(); - - rank = new TopLocalRank(beatmap) - { - ScoreLoaded = scaleDisplay - }; - - InternalChild = new DelayedLoadWrapper(rank) - { - RelativeSizeAxes = Axes.Both - }; - } - - private void rulesetChanged() - { - rank.FetchAndLoadTopScore(); - } - - private void scoreChanged(ScoreInfo score) - { - if (score.BeatmapInfoID == beatmap.ID) - { - rank.FetchAndLoadTopScore(); - } - } - - private void scaleDisplay(ScoreInfo score) - { - if (score != null) - Width = rank_size * 2; - else - Width = 0; - } - } -} diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 4b42d818f5..5357f9a652 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -19,6 +19,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Leaderboards; using osu.Game.Overlays; using osuTK; using osuTK.Graphics; @@ -129,9 +130,10 @@ namespace osu.Game.Screens.Select.Carousel AutoSizeAxes = Axes.Both, Children = new Drawable[] { - new CarouselBeatmapRank(beatmap) + new TopLocalRank(beatmap) { - Scale = new Vector2(0.8f) + Scale = new Vector2(0.8f), + Size = new Vector2(40, 20) }, starCounter = new StarCounter { From da59baa7798fc9f9851e0bd716a97ae1179a812e Mon Sep 17 00:00:00 2001 From: Santeri Nogelainen Date: Sat, 4 Apr 2020 22:42:13 +0300 Subject: [PATCH 094/655] Add line break --- osu.Game/Online/Leaderboards/TopLocalRank.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/TopLocalRank.cs b/osu.Game/Online/Leaderboards/TopLocalRank.cs index 51c171a176..be014dafc3 100644 --- a/osu.Game/Online/Leaderboards/TopLocalRank.cs +++ b/osu.Game/Online/Leaderboards/TopLocalRank.cs @@ -21,7 +21,8 @@ namespace osu.Game.Online.Leaderboards protected override double LoadDelay => 250; - public TopLocalRank(BeatmapInfo beatmap) : base(null) + public TopLocalRank(BeatmapInfo beatmap) + : base(null) { this.beatmap = beatmap; } From c4f7b4576848da614959c8a427f7886e7a2f66f3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Apr 2020 00:02:33 +0300 Subject: [PATCH 095/655] Revert "Add support for custom hyper-dash fruit colouring" This reverts commit 6f2cc5471adabc4392fcf1f63a5de32266016c10 and also its testing cases. This became dead code after actual correct osu!catch skin colouring, we don't support modern skinning (non-legacy skinning) at the moment, so for what it's worth this can be reverted to default red-coloured --- .../TestSceneHyperDashColouring.cs | 67 ++++++------------- .../Objects/Drawables/FruitPiece.cs | 12 +--- 2 files changed, 23 insertions(+), 56 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index 2009099a61..10739a3131 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -4,15 +4,10 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Audio.Sample; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using osu.Framework.Testing; -using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Catch.Objects; @@ -31,9 +26,8 @@ namespace osu.Game.Rulesets.Catch.Tests [Resolved] private SkinManager skins { get; set; } - [TestCase(false)] - [TestCase(true)] - public void TestHyperDashFruitColour(bool legacyFruit) + [Test] + public void TestHyperDashFruitColour() { DrawableFruit drawableFruit = null; @@ -47,20 +41,15 @@ namespace osu.Game.Rulesets.Catch.Tests Anchor = Anchor.Centre, Origin = Anchor.Centre, Scale = new Vector2(4f), - }, false, false, false, legacyFruit); + }, false, false, false); }); - AddAssert("hyper-dash fruit has default colour", () => - legacyFruit - ? checkLegacyFruitHyperDashColour(drawableFruit, Catcher.DefaultHyperDashColour) - : checkFruitHyperDashColour(drawableFruit, Catcher.DefaultHyperDashColour)); + AddAssert("hyper-dash fruit has default colour", () => checkLegacyFruitHyperDashColour(drawableFruit, Catcher.DefaultHyperDashColour)); } - [TestCase(false, true)] - [TestCase(false, false)] - [TestCase(true, true)] - [TestCase(true, false)] - public void TestCustomHyperDashFruitColour(bool legacyFruit, bool customCatcherHyperDashColour) + [TestCase(true)] + [TestCase(false)] + public void TestCustomHyperDashFruitColour(bool customCatcherHyperDashColour) { DrawableFruit drawableFruit = null; @@ -74,18 +63,14 @@ namespace osu.Game.Rulesets.Catch.Tests Anchor = Anchor.Centre, Origin = Anchor.Centre, Scale = new Vector2(4f), - }, customCatcherHyperDashColour, false, true, legacyFruit); + }, customCatcherHyperDashColour, false, true); }); - AddAssert("hyper-dash fruit use fruit colour from skin", () => - legacyFruit - ? checkLegacyFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashFruitColour) - : checkFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashFruitColour)); + AddAssert("hyper-dash fruit use fruit colour from skin", () => checkLegacyFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashFruitColour)); } - [TestCase(false)] - [TestCase(true)] - public void TestCustomHyperDashFruitColourFallback(bool legacyFruit) + [Test] + public void TestCustomHyperDashFruitColourFallback() { DrawableFruit drawableFruit = null; @@ -100,36 +85,24 @@ namespace osu.Game.Rulesets.Catch.Tests Anchor = Anchor.Centre, Origin = Anchor.Centre, Scale = new Vector2(4f), - }, true, false, false, legacyFruit); + }, true, false, false); }); - AddAssert("hyper-dash fruit colour falls back to catcher colour from skin", () => - legacyFruit - ? checkLegacyFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashColour) - : checkFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashColour)); + AddAssert("hyper-dash fruit colour falls back to catcher colour from skin", () => checkLegacyFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashColour)); } - private Drawable setupSkinHierarchy(Drawable child, bool customCatcherColour, bool customAfterColour, bool customFruitColour, bool legacySkin = true) + private Drawable setupSkinHierarchy(Drawable child, bool customCatcherColour, bool customAfterColour, bool customFruitColour) { + var legacySkinProvider = new SkinProvidingContainer(skins.GetSkin(DefaultLegacySkin.Info)); var testSkinProvider = new SkinProvidingContainer(new TestSkin(customCatcherColour, customAfterColour, customFruitColour)); + var legacySkinTransformer = new SkinProvidingContainer(new CatchLegacySkinTransformer(testSkinProvider)); - if (legacySkin) - { - var legacySkinProvider = new SkinProvidingContainer(skins.GetSkin(DefaultLegacySkin.Info)); - var legacySkinTransformer = new SkinProvidingContainer(new CatchLegacySkinTransformer(testSkinProvider)); - - return legacySkinProvider - .WithChild(testSkinProvider - .WithChild(legacySkinTransformer - .WithChild(child))); - } - - return testSkinProvider.WithChild(child); + return legacySkinProvider + .WithChild(testSkinProvider + .WithChild(legacySkinTransformer + .WithChild(child))); } - private bool checkFruitHyperDashColour(DrawableFruit fruit, Color4 expectedColour) => - fruit.ChildrenOfType().First().Drawable.ChildrenOfType().Single(c => c.BorderColour == expectedColour).Any(d => d.Colour == expectedColour); - private bool checkLegacyFruitHyperDashColour(DrawableFruit fruit, Color4 expectedColour) => fruit.ChildrenOfType().First().Drawable.ChildrenOfType().Any(c => c.Colour == expectedColour); diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs index 2437958916..359329885c 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs @@ -7,10 +7,8 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Catch.Skinning; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Skinning; using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Objects.Drawables @@ -34,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables } [BackgroundDependencyLoader] - private void load(DrawableHitObject drawableObject, ISkinSource skin) + private void load(DrawableHitObject drawableObject) { DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject; hitObject = drawableCatchObject.HitObject; @@ -63,10 +61,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables }, }); - var hyperDashColour = - skin.GetHyperDashFruitColour()?.Value ?? - Catcher.DefaultHyperDashColour; - if (hitObject.HyperDash) { AddInternal(new Circle @@ -74,7 +68,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - BorderColour = hyperDashColour, + BorderColour = Catcher.DefaultHyperDashColour, BorderThickness = 12f * RADIUS_ADJUST, Children = new Drawable[] { @@ -84,7 +78,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables Alpha = 0.3f, Blending = BlendingParameters.Additive, RelativeSizeAxes = Axes.Both, - Colour = hyperDashColour, + Colour = Catcher.DefaultHyperDashColour, } } }); From 10e65c4f53929cf477042c58aa302fd0f6e3b076 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Apr 2020 00:10:12 +0300 Subject: [PATCH 096/655] Add handling for legacy CatchTheBeat section in LegacyDecoder --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 561707f9ef..743a470e6e 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -73,6 +73,9 @@ namespace osu.Game.Beatmaps.Formats switch (section) { case Section.Colours: + // osu!catch section only has colour settings + // so no harm in handling the entire section + case Section.CatchTheBeat: HandleColours(output, line); return; } @@ -149,7 +152,8 @@ namespace osu.Game.Beatmaps.Formats HitObjects, Variables, Fonts, - Mania + CatchTheBeat, + Mania, } internal class LegacyDifficultyControlPoint : DifficultyControlPoint From 55d076d6f359ed50edd3cf371195b656effd9929 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Apr 2020 00:10:25 +0300 Subject: [PATCH 097/655] Transform CatchSkinColour lookup to skin configuration custom colours lookup --- .../Skinning/CatchLegacySkinTransformer.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs index 65e6e6f209..4a87eb95e7 100644 --- a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs @@ -65,6 +65,15 @@ namespace osu.Game.Rulesets.Catch.Skinning public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample); - public IBindable GetConfig(TLookup lookup) => source.GetConfig(lookup); + public IBindable GetConfig(TLookup lookup) + { + switch (lookup) + { + case CatchSkinColour colour: + return source.GetConfig(new SkinCustomColourLookup(colour)); + } + + return source.GetConfig(lookup); + } } } From b100230538707ffcbed2a70fc17b0981b618b3eb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Apr 2020 00:13:23 +0300 Subject: [PATCH 098/655] Test CatchSkinColour transformation on colour retrieval implicitly --- .../TestSceneHyperDashColouring.cs | 37 ++++--------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index 10739a3131..c8d28dbaeb 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -106,44 +106,23 @@ namespace osu.Game.Rulesets.Catch.Tests private bool checkLegacyFruitHyperDashColour(DrawableFruit fruit, Color4 expectedColour) => fruit.ChildrenOfType().First().Drawable.ChildrenOfType().Any(c => c.Colour == expectedColour); - private class TestSkin : ISkin + private class TestSkin : LegacySkin { public static Color4 CustomHyperDashColour { get; } = Color4.Goldenrod; public static Color4 CustomHyperDashFruitColour { get; } = Color4.Cyan; public static Color4 CustomHyperDashAfterColour { get; } = Color4.Lime; - private readonly bool customCatcherColour; - private readonly bool customAfterColour; - private readonly bool customFruitColour; - public TestSkin(bool customCatcherColour, bool customAfterColour, bool customFruitColour) + : base(new SkinInfo(), null, null, string.Empty) { - this.customCatcherColour = customCatcherColour; - this.customAfterColour = customAfterColour; - this.customFruitColour = customFruitColour; - } + if (customCatcherColour) + Configuration.CustomColours[CatchSkinColour.HyperDash.ToString()] = CustomHyperDashColour; - public Drawable GetDrawableComponent(ISkinComponent component) => null; + if (customAfterColour) + Configuration.CustomColours[CatchSkinColour.HyperDashAfterImage.ToString()] = CustomHyperDashAfterColour; - public Texture GetTexture(string componentName) => null; - - public SampleChannel GetSample(ISampleInfo sampleInfo) => null; - - public IBindable GetConfig(TLookup lookup) - { - if (lookup is CatchSkinColour config) - { - if (config == CatchSkinColour.HyperDash && customCatcherColour) - return SkinUtils.As(new Bindable(CustomHyperDashColour)); - - if (config == CatchSkinColour.HyperDashFruit && customFruitColour) - return SkinUtils.As(new Bindable(CustomHyperDashFruitColour)); - - if (config == CatchSkinColour.HyperDashAfterImage && customAfterColour) - return SkinUtils.As(new Bindable(CustomHyperDashAfterColour)); - } - - return null; + if (customFruitColour) + Configuration.CustomColours[CatchSkinColour.HyperDashFruit.ToString()] = CustomHyperDashFruitColour; } } } From dfd86e643bd415e5653a942e2432d56d224c6a1b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Apr 2020 00:14:07 +0300 Subject: [PATCH 099/655] Add custom-coloured osu!catch skin configuration to 'Resources/special-skin' --- osu.Game.Rulesets.Catch.Tests/Resources/special-skin/skin.ini | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/special-skin/skin.ini diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/skin.ini new file mode 100644 index 0000000000..36515f33c5 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/skin.ini @@ -0,0 +1,4 @@ +[CatchTheBeat] +HyperDash: 232,185,35 +HyperDashFruit: 0,255,255 +HyperDashAfterImage: 232,74,35 \ No newline at end of file From f6bbec72bfd664f1a29f27de192496bba08df6ca Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Apr 2020 00:20:21 +0300 Subject: [PATCH 100/655] Revert "Add custom-coloured osu!catch skin configuration to 'Resources/special-skin'" This reverts commit dfd86e643bd415e5653a942e2432d56d224c6a1b. --- osu.Game.Rulesets.Catch.Tests/Resources/special-skin/skin.ini | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/special-skin/skin.ini diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/skin.ini deleted file mode 100644 index 36515f33c5..0000000000 --- a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/skin.ini +++ /dev/null @@ -1,4 +0,0 @@ -[CatchTheBeat] -HyperDash: 232,185,35 -HyperDashFruit: 0,255,255 -HyperDashAfterImage: 232,74,35 \ No newline at end of file From 42ac0c72eac12ea39415f9bc9926adcc5a1a6ff6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Apr 2020 00:46:52 +0300 Subject: [PATCH 101/655] Fix grammer issue and more rewording MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game.Rulesets.Catch/Skinning/CatchSkinColour.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchSkinColour.cs b/osu.Game.Rulesets.Catch/Skinning/CatchSkinColour.cs index 2ad8f89739..4506111498 100644 --- a/osu.Game.Rulesets.Catch/Skinning/CatchSkinColour.cs +++ b/osu.Game.Rulesets.Catch/Skinning/CatchSkinColour.cs @@ -6,12 +6,12 @@ namespace osu.Game.Rulesets.Catch.Skinning public enum CatchSkinColour { /// - /// The colour to be used for the catcher while on hyper-dashing state. + /// The colour to be used for the catcher while in hyper-dashing state. /// HyperDash, /// - /// The colour to be used for hyper-dash fruits. + /// The colour to be used for fruits that grant the catcher the ability to hyper-dash. /// HyperDashFruit, From 2fec8b7b8555101200250846e9ea0958f77b6b1c Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Sun, 5 Apr 2020 13:01:10 +0930 Subject: [PATCH 102/655] Use DisplayModes rather than AvailableResolutions --- .../Settings/Sections/Graphics/LayoutSettings.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index b73c8f7622..00b7643332 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -209,15 +209,16 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private IReadOnlyList getResolutions() { var resolutions = new List { new Size(9999, 9999) }; + var currentDisplay = game.Window?.CurrentDisplay.Value; - if (game.Window != null) + if (currentDisplay != null) { - resolutions.AddRange(game.Window.AvailableResolutions - .Where(r => r.Width >= 800 && r.Height >= 600) - .OrderByDescending(r => r.Width) - .ThenByDescending(r => r.Height) - .Select(res => new Size(res.Width, res.Height)) - .Distinct()); + resolutions.AddRange(currentDisplay.DisplayModes + .Where(m => m.Size.Width >= 800 && m.Size.Height >= 600) + .OrderByDescending(m => m.Size.Width) + .ThenByDescending(m => m.Size.Height) + .Select(m => m.Size) + .Distinct()); } return resolutions; From 8d3e228f78b5b16f31e9fd250ca82d4ad0c5a770 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 5 Apr 2020 11:22:52 +0300 Subject: [PATCH 103/655] Split and rename tests --- .../TestSceneSliderSnaking.cs | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 51d4d1c008..c282314be7 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -64,23 +64,33 @@ namespace osu.Game.Rulesets.Osu.Tests [SetUpSteps] public override void SetUpSteps() { } - [Test] - public void TestSnaking() + [TestCase(0)] + [TestCase(1)] + [TestCase(2)] + public void TestSnakingEnabled(int repeatAmount) { AddStep("have autoplay", () => autoplay = true); base.SetUpSteps(); AddUntilStep("wait for track to start running", () => track.IsRunning); - for (int i = 0; i < 3; i++) - { - testSlider(i, true); - testSlider(i, false); - } + testSlider(repeatAmount, true); + } + + [TestCase(0)] + [TestCase(1)] + [TestCase(2)] + public void TestSnakingDisabled(int repeatAmount) + { + AddStep("have autoplay", () => autoplay = true); + base.SetUpSteps(); + AddUntilStep("wait for track to start running", () => track.IsRunning); + + testSlider(repeatAmount, false); } [TestCase(true)] [TestCase(false)] - public void TestArrowStays(bool isHit) + public void TestArrowMovement(bool isHit) { AddStep($"{(isHit ? "enable" : "disable")} autoplay", () => autoplay = isHit); setSnaking(true); From 1f6a4fa4b812752767b3aa7b36ef3d1e90ee052e Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 5 Apr 2020 12:45:10 +0300 Subject: [PATCH 104/655] Remove transformations --- .../Objects/Drawables/DrawableSliderRepeat.cs | 5 ++++- .../Objects/Drawables/Pieces/ReverseArrowPiece.cs | 9 +++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index b04d484195..6c818f4a3e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Utils; using osu.Game.Rulesets.Objects.Drawables; @@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private double animDuration; - private readonly Drawable scaleContainer; + private readonly ReverseArrowPiece scaleContainer; public DrawableSliderRepeat(SliderRepeat sliderRepeat, DrawableSlider drawableSlider) : base(sliderRepeat) @@ -79,6 +80,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Hit: this.FadeOut(animDuration, Easing.Out) .ScaleTo(Scale * 1.5f, animDuration, Easing.Out); + scaleContainer.ShouldFollowBeats = false; + scaleContainer.Transforms.ForEach(t => scaleContainer.RemoveTransform(t)); break; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs index 35a27bb0a6..73f02aa59c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs @@ -13,6 +13,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class ReverseArrowPiece : BeatSyncedContainer { + public bool ShouldFollowBeats = true; + public ReverseArrowPiece() { Divisor = 2; @@ -37,7 +39,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces }; } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) => - Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out); + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + { + if (ShouldFollowBeats) + Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out); + } } } From a3626333bebb580005c353466f6e130ef9c1f798 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 5 Apr 2020 13:36:52 +0300 Subject: [PATCH 105/655] Use DI instead --- .../Objects/Drawables/DrawableSliderRepeat.cs | 3 +-- .../Objects/Drawables/Pieces/ReverseArrowPiece.cs | 7 +++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 6c818f4a3e..517af630fc 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private double animDuration; - private readonly ReverseArrowPiece scaleContainer; + private readonly Drawable scaleContainer; public DrawableSliderRepeat(SliderRepeat sliderRepeat, DrawableSlider drawableSlider) : base(sliderRepeat) @@ -80,7 +80,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Hit: this.FadeOut(animDuration, Easing.Out) .ScaleTo(Scale * 1.5f, animDuration, Easing.Out); - scaleContainer.ShouldFollowBeats = false; scaleContainer.Transforms.ForEach(t => scaleContainer.RemoveTransform(t)); break; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs index 73f02aa59c..d792665d9d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs @@ -8,12 +8,15 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; using osu.Game.Skinning; +using osu.Framework.Allocation; +using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class ReverseArrowPiece : BeatSyncedContainer { - public bool ShouldFollowBeats = true; + [Resolved] + private DrawableHitObject drawableSlider { get; set; } public ReverseArrowPiece() { @@ -41,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) { - if (ShouldFollowBeats) + if (!drawableSlider.IsHit) Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out); } } From 23c3be0969b3c240d53844221f71928fdaa20520 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 5 Apr 2020 13:39:31 +0300 Subject: [PATCH 106/655] Rename variable --- .../Objects/Drawables/Pieces/ReverseArrowPiece.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs index d792665d9d..6f3b2b6890 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public class ReverseArrowPiece : BeatSyncedContainer { [Resolved] - private DrawableHitObject drawableSlider { get; set; } + private DrawableHitObject drawableRepeat { get; set; } public ReverseArrowPiece() { @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) { - if (!drawableSlider.IsHit) + if (!drawableRepeat.IsHit) Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out); } } From d68c45e22b38173d4c30b21d9eef8c0f48b71a46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Apr 2020 13:47:30 +0200 Subject: [PATCH 107/655] Use ElementAt() where applicable --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index c282314be7..98b039c9b4 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Tests addSeekStep(16500); AddStep("retrieve 2nd slider repeat", () => { - var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.Skip(1).First(); + var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(1); repeat = drawable.ChildrenOfType>().First().Children.First(); }); AddStep("Save repeat vector", () => savedVector = repeat.Position); @@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Tests int repeats = index; AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => { - slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.Skip(index).First(); + slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(index); }); setSnaking(snaking); testSnakingIn(startTime + fade_in_modifier, snaking); From 4170c210b29872b1edcb0072cd42b037245f34bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Apr 2020 13:50:27 +0200 Subject: [PATCH 108/655] Centralise hitobject start time calculation --- .../TestSceneSliderSnaking.cs | 72 ++++++++++--------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 98b039c9b4..287da2d25c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void testSlider(int index, bool snaking) { - double startTime = index * 10000 + 3000; + double startTime = hitObjects[index].StartTime; int repeats = index; AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => { @@ -189,46 +189,48 @@ namespace osu.Game.Rulesets.Osu.Tests protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap { - HitObjects = new List + HitObjects = hitObjects + }; + + private readonly List hitObjects = new List + { + new Slider { - new Slider + StartTime = 3000, + Position = new Vector2(100, 100), + Path = new SliderPath(PathType.PerfectCurve, new[] { - StartTime = 3000, - Position = new Vector2(100, 100), - Path = new SliderPath(PathType.PerfectCurve, new[] - { - Vector2.Zero, - new Vector2(300, 200) - }), - }, - new Slider + Vector2.Zero, + new Vector2(300, 200) + }), + }, + new Slider + { + StartTime = 13000, + Position = new Vector2(100, 100), + Path = new SliderPath(PathType.PerfectCurve, new[] { - StartTime = 13000, - Position = new Vector2(100, 100), - Path = new SliderPath(PathType.PerfectCurve, new[] - { - Vector2.Zero, - new Vector2(300, 200) - }), - RepeatCount = 1, - }, + Vector2.Zero, + new Vector2(300, 200) + }), + RepeatCount = 1, + }, - new Slider + new Slider + { + StartTime = 23000, + Position = new Vector2(100, 100), + Path = new SliderPath(PathType.PerfectCurve, new[] { - StartTime = 23000, - Position = new Vector2(100, 100), - Path = new SliderPath(PathType.PerfectCurve, new[] - { - Vector2.Zero, - new Vector2(300, 200) - }), - RepeatCount = 2, - }, + Vector2.Zero, + new Vector2(300, 200) + }), + RepeatCount = 2, + }, - new HitCircle - { - StartTime = 199999, - } + new HitCircle + { + StartTime = 199999, } }; } From cbc546905ff103ace3583a9446b1764849228392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Apr 2020 15:26:32 +0200 Subject: [PATCH 109/655] Rewrite snaking tests --- .../TestSceneSliderSnaking.cs | 117 ++++++++++-------- 1 file changed, 67 insertions(+), 50 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 287da2d25c..19b05f6b51 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using Humanizer; @@ -67,25 +68,48 @@ namespace osu.Game.Rulesets.Osu.Tests [TestCase(0)] [TestCase(1)] [TestCase(2)] - public void TestSnakingEnabled(int repeatAmount) + public void TestSnakingEnabled(int sliderIndex) { - AddStep("have autoplay", () => autoplay = true); + AddStep("enable autoplay", () => autoplay = true); base.SetUpSteps(); AddUntilStep("wait for track to start running", () => track.IsRunning); - testSlider(repeatAmount, true); + double startTime = hitObjects[sliderIndex].StartTime; + retrieveSlider(sliderIndex); + setSnaking(true); + + ensureSnakingIn(startTime + fade_in_modifier); + + for (int i = 0; i < sliderIndex; i++) + { + // non-final repeats should not snake out + ensureNoSnakingOut(startTime, i); + } + + // final repeat should snake out + ensureSnakingOut(startTime, sliderIndex); } [TestCase(0)] [TestCase(1)] [TestCase(2)] - public void TestSnakingDisabled(int repeatAmount) + public void TestSnakingDisabled(int sliderIndex) { AddStep("have autoplay", () => autoplay = true); base.SetUpSteps(); AddUntilStep("wait for track to start running", () => track.IsRunning); - testSlider(repeatAmount, false); + double startTime = hitObjects[sliderIndex].StartTime; + retrieveSlider(sliderIndex); + setSnaking(false); + + ensureNoSnakingIn(startTime + fade_in_modifier); + + for (int i = 0; i <= sliderIndex; i++) + { + // no snaking out ever, including final repeat + ensureNoSnakingOut(startTime, i); + } } [TestCase(true)] @@ -115,62 +139,55 @@ namespace osu.Game.Rulesets.Osu.Tests }); } - private void testSlider(int index, bool snaking) + private void retrieveSlider(int index) => AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => { - double startTime = hitObjects[index].StartTime; - int repeats = index; - AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => - { - slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(index); - }); - setSnaking(snaking); - testSnakingIn(startTime + fade_in_modifier, snaking); + slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(index); + }); - for (int i = 0; i < repeats + 1; i++) - { - testSnakingOut(startTime + 100 + duration_of_span * i, snaking && i == repeats, i % 2 == 1); - } + private void ensureSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionIncreased); + private void ensureNoSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionRemainsSame); + + private void ensureSnakingOut(double startTime, int repeatIndex) + { + var repeatTime = timeAtRepeat(startTime, repeatIndex); + + if (repeatIndex % 2 == 0) + checkPositionChange(repeatTime, sliderStart, positionIncreased); + else + checkPositionChange(repeatTime, sliderEnd, positionDecreased); } - private void testSnakingIn(double startTime, bool isSnakingExpected) + private void ensureNoSnakingOut(double startTime, int repeatIndex) => + checkPositionChange(timeAtRepeat(startTime, repeatIndex), positionAtRepeat(repeatIndex), positionRemainsSame); + + private double timeAtRepeat(double startTime, int repeatIndex) => startTime + 100 + duration_of_span * repeatIndex; + private Func positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? (Func)sliderStart : sliderEnd; + + private List sliderCurve => ((PlaySliderBody)slider.Body.Drawable).CurrentCurve; + private Vector2 sliderStart() => sliderCurve.First(); + private Vector2 sliderEnd() => sliderCurve.Last(); + + private bool positionRemainsSame(Vector2 previous, Vector2 current) => previous == current; + private bool positionIncreased(Vector2 previous, Vector2 current) => current.X > previous.X && current.Y > previous.Y; + private bool positionDecreased(Vector2 previous, Vector2 current) => current.X < previous.X && current.Y < previous.Y; + + private void checkPositionChange(double startTime, Func positionToCheck, Func positionAssertion) { + Vector2 previousPosition = Vector2.Zero; + + string positionDescription = positionToCheck.Method.Name.Humanize(LetterCasing.LowerCase); + string assertionDescription = positionAssertion.Method.Name.Humanize(LetterCasing.LowerCase); + addSeekStep(startTime); - AddStep("Save end vector", () => savedVector = getCurrentSliderVector(true)); + AddStep($"save {positionDescription} position", () => previousPosition = positionToCheck.Invoke()); addSeekStep(startTime + 100); - AddAssert($"End vector {(isSnakingExpected ? "increased" : "is same")}", () => + AddAssert($"{positionDescription} {assertionDescription}", () => { - var currentVector = getCurrentSliderVector(true); - return isSnakingExpected ? currentVector.X > savedVector.X && currentVector.Y > savedVector.Y : currentVector == savedVector; + var currentPosition = positionToCheck.Invoke(); + return positionAssertion.Invoke(previousPosition, currentPosition); }); } - private void testSnakingOut(double startTime, bool isSnakingExpected, bool testSliderEnd) - { - addSeekStep(startTime); - AddStep($"Save {(testSliderEnd ? "end" : "start")} vector", () => savedVector = getCurrentSliderVector(testSliderEnd)); - addSeekStep(startTime + 100); - AddAssert($"{(testSliderEnd ? "End" : "Start")} vector {(isSnakingExpected ? (testSliderEnd ? "decreased" : "increased") : "is same")}", () => - { - var currentVector = getCurrentSliderVector(testSliderEnd); - - bool check(Vector2 a, Vector2 b) - { - if (testSliderEnd) - return a.X < b.X && a.Y < b.Y; - - return a.X > b.X && a.Y > b.Y; - } - - return isSnakingExpected ? check(currentVector, savedVector) : currentVector == savedVector; - }); - } - - private Vector2 getCurrentSliderVector(bool getEndOne) - { - var body = (PlaySliderBody)slider.Body.Drawable; - return getEndOne ? body.CurrentCurve.Last() : body.CurrentCurve.First(); - } - private void setSnaking(bool value) { AddStep($"{(value ? "Enable" : "Disable")} snaking", () => From c817cc726afdddd759dd4ee4379b2bf8b5548bb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Apr 2020 15:37:31 +0200 Subject: [PATCH 110/655] Rewrite repeat arrow test --- .../TestSceneSliderSnaking.cs | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 19b05f6b51..e26a91eb0e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -59,8 +59,6 @@ namespace osu.Game.Rulesets.Osu.Tests } private DrawableSlider slider; - private DrawableSliderRepeat repeat; - private Vector2 savedVector; [SetUpSteps] public override void SetUpSteps() { } @@ -112,31 +110,24 @@ namespace osu.Game.Rulesets.Osu.Tests } } - [TestCase(true)] - [TestCase(false)] - public void TestArrowMovement(bool isHit) + [Test] + public void TestRepeatArrowDoesNotMoveWhenHit() { - AddStep($"{(isHit ? "enable" : "disable")} autoplay", () => autoplay = isHit); + AddStep("enable autoplay", () => autoplay = true); setSnaking(true); base.SetUpSteps(); - addSeekStep(16500); - AddStep("retrieve 2nd slider repeat", () => - { - var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(1); - repeat = drawable.ChildrenOfType>().First().Children.First(); - }); - AddStep("Save repeat vector", () => savedVector = repeat.Position); - addSeekStep(16700); + checkPositionChange(16600, sliderRepeat, positionAlmostSame); + } - AddAssert($"Repeat vector {(isHit ? "is same" : "decreased")}", () => - { - if (isHit) - // Precision.AlmostEquals is used because repeat might have a chance to update its position depending on where in the frame its hit - return Precision.AlmostEquals(savedVector, repeat.Position, 1); + [Test] + public void TestRepeatArrowMovesWhenNotHit() + { + AddStep("disable autoplay", () => autoplay = false); + setSnaking(true); + base.SetUpSteps(); - return repeat.X < savedVector.X && repeat.Y < savedVector.Y; - }); + checkPositionChange(16600, sliderRepeat, positionDecreased); } private void retrieveSlider(int index) => AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => @@ -166,10 +157,17 @@ namespace osu.Game.Rulesets.Osu.Tests private List sliderCurve => ((PlaySliderBody)slider.Body.Drawable).CurrentCurve; private Vector2 sliderStart() => sliderCurve.First(); private Vector2 sliderEnd() => sliderCurve.Last(); + private Vector2 sliderRepeat() + { + var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(1); + var repeat = drawable.ChildrenOfType>().First().Children.First(); + return repeat.Position; + } private bool positionRemainsSame(Vector2 previous, Vector2 current) => previous == current; private bool positionIncreased(Vector2 previous, Vector2 current) => current.X > previous.X && current.Y > previous.Y; private bool positionDecreased(Vector2 previous, Vector2 current) => current.X < previous.X && current.Y < previous.Y; + private bool positionAlmostSame(Vector2 previous, Vector2 current) => Precision.AlmostEquals(previous, current, 1); private void checkPositionChange(double startTime, Func positionToCheck, Func positionAssertion) { From 7135c997466f0ed207390a8f96604b28fcf0d464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Apr 2020 15:39:32 +0200 Subject: [PATCH 111/655] Final cleanups --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index e26a91eb0e..2eee5c4825 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -157,6 +157,7 @@ namespace osu.Game.Rulesets.Osu.Tests private List sliderCurve => ((PlaySliderBody)slider.Body.Drawable).CurrentCurve; private Vector2 sliderStart() => sliderCurve.First(); private Vector2 sliderEnd() => sliderCurve.Last(); + private Vector2 sliderRepeat() { var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(1); @@ -188,7 +189,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void setSnaking(bool value) { - AddStep($"{(value ? "Enable" : "Disable")} snaking", () => + AddStep($"{(value ? "enable" : "disable")} snaking", () => { snakingIn.Value = value; snakingOut.Value = value; From f9e44ae53ee8b7116a87fc4fa43b18ccaf5f2a96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Apr 2020 15:53:56 +0200 Subject: [PATCH 112/655] Bring back comment about AlmostEquals --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 2eee5c4825..75adbd0987 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -117,6 +117,8 @@ namespace osu.Game.Rulesets.Osu.Tests setSnaking(true); base.SetUpSteps(); + // repeat might have a chance to update its position depending on where in the frame its hit, + // so some leniency is allowed here instead of checking strict equality checkPositionChange(16600, sliderRepeat, positionAlmostSame); } From 25c96744870547b4e0297c4b41495d73cd891e24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Apr 2020 15:54:15 +0200 Subject: [PATCH 113/655] Rename method to justify its existence better --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 75adbd0987..e320cfff45 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddUntilStep("wait for track to start running", () => track.IsRunning); double startTime = hitObjects[sliderIndex].StartTime; - retrieveSlider(sliderIndex); + retrieveDrawableSlider(sliderIndex); setSnaking(true); ensureSnakingIn(startTime + fade_in_modifier); @@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddUntilStep("wait for track to start running", () => track.IsRunning); double startTime = hitObjects[sliderIndex].StartTime; - retrieveSlider(sliderIndex); + retrieveDrawableSlider(sliderIndex); setSnaking(false); ensureNoSnakingIn(startTime + fade_in_modifier); @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Tests checkPositionChange(16600, sliderRepeat, positionDecreased); } - private void retrieveSlider(int index) => AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => + private void retrieveDrawableSlider(int index) => AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => { slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(index); }); From 3ff27816be8ac5d98bbbd4ee6aa90469271bc130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Apr 2020 15:54:50 +0200 Subject: [PATCH 114/655] Trim excess newlines --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index e320cfff45..f5b20fd1c5 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -233,7 +233,6 @@ namespace osu.Game.Rulesets.Osu.Tests }), RepeatCount = 1, }, - new Slider { StartTime = 23000, @@ -245,7 +244,6 @@ namespace osu.Game.Rulesets.Osu.Tests }), RepeatCount = 2, }, - new HitCircle { StartTime = 199999, From 1b76a53d329acbe4c3dc73800e867b69277e5a0a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Apr 2020 22:10:35 +0300 Subject: [PATCH 115/655] Move CatchTheBeat section handling to LegacySkinDecoder Best place to reside at --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 3 --- osu.Game/Skinning/LegacySkinDecoder.cs | 6 ++++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 743a470e6e..113526f9dd 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -73,9 +73,6 @@ namespace osu.Game.Beatmaps.Formats switch (section) { case Section.Colours: - // osu!catch section only has colour settings - // so no harm in handling the entire section - case Section.CatchTheBeat: HandleColours(output, line); return; } diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index 88ba7b23b7..b5734edacf 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.cs @@ -44,6 +44,12 @@ namespace osu.Game.Skinning } break; + + // osu!catch section only has colour settings + // so no harm in handling the entire section + case Section.CatchTheBeat: + HandleColours(skin, line); + return; } if (!string.IsNullOrEmpty(pair.Key)) From 7f3ad6d5be7b79358f6c1d9ed2c44c3e60a30dda Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Apr 2020 22:15:11 +0300 Subject: [PATCH 116/655] Move default colour fallback to the extension methods itself --- osu.Game.Rulesets.Catch/Skinning/CatchSkinExtensions.cs | 6 +++++- osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs | 3 +-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchSkinExtensions.cs b/osu.Game.Rulesets.Catch/Skinning/CatchSkinExtensions.cs index 8fc0831918..623f87bf11 100644 --- a/osu.Game.Rulesets.Catch/Skinning/CatchSkinExtensions.cs +++ b/osu.Game.Rulesets.Catch/Skinning/CatchSkinExtensions.cs @@ -1,7 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using JetBrains.Annotations; using osu.Framework.Bindables; +using osu.Game.Rulesets.Catch.UI; using osu.Game.Skinning; using osuTK.Graphics; @@ -9,8 +11,10 @@ namespace osu.Game.Rulesets.Catch.Skinning { internal static class CatchSkinExtensions { + [NotNull] public static IBindable GetHyperDashFruitColour(this ISkin skin) => skin.GetConfig(CatchSkinColour.HyperDashFruit) ?? - skin.GetConfig(CatchSkinColour.HyperDash); + skin.GetConfig(CatchSkinColour.HyperDash) ?? + new Bindable(Catcher.DefaultHyperDashColour); } } diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs index d8489399d2..470c12559e 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Catch.Objects.Drawables; -using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Skinning; using osuTK; @@ -57,7 +56,7 @@ namespace osu.Game.Rulesets.Catch.Skinning var hyperDash = new Sprite { Texture = skin.GetTexture(lookupName), - Colour = skin.GetHyperDashFruitColour()?.Value ?? Catcher.DefaultHyperDashColour, + Colour = skin.GetHyperDashFruitColour().Value, Anchor = Anchor.Centre, Origin = Anchor.Centre, Blending = BlendingParameters.Additive, From 0f11ecce018984ccac589d126052e35b7a500b3b Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 5 Apr 2020 14:53:49 -0700 Subject: [PATCH 117/655] Make icons container private --- osu.Game/Screens/Play/HUD/ModDisplay.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index cd15886c0b..99c31241f1 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Play.HUD } } - protected readonly FillFlowContainer IconsContainer; + private readonly FillFlowContainer iconsContainer; private readonly OsuSpriteText unrankedText; public ModDisplay() @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Play.HUD Children = new Drawable[] { - IconsContainer = new ReverseChildIDFillFlowContainer + iconsContainer = new ReverseChildIDFillFlowContainer { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -68,11 +68,11 @@ namespace osu.Game.Screens.Play.HUD Current.ValueChanged += mods => { - IconsContainer.Clear(); + iconsContainer.Clear(); foreach (Mod mod in mods.NewValue) { - IconsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.6f) }); + iconsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.6f) }); } if (IsLoaded) @@ -91,7 +91,7 @@ namespace osu.Game.Screens.Play.HUD base.LoadComplete(); appearTransform(); - IconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint); + iconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint); } private void appearTransform() @@ -103,20 +103,20 @@ namespace osu.Game.Screens.Play.HUD expand(); - using (IconsContainer.BeginDelayedSequence(1200)) + using (iconsContainer.BeginDelayedSequence(1200)) contract(); } private void expand() { if (ExpansionMode != ExpansionMode.AlwaysContracted) - IconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint); + iconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint); } private void contract() { if (ExpansionMode != ExpansionMode.AlwaysExpanded) - IconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, Easing.OutQuint); + iconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, Easing.OutQuint); } protected override bool OnHover(HoverEvent e) From 57b6a91449bd557530baee213fe82c2ecc5f9692 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 5 Apr 2020 14:57:44 -0700 Subject: [PATCH 118/655] Remove unnecessary input override on footer button mods Was used when it expanded on hover, but doesn't anymore. --- osu.Game/Screens/Select/FooterButtonMods.cs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index b18301c082..02333da0dc 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -27,18 +27,19 @@ namespace osu.Game.Screens.Select } protected readonly OsuSpriteText MultiplierText; - private readonly FooterModDisplay modDisplay; + private readonly ModDisplay modDisplay; private Color4 lowMultiplierColour; private Color4 highMultiplierColour; public FooterButtonMods() { - ButtonContentContainer.Add(modDisplay = new FooterModDisplay + ButtonContentContainer.Add(modDisplay = new ModDisplay { Anchor = Anchor.Centre, Origin = Anchor.Centre, DisplayUnrankedText = false, - Scale = new Vector2(0.8f) + Scale = new Vector2(0.8f), + ExpansionMode = ExpansionMode.AlwaysContracted, }); ButtonContentContainer.Add(MultiplierText = new OsuSpriteText { @@ -84,15 +85,5 @@ namespace osu.Game.Screens.Select else modDisplay.FadeOut(); } - - private class FooterModDisplay : ModDisplay - { - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.Parent?.ReceivePositionalInputAt(screenSpacePos) ?? false; - - public FooterModDisplay() - { - ExpansionMode = ExpansionMode.AlwaysContracted; - } - } } } From cfa2404626674b5ba37673bb8fe8007f1c016ad9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Apr 2020 12:39:49 +0900 Subject: [PATCH 119/655] Remove explicit specification of new default --- osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs index c87a1d438b..ce0b9fe4b6 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Skinning if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0) frameLength = Math.Max(1000 / 60.0, 170.0 / tmpAnimation.FrameCount); - explosion = skin.GetAnimation(imageName, true, false, startAtCurrentTime: true, frameLength: frameLength).With(d => + explosion = skin.GetAnimation(imageName, true, false, frameLength: frameLength).With(d => { if (d == null) return; From 66b8a8ad2eadf04b6dcd88b9ba9740044f4cf92e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Apr 2020 12:45:58 +0900 Subject: [PATCH 120/655] Remove stray default value specification --- .../Objects/Drawables/Connections/FollowPoint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index 8bb324d02e..a981648444 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections Anchor = Anchor.Centre, Alpha = 0.5f, } - }, confineMode: ConfineMode.NoScaling); + }); } public double AnimationStartTime { get; set; } From 33c64428a891bb01b85a2e88fb2111edd454acea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Apr 2020 13:04:32 +0900 Subject: [PATCH 121/655] Fix playback position being set incorrectly for IAnimationTimeReference --- osu.Game/Skinning/LegacySkinExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index 476e53bdaa..549571dec4 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -72,7 +72,7 @@ namespace osu.Game.Skinning if (timeReference != null) { Clock = timeReference.Clock; - PlaybackPosition = timeReference.AnimationStartTime - timeReference.Clock.CurrentTime; + PlaybackPosition = timeReference.Clock.CurrentTime - timeReference.AnimationStartTime; } } } From a4b4b7df211d2f2ddb80241c45dca36059513f11 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Apr 2020 13:04:46 +0900 Subject: [PATCH 122/655] Fix follow points not starting at correct time --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 487401c939..ba0003b5cd 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Skinning switch (osuComponent.Component) { case OsuSkinComponents.FollowPoint: - return this.GetAnimation(component.LookupName, true, false, true); + return this.GetAnimation(component.LookupName, true, false, true, startAtCurrentTime: false); case OsuSkinComponents.SliderFollowCircle: var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true); From 018244826221080e9c0bba050787001c3aa0f107 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Apr 2020 18:35:39 +0900 Subject: [PATCH 123/655] Fix performance when parsing mania skins --- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index 3393fe09b3..eb90225d1c 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -108,6 +108,8 @@ namespace osu.Game.Skinning break; } } + + pendingLines.Clear(); } private void parseArrayValue(string value, float[] output) From eff17c2da57f26fdafff02fc979e949abf7611c6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Apr 2020 19:02:50 +0900 Subject: [PATCH 124/655] Allow legacy skin textures from subpaths --- osu.Game/Skinning/LegacySkin.cs | 35 ++++++++++++-------- osu.Game/Skinning/LegacySkinResourceStore.cs | 3 +- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 3d3eac97f6..5e2d0fb25f 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -243,21 +243,24 @@ namespace osu.Game.Skinning public override Texture GetTexture(string componentName) { - componentName = getFallbackName(componentName); - - float ratio = 2; - var texture = Textures?.Get($"{componentName}@2x"); - - if (texture == null) + foreach (var name in getFallbackNames(componentName)) { - ratio = 1; - texture = Textures?.Get(componentName); + float ratio = 2; + var texture = Textures?.Get($"{name}@2x"); + + if (texture == null) + { + ratio = 1; + texture = Textures?.Get(name); + } + + if (texture != null) + texture.ScaleAdjust = ratio; + + return texture; } - if (texture != null) - texture.ScaleAdjust = ratio; - - return texture; + return null; } public override SampleChannel GetSample(ISampleInfo sampleInfo) @@ -277,10 +280,14 @@ namespace osu.Game.Skinning return null; } - private string getFallbackName(string componentName) + private IEnumerable getFallbackNames(string componentName) { + // May be something like "Gameplay/osu/approachcircle" from lazer, or "Arrows/note1" from a user skin. + yield return componentName; + + // Fall back to using the last piece for components coming from lazer (e.g. "Gameplay/osu/approachcircle" -> "approachcircle"). string lastPiece = componentName.Split('/').Last(); - return componentName.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece; + yield return componentName.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece; } } } diff --git a/osu.Game/Skinning/LegacySkinResourceStore.cs b/osu.Game/Skinning/LegacySkinResourceStore.cs index 249d48b34b..05d0dee05f 100644 --- a/osu.Game/Skinning/LegacySkinResourceStore.cs +++ b/osu.Game/Skinning/LegacySkinResourceStore.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Extensions; using osu.Framework.IO.Stores; using osu.Game.Database; @@ -27,7 +28,7 @@ namespace osu.Game.Skinning foreach (var filename in base.GetFilenames(name)) { - var path = getPathForFile(filename); + var path = getPathForFile(filename.ToStandardisedPath()); if (path != null) yield return path; } From 707a6269b3f4f153a1baf3b5272770b97475205e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Apr 2020 19:03:37 +0900 Subject: [PATCH 125/655] Fix incorrect key texture lookup --- osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index cbe2036343..78ea4b68ae 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Skinning { isLegacySkin = new Lazy(() => source.GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null); hasKeyTexture = new Lazy(() => source.GetAnimation( - source.GetConfig( + GetConfig( new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value ?? "mania-key1", true, true) != null); } From db6db861c069256915643494766c172dc3925cc9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Apr 2020 19:04:02 +0900 Subject: [PATCH 126/655] Implement mania note + key image configs --- .../Skinning/LegacyManiaSkinConfiguration.cs | 2 ++ osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 18 ++++++++----- osu.Game/Skinning/LegacySkin.cs | 27 +++++++++++++++++++ 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index ac257b8c80..603487a603 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -24,6 +24,8 @@ namespace osu.Game.Skinning public Dictionary CustomColours { get; set; } = new Dictionary(); + public Dictionary ImageLookups = new Dictionary(); + public readonly float[] ColumnLineWidth; public readonly float[] ColumnSpacing; public readonly float[] ColumnWidth; diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index 3393fe09b3..b23b115c14 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -71,12 +71,6 @@ namespace osu.Game.Skinning { var pair = SplitKeyVal(line); - if (pair.Key.StartsWith("Colour")) - { - HandleColours(currentConfig, line); - continue; - } - switch (pair.Key) { case "ColumnLineWidth": @@ -106,6 +100,18 @@ namespace osu.Game.Skinning case "LightingNWidth": parseArrayValue(pair.Value, currentConfig.ExplosionWidth); break; + + case string _ when pair.Key.StartsWith("Colour"): + HandleColours(currentConfig, line); + break; + + case string _ when pair.Key.StartsWith("NoteImage"): + currentConfig.ImageLookups[pair.Key] = pair.Value; + break; + + case string _ when pair.Key.StartsWith("KeyImage"): + currentConfig.ImageLookups[pair.Key] = pair.Value; + break; } } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 5e2d0fb25f..ff050d90e2 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -207,6 +207,30 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.ColumnLineColour: return SkinUtils.As(getCustomColour(existing, "ColourColumnLine")); + + case LegacyManiaSkinConfigurationLookups.NoteImage: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}")); + + case LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}H")); + + case LegacyManiaSkinConfigurationLookups.HoldNoteTailImage: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}T")); + + case LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}L")); + + case LegacyManiaSkinConfigurationLookups.KeyImage: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(getManiaImage(existing, $"KeyImage{maniaLookup.TargetColumn}")); + + case LegacyManiaSkinConfigurationLookups.KeyImageDown: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(getManiaImage(existing, $"KeyImage{maniaLookup.TargetColumn}D")); } return null; @@ -215,6 +239,9 @@ namespace osu.Game.Skinning private IBindable getCustomColour(IHasCustomColours source, string lookup) => source.CustomColours.TryGetValue(lookup, out var col) ? new Bindable(col) : null; + private IBindable getManiaImage(LegacyManiaSkinConfiguration source, string lookup) + => source.ImageLookups.TryGetValue(lookup, out var image) ? new Bindable(image) : null; + public override Drawable GetDrawableComponent(ISkinComponent component) { switch (component) From 8438ee7e0787fe472199571566482b3a21265b99 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Apr 2020 19:35:27 +0900 Subject: [PATCH 127/655] Improve testing --- .../Skins/TestSceneSkinConfigurationLookup.cs | 77 ++++++++++++++----- 1 file changed, 57 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs index 35313ee858..9c1a6a1346 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.IO; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -12,7 +13,10 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.Testing; using osu.Game.Audio; +using osu.Game.IO; +using osu.Game.Rulesets.Osu; using osu.Game.Skinning; +using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Visual; using osuTK.Graphics; @@ -22,15 +26,15 @@ namespace osu.Game.Tests.Skins [HeadlessTest] public class TestSceneSkinConfigurationLookup : OsuTestScene { - private SkinSource source1; - private SkinSource source2; + private UserSkinSource userSource; + private BeatmapSkinSource beatmapSource; private SkinRequester requester; [SetUp] public void SetUp() => Schedule(() => { - Add(new SkinProvidingContainer(source1 = new SkinSource()) - .WithChild(new SkinProvidingContainer(source2 = new SkinSource()) + Add(new SkinProvidingContainer(userSource = new UserSkinSource()) + .WithChild(new SkinProvidingContainer(beatmapSource = new BeatmapSkinSource()) .WithChild(requester = new SkinRequester()))); }); @@ -39,8 +43,8 @@ namespace osu.Game.Tests.Skins { AddStep("Add config values", () => { - source1.Configuration.ConfigDictionary["Lookup"] = "source1"; - source2.Configuration.ConfigDictionary["Lookup"] = "source2"; + userSource.Configuration.ConfigDictionary["Lookup"] = "source1"; + beatmapSource.Configuration.ConfigDictionary["Lookup"] = "source2"; }); AddAssert("Check lookup finds source2", () => requester.GetConfig("Lookup")?.Value == "source2"); @@ -49,21 +53,21 @@ namespace osu.Game.Tests.Skins [Test] public void TestFloatLookup() { - AddStep("Add config values", () => source1.Configuration.ConfigDictionary["FloatTest"] = "1.1"); + AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["FloatTest"] = "1.1"); AddAssert("Check float parse lookup", () => requester.GetConfig("FloatTest")?.Value == 1.1f); } [Test] public void TestBoolLookup() { - AddStep("Add config values", () => source1.Configuration.ConfigDictionary["BoolTest"] = "1"); + AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["BoolTest"] = "1"); AddAssert("Check bool parse lookup", () => requester.GetConfig("BoolTest")?.Value == true); } [Test] public void TestEnumLookup() { - AddStep("Add config values", () => source1.Configuration.ConfigDictionary["Test"] = "Test2"); + AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["Test"] = "Test2"); AddAssert("Check enum parse lookup", () => requester.GetConfig(LookupType.Test)?.Value == ValueType.Test2); } @@ -76,7 +80,7 @@ namespace osu.Game.Tests.Skins [Test] public void TestLookupNull() { - AddStep("Add config values", () => source1.Configuration.ConfigDictionary["Lookup"] = null); + AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["Lookup"] = null); AddAssert("Check lookup null", () => { @@ -88,7 +92,7 @@ namespace osu.Game.Tests.Skins [Test] public void TestColourLookup() { - AddStep("Add config colour", () => source1.Configuration.CustomColours["Lookup"] = Color4.Red); + AddStep("Add config colour", () => userSource.Configuration.CustomColours["Lookup"] = Color4.Red); AddAssert("Check colour lookup", () => requester.GetConfig(new SkinCustomColourLookup("Lookup"))?.Value == Color4.Red); } @@ -101,7 +105,7 @@ namespace osu.Game.Tests.Skins [Test] public void TestWrongColourType() { - AddStep("Add config colour", () => source1.Configuration.CustomColours["Lookup"] = Color4.Red); + AddStep("Add config colour", () => userSource.Configuration.CustomColours["Lookup"] = Color4.Red); AddAssert("perform incorrect lookup", () => { @@ -127,26 +131,51 @@ namespace osu.Game.Tests.Skins [Test] public void TestEmptyComboColoursNoFallback() { - AddStep("Add custom combo colours to source1", () => source1.Configuration.AddComboColours( + AddStep("Add custom combo colours to source1", () => userSource.Configuration.AddComboColours( new Color4(100, 150, 200, 255), new Color4(55, 110, 166, 255), new Color4(75, 125, 175, 255) )); - AddStep("Disallow default colours fallback in source2", () => source2.Configuration.AllowDefaultComboColoursFallback = false); + AddStep("Disallow default colours fallback in source2", () => beatmapSource.Configuration.AllowDefaultComboColoursFallback = false); AddAssert("Check retrieved combo colours from source1", () => - requester.GetConfig>(GlobalSkinColours.ComboColours)?.Value?.SequenceEqual(source1.Configuration.ComboColours) ?? false); + requester.GetConfig>(GlobalSkinColours.ComboColours)?.Value?.SequenceEqual(userSource.Configuration.ComboColours) ?? false); } [Test] - public void TestLegacyVersionLookup() + public void TestNullBeatmapVersionFallsBackToUserSkin() { - AddStep("Set source1 version 2.3", () => source1.Configuration.LegacyVersion = 2.3m); - AddStep("Set source2 version null", () => source2.Configuration.LegacyVersion = null); + AddStep("Set source1 version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m); + AddStep("Set source2 version null", () => beatmapSource.Configuration.LegacyVersion = null); AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 2.3m); } + [Test] + public void TestSetBeatmapVersionNoFallback() + { + AddStep("Set source1 version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m); + AddStep("Set source2 version null", () => beatmapSource.Configuration.LegacyVersion = 1.7m); + AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 1.7m); + } + + [Test] + public void TestNullBeatmapAndUserVersionFallsBackToLatest() + { + AddStep("Set source1 version 2.3", () => userSource.Configuration.LegacyVersion = null); + AddStep("Set source2 version null", () => beatmapSource.Configuration.LegacyVersion = null); + AddAssert("Check legacy version lookup", + () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == LegacySkinConfiguration.LATEST_VERSION); + } + + [Test] + public void TestIniWithNoVersionFallsBackTo1() + { + AddStep("Parse skin with no version", () => userSource.Configuration = new LegacySkinDecoder().Decode(new LineBufferedReader(new MemoryStream()))); + AddStep("Set source2 version null", () => beatmapSource.Configuration.LegacyVersion = null); + AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 1.0m); + } + public enum LookupType { Test @@ -159,14 +188,22 @@ namespace osu.Game.Tests.Skins Test3 } - public class SkinSource : LegacySkin + public class UserSkinSource : LegacySkin { - public SkinSource() + public UserSkinSource() : base(new SkinInfo(), null, null, string.Empty) { } } + public class BeatmapSkinSource : LegacyBeatmapSkin + { + public BeatmapSkinSource() + : base(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, null, null) + { + } + } + public class SkinRequester : Drawable, ISkin { private ISkinSource skin; From a4208f35c49c55ea1bbe4907d374e17a497cde30 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Apr 2020 19:36:04 +0900 Subject: [PATCH 128/655] Make versionless skins fallback to version 1.0 --- osu.Game.Tests/Skins/LegacySkinDecoderTest.cs | 2 +- osu.Game/Skinning/LegacyBeatmapSkin.cs | 15 +++++++++++++++ osu.Game/Skinning/LegacySkin.cs | 7 ++----- osu.Game/Skinning/LegacySkinDecoder.cs | 7 +++++++ 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs index cef38bbbb8..aedf26ee75 100644 --- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs +++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs @@ -106,7 +106,7 @@ namespace osu.Game.Tests.Skins var decoder = new LegacySkinDecoder(); using (var resStream = TestResources.OpenResource("skin-empty.ini")) using (var stream = new LineBufferedReader(resStream)) - Assert.IsNull(decoder.Decode(stream).LegacyVersion); + Assert.That(decoder.Decode(stream).LegacyVersion, Is.EqualTo(1.0m)); } } } diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 1c39fc41bb..1190a330fe 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.IO.Stores; using osu.Game.Beatmaps; @@ -18,6 +19,20 @@ namespace osu.Game.Skinning Configuration.AllowDefaultComboColoursFallback = false; } + public override IBindable GetConfig(TLookup lookup) + { + switch (lookup) + { + case LegacySkinConfiguration.LegacySetting s when s == LegacySkinConfiguration.LegacySetting.Version: + if (Configuration.LegacyVersion is decimal version) + return SkinUtils.As(new Bindable(version)); + + return null; + } + + return base.GetConfig(lookup); + } + private static SkinInfo createSkinInfo(BeatmapInfo beatmap) => new SkinInfo { Name = beatmap.ToString(), Creator = beatmap.Metadata.Author.ToString() }; } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 3d3eac97f6..a68ae11288 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -71,7 +71,7 @@ namespace osu.Game.Skinning } } else - Configuration = new LegacySkinConfiguration { LegacyVersion = LegacySkinConfiguration.LATEST_VERSION }; + Configuration = new LegacySkinConfiguration(); } if (storage != null) @@ -122,10 +122,7 @@ namespace osu.Game.Skinning switch (legacy) { case LegacySkinConfiguration.LegacySetting.Version: - if (Configuration.LegacyVersion is decimal version) - return SkinUtils.As(new Bindable(version)); - - break; + return SkinUtils.As(new Bindable(Configuration.LegacyVersion ?? LegacySkinConfiguration.LATEST_VERSION)); } break; diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index 88ba7b23b7..5d4b8de7ac 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.cs @@ -52,5 +52,12 @@ namespace osu.Game.Skinning base.ParseLine(skin, section, line); } + + protected override LegacySkinConfiguration CreateTemplateObject() + { + var config = base.CreateTemplateObject(); + config.LegacyVersion = 1.0m; + return config; + } } } From 00f390c850e31f86c3fbe5df759524bfb9e4bb5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Apr 2020 20:13:53 +0900 Subject: [PATCH 129/655] 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 77365b51a9..161a15fa4e 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8d31fbf280..84c3c0ec8d 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,7 +22,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index e2b98720be..7a894facce 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -79,7 +79,7 @@ - + From 9ed0560da30f8af0f729b48395316899fe3e413b Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2020 11:37:56 +0000 Subject: [PATCH 130/655] Bump SharpCompress from 0.24.0 to 0.25.0 Bumps [SharpCompress](https://github.com/adamhathcock/sharpcompress) from 0.24.0 to 0.25.0. - [Release notes](https://github.com/adamhathcock/sharpcompress/releases) - [Commits](https://github.com/adamhathcock/sharpcompress/compare/0.24...0.25) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 84c3c0ec8d..c62aec7250 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -25,7 +25,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 7a894facce..834c0ee956 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -80,7 +80,7 @@ - + From 678ac0f9e1f7e3a5378aa65386ee7cc428d11825 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2020 12:08:26 +0000 Subject: [PATCH 131/655] Bump Microsoft.Build.Traversal from 2.0.32 to 2.0.34 Bumps [Microsoft.Build.Traversal](https://github.com/Microsoft/MSBuildSdks) from 2.0.32 to 2.0.34. - [Release notes](https://github.com/Microsoft/MSBuildSdks/releases) - [Changelog](https://github.com/microsoft/MSBuildSdks/blob/master/RELEASE.md) - [Commits](https://github.com/Microsoft/MSBuildSdks/compare/Microsoft.Build.Traversal.2.0.32...Microsoft.Build.Traversal.2.0.34) Signed-off-by: dependabot-preview[bot] --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index 0223dc7330..6c793a3f1d 100644 --- a/global.json +++ b/global.json @@ -5,6 +5,6 @@ "version": "3.1.100" }, "msbuild-sdks": { - "Microsoft.Build.Traversal": "2.0.32" + "Microsoft.Build.Traversal": "2.0.34" } } \ No newline at end of file From b7308f5ed479623e67150e9886450436617410d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Apr 2020 00:26:38 +0900 Subject: [PATCH 132/655] Fix storyboard videos being offset incorrectly --- osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index 2e7b66ea4f..a85936edf7 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -46,7 +46,6 @@ namespace osu.Game.Storyboards.Drawables Anchor = Anchor.Centre, Origin = Anchor.Centre, Alpha = 0, - PlaybackPosition = Video.StartTime }; } @@ -56,6 +55,8 @@ namespace osu.Game.Storyboards.Drawables if (video == null) return; + video.PlaybackPosition = Clock.CurrentTime - Video.StartTime; + using (video.BeginAbsoluteSequence(0)) video.FadeIn(500); } From 5dfa2a2bad5121104a5ada46c148c6bb139ebd6c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 11:50:40 +0900 Subject: [PATCH 133/655] Fix step namings --- .../Skins/TestSceneSkinConfigurationLookup.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs index 9c1a6a1346..685decf097 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs @@ -43,11 +43,11 @@ namespace osu.Game.Tests.Skins { AddStep("Add config values", () => { - userSource.Configuration.ConfigDictionary["Lookup"] = "source1"; - beatmapSource.Configuration.ConfigDictionary["Lookup"] = "source2"; + userSource.Configuration.ConfigDictionary["Lookup"] = "user skin"; + beatmapSource.Configuration.ConfigDictionary["Lookup"] = "beatmap skin"; }); - AddAssert("Check lookup finds source2", () => requester.GetConfig("Lookup")?.Value == "source2"); + AddAssert("Check lookup finds beatmap skin", () => requester.GetConfig("Lookup")?.Value == "beatmap skin"); } [Test] @@ -131,39 +131,39 @@ namespace osu.Game.Tests.Skins [Test] public void TestEmptyComboColoursNoFallback() { - AddStep("Add custom combo colours to source1", () => userSource.Configuration.AddComboColours( + AddStep("Add custom combo colours to user skin", () => userSource.Configuration.AddComboColours( new Color4(100, 150, 200, 255), new Color4(55, 110, 166, 255), new Color4(75, 125, 175, 255) )); - AddStep("Disallow default colours fallback in source2", () => beatmapSource.Configuration.AllowDefaultComboColoursFallback = false); + AddStep("Disallow default colours fallback in beatmap skin", () => beatmapSource.Configuration.AllowDefaultComboColoursFallback = false); - AddAssert("Check retrieved combo colours from source1", () => + AddAssert("Check retrieved combo colours from user skin", () => requester.GetConfig>(GlobalSkinColours.ComboColours)?.Value?.SequenceEqual(userSource.Configuration.ComboColours) ?? false); } [Test] public void TestNullBeatmapVersionFallsBackToUserSkin() { - AddStep("Set source1 version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m); - AddStep("Set source2 version null", () => beatmapSource.Configuration.LegacyVersion = null); + AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m); + AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = null); AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 2.3m); } [Test] public void TestSetBeatmapVersionNoFallback() { - AddStep("Set source1 version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m); - AddStep("Set source2 version null", () => beatmapSource.Configuration.LegacyVersion = 1.7m); + AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m); + AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = 1.7m); AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 1.7m); } [Test] public void TestNullBeatmapAndUserVersionFallsBackToLatest() { - AddStep("Set source1 version 2.3", () => userSource.Configuration.LegacyVersion = null); - AddStep("Set source2 version null", () => beatmapSource.Configuration.LegacyVersion = null); + AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = null); + AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = null); AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == LegacySkinConfiguration.LATEST_VERSION); } @@ -172,7 +172,7 @@ namespace osu.Game.Tests.Skins public void TestIniWithNoVersionFallsBackTo1() { AddStep("Parse skin with no version", () => userSource.Configuration = new LegacySkinDecoder().Decode(new LineBufferedReader(new MemoryStream()))); - AddStep("Set source2 version null", () => beatmapSource.Configuration.LegacyVersion = null); + AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = null); AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 1.0m); } From 8506029237cc57cb8f5b9f457daf5185eb271325 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Apr 2020 13:46:37 +0900 Subject: [PATCH 134/655] Fix SkinnableTestScene losing test resources on dynamic recompilation --- osu.Game/Tests/Visual/SkinnableTestScene.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index d0113b3096..71d3266d18 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -26,6 +26,9 @@ namespace osu.Game.Tests.Visual private Skin specialSkin; private Skin oldSkin; + // Keep a static reference to ensure we don't use a dynamically recompiled DLL as a source (resources will be missing). + private static DllResourceStore dllStore; + protected SkinnableTestScene() : base(2, 3) { @@ -34,7 +37,7 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load(AudioManager audio, SkinManager skinManager) { - var dllStore = new DllResourceStore(GetType().Assembly); + dllStore ??= new DllResourceStore(GetType().Assembly); metricsSkin = new TestLegacySkin(new SkinInfo { Name = "metrics-skin" }, new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), audio, true); defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info); From c46ea7bdef8a42c0f06a8920e60df8a6a4d8dea5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 14:49:24 +0900 Subject: [PATCH 135/655] Add disposal, prevent memory leaks --- osu.Game/Online/Leaderboards/TopLocalRank.cs | 30 +++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/osu.Game/Online/Leaderboards/TopLocalRank.cs b/osu.Game/Online/Leaderboards/TopLocalRank.cs index be014dafc3..f355a907af 100644 --- a/osu.Game/Online/Leaderboards/TopLocalRank.cs +++ b/osu.Game/Online/Leaderboards/TopLocalRank.cs @@ -15,9 +15,14 @@ namespace osu.Game.Online.Leaderboards { private readonly BeatmapInfo beatmap; - private ScoreManager scores; - private IBindable ruleset; - private IAPIProvider api; + [Resolved] + private ScoreManager scores { get; set; } + + [Resolved] + private IBindable ruleset { get; set; } + + [Resolved] + private IAPIProvider api { get; set; } protected override double LoadDelay => 250; @@ -28,25 +33,19 @@ namespace osu.Game.Online.Leaderboards } [BackgroundDependencyLoader] - private void load(ScoreManager scores, IBindable ruleset, IAPIProvider api) + private void load() { scores.ItemAdded += scoreChanged; scores.ItemRemoved += scoreChanged; ruleset.ValueChanged += _ => fetchAndLoadTopScore(); - this.ruleset = ruleset.GetBoundCopy(); - this.scores = scores; - this.api = api; - fetchAndLoadTopScore(); } private void scoreChanged(ScoreInfo score) { if (score.BeatmapInfoID == beatmap.ID) - { fetchAndLoadTopScore(); - } } private void fetchAndLoadTopScore() @@ -79,5 +78,16 @@ namespace osu.Game.Online.Leaderboards .OrderByDescending(s => s.TotalScore) .FirstOrDefault(); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (scores != null) + { + scores.ItemAdded -= scoreChanged; + scores.ItemRemoved -= scoreChanged; + } + } } } From 933314d724169677f5ec39a071dc009134d8b355 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 14:50:11 +0900 Subject: [PATCH 136/655] Remove unnecessary method --- osu.Game/Online/Leaderboards/TopLocalRank.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game/Online/Leaderboards/TopLocalRank.cs b/osu.Game/Online/Leaderboards/TopLocalRank.cs index f355a907af..3e77549851 100644 --- a/osu.Game/Online/Leaderboards/TopLocalRank.cs +++ b/osu.Game/Online/Leaderboards/TopLocalRank.cs @@ -50,14 +50,7 @@ namespace osu.Game.Online.Leaderboards private void fetchAndLoadTopScore() { - var score = fetchTopScore(); - - loadTopScore(score); - } - - private void loadTopScore(ScoreInfo score) - { - var rank = score?.Rank; + var rank = fetchTopScore()?.Rank; // toggle the display of this drawable // we do not want empty space if there is no rank to be displayed From ed17a1c99016cfd1668d2e8be9158a95ea3bcf7e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 15:30:06 +0900 Subject: [PATCH 137/655] Improve visual display --- osu.Game/Online/Leaderboards/TopLocalRank.cs | 23 +++++++++++--------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game/Online/Leaderboards/TopLocalRank.cs b/osu.Game/Online/Leaderboards/TopLocalRank.cs index 3e77549851..345e8cb221 100644 --- a/osu.Game/Online/Leaderboards/TopLocalRank.cs +++ b/osu.Game/Online/Leaderboards/TopLocalRank.cs @@ -4,6 +4,8 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Rulesets; @@ -24,8 +26,6 @@ namespace osu.Game.Online.Leaderboards [Resolved] private IAPIProvider api { get; set; } - protected override double LoadDelay => 250; - public TopLocalRank(BeatmapInfo beatmap) : base(null) { @@ -48,20 +48,23 @@ namespace osu.Game.Online.Leaderboards fetchAndLoadTopScore(); } + private ScheduledDelegate scheduledRankUpdate; + private void fetchAndLoadTopScore() { var rank = fetchTopScore()?.Rank; + scheduledRankUpdate = Schedule(() => + { + Rank = rank; - // toggle the display of this drawable - // we do not want empty space if there is no rank to be displayed - if (rank.HasValue) - Show(); - else - Hide(); - - Schedule(() => Rank = rank); + // Required since presence is changed via IsPresent override + Invalidate(Invalidation.Presence); + }); } + // We're present if a rank is set, or if there is a pending rank update (IsPresent = true is required for the scheduler to run). + public override bool IsPresent => base.IsPresent && (Rank != null || scheduledRankUpdate?.Completed == false); + private ScoreInfo fetchTopScore() { if (scores == null || beatmap == null || ruleset?.Value == null || api?.LocalUser.Value == null) From ed3e0a01e162720cf3c40482085f11cd1263179f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 15:31:22 +0900 Subject: [PATCH 138/655] Re-namespace into song select --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 1 - .../Leaderboards => Screens/Select/Carousel}/TopLocalRank.cs | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game/{Online/Leaderboards => Screens/Select/Carousel}/TopLocalRank.cs (97%) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 5357f9a652..2520c70989 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -19,7 +19,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Online.Leaderboards; using osu.Game.Overlays; using osuTK; using osuTK.Graphics; diff --git a/osu.Game/Online/Leaderboards/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs similarity index 97% rename from osu.Game/Online/Leaderboards/TopLocalRank.cs rename to osu.Game/Screens/Select/Carousel/TopLocalRank.cs index 345e8cb221..e981550c84 100644 --- a/osu.Game/Online/Leaderboards/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -8,10 +8,11 @@ using osu.Framework.Graphics; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Online.API; +using osu.Game.Online.Leaderboards; using osu.Game.Rulesets; using osu.Game.Scoring; -namespace osu.Game.Online.Leaderboards +namespace osu.Game.Screens.Select.Carousel { public class TopLocalRank : UpdateableRank { From 3ecb99462fce3ac20e5e3aefd5bc909de6c99a81 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 16:07:18 +0900 Subject: [PATCH 139/655] Make note height scale by minimum column width --- osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs | 10 ++++++++-- osu.Game/Skinning/LegacyManiaSkinConfiguration.cs | 9 +++++++++ .../Skinning/LegacyManiaSkinConfigurationLookup.cs | 3 ++- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 4 ++++ osu.Game/Skinning/LegacySkin.cs | 3 +++ 5 files changed, 26 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs index d2ceb06d0b..85523ae3c0 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs @@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Mania.Skinning private Container directionContainer; private Sprite noteSprite; + private float? minimumColumnWidth; + public LegacyNotePiece() { RelativeSizeAxes = Axes.X; @@ -29,6 +31,8 @@ namespace osu.Game.Rulesets.Mania.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin, IScrollingInfo scrollingInfo) { + minimumColumnWidth = skin.GetConfig(new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.MinimumColumnWidth))?.Value; + InternalChild = directionContainer = new Container { Origin = Anchor.BottomCentre, @@ -47,8 +51,10 @@ namespace osu.Game.Rulesets.Mania.Skinning if (noteSprite.Texture != null) { - var scale = DrawWidth / noteSprite.Texture.DisplayWidth; - noteSprite.Scale = new Vector2(scale); + // The height is scaled to the minimum column width, if provided. + float minimumWidth = minimumColumnWidth ?? DrawWidth; + + noteSprite.Scale = Vector2.Divide(new Vector2(DrawWidth, minimumWidth), noteSprite.Texture.DisplayWidth); } } diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index ac257b8c80..08b3b8ff5a 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Game.Beatmaps.Formats; using osuTK.Graphics; @@ -45,5 +46,13 @@ namespace osu.Game.Skinning ColumnLineWidth.AsSpan().Fill(2); ColumnWidth.AsSpan().Fill(DEFAULT_COLUMN_SIZE); } + + private float? minimumColumnWidth; + + public float MinimumColumnWidth + { + get => minimumColumnWidth ?? ColumnWidth.Min(); + set => minimumColumnWidth = value; + } } } diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index 853d07c060..588e9e3ee2 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -36,6 +36,7 @@ namespace osu.Game.Skinning HoldNoteBodyImage, ExplosionImage, ExplosionScale, - ColumnLineColour + ColumnLineColour, + MinimumColumnWidth } } diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index eb90225d1c..4fe36c2239 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -106,6 +106,10 @@ namespace osu.Game.Skinning case "LightingNWidth": parseArrayValue(pair.Value, currentConfig.ExplosionWidth); break; + + case "WidthForNoteHeightScale": + currentConfig.MinimumColumnWidth = float.Parse(pair.Value, CultureInfo.InvariantCulture) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; + break; } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 3d3eac97f6..d5ef5220cf 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -207,6 +207,9 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.ColumnLineColour: return SkinUtils.As(getCustomColour(existing, "ColourColumnLine")); + + case LegacyManiaSkinConfigurationLookups.MinimumColumnWidth: + return SkinUtils.As(new Bindable(existing.MinimumColumnWidth)); } return null; From 2c840c52a3dfe841e4e77dcf6b4579e9165c65a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Apr 2020 15:38:29 +0900 Subject: [PATCH 140/655] Add skinnable test scene per ruleset to better allow dynamic compilation --- .../CatchSkinnableTestScene.cs | 19 +++++++++++++++++ .../TestSceneCatcher.cs | 8 +++---- .../TestSceneCatcherArea.cs | 3 +-- .../TestSceneFruitObjects.cs | 8 +++---- .../Skinning/ManiaSkinnableTestScene.cs | 9 ++++++++ .../OsuSkinnableTestScene.cs | 19 +++++++++++++++++ .../TestSceneDrawableJudgement.cs | 7 +++---- .../TestSceneGameplayCursor.cs | 14 +++++++++---- .../TestSceneHitCircle.cs | 3 +-- .../TestSceneSlider.cs | 3 +-- .../TaikoSkinnableTestScene.cs | 21 +++++++++++++++++++ .../TestSceneInputDrum.cs | 10 +++++---- 12 files changed, 98 insertions(+), 26 deletions(-) create mode 100644 osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs create mode 100644 osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs create mode 100644 osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs diff --git a/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs b/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs new file mode 100644 index 0000000000..f7f1a8d58f --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Game.Rulesets.Catch.Skinning; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Catch.Tests +{ + public abstract class CatchSkinnableTestScene : SkinnableTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(CatchRuleset), + typeof(CatchLegacySkinTransformer), + }; + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index fe0d512166..acc5f4e428 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -4,21 +4,21 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Rulesets.Catch.UI; -using osu.Game.Tests.Visual; using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Graphics; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestSceneCatcher : SkinnableTestScene + public class TestSceneCatcher : CatchSkinnableTestScene { - public override IReadOnlyList RequiredTypes => new[] + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(CatcherArea), typeof(CatcherSprite) - }; + }).ToList(); [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs index cf68c5424d..2b30edb70b 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs @@ -17,12 +17,11 @@ using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestSceneCatcherArea : SkinnableTestScene + public class TestSceneCatcherArea : CatchSkinnableTestScene { private RulesetInfo catchRuleset; diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs index 82d5aa936f..cd674bb754 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs @@ -3,20 +3,20 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; -using osu.Game.Tests.Visual; using osuTK; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestSceneFruitObjects : SkinnableTestScene + public class TestSceneFruitObjects : CatchSkinnableTestScene { - public override IReadOnlyList RequiredTypes => new[] + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(CatchHitObject), typeof(Fruit), @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Tests typeof(DrawableBanana), typeof(DrawableBananaShower), typeof(Pulp), - }; + }).ToList(); protected override void LoadComplete() { diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs index eaa2a56e36..009e609c56 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs @@ -1,12 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Mania.Skinning; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling.Algorithms; using osu.Game.Tests.Visual; @@ -24,6 +27,12 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning [Cached(Type = typeof(IScrollingInfo))] private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ManiaRuleset), + typeof(ManiaLegacySkinTransformer), + }; + protected ManiaSkinnableTestScene() { scrollingInfo.Direction.Value = ScrollingDirection.Down; diff --git a/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs new file mode 100644 index 0000000000..929ce5dcc0 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Game.Rulesets.Osu.Skinning; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public abstract class OsuSkinnableTestScene : SkinnableTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OsuRuleset), + typeof(OsuLegacySkinTransformer), + }; + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs index 02d4406809..f867630df6 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs @@ -10,17 +10,16 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Scoring; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { - public class TestSceneDrawableJudgement : SkinnableTestScene + public class TestSceneDrawableJudgement : OsuSkinnableTestScene { - public override IReadOnlyList RequiredTypes => new[] + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(DrawableJudgement), typeof(DrawableOsuJudgement) - }; + }).ToList(); public TestSceneDrawableJudgement() { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index 7b96e2ec6a..22dacc6f5e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -3,26 +3,32 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing.Input; using osu.Game.Configuration; +using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; -using osu.Game.Tests.Visual; using osuTK; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestSceneGameplayCursor : SkinnableTestScene + public class TestSceneGameplayCursor : OsuSkinnableTestScene { - public override IReadOnlyList RequiredTypes => new[] + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { + typeof(GameplayCursorContainer), typeof(OsuCursorContainer), + typeof(OsuCursor), + typeof(LegacyCursor), + typeof(LegacyCursorTrail), typeof(CursorTrail) - }; + }).ToList(); [Cached] private GameplayBeatmap gameplayBeatmap; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index ae5a28217c..e117729f01 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -14,12 +14,11 @@ using osu.Game.Rulesets.Mods; using System.Linq; using NUnit.Framework; using osu.Game.Rulesets.Scoring; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestSceneHitCircle : SkinnableTestScene + public class TestSceneHitCircle : OsuSkinnableTestScene { public override IReadOnlyList RequiredTypes => new[] { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index a201364de4..eb6130c8a6 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -22,12 +22,11 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestSceneSlider : SkinnableTestScene + public class TestSceneSlider : OsuSkinnableTestScene { public override IReadOnlyList RequiredTypes => new[] { diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs new file mode 100644 index 0000000000..6db2a6907f --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Game.Rulesets.Taiko.Skinning; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public abstract class TaikoSkinnableTestScene : SkinnableTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(TaikoRuleset), + typeof(TaikoLegacySkinTransformer), + }; + + protected override Ruleset CreateRulesetForSkinProvider() => new TaikoRuleset(); + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs index c79088056f..1928e9f66f 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs @@ -3,24 +3,26 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Taiko.Skinning; using osu.Game.Rulesets.Taiko.UI; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests { [TestFixture] - public class TestSceneInputDrum : SkinnableTestScene + public class TestSceneInputDrum : TaikoSkinnableTestScene { - public override IReadOnlyList RequiredTypes => new[] + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(InputDrum), - }; + typeof(LegacyInputDrum), + }).ToList(); [BackgroundDependencyLoader] private void load() From 0a340bac5a48abce060e0ee88b9ed86a3bd9c5c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Apr 2020 16:18:24 +0900 Subject: [PATCH 141/655] Ensure the correct (up-to-date) ruleset is retrieved --- osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs | 2 ++ .../Skinning/ManiaSkinnableTestScene.cs | 2 ++ osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs | 2 ++ osu.Game/Tests/Visual/SkinnableTestScene.cs | 6 +++++- 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs b/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs index f7f1a8d58f..0c46b078b5 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs @@ -15,5 +15,7 @@ namespace osu.Game.Rulesets.Catch.Tests typeof(CatchRuleset), typeof(CatchLegacySkinTransformer), }; + + protected override Ruleset CreateRulesetForSkinProvider() => new CatchRuleset(); } } diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs index 009e609c56..7f0503913f 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs @@ -33,6 +33,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning typeof(ManiaLegacySkinTransformer), }; + protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset(); + protected ManiaSkinnableTestScene() { scrollingInfo.Direction.Value = ScrollingDirection.Down; diff --git a/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs index 929ce5dcc0..90ebbd9f04 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs @@ -15,5 +15,7 @@ namespace osu.Game.Rulesets.Osu.Tests typeof(OsuRuleset), typeof(OsuLegacySkinTransformer), }; + + protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); } } diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index 71d3266d18..50cc5b6c5c 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -34,6 +35,9 @@ namespace osu.Game.Tests.Visual { } + // Required to be part of the per-ruleset implementation to construct the newer version of the Ruleset. + protected abstract Ruleset CreateRulesetForSkinProvider(); + [BackgroundDependencyLoader] private void load(AudioManager audio, SkinManager skinManager) { @@ -106,7 +110,7 @@ namespace osu.Game.Tests.Visual { new OutlineBox { Alpha = autoSize ? 1 : 0 }, mainProvider.WithChild( - new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider, beatmap)) + new SkinProvidingContainer(CreateRulesetForSkinProvider().CreateLegacySkinProvider(mainProvider, beatmap)) { Child = created, RelativeSizeAxes = !autoSize ? Axes.Both : Axes.None, From 9071bf5cbb72bdc02bffd822620ddfa19efc75c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Apr 2020 16:18:34 +0900 Subject: [PATCH 142/655] Fix mania test scene not using mania skinnable test scene --- .../Skinning/TestSceneDrawableJudgement.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs index a6bc64550f..6ab8a68176 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs @@ -10,11 +10,10 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests.Skinning { - public class TestSceneDrawableJudgement : SkinnableTestScene + public class TestSceneDrawableJudgement : ManiaSkinnableTestScene { public override IReadOnlyList RequiredTypes => new[] { From 9cfeb60afc96f1dbdb9abc6a026cad59436aebd9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 16:30:58 +0900 Subject: [PATCH 143/655] Fix missed speed removal in mania --- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index c8c537964f..14cad39b04 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -64,6 +64,7 @@ namespace osu.Game.Rulesets.Mania.UI { // Mania doesn't care about global velocity p.Velocity = 1; + p.BaseBeatLength *= Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier; // For non-mania beatmap, speed changes should only happen through timing points if (!isForCurrentRuleset) From 9fd73492ca7c354668699620744d22bfdc0f36e4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 16:50:08 +0900 Subject: [PATCH 144/655] Implement judgement line colour --- osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs | 5 +++++ osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs | 3 ++- osu.Game/Skinning/LegacySkin.cs | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs index 53e4f3cd14..40752d3f4b 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning { @@ -33,6 +34,9 @@ namespace osu.Game.Rulesets.Mania.Skinning bool showJudgementLine = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ShowJudgementLine)?.Value ?? true; + Color4 lineColour = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.JudgementLineColour)?.Value + ?? Color4.White; + InternalChild = directionContainer = new Container { Origin = Anchor.CentreLeft, @@ -52,6 +56,7 @@ namespace osu.Game.Rulesets.Mania.Skinning Anchor = Anchor.CentreLeft, RelativeSizeAxes = Axes.X, Height = 1, + Colour = lineColour, Alpha = showJudgementLine ? 0.9f : 0 } } diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index 853d07c060..ee5db2a77f 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -36,6 +36,7 @@ namespace osu.Game.Skinning HoldNoteBodyImage, ExplosionImage, ExplosionScale, - ColumnLineColour + ColumnLineColour, + JudgementLineColour, } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 3d3eac97f6..f206bc792d 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -207,6 +207,9 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.ColumnLineColour: return SkinUtils.As(getCustomColour(existing, "ColourColumnLine")); + + case LegacyManiaSkinConfigurationLookups.JudgementLineColour: + return SkinUtils.As(getCustomColour(existing, "ColourJudgementLine")); } return null; From 11d58fb7f61ccfd17bd96d080886cd2853feeb53 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 16:53:29 +0900 Subject: [PATCH 145/655] Implement column background and light colours --- .../Skinning/LegacyColumnBackground.cs | 9 ++++++++- .../Skinning/LegacyManiaSkinConfigurationLookup.cs | 2 ++ osu.Game/Skinning/LegacySkin.cs | 11 ++++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs index 8cd0272b52..6504321bb2 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs @@ -50,12 +50,18 @@ namespace osu.Game.Rulesets.Mania.Skinning Color4 lineColour = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnLineColour)?.Value ?? Color4.White; + Color4 backgroundColour = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour)?.Value + ?? Color4.Black; + + Color4 lightColour = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnLightColour)?.Value + ?? Color4.White; + InternalChildren = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.Black + Colour = backgroundColour }, new Box { @@ -82,6 +88,7 @@ namespace osu.Game.Rulesets.Mania.Skinning { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, + Colour = lightColour, Texture = skin.GetTexture(lightImage), RelativeSizeAxes = Axes.X, Width = 1, diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index ee5db2a77f..7d3614bf83 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -38,5 +38,7 @@ namespace osu.Game.Skinning ExplosionScale, ColumnLineColour, JudgementLineColour, + ColumnBackgroundColour, + ColumnLightColour } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index f206bc792d..f1a911e652 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -88,7 +88,8 @@ namespace osu.Game.Skinning // todo: this shouldn't really be duplicated here (from ManiaLegacySkinTransformer). we need to come up with a better solution. hasKeyTexture = new Lazy(() => this.GetAnimation( - lookupForMania(new LegacyManiaSkinConfigurationLookup(4, LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value ?? "mania-key1", true, true) != null); + lookupForMania(new LegacyManiaSkinConfigurationLookup(4, LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value ?? "mania-key1", true, + true) != null); } protected override void Dispose(bool isDisposing) @@ -210,6 +211,14 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.JudgementLineColour: return SkinUtils.As(getCustomColour(existing, "ColourJudgementLine")); + + case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(getCustomColour(existing, $"Colour{maniaLookup.TargetColumn}")); + + case LegacyManiaSkinConfigurationLookups.ColumnLightColour: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(getCustomColour(existing, $"ColourLight{maniaLookup.TargetColumn}")); } return null; From 2568f3f5886fd537c52ca0877d7edd51dd1d08b1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 17:11:32 +0900 Subject: [PATCH 146/655] Fix off-by-one indexing --- osu.Game/Skinning/LegacySkin.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index f1a911e652..9e0f4007a1 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -214,11 +214,11 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour: Debug.Assert(maniaLookup.TargetColumn != null); - return SkinUtils.As(getCustomColour(existing, $"Colour{maniaLookup.TargetColumn}")); + return SkinUtils.As(getCustomColour(existing, $"Colour{maniaLookup.TargetColumn + 1}")); case LegacyManiaSkinConfigurationLookups.ColumnLightColour: Debug.Assert(maniaLookup.TargetColumn != null); - return SkinUtils.As(getCustomColour(existing, $"ColourLight{maniaLookup.TargetColumn}")); + return SkinUtils.As(getCustomColour(existing, $"ColourLight{maniaLookup.TargetColumn + 1}")); } return null; From ccc764eace14444e4421d83eb4ee1c842f46f2f1 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Tue, 7 Apr 2020 16:39:41 +0800 Subject: [PATCH 147/655] =?UTF-8?q?Added=20=E2=80=9Cinstant=20fly=E2=80=9D?= =?UTF-8?q?=20variant=20of=20hit=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Objects/Drawables/DrawableCentreHit.cs | 17 +++++++++++++++++ .../Objects/Drawables/DrawableRimHit.cs | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs index 4979135f50..08df05e719 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs @@ -2,7 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; namespace osu.Game.Rulesets.Taiko.Objects.Drawables @@ -23,4 +26,18 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables MainPiece.AccentColour = colours.PinkDarker; } } + + public class DrawableFlyingCentreHit : DrawableCentreHit + { + protected override void CheckForResult(bool userTriggered, double timeOffset) + { + ApplyResult(r => r.Type = HitResult.Good); + } + + public DrawableFlyingCentreHit(double time) + : base(new Hit { StartTime = time }) + { + HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + } + } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs index 5a12d71cea..0c2c9fbdef 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs @@ -2,7 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; namespace osu.Game.Rulesets.Taiko.Objects.Drawables @@ -23,4 +26,18 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables MainPiece.AccentColour = colours.BlueDarker; } } + + public class DrawableFlyingRimHit : DrawableRimHit + { + protected override void CheckForResult(bool userTriggered, double timeOffset) + { + ApplyResult(r => r.Type = HitResult.Good); + } + + public DrawableFlyingRimHit(double time) + : base(new Hit { StartTime = time }) + { + HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + } + } } From 2705de70a206ad71d8db84d70c4af6cbd259b4ca Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Tue, 7 Apr 2020 16:40:01 +0800 Subject: [PATCH 148/655] Added arbitrary hit handler to drum roll object --- .../Objects/Drawables/DrawableDrumRoll.cs | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 5806c90115..3e7b6dfd31 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -34,6 +34,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private Color4 colourIdle; private Color4 colourEngaged; + private bool judgingStarted; + + /// + /// A handler action for when the drumroll has been hit, + /// regardless of any judgement. + /// + public Action OnHit; + public DrawableDrumRoll(DrumRoll drumRoll) : base(drumRoll) { @@ -86,15 +94,27 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override TaikoPiece CreateMainPiece() => new ElongatedCirclePiece(); - public override bool OnPressed(TaikoAction action) => false; + public override bool OnPressed(TaikoAction action) + { + if (judgingStarted) + OnHit.Invoke(action); + + return false; + } private void onNewResult(DrawableHitObject obj, JudgementResult result) { if (!(obj is DrawableDrumRollTick)) return; + DrawableDrumRollTick drumRollTick = (DrawableDrumRollTick)obj; + if (result.Type > HitResult.Miss) + { + OnHit.Invoke(drumRollTick.JudgedAction); + judgingStarted = true; rollingHits++; + } else rollingHits--; @@ -113,8 +133,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables return; int countHit = NestedHitObjects.Count(o => o.IsHit); + if (countHit >= HitObject.RequiredGoodHits) + { ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Good); + } else ApplyResult(r => r.Type = HitResult.Miss); } From 7c3c198212d70205ee4897eafc7cff815e55c929 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Tue, 7 Apr 2020 16:40:18 +0800 Subject: [PATCH 149/655] Added judgement forwarder to drumroll tick object --- .../Objects/Drawables/DrawableDrumRollTick.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index 25b6141a0e..9961cb6ea2 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -11,6 +11,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { public class DrawableDrumRollTick : DrawableTaikoHitObject { + /// + /// The action type that the user took which caused this tick to + /// have been judged as "hit" + /// + public TaikoAction JudgedAction; + public DrawableDrumRollTick(DrumRollTick tick) : base(tick) { @@ -49,7 +55,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } } - public override bool OnPressed(TaikoAction action) => UpdateResult(true); + public override bool OnPressed(TaikoAction action) + { + JudgedAction = action; + return UpdateResult(true); + } protected override DrawableStrongNestedHit CreateStrongHit(StrongHitObject hitObject) => new StrongNestedHit(hitObject, this); From 3fec213c928db64bb7dcb07bf6b468fb9d9e39c8 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Tue, 7 Apr 2020 16:40:32 +0800 Subject: [PATCH 150/655] Added separate scrolling track to display drum roll notes --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 25 ++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index bde9085c23..b32e7b53da 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -42,6 +42,7 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Container hitExplosionContainer; private readonly Container kiaiExplosionContainer; private readonly JudgementContainer judgementContainer; + private readonly ScrollingHitObjectContainer drumRollHitContainer; internal readonly HitTarget HitTarget; private readonly ProxyContainer topLevelHitContainer; @@ -135,6 +136,14 @@ namespace osu.Game.Rulesets.Taiko.UI Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, Blending = BlendingParameters.Additive }, + drumRollHitContainer = new ScrollingHitObjectContainer + { + Name = "Drumroll hit", + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Stretch, + Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Width = 1.0f + } } }, overlayBackgroundContainer = new Container @@ -212,12 +221,28 @@ namespace osu.Game.Rulesets.Taiko.UI barlineContainer.Add(barline.CreateProxy()); break; + case DrawableDrumRoll drumRoll: + drumRoll.OnHit += onDrumrollArbitraryHit; + break; + case DrawableTaikoHitObject taikoObject: topLevelHitContainer.Add(taikoObject.CreateProxiedContent()); break; } } + private void onDrumrollArbitraryHit(TaikoAction action) + { + DrawableHit drawableHit; + + if (action == TaikoAction.LeftRim || action == TaikoAction.RightRim) + drawableHit = new DrawableFlyingRimHit(Time.Current); + else + drawableHit = new DrawableFlyingCentreHit(Time.Current); + + drumRollHitContainer.Add(drawableHit); + } + internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result) { if (!DisplayJudgements.Value) From a1e215888eda520a432e63465e5a6c4c33bc111c Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Tue, 7 Apr 2020 17:25:47 +0800 Subject: [PATCH 151/655] Added logic to allow strong notes --- .../Objects/Drawables/DrawableCentreHit.cs | 4 ++-- .../Objects/Drawables/DrawableDrumRoll.cs | 6 +++--- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs | 4 ++-- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs index 08df05e719..86e885239f 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs @@ -34,8 +34,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables ApplyResult(r => r.Type = HitResult.Good); } - public DrawableFlyingCentreHit(double time) - : base(new Hit { StartTime = time }) + public DrawableFlyingCentreHit(double time, bool isStrong = false) + : base(new Hit { StartTime = time, IsStrong = isStrong }) { HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 3e7b6dfd31..64be870262 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// A handler action for when the drumroll has been hit, /// regardless of any judgement. /// - public Action OnHit; + public Action OnHit; public DrawableDrumRoll(DrumRoll drumRoll) : base(drumRoll) @@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public override bool OnPressed(TaikoAction action) { if (judgingStarted) - OnHit.Invoke(action); + OnHit.Invoke(action, HitObject.IsStrong); return false; } @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (result.Type > HitResult.Miss) { - OnHit.Invoke(drumRollTick.JudgedAction); + OnHit.Invoke(drumRollTick.JudgedAction, HitObject.IsStrong); judgingStarted = true; rollingHits++; } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs index 0c2c9fbdef..ad9872b21f 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs @@ -34,8 +34,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables ApplyResult(r => r.Type = HitResult.Good); } - public DrawableFlyingRimHit(double time) - : base(new Hit { StartTime = time }) + public DrawableFlyingRimHit(double time, bool isStrong = false) + : base(new Hit { StartTime = time, IsStrong = isStrong }) { HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index b32e7b53da..59cf9193b5 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -231,14 +231,14 @@ namespace osu.Game.Rulesets.Taiko.UI } } - private void onDrumrollArbitraryHit(TaikoAction action) + private void onDrumrollArbitraryHit(TaikoAction action, bool isStrong) { DrawableHit drawableHit; if (action == TaikoAction.LeftRim || action == TaikoAction.RightRim) - drawableHit = new DrawableFlyingRimHit(Time.Current); + drawableHit = new DrawableFlyingRimHit(Time.Current, isStrong); else - drawableHit = new DrawableFlyingCentreHit(Time.Current); + drawableHit = new DrawableFlyingCentreHit(Time.Current, isStrong); drumRollHitContainer.Add(drawableHit); } From c9872f1d93369601dd5787994772673790a904e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Apr 2020 18:55:03 +0900 Subject: [PATCH 152/655] Retrieve dll resources using a more reliable method --- osu.Game/Tests/Visual/SkinnableTestScene.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index 71d3266d18..69e17af01b 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -26,9 +26,6 @@ namespace osu.Game.Tests.Visual private Skin specialSkin; private Skin oldSkin; - // Keep a static reference to ensure we don't use a dynamically recompiled DLL as a source (resources will be missing). - private static DllResourceStore dllStore; - protected SkinnableTestScene() : base(2, 3) { @@ -37,7 +34,7 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load(AudioManager audio, SkinManager skinManager) { - dllStore ??= new DllResourceStore(GetType().Assembly); + var dllStore = new DllResourceStore(DynamicCompilationOriginal.GetType().Assembly); metricsSkin = new TestLegacySkin(new SkinInfo { Name = "metrics-skin" }, new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), audio, true); defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info); From 08308e07e7a1ea594214594400f3cb784936a9a9 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Tue, 7 Apr 2020 12:20:54 +0200 Subject: [PATCH 153/655] Apply review suggestions --- osu.Game/Rulesets/RulesetStore.cs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 7b4c0302aa..ef72f187c3 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -24,12 +24,12 @@ namespace osu.Game.Rulesets : base(factory) { rulesetStorage = storage?.GetStorageForDirectory("rulesets"); - AppDomain.CurrentDomain.AssemblyResolve += resolveRulesetDependencyAssembly; // On android in release configuration assemblies are loaded from the apk directly into memory. // We cannot read assemblies from cwd, so should check loaded assemblies instead. loadFromAppDomain(); loadFromDisk(); + AppDomain.CurrentDomain.AssemblyResolve += resolveRulesetDependencyAssembly; loadUserRulesets(); addMissingRulesets(); } @@ -57,6 +57,7 @@ namespace osu.Game.Rulesets { var asm = new AssemblyName(args.Name); + // the requesting assembly may be located out of the executable's base directory, thus requiring manual resolving of its dependencies. // this assumes the only explicit dependency of the ruleset is the game core assembly. // the ruleset dependency on the game core assembly requires manual resolving, transient dependencies should be resolved automatically if (asm.Name.Equals(typeof(OsuGame).Assembly.GetName().Name, StringComparison.Ordinal)) @@ -137,17 +138,10 @@ namespace osu.Game.Rulesets private void loadUserRulesets() { - try - { - var rulesets = rulesetStorage?.GetFiles(".", $"{ruleset_library_prefix}.*.dll"); + var rulesets = rulesetStorage?.GetFiles(".", $"{ruleset_library_prefix}.*.dll"); - foreach (var ruleset in rulesets.Where(f => !f.Contains("Tests"))) - loadRulesetFromFile(rulesetStorage?.GetFullPath(ruleset)); - } - catch (Exception e) - { - Logger.Error(e, "Couldn't load user rulesets"); - } + foreach (var ruleset in rulesets.Where(f => !f.Contains("Tests"))) + loadRulesetFromFile(rulesetStorage?.GetFullPath(ruleset)); } private void loadFromDisk() From e597ee9ffd47628c6a18e238f3cbad1e93e5af1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Apr 2020 21:52:15 +0900 Subject: [PATCH 154/655] 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 161a15fa4e..aaac6ec427 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c62aec7250..3e2c2b1599 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,7 +22,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 834c0ee956..7903d964ce 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -79,7 +79,7 @@ - + From 2087d8d09e92c4662a9cc228e25476801624539a Mon Sep 17 00:00:00 2001 From: Lucas A Date: Tue, 7 Apr 2020 16:01:47 +0200 Subject: [PATCH 155/655] Don't search for user rulesets if rulesetsStorage isn't set (Testing environnment) --- osu.Game/Rulesets/RulesetStore.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index ef72f187c3..34da2dc2db 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -138,10 +138,12 @@ namespace osu.Game.Rulesets private void loadUserRulesets() { - var rulesets = rulesetStorage?.GetFiles(".", $"{ruleset_library_prefix}.*.dll"); + if (rulesetStorage == null) return; + + var rulesets = rulesetStorage.GetFiles(".", $"{ruleset_library_prefix}.*.dll"); foreach (var ruleset in rulesets.Where(f => !f.Contains("Tests"))) - loadRulesetFromFile(rulesetStorage?.GetFullPath(ruleset)); + loadRulesetFromFile(rulesetStorage.GetFullPath(ruleset)); } private void loadFromDisk() From 16d906d769b576e1678c983f37e2ff0ac114e6b9 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Tue, 7 Apr 2020 17:16:06 +0300 Subject: [PATCH 156/655] Get rid of unnecessary removal --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 517af630fc..b04d484195 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Utils; using osu.Game.Rulesets.Objects.Drawables; @@ -80,7 +79,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Hit: this.FadeOut(animDuration, Easing.Out) .ScaleTo(Scale * 1.5f, animDuration, Easing.Out); - scaleContainer.Transforms.ForEach(t => scaleContainer.RemoveTransform(t)); break; } } From 35d66c3c1df447370eca1baaf5f1281b60c015df Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 7 Apr 2020 23:37:30 +0900 Subject: [PATCH 157/655] Fix missing comma --- osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index f8089a9590..9a2f9f2fe5 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -39,7 +39,7 @@ namespace osu.Game.Skinning ColumnLineColour, JudgementLineColour, ColumnBackgroundColour, - ColumnLightColour + ColumnLightColour, MinimumColumnWidth } } From 65db64e13e615de506c07d9c71816c294e414361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 7 Apr 2020 22:41:20 +0200 Subject: [PATCH 158/655] Add failing test cases --- .../Skinning/LegacySkinTextureFallbackTest.cs | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs new file mode 100644 index 0000000000..867af9c1b8 --- /dev/null +++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs @@ -0,0 +1,109 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics.Textures; +using osu.Game.Skinning; + +namespace osu.Game.Tests.NonVisual.Skinning +{ + [TestFixture] + public sealed class LegacySkinTextureFallbackTest + { + private static object[][] fallbackTestCases = + { + new object[] + { + // textures in store + new[] { "Gameplay/osu/followpoint@2x", "Gameplay/osu/followpoint" }, + // requested component + "Gameplay/osu/followpoint", + // returned texture name & scale + "Gameplay/osu/followpoint@2x", 2 + }, + new object[] + { + new[] { "Gameplay/osu/followpoint@2x" }, + "Gameplay/osu/followpoint", + "Gameplay/osu/followpoint@2x", 2 + }, + new object[] + { + new[] { "Gameplay/osu/followpoint" }, + "Gameplay/osu/followpoint", + "Gameplay/osu/followpoint", 1 + }, + new object[] + { + new[] { "Gameplay/osu/followpoint", "followpoint@2x" }, + "Gameplay/osu/followpoint", + "Gameplay/osu/followpoint", 1 + }, + new object[] + { + new[] { "followpoint@2x", "followpoint" }, + "Gameplay/osu/followpoint", + "followpoint@2x", 2 + }, + new object[] + { + new[] { "followpoint@2x" }, + "Gameplay/osu/followpoint", + "followpoint@2x", 2 + }, + new object[] + { + new[] { "followpoint" }, + "Gameplay/osu/followpoint", + "followpoint", 1 + }, + }; + + [TestCaseSource(nameof(fallbackTestCases))] + public void TestFallbackOrder(string[] filesInStore, string requestedComponent, string expectedTexture, float expectedScale) + { + var textureStore = new TestTextureStore(filesInStore); + var legacySkin = new TestLegacySkin(textureStore); + + var texture = legacySkin.GetTexture(requestedComponent); + + Assert.IsNotNull(texture); + Assert.AreEqual(textureStore.Textures[expectedTexture], texture); + Assert.AreEqual(expectedScale, texture.ScaleAdjust); + } + + [Test] + public void TestReturnNullOnFallbackFailure() + { + var textureStore = new TestTextureStore("sliderb", "hit100"); + var legacySkin = new TestLegacySkin(textureStore); + + var texture = legacySkin.GetTexture("Gameplay/osu/followpoint"); + + Assert.IsNull(texture); + } + + private class TestLegacySkin : LegacySkin + { + public TestLegacySkin(TextureStore textureStore) + : base(new SkinInfo(), null, null, string.Empty) + { + Textures = textureStore; + } + } + + private class TestTextureStore : TextureStore + { + public readonly Dictionary Textures; + + public TestTextureStore(params string[] fileNames) + { + Textures = fileNames.ToDictionary(fileName => fileName, fileName => new Texture(1, 1)); + } + + public override Texture Get(string name) => Textures.GetValueOrDefault(name); + } + } +} From f5f0b94944af1f1455859420aba1ed4f2fed083c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 7 Apr 2020 22:50:25 +0200 Subject: [PATCH 159/655] Fix incorrect fallback logic The recently-modified skin texture fallback logic was very subtly incorrect. If at the end of the first loop no texture was found, it would be checked for null to avoid setting scale adjust on a null texture, but then returned anyway, bypassing the fallback logic for subsequent possible paths entirely. Invert the check and explicitly continue to the next fallback path if neither a 2x, nor 1x texture with the given name is found in the store. --- osu.Game/Skinning/LegacySkin.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 60eb3d8e51..ea1cc203d7 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -293,9 +293,10 @@ namespace osu.Game.Skinning texture = Textures?.Get(name); } - if (texture != null) - texture.ScaleAdjust = ratio; + if (texture == null) + continue; + texture.ScaleAdjust = ratio; return texture; } From 737a3b608a925aff03cbe91214a2f8f3ecc2df7d Mon Sep 17 00:00:00 2001 From: Alchyr Date: Tue, 7 Apr 2020 17:34:18 -0700 Subject: [PATCH 160/655] Correct spelling --- osu.Game/Screens/Menu/MainMenu.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index dcee5e83b7..174eadfe26 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -294,7 +294,7 @@ namespace osu.Game.Screens.Menu { new PopupDialogOkButton { - Text = @"Good bye", + Text = @"Goodbye", Action = confirm }, new PopupDialogCancelButton From 66a474619ce2f56d127d92c2c0ca2429f845ab10 Mon Sep 17 00:00:00 2001 From: Alchyr Date: Tue, 7 Apr 2020 18:13:26 -0700 Subject: [PATCH 161/655] Adjust TimingControlPoint equivalency --- osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 51b3377394..158788964b 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -50,6 +50,6 @@ namespace osu.Game.Beatmaps.ControlPoints public override bool EquivalentTo(ControlPoint other) => other is TimingControlPoint otherTyped - && TimeSignature == otherTyped.TimeSignature && BeatLength.Equals(otherTyped.BeatLength); + && Time == otherTyped.Time && TimeSignature == otherTyped.TimeSignature && BeatLength.Equals(otherTyped.BeatLength); } } From c5aae9b757ef7c726b513a7b0dfd2e1b55d2cda2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 8 Apr 2020 12:19:09 +0900 Subject: [PATCH 162/655] Fix post-merge errors --- .../Difficulty/CatchDifficultyCalculator.cs | 14 +++++--------- .../Preprocessing/CatchDifficultyHitObject.cs | 1 + 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 7d763f1792..ee3b410780 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -50,15 +50,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { - float halfCatchWidth; - - using (var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty)) - { - halfCatchWidth = catcher.CatchWidth * 0.5f; - // We're only using 80% of the catcher's width to simulate imperfect gameplay, reduced further at circle sizes above 5.5 - halfCatchWidth *= Math.Min(1.075f - (0.05f * beatmap.BeatmapInfo.BaseDifficulty.CircleSize), 0.8f); - } - CatchHitObject lastObject = null; // In 2B beatmaps, it is possible that a normal Fruit is placed in the middle of a JuiceStream. @@ -81,8 +72,13 @@ namespace osu.Game.Rulesets.Catch.Difficulty protected override Skill[] CreateSkills(IBeatmap beatmap) { using (var catcher = new Catcher(beatmap.BeatmapInfo.BaseDifficulty)) + { halfCatcherWidth = catcher.CatchWidth * 0.5f; + // For circle sizes above 5.5, reduce the catcher width further to simulate imperfect gameplay. + halfCatcherWidth *= 1 - (Math.Max(0, beatmap.BeatmapInfo.BaseDifficulty.CircleSize - 5.5f) * 0.0625f); + } + return new Skill[] { new Movement(halfCatcherWidth), diff --git a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs index b2b4129c8a..360af1a8c9 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs @@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing /// Milliseconds elapsed since the start time of the previous , with a minimum of 40ms. /// public readonly double StrainTime; + public readonly double ClockRate; public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, float halfCatcherWidth) From fd51bbb9ecd1e7e381c481d50c85ba392a63bc75 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 8 Apr 2020 12:20:46 +0900 Subject: [PATCH 163/655] Apply latest changes --- .../Difficulty/Skills/Movement.cs | 25 ++++--------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index 3ab4ae63a1..5cd2f1f581 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -46,29 +46,29 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills float distanceMoved = playerPosition - lastPlayerPosition.Value; - double weightedStrainTime = catchCurrent.StrainTime + 10 + (8 / catchCurrent.ClockRate); + double weightedStrainTime = catchCurrent.StrainTime + 13 + (3 / catchCurrent.ClockRate); double distanceAddition = (Math.Pow(Math.Abs(distanceMoved), 1.3) / 510); double sqrtStrain = Math.Sqrt(weightedStrainTime); double edgeDashBonus = 0; - // Direction changes give an extra point! + // Direction change bonus. if (Math.Abs(distanceMoved) > 0.1) { if (Math.Abs(lastDistanceMoved) > 0.1 && Math.Sign(distanceMoved) != Math.Sign(lastDistanceMoved)) { double bonusFactor = Math.Min(50, Math.Abs(distanceMoved)) / 50; - double antiflowFactor = Math.Max(Math.Min(70, Math.Abs(lastDistanceMoved)) / 70, 0.3); + double antiflowFactor = Math.Max(Math.Min(70, Math.Abs(lastDistanceMoved)) / 70, 0.38); - distanceAddition += direction_change_bonus / Math.Sqrt(lastStrainTime + 18) * bonusFactor * antiflowFactor * Math.Max(1 - Math.Pow(weightedStrainTime / 1000, 3), 0); + distanceAddition += direction_change_bonus / Math.Sqrt(lastStrainTime + 16) * bonusFactor * antiflowFactor * Math.Max(1 - Math.Pow(weightedStrainTime / 1000, 3), 0); } // Base bonus for every movement, giving some weight to streams. distanceAddition += 12.5 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain; } - // Bonus for "almost" hyperdashes at corner points + // Bonus for edge dashes. if (catchCurrent.LastObject.DistanceToHyperDash <= 20.0f / CatchPlayfield.BASE_WIDTH) { if (!catchCurrent.LastObject.HyperDash) @@ -82,21 +82,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills distanceAddition *= 1.0 + edgeDashBonus * ((20 - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 20) * Math.Pow((Math.Min(catchCurrent.StrainTime * catchCurrent.ClockRate, 265) / 265), 1.5); // Edge Dashes are easier at lower ms values } - // Prevent wide dense stacks of notes which fit on the catcher from greatly increasing SR - if (Math.Abs(distanceMoved) > 0.1) - { - if (Math.Abs(lastDistanceMoved) > 0.1 && Math.Sign(distanceMoved) != Math.Sign(lastDistanceMoved)) - { - if (Math.Abs(distanceMoved) <= (CatcherArea.CATCHER_SIZE) && Math.Abs(lastDistanceMoved) == Math.Abs(distanceMoved)) - { - if (catchCurrent.StrainTime <= 80 && lastStrainTime == catchCurrent.StrainTime) - { - distanceAddition *= Math.Max(((catchCurrent.StrainTime / 80) - 0.75) * 4, 0); - } - } - } - } - lastPlayerPosition = playerPosition; lastDistanceMoved = distanceMoved; lastStrainTime = catchCurrent.StrainTime; From a7d1eed3f5d938608eb9581ccde72072efd014b9 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Wed, 8 Apr 2020 12:12:59 +0800 Subject: [PATCH 164/655] Added content proxying to drull roll elements --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 59cf9193b5..e947795fe5 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -241,6 +241,7 @@ namespace osu.Game.Rulesets.Taiko.UI drawableHit = new DrawableFlyingCentreHit(Time.Current, isStrong); drumRollHitContainer.Add(drawableHit); + topLevelHitContainer.Add(drawableHit.CreateProxiedContent()); } internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result) From f4dc604dbf5928e8142573562d18274030bbdd0f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Apr 2020 13:32:37 +0900 Subject: [PATCH 165/655] Fix dragging tournament ladder too far causing it to disappear --- osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs b/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs index bdaa1ae7fd..fa03518c47 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs @@ -22,6 +22,8 @@ namespace osu.Game.Tournament.Screens.Ladder protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false; + public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => false; + protected override void OnDrag(DragEvent e) { this.MoveTo(target += e.Delta, 1000, Easing.OutQuint); From 6e12f1b69b95e0d9b0fbe84b4666b6a87f8b5caa Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Tue, 7 Apr 2020 16:39:41 +0800 Subject: [PATCH 166/655] =?UTF-8?q?Added=20=E2=80=9Cinstant=20fly=E2=80=9D?= =?UTF-8?q?=20variant=20of=20hit=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Objects/Drawables/DrawableCentreHit.cs | 17 +++++++++++++++++ .../Objects/Drawables/DrawableRimHit.cs | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs index 4979135f50..08df05e719 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs @@ -2,7 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; namespace osu.Game.Rulesets.Taiko.Objects.Drawables @@ -23,4 +26,18 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables MainPiece.AccentColour = colours.PinkDarker; } } + + public class DrawableFlyingCentreHit : DrawableCentreHit + { + protected override void CheckForResult(bool userTriggered, double timeOffset) + { + ApplyResult(r => r.Type = HitResult.Good); + } + + public DrawableFlyingCentreHit(double time) + : base(new Hit { StartTime = time }) + { + HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + } + } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs index 5a12d71cea..0c2c9fbdef 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs @@ -2,7 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; namespace osu.Game.Rulesets.Taiko.Objects.Drawables @@ -23,4 +26,18 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables MainPiece.AccentColour = colours.BlueDarker; } } + + public class DrawableFlyingRimHit : DrawableRimHit + { + protected override void CheckForResult(bool userTriggered, double timeOffset) + { + ApplyResult(r => r.Type = HitResult.Good); + } + + public DrawableFlyingRimHit(double time) + : base(new Hit { StartTime = time }) + { + HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + } + } } From 1057981c793dfabd262123cacfb47f36df57a844 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Tue, 7 Apr 2020 16:40:01 +0800 Subject: [PATCH 167/655] Added arbitrary hit handler to drum roll object --- .../Objects/Drawables/DrawableDrumRoll.cs | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 5806c90115..3e7b6dfd31 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -34,6 +34,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private Color4 colourIdle; private Color4 colourEngaged; + private bool judgingStarted; + + /// + /// A handler action for when the drumroll has been hit, + /// regardless of any judgement. + /// + public Action OnHit; + public DrawableDrumRoll(DrumRoll drumRoll) : base(drumRoll) { @@ -86,15 +94,27 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override TaikoPiece CreateMainPiece() => new ElongatedCirclePiece(); - public override bool OnPressed(TaikoAction action) => false; + public override bool OnPressed(TaikoAction action) + { + if (judgingStarted) + OnHit.Invoke(action); + + return false; + } private void onNewResult(DrawableHitObject obj, JudgementResult result) { if (!(obj is DrawableDrumRollTick)) return; + DrawableDrumRollTick drumRollTick = (DrawableDrumRollTick)obj; + if (result.Type > HitResult.Miss) + { + OnHit.Invoke(drumRollTick.JudgedAction); + judgingStarted = true; rollingHits++; + } else rollingHits--; @@ -113,8 +133,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables return; int countHit = NestedHitObjects.Count(o => o.IsHit); + if (countHit >= HitObject.RequiredGoodHits) + { ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Good); + } else ApplyResult(r => r.Type = HitResult.Miss); } From 9d5a9775017f7ffa67207419b28b5cf3e6fdca61 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Tue, 7 Apr 2020 16:40:18 +0800 Subject: [PATCH 168/655] Added judgement forwarder to drumroll tick object --- .../Objects/Drawables/DrawableDrumRollTick.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index 25b6141a0e..9961cb6ea2 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -11,6 +11,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { public class DrawableDrumRollTick : DrawableTaikoHitObject { + /// + /// The action type that the user took which caused this tick to + /// have been judged as "hit" + /// + public TaikoAction JudgedAction; + public DrawableDrumRollTick(DrumRollTick tick) : base(tick) { @@ -49,7 +55,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } } - public override bool OnPressed(TaikoAction action) => UpdateResult(true); + public override bool OnPressed(TaikoAction action) + { + JudgedAction = action; + return UpdateResult(true); + } protected override DrawableStrongNestedHit CreateStrongHit(StrongHitObject hitObject) => new StrongNestedHit(hitObject, this); From 7751c5e3aa30fc530b4d9c8973c307765b5f7b16 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Tue, 7 Apr 2020 16:40:32 +0800 Subject: [PATCH 169/655] Added separate scrolling track to display drum roll notes --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 25 ++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index bde9085c23..b32e7b53da 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -42,6 +42,7 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Container hitExplosionContainer; private readonly Container kiaiExplosionContainer; private readonly JudgementContainer judgementContainer; + private readonly ScrollingHitObjectContainer drumRollHitContainer; internal readonly HitTarget HitTarget; private readonly ProxyContainer topLevelHitContainer; @@ -135,6 +136,14 @@ namespace osu.Game.Rulesets.Taiko.UI Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, Blending = BlendingParameters.Additive }, + drumRollHitContainer = new ScrollingHitObjectContainer + { + Name = "Drumroll hit", + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Stretch, + Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Width = 1.0f + } } }, overlayBackgroundContainer = new Container @@ -212,12 +221,28 @@ namespace osu.Game.Rulesets.Taiko.UI barlineContainer.Add(barline.CreateProxy()); break; + case DrawableDrumRoll drumRoll: + drumRoll.OnHit += onDrumrollArbitraryHit; + break; + case DrawableTaikoHitObject taikoObject: topLevelHitContainer.Add(taikoObject.CreateProxiedContent()); break; } } + private void onDrumrollArbitraryHit(TaikoAction action) + { + DrawableHit drawableHit; + + if (action == TaikoAction.LeftRim || action == TaikoAction.RightRim) + drawableHit = new DrawableFlyingRimHit(Time.Current); + else + drawableHit = new DrawableFlyingCentreHit(Time.Current); + + drumRollHitContainer.Add(drawableHit); + } + internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result) { if (!DisplayJudgements.Value) From b883586addd31c3933c8549af61a071ad65a143d Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Tue, 7 Apr 2020 17:25:47 +0800 Subject: [PATCH 170/655] Added logic to allow strong notes --- .../Objects/Drawables/DrawableCentreHit.cs | 4 ++-- .../Objects/Drawables/DrawableDrumRoll.cs | 6 +++--- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs | 4 ++-- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs index 08df05e719..86e885239f 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs @@ -34,8 +34,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables ApplyResult(r => r.Type = HitResult.Good); } - public DrawableFlyingCentreHit(double time) - : base(new Hit { StartTime = time }) + public DrawableFlyingCentreHit(double time, bool isStrong = false) + : base(new Hit { StartTime = time, IsStrong = isStrong }) { HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 3e7b6dfd31..64be870262 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// A handler action for when the drumroll has been hit, /// regardless of any judgement. /// - public Action OnHit; + public Action OnHit; public DrawableDrumRoll(DrumRoll drumRoll) : base(drumRoll) @@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public override bool OnPressed(TaikoAction action) { if (judgingStarted) - OnHit.Invoke(action); + OnHit.Invoke(action, HitObject.IsStrong); return false; } @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (result.Type > HitResult.Miss) { - OnHit.Invoke(drumRollTick.JudgedAction); + OnHit.Invoke(drumRollTick.JudgedAction, HitObject.IsStrong); judgingStarted = true; rollingHits++; } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs index 0c2c9fbdef..ad9872b21f 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs @@ -34,8 +34,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables ApplyResult(r => r.Type = HitResult.Good); } - public DrawableFlyingRimHit(double time) - : base(new Hit { StartTime = time }) + public DrawableFlyingRimHit(double time, bool isStrong = false) + : base(new Hit { StartTime = time, IsStrong = isStrong }) { HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index b32e7b53da..59cf9193b5 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -231,14 +231,14 @@ namespace osu.Game.Rulesets.Taiko.UI } } - private void onDrumrollArbitraryHit(TaikoAction action) + private void onDrumrollArbitraryHit(TaikoAction action, bool isStrong) { DrawableHit drawableHit; if (action == TaikoAction.LeftRim || action == TaikoAction.RightRim) - drawableHit = new DrawableFlyingRimHit(Time.Current); + drawableHit = new DrawableFlyingRimHit(Time.Current, isStrong); else - drawableHit = new DrawableFlyingCentreHit(Time.Current); + drawableHit = new DrawableFlyingCentreHit(Time.Current, isStrong); drumRollHitContainer.Add(drawableHit); } From c30ea2ec2916d2f5852a63b0e21f599bb34b6ef0 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Wed, 8 Apr 2020 12:12:59 +0800 Subject: [PATCH 171/655] Added content proxying to drull roll elements --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 59cf9193b5..e947795fe5 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -241,6 +241,7 @@ namespace osu.Game.Rulesets.Taiko.UI drawableHit = new DrawableFlyingCentreHit(Time.Current, isStrong); drumRollHitContainer.Add(drawableHit); + topLevelHitContainer.Add(drawableHit.CreateProxiedContent()); } internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result) From 3794b55eef5eed19a8acd0656f3af1ccd3f01380 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Apr 2020 14:08:34 +0900 Subject: [PATCH 172/655] Rename ManiaStage to Stage --- osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs | 2 +- osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs | 8 ++++---- osu.Game.Rulesets.Mania/UI/Column.cs | 2 +- osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs | 2 +- osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs | 2 +- osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 6 +++--- osu.Game.Rulesets.Mania/UI/{ManiaStage.cs => Stage.cs} | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) rename osu.Game.Rulesets.Mania/UI/{ManiaStage.cs => Stage.cs} (97%) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs index 0d5ebd33e9..37b97a444a 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning return new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4) { - Child = new ManiaStage(0, new StageDefinition { Columns = 4 }, ref normalAction, ref specialAction) + Child = new Stage(0, new StageDefinition { Columns = 4 }, ref normalAction, ref specialAction) }; }); } diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs index d5fd2808b8..7376a90f17 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Tests [Cached(typeof(IReadOnlyList))] private IReadOnlyList mods { get; set; } = Array.Empty(); - private readonly List stages = new List(); + private readonly List stages = new List(); private FillFlowContainer fill; @@ -81,9 +81,9 @@ namespace osu.Game.Rulesets.Mania.Tests AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[1], Anchor.TopCentre)); } - private bool notesInStageAreAnchored(ManiaStage stage, Anchor anchor) => stage.Columns.SelectMany(c => c.AllHitObjects).All(o => o.Anchor == anchor); + private bool notesInStageAreAnchored(Stage stage, Anchor anchor) => stage.Columns.SelectMany(c => c.AllHitObjects).All(o => o.Anchor == anchor); - private bool barsInStageAreAnchored(ManiaStage stage, Anchor anchor) => stage.AllHitObjects.Where(obj => obj is DrawableBarLine).All(o => o.Anchor == anchor); + private bool barsInStageAreAnchored(Stage stage, Anchor anchor) => stage.AllHitObjects.Where(obj => obj is DrawableBarLine).All(o => o.Anchor == anchor); private void createNote() { @@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Mania.Tests { var specialAction = ManiaAction.Special1; - var stage = new ManiaStage(0, new StageDefinition { Columns = 2 }, ref action, ref specialAction); + var stage = new Stage(0, new StageDefinition { Columns = 2 }, ref action, ref specialAction); stages.Add(stage); return new ScrollingTestContainer(direction) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index d2f58d7255..d1da102be5 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -138,6 +138,6 @@ 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(ManiaStage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos)); + => 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 982a18cb60..a5de09ca75 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components InternalChild = directionContainer = new Container { RelativeSizeAxes = Axes.X, - Height = ManiaStage.HIT_TARGET_POSITION, + Height = Stage.HIT_TARGET_POSITION, Children = new[] { gradient = new Box diff --git a/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs index bca7c3ff08..ba5281a1a2 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components { float hitPosition = CurrentSkin.GetConfig( new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition))?.Value - ?? ManiaStage.HIT_TARGET_POSITION; + ?? Stage.HIT_TARGET_POSITION; Padding = Direction.Value == ScrollingDirection.Up ? new MarginPadding { Top = hitPosition } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 08f6049782..c2eb48b774 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.UI { public class ManiaPlayfield : ScrollingPlayfield { - private readonly List stages = new List(); + private readonly List stages = new List(); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => stages.Any(s => s.ReceivePositionalInputAt(screenSpacePos)); @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.UI for (int i = 0; i < stageDefinitions.Count; i++) { - var newStage = new ManiaStage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction); + var newStage = new Stage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction); playfieldGrid.Content[0][i] = newStage; @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Mania.UI /// public int TotalColumns => stages.Sum(s => s.Columns.Count); - private ManiaStage getStageByColumn(int column) + private Stage getStageByColumn(int column) { int sum = 0; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs similarity index 97% rename from osu.Game.Rulesets.Mania/UI/ManiaStage.cs rename to osu.Game.Rulesets.Mania/UI/Stage.cs index adab08eb06..1d64672035 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.UI /// /// A collection of s. /// - public class ManiaStage : ScrollingPlayfield + public class Stage : ScrollingPlayfield { public const float COLUMN_SPACING = 1; @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.UI private readonly int firstColumnIndex; - public ManiaStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction) + public Stage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction) { this.firstColumnIndex = firstColumnIndex; From 9db996a91f171c4618477eadea2db9dcb6b7e3a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Apr 2020 14:08:58 +0900 Subject: [PATCH 173/655] Increase size of default osu!mania skin's keys to allow clearance with HUD --- osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs | 7 +++++-- osu.Game.Rulesets.Mania/UI/Stage.cs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs index a5de09ca75..47cb9bd45a 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs @@ -53,9 +53,8 @@ namespace osu.Game.Rulesets.Mania.UI.Components keyIcon = new Container { Name = "Key icon", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, Size = new Vector2(key_icon_size), + Origin = Anchor.Centre, Masking = true, CornerRadius = key_icon_corner_radius, BorderThickness = 2, @@ -88,11 +87,15 @@ namespace osu.Game.Rulesets.Mania.UI.Components { if (direction.NewValue == ScrollingDirection.Up) { + keyIcon.Anchor = Anchor.BottomCentre; + keyIcon.Y = -20; directionContainer.Anchor = directionContainer.Origin = Anchor.TopLeft; gradient.Colour = ColourInfo.GradientVertical(Color4.Black, Color4.Black.Opacity(0)); } else { + keyIcon.Anchor = Anchor.TopCentre; + keyIcon.Y = 20; directionContainer.Anchor = directionContainer.Origin = Anchor.BottomLeft; gradient.Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Color4.Black); } diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index 1d64672035..58e7fba4df 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.UI { public const float COLUMN_SPACING = 1; - public const float HIT_TARGET_POSITION = 50; + public const float HIT_TARGET_POSITION = 110; public IReadOnlyList Columns => columnFlow.Children; private readonly FillFlowContainer columnFlow; From e429c274a99ac0b0d6428468d0b680a55e7efbb9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 8 Apr 2020 15:08:13 +0900 Subject: [PATCH 174/655] Initial structure --- .../Skinning/ManiaSkinnableTestScene.cs | 1 + .../Skinning/TestSceneStageBackground.cs | 35 +++++++++++++++++++ osu.Game.Rulesets.Mania/ManiaSkinComponent.cs | 3 +- .../Skinning/LegacyStageBackground.cs | 11 ++++++ .../Skinning/ManiaLegacySkinTransformer.cs | 3 ++ .../UI/Components/DefaultStageBackground.cs | 11 ++++++ 6 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs create mode 100644 osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs create mode 100644 osu.Game.Rulesets.Mania/UI/Components/DefaultStageBackground.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs index 7f0503913f..a3c1d518c5 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs @@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { typeof(ManiaRuleset), typeof(ManiaLegacySkinTransformer), + typeof(ManiaSettingsSubsection) }; protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset(); diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs new file mode 100644 index 0000000000..a8fc68188a --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs @@ -0,0 +1,35 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Mania.Skinning; +using osu.Game.Rulesets.Mania.UI.Components; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Mania.Tests.Skinning +{ + public class TestSceneStageBackground : ManiaSkinnableTestScene + { + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] + { + typeof(DefaultStageBackground), + typeof(LegacyStageBackground), + }).ToList(); + + [BackgroundDependencyLoader] + private void load() + { + SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground), _ => new DefaultStageBackground()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + }); + } + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs index 2371d74a2b..a7252a348a 100644 --- a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs +++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs @@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.Mania HoldNoteHead, HoldNoteTail, HoldNoteBody, - HitExplosion + HitExplosion, + StageBackground } } diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs new file mode 100644 index 0000000000..d2ea47cfeb --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs @@ -0,0 +1,11 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Rulesets.Mania.Skinning +{ + public class LegacyStageBackground : CompositeDrawable + { + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index 78ea4b68ae..27df534ddd 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -81,6 +81,9 @@ namespace osu.Game.Rulesets.Mania.Skinning case ManiaSkinComponents.HitExplosion: return new LegacyHitExplosion(); + + case ManiaSkinComponents.StageBackground: + return new LegacyStageBackground(); } break; diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultStageBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultStageBackground.cs new file mode 100644 index 0000000000..1e10cd8d59 --- /dev/null +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultStageBackground.cs @@ -0,0 +1,11 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Rulesets.Mania.UI.Components +{ + public class DefaultStageBackground : CompositeDrawable + { + } +} From cd15b672eba7c97ed90b58d61f936d058d20df14 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 8 Apr 2020 15:36:07 +0900 Subject: [PATCH 175/655] Implement left and right stage images --- .../Skinning/LegacyStageBackground.cs | 54 ++++++++++++++++++- .../UI/Components/DefaultStageBackground.cs | 19 +++++++ osu.Game.Rulesets.Mania/UI/Stage.cs | 7 +-- .../LegacyManiaSkinConfigurationLookup.cs | 4 +- osu.Game/Skinning/LegacySkin.cs | 6 +++ 5 files changed, 82 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs index d2ea47cfeb..7680526ac4 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs @@ -1,11 +1,61 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics.Containers; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Mania.Skinning { - public class LegacyStageBackground : CompositeDrawable + public class LegacyStageBackground : LegacyManiaElement { + private Drawable leftSprite; + private Drawable rightSprite; + + public LegacyStageBackground() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + string leftImage = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LeftStageImage)?.Value + ?? "mania-stage-left"; + + string rightImage = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.RightStageImage)?.Value + ?? "mania-stage-right"; + + InternalChildren = new[] + { + leftSprite = new Sprite + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopRight, + X = 0.05f, + Texture = skin.GetTexture(leftImage), + }, + rightSprite = new Sprite + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopLeft, + X = -0.05f, + Texture = skin.GetTexture(rightImage) + } + }; + } + + protected override void Update() + { + base.Update(); + + if (leftSprite?.Height > 0) + leftSprite.Scale = new Vector2(DrawHeight / leftSprite.Height); + + if (rightSprite?.Height > 0) + rightSprite.Scale = new Vector2(DrawHeight / rightSprite.Height); + } } } diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultStageBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultStageBackground.cs index 1e10cd8d59..f5b542d085 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/DefaultStageBackground.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultStageBackground.cs @@ -1,11 +1,30 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.UI.Components { public class DefaultStageBackground : CompositeDrawable { + public DefaultStageBackground() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new Box + { + Name = "Background", + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black + }; + } } } diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index 58e7fba4df..91839bd043 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; @@ -72,11 +71,9 @@ namespace osu.Game.Rulesets.Mania.UI AutoSizeAxes = Axes.X, Children = new Drawable[] { - new Box + new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground), _ => new DefaultStageBackground()) { - Name = "Background", - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black + RelativeSizeAxes = Axes.Both }, columnFlow = new FillFlowContainer { diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index 9a2f9f2fe5..59847017ec 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -40,6 +40,8 @@ namespace osu.Game.Skinning JudgementLineColour, ColumnBackgroundColour, ColumnLightColour, - MinimumColumnWidth + MinimumColumnWidth, + LeftStageImage, + RightStageImage, } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index ea1cc203d7..91f970d19f 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -243,6 +243,12 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.KeyImageDown: Debug.Assert(maniaLookup.TargetColumn != null); return SkinUtils.As(getManiaImage(existing, $"KeyImage{maniaLookup.TargetColumn}D")); + + case LegacyManiaSkinConfigurationLookups.LeftStageImage: + return SkinUtils.As(getManiaImage(existing, "StageLeft")); + + case LegacyManiaSkinConfigurationLookups.RightStageImage: + return SkinUtils.As(getManiaImage(existing, "StageRight")); } return null; From 83db6cebb655d9ea25c5c6d1dfde8f3057d57630 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 8 Apr 2020 16:20:26 +0900 Subject: [PATCH 176/655] Implement bottom stage image --- .../Resources/metrics-skin/mania-key1@2x.png | Bin 0 -> 12914 bytes .../metrics-skin/mania-stage-bottom@2x.png | Bin 0 -> 1965 bytes .../Skinning/TestSceneStageForeground.cs | 33 +++++++++++ osu.Game.Rulesets.Mania/ManiaSkinComponent.cs | 3 +- .../Skinning/LegacyStageForeground.cs | 56 ++++++++++++++++++ .../Skinning/ManiaLegacySkinTransformer.cs | 3 + osu.Game.Rulesets.Mania/UI/Stage.cs | 4 ++ .../LegacyManiaSkinConfigurationLookup.cs | 1 + 8 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-key1@2x.png create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-stage-bottom@2x.png create mode 100644 osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageForeground.cs create mode 100644 osu.Game.Rulesets.Mania/Skinning/LegacyStageForeground.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-key1@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-key1@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..aa681f6f223ea44319b3a383935237d1a2e4b345 GIT binary patch literal 12914 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-stage-bottom@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-stage-bottom@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ca590eaf08e65dc1ae40bce9f9f74e69c3a0f8a4 GIT binary patch literal 1965 zcmV;e2U7TnP)3L?K*_rKGQL~x> z08mZsB?AB;00{&D5QGE*00=??0RRLcfdBx4kU#(cK}a9~fFL9g06-8D2ml}m2?PKT zgaiTr2ton@00ep2fXKN$&0jO!YxaDt*~2%@9=&BYwcLXAPMQr~G21k5w&6kJxjb8A z_D|IAZ~bOB28!G08;c&-tz|}co88}PcJ#Q}%BR*6wH~u$-xc>$EBeCeshNF!$ZXkE z{4@Pc`rGCr-6ySR6WyVQ-FPAY_E=?Chs?hHsuAqU`YZ}YpPY$;++^|Vo<)i3z{N(e z%4_H7h_!?Q{lQCS*LIoxvfXU+xV41Z-S{|4x@)q3->SZFltBJiXSV8T{Ikhd%-($* zp6p+HV)pyGz&!{aR!1JHReDt%hW5g z1K;zRuk~Pe(Cp;lMj{afL)Gwe%S|Ve0rQ)YM(Ha}HGe(sZ;8)r6ciOiZ}ulkWz);! zV4G#u@vkXPAZ6QC-xPgLGxO0G&FJ%#jMQEgnrBKU55PwW3zcakeCk+SS65)``!)Nu zs-X{Fj-OxMlN~U98rf^a?Aq5z_{3rBc&+M-vwN)+|NrxT#S0Gfo_*)y=MO6;A1nFH7I=uowik4k0!)k7a$v5vLSeW3((Z%h21DwFB;V?Aq$W35s$!_q+l zK`=jQWYyQ}lkPL4SOv73A6O*{ewqUi1+`6gtYa-kU+8n(nTy`@@i{9+$LiMDsEw{LB_v=Mv^2@D zpOsnbgQbVdcrlBv6w-umnnt|}B>sf?4^fHa%hTEItb7)KKOGP-B|T4|1QxogdNKKZ zQT5W_y!R=IG;6D#1dhe?A3AfqFr43q>B;kZTMWoW0>LZ_Hk(VlE^iIPd|K9_31l(p z3`BkA7K7^ln_eg?S(E2VP(3U)Mh;3?2=@6%W92jDjrOCU?>0azJDHxIJRjX*W-2aC<`i!vK1W|>Q0q$ktUI|pP`0cSxKJ0lB-ksec|RJs+vn*xXE-!7fSfYz~nhXkU$VjH#_pBS30K`7rs$(9fwB6 z{(Gu!2G~y0>pnvIV&z2qJftsl4DPH^$-}K?&zn34njwK8SUSioOO;p@m}!+!y2V_aS|uvyf{;@iFMEQPPv={(K2ggam?M=`zf8#+-+LJJy{Q_p(w+ zrP*rtPBPcN@V7zC8pJa@9nXoW_&wPpoYO-EPqieNo>|zCSrBU*!f@fI|YoEM>|n zPSIwHzK3s{ZT~a5YF%eAdh8g>e$U-)X1jk5--xkYwL@Ph;U763PoC?HgiCi(6(uZG zq9CJSb1EuXz5b$H@^Ccz-uNWB6-t)7Tg?V;H%98+mOLE4FN&^unyfTq&~sl z1tmf(yq*;`W3Npt3XQlm0(WyM3ZzOb-I_Ft&==@HH4+E_AViH4762d!2?PKTgaiTr z2ton@00bd{004rJKmY(iScwh*)v^2`>Vy9Q*Vy#8=R%=B00000NkvXXu0mjflsBGC literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageForeground.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageForeground.cs new file mode 100644 index 0000000000..d436445b59 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageForeground.cs @@ -0,0 +1,33 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Mania.Skinning; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Mania.Tests.Skinning +{ + public class TestSceneStageForeground : ManiaSkinnableTestScene + { + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] + { + typeof(LegacyStageForeground), + }).ToList(); + + [BackgroundDependencyLoader] + private void load() + { + SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground), _ => null) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + }); + } + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs index a7252a348a..c0c8505f44 100644 --- a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs +++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs @@ -40,6 +40,7 @@ namespace osu.Game.Rulesets.Mania HoldNoteTail, HoldNoteBody, HitExplosion, - StageBackground + StageBackground, + StageForeground, } } diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyStageForeground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyStageForeground.cs new file mode 100644 index 0000000000..9719005d54 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyStageForeground.cs @@ -0,0 +1,56 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Mania.Skinning +{ + public class LegacyStageForeground : LegacyManiaElement + { + private readonly IBindable direction = new Bindable(); + + private Drawable sprite; + + public LegacyStageForeground() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin, IScrollingInfo scrollingInfo) + { + string bottomImage = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.BottomStageImage)?.Value + ?? "mania-stage-bottom"; + + sprite = skin.GetAnimation(bottomImage, true, true)?.With(d => + { + if (d == null) + return; + + d.Scale = new Vector2(1.6f); + }); + + if (sprite != null) + InternalChild = sprite; + + direction.BindTo(scrollingInfo.Direction); + direction.BindValueChanged(onDirectionChanged, true); + } + + private void onDirectionChanged(ValueChangedEvent direction) + { + if (sprite == null) + return; + + if (direction.NewValue == ScrollingDirection.Up) + sprite.Anchor = sprite.Origin = Anchor.TopCentre; + else + sprite.Anchor = sprite.Origin = Anchor.BottomCentre; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index 27df534ddd..e64178083a 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -84,6 +84,9 @@ namespace osu.Game.Rulesets.Mania.Skinning case ManiaSkinComponents.StageBackground: return new LegacyStageBackground(); + + case ManiaSkinComponents.StageForeground: + return new LegacyStageForeground(); } break; diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index 91839bd043..faa04dea97 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -100,6 +100,10 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Y, } }, + new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground), _ => null) + { + RelativeSizeAxes = Axes.Both + }, judgements = new JudgementContainer { Anchor = Anchor.TopCentre, diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index 59847017ec..c76d5c8784 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -43,5 +43,6 @@ namespace osu.Game.Skinning MinimumColumnWidth, LeftStageImage, RightStageImage, + BottomStageImage } } From 2ddea018cfe443bd82a2f72cd0320dd1bef001af Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 8 Apr 2020 17:15:59 +0900 Subject: [PATCH 177/655] Fix hidden notes due to 0 minimum width --- .../Resources/mania-skin-zero-minwidth.ini | 4 ++++ .../Skins/LegacyManiaSkinDecoderTest.cs | 15 +++++++++++++++ osu.Game/Skinning/LegacyManiaSkinConfiguration.cs | 2 +- 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Resources/mania-skin-zero-minwidth.ini diff --git a/osu.Game.Tests/Resources/mania-skin-zero-minwidth.ini b/osu.Game.Tests/Resources/mania-skin-zero-minwidth.ini new file mode 100644 index 0000000000..fd22e2e299 --- /dev/null +++ b/osu.Game.Tests/Resources/mania-skin-zero-minwidth.ini @@ -0,0 +1,4 @@ +[Mania] +Keys: 4 +ColumnWidth: 10,10,10,10 +WidthForNoteHeightScale: 0 \ No newline at end of file diff --git a/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs index 83fd4878aa..e811979aed 100644 --- a/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs +++ b/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs @@ -99,5 +99,20 @@ namespace osu.Game.Tests.Skins Assert.That(configs[0].CustomColours, Contains.Key("ColourBarline").And.ContainValue(new Color4(50, 50, 50, 50))); } } + + [Test] + public void TestMinimumColumnWidthFallsBackWhenZeroIsProvided() + { + var decoder = new LegacyManiaSkinDecoder(); + + using (var resStream = TestResources.OpenResource("mania-skin-zero-minwidth.ini")) + using (var stream = new LineBufferedReader(resStream)) + { + var configs = decoder.Decode(stream); + + Assert.That(configs.Count, Is.EqualTo(1)); + Assert.That(configs[0].MinimumColumnWidth, Is.EqualTo(16)); + } + } } } diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index af7d6007f3..fb591969fb 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -54,7 +54,7 @@ namespace osu.Game.Skinning public float MinimumColumnWidth { get => minimumColumnWidth ?? ColumnWidth.Min(); - set => minimumColumnWidth = value; + set => minimumColumnWidth = value > 0 ? (float?)value : null; } } } From 65823fb2e1101f4b73bc7446063520ca95bbf846 Mon Sep 17 00:00:00 2001 From: Alchyr Date: Wed, 8 Apr 2020 01:42:35 -0700 Subject: [PATCH 178/655] Use redundancy test --- .../NonVisual/ControlPointInfoTest.cs | 23 ++++++++++++------- .../Beatmaps/ControlPoints/ControlPoint.cs | 8 +++++++ .../ControlPoints/ControlPointInfo.cs | 2 +- .../ControlPoints/DifficultyControlPoint.cs | 1 + .../ControlPoints/EffectControlPoint.cs | 1 + .../ControlPoints/SampleControlPoint.cs | 1 + .../ControlPoints/TimingControlPoint.cs | 6 ++++- 7 files changed, 32 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs index 2782e902fe..158954106d 100644 --- a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs +++ b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs @@ -29,11 +29,17 @@ namespace osu.Game.Tests.NonVisual var cpi = new ControlPointInfo(); cpi.Add(0, new TimingControlPoint()); // is *not* redundant, special exception for first timing point. - cpi.Add(1000, new TimingControlPoint()); // is redundant + cpi.Add(1000, new TimingControlPoint()); // is also not redundant, due to change of offset - Assert.That(cpi.Groups.Count, Is.EqualTo(1)); - Assert.That(cpi.TimingPoints.Count, Is.EqualTo(1)); - Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1)); + Assert.That(cpi.Groups.Count, Is.EqualTo(2)); + Assert.That(cpi.TimingPoints.Count, Is.EqualTo(2)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2)); + + cpi.Add(1000, new TimingControlPoint()); //is redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(2)); + Assert.That(cpi.TimingPoints.Count, Is.EqualTo(2)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2)); } [Test] @@ -86,11 +92,12 @@ namespace osu.Game.Tests.NonVisual Assert.That(cpi.EffectPoints.Count, Is.EqualTo(0)); Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0)); - cpi.Add(1000, new EffectControlPoint { KiaiMode = true }); // is not redundant + cpi.Add(1000, new EffectControlPoint { KiaiMode = true, OmitFirstBarLine = true }); // is not redundant + cpi.Add(1400, new EffectControlPoint { KiaiMode = true, OmitFirstBarLine = true }); // same settings, but is not redundant - Assert.That(cpi.Groups.Count, Is.EqualTo(1)); - Assert.That(cpi.EffectPoints.Count, Is.EqualTo(1)); - Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1)); + Assert.That(cpi.Groups.Count, Is.EqualTo(2)); + Assert.That(cpi.EffectPoints.Count, Is.EqualTo(2)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2)); } [Test] diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index 39a0e6f6d4..411a4441de 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -25,6 +25,14 @@ namespace osu.Game.Beatmaps.ControlPoints /// Whether equivalent. public abstract bool EquivalentTo(ControlPoint other); + /// + /// Whether this control point results in a meaningful change when placed after another. + /// + /// Another control point to compare with. + /// The time this timing point will be placed at. + /// Whether redundant. + public abstract bool IsRedundant(ControlPoint other, double time); + public bool Equals(ControlPoint other) => Time == other?.Time && EquivalentTo(other); } } diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index df68d8acd2..37a3dbf592 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -247,7 +247,7 @@ namespace osu.Game.Beatmaps.ControlPoints break; } - return existing?.EquivalentTo(newPoint) == true; + return newPoint.IsRedundant(existing, time); } private void groupItemAdded(ControlPoint controlPoint) diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 8b21098a51..44522dc927 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -29,5 +29,6 @@ namespace osu.Game.Beatmaps.ControlPoints public override bool EquivalentTo(ControlPoint other) => other is DifficultyControlPoint otherTyped && otherTyped.SpeedMultiplier.Equals(SpeedMultiplier); + public override bool IsRedundant(ControlPoint other, double time) => EquivalentTo(other); } } diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index 369b93ff3d..8066c6b577 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -38,5 +38,6 @@ namespace osu.Game.Beatmaps.ControlPoints public override bool EquivalentTo(ControlPoint other) => other is EffectControlPoint otherTyped && KiaiMode == otherTyped.KiaiMode && OmitFirstBarLine == otherTyped.OmitFirstBarLine; + public override bool IsRedundant(ControlPoint other, double time) => !OmitFirstBarLine && EquivalentTo(other); } } diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index 393bcfdb3c..cf7c842b24 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -71,5 +71,6 @@ namespace osu.Game.Beatmaps.ControlPoints public override bool EquivalentTo(ControlPoint other) => other is SampleControlPoint otherTyped && SampleBank == otherTyped.SampleBank && SampleVolume == otherTyped.SampleVolume; + public override bool IsRedundant(ControlPoint other, double time) => EquivalentTo(other); } } diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 158788964b..d14ac1221b 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -50,6 +50,10 @@ namespace osu.Game.Beatmaps.ControlPoints public override bool EquivalentTo(ControlPoint other) => other is TimingControlPoint otherTyped - && Time == otherTyped.Time && TimeSignature == otherTyped.TimeSignature && BeatLength.Equals(otherTyped.BeatLength); + && TimeSignature == otherTyped.TimeSignature && BeatLength.Equals(otherTyped.BeatLength); + + public override bool IsRedundant(ControlPoint other, double time) => + EquivalentTo(other) + && other.Time == time; } } From e6b87656ba1164e4e8e81ac532be9916a6ae0426 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Apr 2020 18:04:53 +0900 Subject: [PATCH 179/655] Fix TestSceneColumn columns not getting a width --- osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs index 9aad08c433..8b35a57380 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs @@ -28,7 +28,9 @@ namespace osu.Game.Rulesets.Mania.Tests { typeof(Column), typeof(ColumnBackground), - typeof(ColumnHitObjectArea) + typeof(ColumnHitObjectArea), + typeof(DefaultKeyArea), + typeof(DefaultHitTarget) }; [Cached(typeof(IReadOnlyList))] @@ -94,6 +96,7 @@ namespace osu.Game.Rulesets.Mania.Tests { Anchor = Anchor.Centre, Origin = Anchor.Centre, + Width = 50, Height = 0.85f, AccentColour = Color4.OrangeRed, Action = { Value = action }, From 7d787dde8926331a03a96140ed0ff1827520ee95 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 8 Apr 2020 18:17:45 +0900 Subject: [PATCH 180/655] Move comparison to decoder --- osu.Game/Skinning/LegacyManiaSkinConfiguration.cs | 2 +- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index fb591969fb..af7d6007f3 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -54,7 +54,7 @@ namespace osu.Game.Skinning public float MinimumColumnWidth { get => minimumColumnWidth ?? ColumnWidth.Min(); - set => minimumColumnWidth = value > 0 ? (float?)value : null; + set => minimumColumnWidth = value; } } } diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index 8b76749e3e..2db902c182 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -102,7 +102,9 @@ namespace osu.Game.Skinning break; case "WidthForNoteHeightScale": - currentConfig.MinimumColumnWidth = float.Parse(pair.Value, CultureInfo.InvariantCulture) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; + float minWidth = float.Parse(pair.Value, CultureInfo.InvariantCulture) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; + if (minWidth > 0) + currentConfig.MinimumColumnWidth = minWidth; break; case string _ when pair.Key.StartsWith("Colour"): From d13231eff744ae95590ef7a06987a4529593c77a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 8 Apr 2020 18:23:24 +0900 Subject: [PATCH 181/655] Use ctor for default width --- osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs | 1 - osu.Game.Rulesets.Mania/UI/Column.cs | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs index 8b35a57380..5e06002f41 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs @@ -96,7 +96,6 @@ namespace osu.Game.Rulesets.Mania.Tests { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Width = 50, Height = 0.85f, AccentColour = Color4.OrangeRed, Action = { Value = action }, diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index d1da102be5..506a07f26b 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -42,6 +42,7 @@ namespace osu.Game.Rulesets.Mania.UI Index = index; RelativeSizeAxes = Axes.Y; + Width = COLUMN_WIDTH; Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, Index), _ => new DefaultColumnBackground()) { From f3e909539df1566e43b86ff0af2520369e04f995 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Apr 2020 18:39:18 +0900 Subject: [PATCH 182/655] Fix slider ball and follow circle blending for legacy skins --- osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index 5a6dd49c44..395c76a233 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -40,7 +40,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces this.drawableSlider = drawableSlider; this.slider = slider; - Blending = BlendingParameters.Additive; Origin = Anchor.Centre; Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); @@ -241,6 +240,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Scale = new Vector2(radius / OsuHitObject.OBJECT_RADIUS), Anchor = Anchor.Centre, Origin = Anchor.Centre, + Blending = BlendingParameters.Additive, BorderThickness = 10, BorderColour = Color4.White, Alpha = 1, From 067ec2785919118b183bbda158aa40ce1dff83b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Apr 2020 18:58:09 +0900 Subject: [PATCH 183/655] Also fix slider repeat circles --- .../Objects/Drawables/DrawableSliderRepeat.cs | 1 - .../Objects/Drawables/Pieces/ReverseArrowPiece.cs | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index b04d484195..720ffcd51c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -31,7 +31,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); - Blending = BlendingParameters.Additive; Origin = Anchor.Centre; InternalChild = scaleContainer = new ReverseArrowPiece(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs index 35a27bb0a6..c0ee874545 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs @@ -21,13 +21,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Anchor = Anchor.Centre; Origin = Anchor.Centre; - Blending = BlendingParameters.Additive; - Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.ReverseArrow), _ => new SpriteIcon { RelativeSizeAxes = Axes.Both, + Blending = BlendingParameters.Additive, Icon = FontAwesome.Solid.ChevronRight, Size = new Vector2(0.35f) }) From 40267cb1fe9e1bbc4878fceef1d24c57adc02fc7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Apr 2020 20:13:25 +0900 Subject: [PATCH 184/655] Add test sprites and make alignment initially better --- .../Resources/old-skin/skin.ini | 5 +++++ .../Resources/old-skin/taiko-bar-left.png | Bin 0 -> 17758 bytes .../Resources/old-skin/taiko-drum-inner.png | Bin 0 -> 4661 bytes .../Resources/old-skin/taiko-drum-outer.png | Bin 0 -> 5585 bytes .../Skinning/LegacyInputDrum.cs | 8 ++++---- 5 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/skin.ini create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-bar-left.png create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-drum-inner.png create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-drum-outer.png diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/skin.ini b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/skin.ini new file mode 100644 index 0000000000..462c2c278e --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/skin.ini @@ -0,0 +1,5 @@ +[General] +Name: an old skin +Author: an old guy + +// no version specified means v1 \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-bar-left.png b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-bar-left.png new file mode 100644 index 0000000000000000000000000000000000000000..ad55fd5a96f702c4a6cda17692d78723b2e8c487 GIT binary patch literal 17758 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-drum-inner.png b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-drum-inner.png new file mode 100644 index 0000000000000000000000000000000000000000..f5c02509fb63c4f5d68a81f626b9ae07fd22e800 GIT binary patch literal 4661 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-drum-outer.png b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-drum-outer.png new file mode 100644 index 0000000000000000000000000000000000000000..53905792cb2e40cc5e799a146c1ccfbcffcfa369 GIT binary patch literal 5585 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs index 8fe7c5e566..de01999e7f 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs @@ -82,15 +82,15 @@ namespace osu.Game.Rulesets.Taiko.Skinning { rimHit = new Sprite { - Anchor = flipped ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreLeft, // opposite due to scale inversion. Scale = new Vector2(-1, 1), Alpha = 0, }, centreHit = new Sprite { - Anchor = flipped ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = flipped ? Anchor.CentreRight : Anchor.CentreLeft, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, Alpha = 0, } }; From 4b16b2e720eced66af563293aeded37b1a6b59d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Apr 2020 19:19:14 +0900 Subject: [PATCH 185/655] Bump legacy skin version --- osu.Game/Skinning/DefaultLegacySkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs index 1929a7e5d2..78d3a37f7c 100644 --- a/osu.Game/Skinning/DefaultLegacySkin.cs +++ b/osu.Game/Skinning/DefaultLegacySkin.cs @@ -20,7 +20,7 @@ namespace osu.Game.Skinning new Color4(242, 24, 57, 255) ); - Configuration.LegacyVersion = 2.0m; + Configuration.LegacyVersion = 2.7m; } public static SkinInfo Info { get; } = new SkinInfo From d786a2c5b33a0ab6be7ff0924094124f3ee82f30 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Apr 2020 19:14:31 +0900 Subject: [PATCH 186/655] Add alignment support for skin versions older than 2.1 --- .../Skinning/LegacyInputDrum.cs | 59 +++++++++++++------ 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs index de01999e7f..c61e35692b 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs @@ -18,9 +18,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning /// internal class LegacyInputDrum : Container { + private LegacyHalfDrum left; + private LegacyHalfDrum right; + public LegacyInputDrum() { - AutoSizeAxes = Axes.Both; + Size = new Vector2(180, 200); } [BackgroundDependencyLoader] @@ -32,25 +35,47 @@ namespace osu.Game.Rulesets.Taiko.Skinning { Texture = skin.GetTexture("taiko-bar-left") }, - new LegacyHalfDrum(false) + left = new LegacyHalfDrum(false) { Name = "Left Half", RelativeSizeAxes = Axes.Both, - Width = 0.5f, RimAction = TaikoAction.LeftRim, CentreAction = TaikoAction.LeftCentre }, - new LegacyHalfDrum(true) + right = new LegacyHalfDrum(true) { Name = "Right Half", - Anchor = Anchor.TopRight, RelativeSizeAxes = Axes.Both, - Width = 0.5f, + Origin = Anchor.TopRight, Scale = new Vector2(-1, 1), RimAction = TaikoAction.RightRim, CentreAction = TaikoAction.RightCentre } }; + + // this will be used in the future for stable skin alignment. keeping here for reference. + const float taiko_bar_y = 0; + + // stable things + const float ratio = 1.6f; + + // because the right half is flipped, we need to position using width - position to get the true "topleft" origin position + float negativeScaleAdjust = Width / ratio; + + if (skin.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.1m) + { + left.Centre.Position = new Vector2(0, taiko_bar_y) * ratio; + right.Centre.Position = new Vector2(negativeScaleAdjust - 56, taiko_bar_y) * ratio; + left.Rim.Position = new Vector2(0, taiko_bar_y) * ratio; + right.Rim.Position = new Vector2(negativeScaleAdjust - 56, taiko_bar_y) * ratio; + } + else + { + left.Centre.Position = new Vector2(18, taiko_bar_y + 31) * ratio; + right.Centre.Position = new Vector2(negativeScaleAdjust - 54, taiko_bar_y + 31) * ratio; + left.Rim.Position = new Vector2(8, taiko_bar_y + 23) * ratio; + right.Rim.Position = new Vector2(negativeScaleAdjust - 53, taiko_bar_y + 23) * ratio; + } } /// @@ -68,8 +93,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning /// public TaikoAction CentreAction; - private readonly Sprite rimHit; - private readonly Sprite centreHit; + public readonly Sprite Rim; + public readonly Sprite Centre; [Resolved] private DrumSampleMapping sampleMappings { get; set; } @@ -80,18 +105,16 @@ namespace osu.Game.Rulesets.Taiko.Skinning Children = new Drawable[] { - rimHit = new Sprite + Rim = new Sprite { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreLeft, // opposite due to scale inversion. Scale = new Vector2(-1, 1), + Origin = flipped ? Anchor.TopLeft : Anchor.TopRight, Alpha = 0, }, - centreHit = new Sprite + Centre = new Sprite { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, Alpha = 0, + Origin = flipped ? Anchor.TopRight : Anchor.TopLeft, } }; } @@ -99,8 +122,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin) { - rimHit.Texture = skin.GetTexture(@"taiko-drum-outer"); - centreHit.Texture = skin.GetTexture(@"taiko-drum-inner"); + Rim.Texture = skin.GetTexture(@"taiko-drum-outer"); + Centre.Texture = skin.GetTexture(@"taiko-drum-inner"); } public bool OnPressed(TaikoAction action) @@ -110,12 +133,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning if (action == CentreAction) { - target = centreHit; + target = Centre; drumSample.Centre?.Play(); } else if (action == RimAction) { - target = rimHit; + target = Rim; drumSample.Rim?.Play(); } From 61d8cfd2241453074c3d5067b18b7bda13afccbd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Apr 2020 19:51:55 +0900 Subject: [PATCH 187/655] Fix triangle intro video being out of time --- osu.Game/Screens/Menu/IntroTriangles.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index b44b6ea993..188a49c147 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -270,7 +270,7 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load() { - InternalChild = new Video(videoStream, false) + InternalChild = new Video(videoStream) { RelativeSizeAxes = Axes.Both, }; From d27d8671ab08e6a334f9859e46a295c68b3d5f01 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 8 Apr 2020 14:23:29 +0300 Subject: [PATCH 188/655] Convert all static getter-only properties to static readonly fields --- .../TestSceneHyperDashColouring.cs | 18 +++++++++--------- .../Objects/Drawables/FruitPiece.cs | 4 ++-- .../Skinning/CatchSkinExtensions.cs | 2 +- osu.Game.Rulesets.Catch/UI/Catcher.cs | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index c8d28dbaeb..846b17f324 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Catch.Tests }, false, false, false); }); - AddAssert("hyper-dash fruit has default colour", () => checkLegacyFruitHyperDashColour(drawableFruit, Catcher.DefaultHyperDashColour)); + AddAssert("hyper-dash fruit has default colour", () => checkLegacyFruitHyperDashColour(drawableFruit, Catcher.DEFAULT_HYPER_DASH_COLOUR)); } [TestCase(true)] @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Catch.Tests }, customCatcherHyperDashColour, false, true); }); - AddAssert("hyper-dash fruit use fruit colour from skin", () => checkLegacyFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashFruitColour)); + AddAssert("hyper-dash fruit use fruit colour from skin", () => checkLegacyFruitHyperDashColour(drawableFruit, TestSkin.CUSTOM_HYPER_DASH_FRUIT_COLOUR)); } [Test] @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Catch.Tests }, true, false, false); }); - AddAssert("hyper-dash fruit colour falls back to catcher colour from skin", () => checkLegacyFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashColour)); + AddAssert("hyper-dash fruit colour falls back to catcher colour from skin", () => checkLegacyFruitHyperDashColour(drawableFruit, TestSkin.CUSTOM_HYPER_DASH_COLOUR)); } private Drawable setupSkinHierarchy(Drawable child, bool customCatcherColour, bool customAfterColour, bool customFruitColour) @@ -108,21 +108,21 @@ namespace osu.Game.Rulesets.Catch.Tests private class TestSkin : LegacySkin { - public static Color4 CustomHyperDashColour { get; } = Color4.Goldenrod; - public static Color4 CustomHyperDashFruitColour { get; } = Color4.Cyan; - public static Color4 CustomHyperDashAfterColour { get; } = Color4.Lime; + public static readonly Color4 CUSTOM_HYPER_DASH_COLOUR = Color4.Goldenrod; + public static readonly Color4 CUSTOM_HYPER_DASH_AFTER_COLOUR = Color4.Lime; + public static readonly Color4 CUSTOM_HYPER_DASH_FRUIT_COLOUR = Color4.Cyan; public TestSkin(bool customCatcherColour, bool customAfterColour, bool customFruitColour) : base(new SkinInfo(), null, null, string.Empty) { if (customCatcherColour) - Configuration.CustomColours[CatchSkinColour.HyperDash.ToString()] = CustomHyperDashColour; + Configuration.CustomColours[CatchSkinColour.HyperDash.ToString()] = CUSTOM_HYPER_DASH_COLOUR; if (customAfterColour) - Configuration.CustomColours[CatchSkinColour.HyperDashAfterImage.ToString()] = CustomHyperDashAfterColour; + Configuration.CustomColours[CatchSkinColour.HyperDashAfterImage.ToString()] = CUSTOM_HYPER_DASH_AFTER_COLOUR; if (customFruitColour) - Configuration.CustomColours[CatchSkinColour.HyperDashFruit.ToString()] = CustomHyperDashFruitColour; + Configuration.CustomColours[CatchSkinColour.HyperDashFruit.ToString()] = CUSTOM_HYPER_DASH_FRUIT_COLOUR; } } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs index 359329885c..7ac9f11ad6 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - BorderColour = Catcher.DefaultHyperDashColour, + BorderColour = Catcher.DEFAULT_HYPER_DASH_COLOUR, BorderThickness = 12f * RADIUS_ADJUST, Children = new Drawable[] { @@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables Alpha = 0.3f, Blending = BlendingParameters.Additive, RelativeSizeAxes = Axes.Both, - Colour = Catcher.DefaultHyperDashColour, + Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR, } } }); diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchSkinExtensions.cs b/osu.Game.Rulesets.Catch/Skinning/CatchSkinExtensions.cs index 623f87bf11..718b22a0fb 100644 --- a/osu.Game.Rulesets.Catch/Skinning/CatchSkinExtensions.cs +++ b/osu.Game.Rulesets.Catch/Skinning/CatchSkinExtensions.cs @@ -15,6 +15,6 @@ namespace osu.Game.Rulesets.Catch.Skinning public static IBindable GetHyperDashFruitColour(this ISkin skin) => skin.GetConfig(CatchSkinColour.HyperDashFruit) ?? skin.GetConfig(CatchSkinColour.HyperDash) ?? - new Bindable(Catcher.DefaultHyperDashColour); + new Bindable(Catcher.DEFAULT_HYPER_DASH_COLOUR); } } diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 9bfff209d5..920d804e72 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Catch.UI { public class Catcher : Container, IKeyBindingHandler { - public static Color4 DefaultHyperDashColour { get; } = Color4.Red; + public static readonly Color4 DEFAULT_HYPER_DASH_COLOUR = Color4.Red; /// /// Whether we are hyper-dashing or not. From 4976f80b7107ae0e7554b02423ef0515c1b023f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Apr 2020 14:31:25 +0900 Subject: [PATCH 189/655] Move implementation to HUD --- osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs | 8 -------- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 3 --- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 8 -------- osu.Game/Screens/Play/HUD/FailingLayer.cs | 12 ++++++++++++ osu.Game/Screens/Play/HUD/HealthDisplay.cs | 2 +- osu.Game/Screens/Play/HUDOverlay.cs | 7 ++++++- 6 files changed, 19 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index 4df2bc0f52..ebe45aa3ab 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osu.Framework.Allocation; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -15,7 +14,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; -using osu.Game.Screens.Play.HUD; namespace osu.Game.Rulesets.Catch.UI { @@ -32,12 +30,6 @@ namespace osu.Game.Rulesets.Catch.UI TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450); } - [BackgroundDependencyLoader] - private void load() - { - Overlays.Add(new FailingLayer()); - } - protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new CatchReplayRecorder(replay, (CatchPlayfield)Playfield); diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 18bdfa5b5d..14cad39b04 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -21,7 +21,6 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; -using osu.Game.Screens.Play.HUD; using osuTK; namespace osu.Game.Rulesets.Mania.UI @@ -78,8 +77,6 @@ namespace osu.Game.Rulesets.Mania.UI configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true); Config.BindWith(ManiaRulesetSetting.ScrollTime, TimeRange); - - Overlays.Add(new FailingLayer()); } protected override void AdjustScrollSpeed(int amount) diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index b04e3cef3b..b4d51d11c9 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Input.Handlers; @@ -17,7 +16,6 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; -using osu.Game.Screens.Play.HUD; using osuTK; namespace osu.Game.Rulesets.Osu.UI @@ -31,12 +29,6 @@ namespace osu.Game.Rulesets.Osu.UI { } - [BackgroundDependencyLoader] - private void load() - { - Overlays.Add(new FailingLayer()); - } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // always show the gameplay cursor protected override Playfield CreatePlayfield() => new OsuPlayfield(); diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index 761178b93d..f026d09c39 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Graphics; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Play.HUD { @@ -48,6 +49,17 @@ namespace osu.Game.Screens.Play.HUD enabled.BindValueChanged(e => this.FadeTo(e.NewValue ? 1 : 0, fade_time, Easing.OutQuint), true); } + public override void BindHealthProcessor(HealthProcessor processor) + { + base.BindHealthProcessor(processor); + + if (!(processor is DrainingHealthProcessor)) + { + enabled.UnbindBindings(); + enabled.Value = false; + } + } + protected override void Update() { box.Alpha = (float)Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, fade_time), box.Alpha, diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index 01cb64a88c..08cb07d7ee 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Play.HUD /// /// Bind the tracked fields of to this health display. /// - public void BindHealthProcessor(HealthProcessor processor) + public virtual void BindHealthProcessor(HealthProcessor processor) { Current.BindTo(processor.Health); } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index e06f6d19c2..5114efd9a9 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -37,6 +37,7 @@ namespace osu.Game.Screens.Play public readonly HitErrorDisplay HitErrorDisplay; public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; + public readonly FailingLayer FailingLayer; public Bindable ShowHealthbar = new Bindable(true); @@ -75,6 +76,7 @@ namespace osu.Game.Screens.Play Children = new Drawable[] { + FailingLayer = CreateFailingLayer(), visibilityContainer = new Container { RelativeSizeAxes = Axes.Both, @@ -260,6 +262,8 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20 } }; + protected virtual FailingLayer CreateFailingLayer() => new FailingLayer(); + protected virtual KeyCounterDisplay CreateKeyCounter() => new KeyCounterDisplay { Anchor = Anchor.BottomRight, @@ -304,7 +308,8 @@ namespace osu.Game.Screens.Play protected virtual void BindHealthProcessor(HealthProcessor processor) { - HealthDisplay?.Current.BindTo(processor.Health); + HealthDisplay?.BindHealthProcessor(processor); + FailingLayer?.BindHealthProcessor(processor); } } } From 947745d87eff2d73b2f6f3c7da091897245217e7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Apr 2020 14:33:11 +0900 Subject: [PATCH 190/655] Change fail effect to be less distracting --- .../Visual/Gameplay/TestSceneFailingLayer.cs | 5 +-- osu.Game/Screens/Play/HUD/FailingLayer.cs | 40 +++++++++++++++---- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs index 97fe0ac769..42a211cb3d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs @@ -1,11 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Screens.Play.HUD; @@ -40,7 +37,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestLayerFading() { AddSliderStep("current health", 0.0, 1.0, 1.0, val => layer.Current.Value = val); - var box = layer.ChildrenOfType().First(); + var box = layer.Child; AddStep("set health to 0.10", () => layer.Current.Value = 0.10); AddWaitStep("wait for fade to finish", 5); diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index f026d09c39..79f6855804 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -4,12 +4,16 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Rulesets.Scoring; +using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD { @@ -22,8 +26,6 @@ namespace osu.Game.Screens.Play.HUD private const int fade_time = 400; - private readonly Box box; - private Bindable enabled; /// @@ -31,20 +33,43 @@ namespace osu.Game.Screens.Play.HUD /// public double LowHealthThreshold = 0.20f; + private readonly Container boxes; + public FailingLayer() { RelativeSizeAxes = Axes.Both; - Child = box = new Box + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Alpha = 0 + boxes = new Container + { + Blending = BlendingParameters.Additive, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(Color4.White, Color4.White.Opacity(0)), + Height = 0.2f, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Height = 0.2f, + Colour = ColourInfo.GradientVertical(Color4.White.Opacity(0), Color4.White), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }, + } + }, }; } [BackgroundDependencyLoader] private void load(OsuColour color, OsuConfigManager config) { - box.Colour = color.Red; + boxes.Colour = color.Red; + enabled = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow); enabled.BindValueChanged(e => this.FadeTo(e.NewValue ? 1 : 0, fade_time, Easing.OutQuint), true); } @@ -53,6 +78,7 @@ namespace osu.Game.Screens.Play.HUD { base.BindHealthProcessor(processor); + // don't display ever if the ruleset is not using a draining health display. if (!(processor is DrainingHealthProcessor)) { enabled.UnbindBindings(); @@ -62,7 +88,7 @@ namespace osu.Game.Screens.Play.HUD protected override void Update() { - box.Alpha = (float)Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, fade_time), box.Alpha, + boxes.Alpha = (float)Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, fade_time), boxes.Alpha, Math.Clamp(max_alpha * (1 - Current.Value / LowHealthThreshold), 0, max_alpha), 0, fade_time, Easing.Out); base.Update(); From 52c976265146e754738a5c8f94222687537cd769 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Apr 2020 14:36:04 +0900 Subject: [PATCH 191/655] Remove pointless keywords --- osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 4b75910454..0e854e8e9f 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -56,7 +56,6 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { LabelText = "Fade playfield to red when health is low", Bindable = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow), - Keywords = new[] { "hp", "low", "playfield", "red" } }, new SettingsCheckbox { From 6db22366e2bcd50bb60aaa5b327d42e6fa639ad0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Apr 2020 14:47:48 +0900 Subject: [PATCH 192/655] Add new tests and tidy up existing tests --- .../Visual/Gameplay/TestSceneFailingLayer.cs | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs index 42a211cb3d..0b5f023007 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs @@ -3,48 +3,63 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Testing; using osu.Game.Configuration; +using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneFailingLayer : OsuTestScene { - private readonly FailingLayer layer; + private FailingLayer layer; [Resolved] private OsuConfigManager config { get; set; } - public TestSceneFailingLayer() + [SetUpSteps] + public void SetUpSteps() { - Child = layer = new FailingLayer(); + AddStep("create layer", () => Child = layer = new FailingLayer()); + AddStep("enable layer", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true)); + AddUntilStep("layer is visible", () => layer.IsPresent); } [Test] - public void TestLayerConfig() + public void TestLayerDisabledViaConfig() { - AddStep("enable layer", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true)); - AddWaitStep("wait for transition to finish", 5); - AddAssert("layer is enabled", () => layer.IsPresent); - AddStep("disable layer", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false)); - AddWaitStep("wait for transition to finish", 5); - AddAssert("layer is disabled", () => !layer.IsPresent); - AddStep("restore layer enabling", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true)); + AddUntilStep("layer is not visible", () => !layer.IsPresent); + } + + [Test] + public void TestLayerVisibilityWithAccumulatingProcessor() + { + AddStep("bind accumulating processor", () => layer.BindHealthProcessor(new AccumulatingHealthProcessor(1))); + AddUntilStep("layer is not visible", () => !layer.IsPresent); + } + + [Test] + public void TestLayerVisibilityWithDrainingProcessor() + { + AddStep("bind accumulating processor", () => layer.BindHealthProcessor(new DrainingHealthProcessor(1))); + AddWaitStep("wait for potential fade", 10); + AddAssert("layer is still visible", () => layer.IsPresent); } [Test] public void TestLayerFading() { - AddSliderStep("current health", 0.0, 1.0, 1.0, val => layer.Current.Value = val); - var box = layer.Child; + AddSliderStep("current health", 0.0, 1.0, 1.0, val => + { + if (layer != null) + layer.Current.Value = val; + }); - AddStep("set health to 0.10", () => layer.Current.Value = 0.10); - AddWaitStep("wait for fade to finish", 5); - AddAssert("layer fade is visible", () => box.IsPresent); + AddStep("set health to 0.10", () => layer.Current.Value = 0.1); + AddUntilStep("layer fade is visible", () => layer.Child.Alpha > 0.1f); AddStep("set health to 1", () => layer.Current.Value = 1f); - AddWaitStep("wait for fade to finish", 10); - AddAssert("layer fade is invisible", () => !box.IsPresent); + AddUntilStep("layer fade is invisible", () => !layer.Child.IsPresent); } } } From c44957db3f8261da13f277c1b225e0bedbdebf3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Apr 2020 14:49:09 +0900 Subject: [PATCH 193/655] Change initial health to 1 to avoid false fail display --- osu.Game/Screens/Play/HUD/FailingLayer.cs | 1 + osu.Game/Screens/Play/HUD/HealthDisplay.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index 79f6855804..335516a767 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -42,6 +42,7 @@ namespace osu.Game.Screens.Play.HUD { boxes = new Container { + Alpha = 0, Blending = BlendingParameters.Additive, RelativeSizeAxes = Axes.Both, Children = new Drawable[] diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index 08cb07d7ee..edc9dedf24 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Play.HUD /// public abstract class HealthDisplay : Container { - public readonly BindableDouble Current = new BindableDouble + public readonly BindableDouble Current = new BindableDouble(1) { MinValue = 0, MaxValue = 1 From 5a78e74470740ab31141c71b623d3d0c4bfa2987 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Apr 2020 14:51:50 +0900 Subject: [PATCH 194/655] Use Lerp instead of ValueAt --- osu.Game/Screens/Play/HUD/FailingLayer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index 335516a767..2a98e277b3 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -89,8 +89,9 @@ namespace osu.Game.Screens.Play.HUD protected override void Update() { - boxes.Alpha = (float)Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, fade_time), boxes.Alpha, - Math.Clamp(max_alpha * (1 - Current.Value / LowHealthThreshold), 0, max_alpha), 0, fade_time, Easing.Out); + double target = Math.Clamp(max_alpha * (1 - Current.Value / LowHealthThreshold), 0, max_alpha); + + boxes.Alpha = (float)Interpolation.Lerp(boxes.Alpha, target, Clock.ElapsedFrameTime * 0.01f); base.Update(); } From 1c72afe8c49bfbe8df6f7670bb21c8f45cbcd271 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Apr 2020 14:52:40 +0900 Subject: [PATCH 195/655] Move fading test to top for convenience --- .../Visual/Gameplay/TestSceneFailingLayer.cs | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs index 0b5f023007..d831ea1835 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs @@ -25,6 +25,21 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("layer is visible", () => layer.IsPresent); } + [Test] + public void TestLayerFading() + { + AddSliderStep("current health", 0.0, 1.0, 1.0, val => + { + if (layer != null) + layer.Current.Value = val; + }); + + AddStep("set health to 0.10", () => layer.Current.Value = 0.1); + AddUntilStep("layer fade is visible", () => layer.Child.Alpha > 0.1f); + AddStep("set health to 1", () => layer.Current.Value = 1f); + AddUntilStep("layer fade is invisible", () => !layer.Child.IsPresent); + } + [Test] public void TestLayerDisabledViaConfig() { @@ -46,20 +61,5 @@ namespace osu.Game.Tests.Visual.Gameplay AddWaitStep("wait for potential fade", 10); AddAssert("layer is still visible", () => layer.IsPresent); } - - [Test] - public void TestLayerFading() - { - AddSliderStep("current health", 0.0, 1.0, 1.0, val => - { - if (layer != null) - layer.Current.Value = val; - }); - - AddStep("set health to 0.10", () => layer.Current.Value = 0.1); - AddUntilStep("layer fade is visible", () => layer.Child.Alpha > 0.1f); - AddStep("set health to 1", () => layer.Current.Value = 1f); - AddUntilStep("layer fade is invisible", () => !layer.Child.IsPresent); - } } } From c5005eb378c657fff32971aebcb336ea4ff20ad1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Apr 2020 14:55:02 +0900 Subject: [PATCH 196/655] Adjust gradient size slightly and make const --- osu.Game/Screens/Play/HUD/FailingLayer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index 2a98e277b3..a1188343ac 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -33,6 +33,8 @@ namespace osu.Game.Screens.Play.HUD /// public double LowHealthThreshold = 0.20f; + private const float gradient_size = 0.3f; + private readonly Container boxes; public FailingLayer() @@ -51,12 +53,12 @@ namespace osu.Game.Screens.Play.HUD { RelativeSizeAxes = Axes.Both, Colour = ColourInfo.GradientVertical(Color4.White, Color4.White.Opacity(0)), - Height = 0.2f, + Height = gradient_size, }, new Box { RelativeSizeAxes = Axes.Both, - Height = 0.2f, + Height = gradient_size, Colour = ColourInfo.GradientVertical(Color4.White.Opacity(0), Color4.White), Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, From 134feefa141b27b81ca9674a8332257216ee4755 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 9 Apr 2020 13:10:09 +0300 Subject: [PATCH 197/655] Remove bindable --- .../TestSceneOverlayScrollContainer.cs | 6 ++-- osu.Game/Overlays/OverlayScrollContainer.cs | 36 +++++++++---------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs index 0eccc907a1..4205d65100 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs @@ -55,13 +55,13 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestButtonVisibility() { - AddAssert("button is hidden", () => scroll.Button.Current.Value == Visibility.Hidden); + AddAssert("button is hidden", () => scroll.Button.State == Visibility.Hidden); AddStep("scroll to end", () => scroll.ScrollToEnd(false)); - AddAssert("button is visible", () => scroll.Button.Current.Value == Visibility.Visible); + AddAssert("button is visible", () => scroll.Button.State == Visibility.Visible); AddStep("scroll to start", () => scroll.ScrollToStart(false)); - AddAssert("button is hidden", () => scroll.Button.Current.Value == Visibility.Hidden); + AddAssert("button is hidden", () => scroll.Button.State == Visibility.Hidden); } [Test] diff --git a/osu.Game/Overlays/OverlayScrollContainer.cs b/osu.Game/Overlays/OverlayScrollContainer.cs index f96d9e3a31..a6c687f28f 100644 --- a/osu.Game/Overlays/OverlayScrollContainer.cs +++ b/osu.Game/Overlays/OverlayScrollContainer.cs @@ -3,14 +3,12 @@ using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osuTK; @@ -43,7 +41,7 @@ namespace osu.Game.Overlays { ScrollToStart(); currentTarget = Target; - Button.Current.Value = Visibility.Hidden; + Button.State = Visibility.Hidden; } }); } @@ -54,7 +52,7 @@ namespace osu.Game.Overlays if (ScrollContent.DrawHeight + button_scroll_position < DrawHeight) { - Button.Current.Value = Visibility.Hidden; + Button.State = Visibility.Hidden; return; } @@ -62,19 +60,27 @@ namespace osu.Game.Overlays return; currentTarget = Target; - Button.Current.Value = Current > button_scroll_position ? Visibility.Visible : Visibility.Hidden; + Button.State = Current > button_scroll_position ? Visibility.Visible : Visibility.Hidden; } - public class ScrollToTopButton : OsuHoverContainer, IHasCurrentValue + public class ScrollToTopButton : OsuHoverContainer { private const int fade_duration = 500; - private readonly BindableWithCurrent current = new BindableWithCurrent(); + private Visibility state; - public Bindable Current + public Visibility State { - get => current.Current; - set => current.Current = value; + get => state; + set + { + if (value == state) + return; + + state = value; + Enabled.Value = state == Visibility.Visible; + this.FadeTo(state == Visibility.Visible ? 1 : 0, fade_duration, Easing.OutQuint); + } } protected override IEnumerable EffectTargets => new[] { background }; @@ -128,16 +134,6 @@ namespace osu.Game.Overlays flashColour = colourProvider.Light1; } - protected override void LoadComplete() - { - base.LoadComplete(); - Current.BindValueChanged(visibility => - { - Enabled.Value = visibility.NewValue == Visibility.Visible; - this.FadeTo(visibility.NewValue == Visibility.Visible ? 1 : 0, fade_duration, Easing.OutQuint); - }, true); - } - protected override bool OnClick(ClickEvent e) { background.FlashColour(flashColour, 800, Easing.OutQuint); From ee6ea08cf85a5c4cdb6de99ed8a445c84248d9ea Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 9 Apr 2020 19:54:58 +0900 Subject: [PATCH 198/655] Cleanup handling of hitobject updates --- .../Sliders/SliderSelectionBlueprint.cs | 6 ++++- osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs | 6 ++--- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 27 ------------------- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 5 ---- osu.Game/Screens/Edit/EditorBeatmap.cs | 18 ++++++++++--- 5 files changed, 22 insertions(+), 40 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index c18b3b0ff3..001100d3ce 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose; using osuTK; using osuTK.Input; @@ -34,6 +35,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders [Resolved(CanBeNull = true)] private IPlacementHandler placementHandler { get; set; } + [Resolved(CanBeNull = true)] + private EditorBeatmap editorBeatmap { get; set; } + public SliderSelectionBlueprint(DrawableSlider slider) : base(slider) { @@ -162,7 +166,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void updatePath() { HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; - UpdateHitObject(); + editorBeatmap?.UpdateHitObject(HitObject); } public override MenuItem[] ContextMenuItems => new MenuItem[] diff --git a/osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs b/osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs index 12d729d09f..f2b13e3a85 100644 --- a/osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Beatmaps var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } }); HitObject changedObject = null; - editorBeatmap.StartTimeChanged += h => changedObject = h; + editorBeatmap.HitObjectUpdated += h => changedObject = h; hitCircle.StartTime = 1000; Assert.That(changedObject, Is.EqualTo(hitCircle)); @@ -74,7 +74,7 @@ namespace osu.Game.Tests.Beatmaps var editorBeatmap = new EditorBeatmap(new OsuBeatmap()); HitObject changedObject = null; - editorBeatmap.StartTimeChanged += h => changedObject = h; + editorBeatmap.HitObjectUpdated += h => changedObject = h; var hitCircle = new HitCircle(); @@ -95,7 +95,7 @@ namespace osu.Game.Tests.Beatmaps var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } }); HitObject changedObject = null; - editorBeatmap.StartTimeChanged += h => changedObject = h; + editorBeatmap.HitObjectUpdated += h => changedObject = h; editorBeatmap.Remove(hitCircle); Assert.That(changedObject, Is.Null); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index fb4e945701..883288d6d7 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -69,10 +69,6 @@ namespace osu.Game.Rulesets.Edit [BackgroundDependencyLoader] private void load(IFrameBasedClock framedClock) { - EditorBeatmap.HitObjectAdded += addHitObject; - EditorBeatmap.HitObjectRemoved += removeHitObject; - EditorBeatmap.StartTimeChanged += UpdateHitObject; - Config = Dependencies.Get().GetConfigFor(Ruleset); try @@ -236,10 +232,6 @@ namespace osu.Game.Rulesets.Edit lastGridUpdateTime = EditorClock.CurrentTime; } - private void addHitObject(HitObject hitObject) => UpdateHitObject(hitObject); - - private void removeHitObject(HitObject hitObject) => UpdateHitObject(null); - public override IEnumerable HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects; public override bool CursorInPlacementArea => drawableRulesetWrapper.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); @@ -302,19 +294,6 @@ namespace osu.Game.Rulesets.Edit return DurationToDistance(referenceTime, snappedEndTime - referenceTime); } - - public override void UpdateHitObject(HitObject hitObject) => EditorBeatmap.UpdateHitObject(hitObject); - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (EditorBeatmap != null) - { - EditorBeatmap.HitObjectAdded -= addHitObject; - EditorBeatmap.HitObjectRemoved -= removeHitObject; - } - } } [Cached(typeof(HitObjectComposer))] @@ -344,12 +323,6 @@ namespace osu.Game.Rulesets.Edit [CanBeNull] protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable selectedHitObjects) => null; - /// - /// Updates a , invoking and re-processing the beatmap. - /// - /// The to update. - public abstract void UpdateHitObject([CanBeNull] HitObject hitObject); - public abstract (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time); public abstract float GetBeatSnapDistanceAt(double referenceTime); diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index a972d28480..e6a63eae4f 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -108,11 +108,6 @@ namespace osu.Game.Rulesets.Edit public bool IsSelected => State == SelectionState.Selected; - /// - /// Updates the , invoking and re-processing the beatmap. - /// - protected void UpdateHitObject() => composer?.UpdateHitObject(HitObject); - /// /// The s to be displayed in the context menu for this . /// diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 5216e85903..7f04a7a58d 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -29,9 +30,9 @@ namespace osu.Game.Screens.Edit public event Action HitObjectRemoved; /// - /// Invoked when the start time of a in this was changed. + /// Invoked when a is updated. /// - public event Action StartTimeChanged; + public event Action HitObjectUpdated; /// /// All currently selected s. @@ -68,7 +69,9 @@ namespace osu.Game.Screens.Edit /// Updates a , invoking and re-processing the beatmap. /// /// The to update. - public void UpdateHitObject(HitObject hitObject) + public void UpdateHitObject([NotNull] HitObject hitObject) => updateHitObject(hitObject, false); + + private void updateHitObject([CanBeNull] HitObject hitObject, bool silent) { scheduledUpdate?.Cancel(); scheduledUpdate = Scheduler.AddDelayed(() => @@ -76,6 +79,9 @@ namespace osu.Game.Screens.Edit beatmapProcessor?.PreProcess(); hitObject?.ApplyDefaults(ControlPointInfo, BeatmapInfo.BaseDifficulty); beatmapProcessor?.PostProcess(); + + if (!silent) + HitObjectUpdated?.Invoke(hitObject); }, 0); } @@ -114,6 +120,8 @@ namespace osu.Game.Screens.Edit mutableHitObjects.Insert(insertionIndex + 1, hitObject); HitObjectAdded?.Invoke(hitObject); + + updateHitObject(hitObject, true); } /// @@ -132,6 +140,8 @@ namespace osu.Game.Screens.Edit startTimeBindables.Remove(hitObject); HitObjectRemoved?.Invoke(hitObject); + + updateHitObject(null, true); } private void trackStartTime(HitObject hitObject) @@ -145,7 +155,7 @@ namespace osu.Game.Screens.Edit var insertionIndex = findInsertionIndex(PlayableBeatmap.HitObjects, hitObject.StartTime); mutableHitObjects.Insert(insertionIndex + 1, hitObject); - StartTimeChanged?.Invoke(hitObject); + UpdateHitObject(hitObject); }; } From b900f229e778c4c9ca5b65daccfea8837e6cc6eb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 9 Apr 2020 20:21:42 +0900 Subject: [PATCH 199/655] Fix possible legacy beatmap encoder nullref --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index ec2ca30535..12f2c58e35 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -111,7 +111,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"Source: {beatmap.Metadata.Source}")); writer.WriteLine(FormattableString.Invariant($"Tags: {beatmap.Metadata.Tags}")); writer.WriteLine(FormattableString.Invariant($"BeatmapID: {beatmap.BeatmapInfo.OnlineBeatmapID ?? 0}")); - writer.WriteLine(FormattableString.Invariant($"BeatmapSetID: {beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID ?? -1}")); + writer.WriteLine(FormattableString.Invariant($"BeatmapSetID: {beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID ?? -1}")); } private void handleDifficulty(TextWriter writer) From 683302a77d63a223ca902ac8f19b558908b941c7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 9 Apr 2020 20:25:26 +0900 Subject: [PATCH 200/655] Fix crash when trying to edit long beatmaps --- osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index ddca5e42c2..1cb4f737c1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -60,8 +60,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline waveform.Waveform = b.NewValue.Waveform; track = b.NewValue.Track; - MinZoom = getZoomLevelForVisibleMilliseconds(10000); MaxZoom = getZoomLevelForVisibleMilliseconds(500); + MinZoom = getZoomLevelForVisibleMilliseconds(10000); Zoom = getZoomLevelForVisibleMilliseconds(2000); }, true); } From e58bf8a0d08154d87043f1a90170e8f46f6eea8c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 9 Apr 2020 20:38:38 +0900 Subject: [PATCH 201/655] Add DiffPlex package --- osu.Game/osu.Game.csproj | 1 + osu.iOS.props | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3e2c2b1599..3dd84caea9 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -18,6 +18,7 @@ + diff --git a/osu.iOS.props b/osu.iOS.props index 7903d964ce..7e6f6b5246 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -75,6 +75,7 @@ + From 86243d463f423367c2f140be93d00986c044894b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 9 Apr 2020 20:48:59 +0900 Subject: [PATCH 202/655] Add legacy beatmap diffing --- .../Editor/LegacyEditorBeatmapDifferTest.cs | 342 ++++++++++++++++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 40 +- .../Screens/Edit/LegacyEditorBeatmapDiffer.cs | 110 ++++++ 3 files changed, 488 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Tests/Editor/LegacyEditorBeatmapDifferTest.cs create mode 100644 osu.Game/Screens/Edit/LegacyEditorBeatmapDiffer.cs diff --git a/osu.Game.Tests/Editor/LegacyEditorBeatmapDifferTest.cs b/osu.Game.Tests/Editor/LegacyEditorBeatmapDifferTest.cs new file mode 100644 index 0000000000..d70a112b7f --- /dev/null +++ b/osu.Game.Tests/Editor/LegacyEditorBeatmapDifferTest.cs @@ -0,0 +1,342 @@ +// 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.Text; +using NUnit.Framework; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Formats; +using osu.Game.IO; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit; +using osuTK; +using Decoder = osu.Game.Beatmaps.Formats.Decoder; + +namespace osu.Game.Tests.Editor +{ + [TestFixture] + public class LegacyEditorBeatmapDifferTest + { + private LegacyEditorBeatmapDiffer differ; + private EditorBeatmap current; + + [SetUp] + public void Setup() + { + differ = new LegacyEditorBeatmapDiffer(current = new EditorBeatmap(new OsuBeatmap + { + BeatmapInfo = + { + Ruleset = new OsuRuleset().RulesetInfo + } + })); + } + + [Test] + public void TestAddHitObject() + { + var patch = new OsuBeatmap + { + HitObjects = + { + new HitCircle { StartTime = 1000 } + } + }; + + runTest(patch); + } + + [Test] + public void TestInsertHitObject() + { + current.AddRange(new[] + { + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 3000 }, + }); + + var patch = new OsuBeatmap + { + HitObjects = + { + (OsuHitObject)current.HitObjects[0], + new HitCircle { StartTime = 2000 }, + (OsuHitObject)current.HitObjects[1], + } + }; + + runTest(patch); + } + + [Test] + public void TestDeleteHitObject() + { + current.AddRange(new[] + { + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 2000 }, + new HitCircle { StartTime = 3000 }, + }); + + var patch = new OsuBeatmap + { + HitObjects = + { + (OsuHitObject)current.HitObjects[0], + (OsuHitObject)current.HitObjects[2], + } + }; + + runTest(patch); + } + + [Test] + public void TestChangeStartTime() + { + current.AddRange(new[] + { + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 2000 }, + new HitCircle { StartTime = 3000 }, + }); + + var patch = new OsuBeatmap + { + HitObjects = + { + new HitCircle { StartTime = 500 }, + (OsuHitObject)current.HitObjects[1], + (OsuHitObject)current.HitObjects[2], + } + }; + + runTest(patch); + } + + [Test] + public void TestChangeSample() + { + current.AddRange(new[] + { + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 2000 }, + new HitCircle { StartTime = 3000 }, + }); + + var patch = new OsuBeatmap + { + HitObjects = + { + (OsuHitObject)current.HitObjects[0], + new HitCircle { StartTime = 2000, Samples = { new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH } } }, + (OsuHitObject)current.HitObjects[2], + } + }; + + runTest(patch); + } + + [Test] + public void TestChangeSliderPath() + { + current.AddRange(new OsuHitObject[] + { + new HitCircle { StartTime = 1000 }, + new Slider + { + StartTime = 2000, + Path = new SliderPath(new[] + { + new PathControlPoint(Vector2.Zero), + new PathControlPoint(Vector2.One), + new PathControlPoint(new Vector2(2), PathType.Bezier), + new PathControlPoint(new Vector2(3)), + }, 50) + }, + new HitCircle { StartTime = 3000 }, + }); + + var patch = new OsuBeatmap + { + HitObjects = + { + (OsuHitObject)current.HitObjects[0], + new Slider + { + StartTime = 2000, + Path = new SliderPath(new[] + { + new PathControlPoint(Vector2.Zero, PathType.Bezier), + new PathControlPoint(new Vector2(4)), + new PathControlPoint(new Vector2(5)), + }, 100) + }, + (OsuHitObject)current.HitObjects[2], + } + }; + + runTest(patch); + } + + [Test] + public void TestAddMultipleHitObjects() + { + current.AddRange(new[] + { + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 2000 }, + new HitCircle { StartTime = 3000 }, + }); + + var patch = new OsuBeatmap + { + HitObjects = + { + new HitCircle { StartTime = 500 }, + (OsuHitObject)current.HitObjects[0], + new HitCircle { StartTime = 1500 }, + (OsuHitObject)current.HitObjects[1], + new HitCircle { StartTime = 2250 }, + new HitCircle { StartTime = 2500 }, + (OsuHitObject)current.HitObjects[2], + new HitCircle { StartTime = 3500 }, + } + }; + + runTest(patch); + } + + [Test] + public void TestDeleteMultipleHitObjects() + { + current.AddRange(new[] + { + new HitCircle { StartTime = 500 }, + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 1500 }, + new HitCircle { StartTime = 2000 }, + new HitCircle { StartTime = 2250 }, + new HitCircle { StartTime = 2500 }, + new HitCircle { StartTime = 3000 }, + new HitCircle { StartTime = 3500 }, + }); + + var patch = new OsuBeatmap + { + HitObjects = + { + (OsuHitObject)current.HitObjects[1], + (OsuHitObject)current.HitObjects[3], + (OsuHitObject)current.HitObjects[6], + } + }; + + runTest(patch); + } + + [Test] + public void TestChangeSamplesOfMultipleHitObjects() + { + current.AddRange(new[] + { + new HitCircle { StartTime = 500 }, + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 1500 }, + new HitCircle { StartTime = 2000 }, + new HitCircle { StartTime = 2250 }, + new HitCircle { StartTime = 2500 }, + new HitCircle { StartTime = 3000 }, + new HitCircle { StartTime = 3500 }, + }); + + var patch = new OsuBeatmap + { + HitObjects = + { + (OsuHitObject)current.HitObjects[0], + new HitCircle { StartTime = 1000, Samples = { new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH } } }, + (OsuHitObject)current.HitObjects[2], + (OsuHitObject)current.HitObjects[3], + new HitCircle { StartTime = 2250, Samples = { new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE } } }, + (OsuHitObject)current.HitObjects[5], + new HitCircle { StartTime = 3000, Samples = { new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP } } }, + (OsuHitObject)current.HitObjects[7], + } + }; + + runTest(patch); + } + + [Test] + public void TestAddAndDeleteHitObjects() + { + current.AddRange(new[] + { + new HitCircle { StartTime = 500 }, + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 1500 }, + new HitCircle { StartTime = 2000 }, + new HitCircle { StartTime = 2250 }, + new HitCircle { StartTime = 2500 }, + new HitCircle { StartTime = 3000 }, + new HitCircle { StartTime = 3500 }, + }); + + var patch = new OsuBeatmap + { + HitObjects = + { + new HitCircle { StartTime = 750 }, + (OsuHitObject)current.HitObjects[1], + (OsuHitObject)current.HitObjects[4], + (OsuHitObject)current.HitObjects[5], + new HitCircle { StartTime = 2650 }, + new HitCircle { StartTime = 2750 }, + new HitCircle { StartTime = 4000 }, + } + }; + + runTest(patch); + } + + private void runTest(IBeatmap patch) + { + // Due to the method of testing, "patch" comes in without having been decoded via a beatmap decoder. + // This causes issues because the decoder adds various default properties (e.g. new combo on first object, default samples). + // To resolve "patch" into a sane state it is encoded and then re-decoded. + patch = decode(encode(patch)); + + // Apply the patch. + differ.Patch(encode(current), encode(patch)); + + // Convert beatmaps to strings for assertion purposes. + string currentStr = Encoding.ASCII.GetString(encode(current).ToArray()); + string patchStr = Encoding.ASCII.GetString(encode(patch).ToArray()); + + Assert.That(currentStr, Is.EqualTo(patchStr)); + } + + private MemoryStream encode(IBeatmap beatmap) + { + var encoded = new MemoryStream(); + + using (var sw = new StreamWriter(encoded, leaveOpen: true)) + new LegacyBeatmapEncoder(beatmap).Encode(sw); + + return encoded; + } + + private IBeatmap decode(Stream stream) + { + stream.Seek(0, SeekOrigin.Begin); + + using (var reader = new LineBufferedReader(stream, true)) + return Decoder.GetDecoder(reader).Decode(reader); + } + } +} diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 7f04a7a58d..22e0061b61 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -107,6 +107,16 @@ namespace osu.Game.Screens.Edit private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; + /// + /// Adds a collection of s to this . + /// + /// The s to add. + public void AddRange(IEnumerable hitObjects) + { + foreach (var h in hitObjects) + Add(h); + } + /// /// Adds a to this . /// @@ -128,12 +138,34 @@ namespace osu.Game.Screens.Edit /// Removes a from this . /// /// The to add. - public void Remove(HitObject hitObject) + /// True if the has been removed, false otherwise. + public bool Remove(HitObject hitObject) { - if (!mutableHitObjects.Contains(hitObject)) - return; + int index = FindIndex(hitObject); - mutableHitObjects.Remove(hitObject); + if (index == -1) + return false; + + RemoveAt(index); + return true; + } + + /// + /// Finds the index of a in this . + /// + /// The to search for. + /// The index of . + public int FindIndex(HitObject hitObject) => mutableHitObjects.IndexOf(hitObject); + + /// + /// Removes a at an index in this . + /// + /// The index of the to remove. + public void RemoveAt(int index) + { + var hitObject = (HitObject)mutableHitObjects[index]; + + mutableHitObjects.RemoveAt(index); var bindable = startTimeBindables[hitObject]; bindable.UnbindAll(); diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapDiffer.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapDiffer.cs new file mode 100644 index 0000000000..8d2f577a1d --- /dev/null +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapDiffer.cs @@ -0,0 +1,110 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.IO; +using DiffPlex; +using osu.Framework.Audio.Track; +using osu.Framework.Graphics.Textures; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Formats; +using osu.Game.IO; + +namespace osu.Game.Screens.Edit +{ + public class LegacyEditorBeatmapDiffer + { + private readonly EditorBeatmap editorBeatmap; + + public LegacyEditorBeatmapDiffer(EditorBeatmap editorBeatmap) + { + this.editorBeatmap = editorBeatmap; + } + + public void Patch(Stream currentState, Stream newState) + { + // Diff the beatmaps + var result = new Differ().CreateLineDiffs(readString(currentState), readString(newState), true, false); + + // Find the index of [HitObject] sections. Lines changed prior to this index are ignored. + int oldHitObjectsIndex = Array.IndexOf(result.PiecesOld, "[HitObjects]"); + int newHitObjectsIndex = Array.IndexOf(result.PiecesNew, "[HitObjects]"); + + var toRemove = new List(); + var toAdd = new List(); + + foreach (var block in result.DiffBlocks) + { + // Removed hitobject + for (int i = 0; i < block.DeleteCountA; i++) + { + int hoIndex = block.DeleteStartA + i - oldHitObjectsIndex - 1; + + if (hoIndex < 0) + continue; + + toRemove.Add(hoIndex); + } + + // Added hitobject + for (int i = 0; i < block.InsertCountB; i++) + { + int hoIndex = block.InsertStartB + i - newHitObjectsIndex - 1; + + if (hoIndex < 0) + continue; + + toAdd.Add(hoIndex); + } + } + + // Make the removal indices are sorted so that iteration order doesn't get messed up post-removal. + toRemove.Sort(); + + // Apply the changes. + for (int i = toRemove.Count - 1; i >= 0; i--) + editorBeatmap.RemoveAt(toRemove[i]); + + if (toAdd.Count > 0) + { + IBeatmap newBeatmap = readBeatmap(newState); + foreach (var i in toAdd) + editorBeatmap.Add(newBeatmap.HitObjects[i]); + } + } + + private string readString(Stream stream) + { + stream.Seek(0, SeekOrigin.Begin); + + using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8, true, 1024, true)) + return sr.ReadToEnd(); + } + + private IBeatmap readBeatmap(Stream stream) + { + stream.Seek(0, SeekOrigin.Begin); + + using (var reader = new LineBufferedReader(stream, true)) + return new PassThroughWorkingBeatmap(Decoder.GetDecoder(reader).Decode(reader)).GetPlayableBeatmap(editorBeatmap.BeatmapInfo.Ruleset); + } + + private class PassThroughWorkingBeatmap : WorkingBeatmap + { + private readonly IBeatmap beatmap; + + public PassThroughWorkingBeatmap(IBeatmap beatmap) + : base(beatmap.BeatmapInfo, null) + { + this.beatmap = beatmap; + } + + protected override IBeatmap GetBeatmap() => beatmap; + + protected override Texture GetBackground() => throw new NotImplementedException(); + + protected override Track GetTrack() => throw new NotImplementedException(); + } + } +} From ecd7ce4b98648f786d9861f1e4c4bf5bd8f5f358 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 9 Apr 2020 21:00:23 +0900 Subject: [PATCH 203/655] Fix test scene --- ...atmapTest.cs => TestSceneEditorBeatmap.cs} | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) rename osu.Game.Tests/Beatmaps/{EditorBeatmapTest.cs => TestSceneEditorBeatmap.cs} (80%) diff --git a/osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs b/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs similarity index 80% rename from osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs rename to osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs index f2b13e3a85..d367d9f88b 100644 --- a/osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs @@ -4,15 +4,17 @@ using System.Linq; using Microsoft.EntityFrameworkCore.Internal; using NUnit.Framework; +using osu.Framework.Testing; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; +using osu.Game.Tests.Visual; namespace osu.Game.Tests.Beatmaps { - [TestFixture] - public class EditorBeatmapTest + [HeadlessTest] + public class TestSceneEditorBeatmap : EditorClockTestScene { /// /// Tests that the addition event is correctly invoked after a hitobject is added. @@ -55,13 +57,19 @@ namespace osu.Game.Tests.Beatmaps public void TestInitialHitObjectStartTimeChangeEvent() { var hitCircle = new HitCircle(); - var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } }); HitObject changedObject = null; - editorBeatmap.HitObjectUpdated += h => changedObject = h; - hitCircle.StartTime = 1000; - Assert.That(changedObject, Is.EqualTo(hitCircle)); + AddStep("add beatmap", () => + { + EditorBeatmap editorBeatmap; + + Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } }); + editorBeatmap.HitObjectUpdated += h => changedObject = h; + }); + + AddStep("change start time", () => hitCircle.StartTime = 1000); + AddAssert("received change event", () => changedObject == hitCircle); } /// @@ -71,18 +79,22 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestAddedHitObjectStartTimeChangeEvent() { - var editorBeatmap = new EditorBeatmap(new OsuBeatmap()); - + EditorBeatmap editorBeatmap = null; HitObject changedObject = null; - editorBeatmap.HitObjectUpdated += h => changedObject = h; + + AddStep("add beatmap", () => + { + Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + editorBeatmap.HitObjectUpdated += h => changedObject = h; + }); var hitCircle = new HitCircle(); - editorBeatmap.Add(hitCircle); - Assert.That(changedObject, Is.Null); + AddStep("add object", () => editorBeatmap.Add(hitCircle)); + AddAssert("event not received", () => changedObject == null); - hitCircle.StartTime = 1000; - Assert.That(changedObject, Is.EqualTo(hitCircle)); + AddStep("change start time", () => hitCircle.StartTime = 1000); + AddAssert("event received", () => changedObject == hitCircle); } /// From 14eca3655b752222b609b19f5fe268d1f467e6e1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 9 Apr 2020 21:22:07 +0900 Subject: [PATCH 204/655] Add change state handling to the editor --- osu.Game/Screens/Edit/Editor.cs | 30 ++++- osu.Game/Screens/Edit/EditorChangeHandler.cs | 108 ++++++++++++++++++ osu.Game/Screens/Edit/IEditorChangeHandler.cs | 33 ++++++ 3 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/Edit/EditorChangeHandler.cs create mode 100644 osu.Game/Screens/Edit/IEditorChangeHandler.cs diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index f1cbed57f1..14a227eb07 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -62,6 +62,7 @@ namespace osu.Game.Screens.Edit private IBeatmap playableBeatmap; private EditorBeatmap editorBeatmap; + private EditorChangeHandler changeHandler; private DependencyContainer dependencies; @@ -100,9 +101,11 @@ namespace osu.Game.Screens.Edit } AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap)); - dependencies.CacheAs(editorBeatmap); + changeHandler = new EditorChangeHandler(editorBeatmap); + dependencies.CacheAs(changeHandler); + EditorMenuBar menuBar; var fileMenuItems = new List @@ -147,6 +150,14 @@ namespace osu.Game.Screens.Edit new MenuItem("File") { Items = fileMenuItems + }, + new MenuItem("Edit") + { + Items = new[] + { + new EditorMenuItem("Undo", MenuItemType.Standard, undo), + new EditorMenuItem("Redo", MenuItemType.Standard, redo) + } } } } @@ -233,6 +244,19 @@ namespace osu.Game.Screens.Edit return true; } + break; + + case Key.Z: + if (e.ControlPressed) + { + if (e.ShiftPressed) + redo(); + else + undo(); + + return true; + } + break; } @@ -297,6 +321,10 @@ namespace osu.Game.Screens.Edit return base.OnExiting(next); } + private void undo() => changeHandler.RestoreState(-1); + + private void redo() => changeHandler.RestoreState(1); + private void resetTrack(bool seekToStart = false) { Beatmap.Value.Track?.Stop(); diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs new file mode 100644 index 0000000000..7e372926ba --- /dev/null +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -0,0 +1,108 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using osu.Game.Beatmaps.Formats; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Screens.Edit +{ + /// + /// Tracks changes to the . + /// + public class EditorChangeHandler : IEditorChangeHandler + { + private readonly LegacyEditorBeatmapDiffer differ; + private readonly List savedStates = new List(); + private int currentState = -1; + + private readonly EditorBeatmap editorBeatmap; + private int bulkChangesStarted; + private bool isRestoring; + + /// + /// Creates a new . + /// + /// The to track the s of. + public EditorChangeHandler(EditorBeatmap editorBeatmap) + { + this.editorBeatmap = editorBeatmap; + + editorBeatmap.HitObjectAdded += hitObjectAdded; + editorBeatmap.HitObjectRemoved += hitObjectRemoved; + editorBeatmap.HitObjectUpdated += hitObjectUpdated; + + differ = new LegacyEditorBeatmapDiffer(editorBeatmap); + + // Initial state. + SaveState(); + } + + private void hitObjectAdded(HitObject obj) => SaveState(); + + private void hitObjectRemoved(HitObject obj) => SaveState(); + + private void hitObjectUpdated(HitObject obj) => SaveState(); + + public void BeginChange() => bulkChangesStarted++; + + public void EndChange() + { + if (bulkChangesStarted == 0) + throw new InvalidOperationException($"Cannot call {nameof(EndChange)} without a previous call to {nameof(BeginChange)}."); + + if (--bulkChangesStarted == 0) + SaveState(); + } + + /// + /// Saves the current state. + /// + public void SaveState() + { + if (bulkChangesStarted > 0) + return; + + if (isRestoring) + return; + + var stream = new MemoryStream(); + + using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) + new LegacyBeatmapEncoder(editorBeatmap).Encode(sw); + + if (currentState < savedStates.Count - 1) + savedStates.RemoveRange(currentState + 1, savedStates.Count - currentState - 1); + + savedStates.Add(stream); + currentState = savedStates.Count - 1; + } + + /// + /// Restores an older or newer state. + /// + /// The direction to restore in. If less than 0, an older state will be used. If greater than 0, a newer state will be used. + public void RestoreState(int direction) + { + if (bulkChangesStarted > 0) + return; + + if (savedStates.Count == 0) + return; + + int newState = Math.Clamp(currentState + direction, 0, savedStates.Count - 1); + if (currentState == newState) + return; + + isRestoring = true; + + differ.Patch(savedStates[currentState], savedStates[newState]); + currentState = newState; + + isRestoring = false; + } + } +} diff --git a/osu.Game/Screens/Edit/IEditorChangeHandler.cs b/osu.Game/Screens/Edit/IEditorChangeHandler.cs new file mode 100644 index 0000000000..c1328252d4 --- /dev/null +++ b/osu.Game/Screens/Edit/IEditorChangeHandler.cs @@ -0,0 +1,33 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Screens.Edit +{ + /// + /// Interface for a component that manages changes in the . + /// + public interface IEditorChangeHandler + { + /// + /// Begins a bulk state change event. should be invoked soon after. + /// + /// + /// This should be invoked when multiple changes to the should be bundled together into one state change event. + /// When nested invocations are involved, a state change will not occur until an equal number of invocations of are received. + /// + /// + /// When a group of s are deleted, a single undo and redo state change should update the state of all . + /// + void BeginChange(); + + /// + /// Ends a bulk state change event. + /// + /// + /// This should be invoked as soon as possible after to cause a state change. + /// + void EndChange(); + } +} From ed4ce54ac3e7149b3a6f3d4cc9406ef74d2f6e38 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 9 Apr 2020 21:56:36 +0900 Subject: [PATCH 205/655] Add tests --- .../Editor/TestSceneEditorChangeStates.cs | 193 ++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs new file mode 100644 index 0000000000..abaa373cac --- /dev/null +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs @@ -0,0 +1,193 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Editor +{ + public class TestSceneEditorChangeStates : ScreenTestScene + { + private EditorBeatmap editorBeatmap; + + public override void SetUpSteps() + { + base.SetUpSteps(); + + Screens.Edit.Editor editor = null; + + AddStep("load editor", () => + { + Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + LoadScreen(editor = new Screens.Edit.Editor()); + }); + + AddUntilStep("wait for editor to load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + AddStep("get beatmap", () => editorBeatmap = editor.ChildrenOfType().Single()); + } + + [Test] + public void TestUndoFromInitialState() + { + int hitObjectCount = 0; + + AddStep("get initial state", () => hitObjectCount = editorBeatmap.HitObjects.Count); + + addUndoSteps(); + + AddAssert("no change occurred", () => hitObjectCount == editorBeatmap.HitObjects.Count); + } + + [Test] + public void TestRedoFromInitialState() + { + int hitObjectCount = 0; + + AddStep("get initial state", () => hitObjectCount = editorBeatmap.HitObjects.Count); + + addRedoSteps(); + + AddAssert("no change occurred", () => hitObjectCount == editorBeatmap.HitObjects.Count); + } + + [Test] + public void TestAddObjectAndUndo() + { + HitObject addedObject = null; + HitObject removedObject = null; + HitObject expectedObject = null; + + AddStep("bind removal", () => + { + editorBeatmap.HitObjectAdded += h => addedObject = h; + editorBeatmap.HitObjectRemoved += h => removedObject = h; + }); + + AddStep("add hitobject", () => editorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 })); + AddAssert("hitobject added", () => addedObject == expectedObject); + + addUndoSteps(); + AddAssert("hitobject removed", () => removedObject == expectedObject); + } + + [Test] + public void TestAddObjectThenUndoThenRedo() + { + HitObject addedObject = null; + HitObject removedObject = null; + HitObject expectedObject = null; + + AddStep("bind removal", () => + { + editorBeatmap.HitObjectAdded += h => addedObject = h; + editorBeatmap.HitObjectRemoved += h => removedObject = h; + }); + + AddStep("add hitobject", () => editorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 })); + addUndoSteps(); + + AddStep("reset variables", () => + { + addedObject = null; + removedObject = null; + }); + + addRedoSteps(); + AddAssert("hitobject added", () => addedObject.StartTime == expectedObject.StartTime); // Can't compare via equality (new hitobject instance) + AddAssert("no hitobject removed", () => removedObject == null); + } + + [Test] + public void TestRemoveObjectThenUndo() + { + HitObject addedObject = null; + HitObject removedObject = null; + HitObject expectedObject = null; + + AddStep("bind removal", () => + { + editorBeatmap.HitObjectAdded += h => addedObject = h; + editorBeatmap.HitObjectRemoved += h => removedObject = h; + }); + + AddStep("add hitobject", () => editorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 })); + AddStep("remove object", () => editorBeatmap.Remove(expectedObject)); + AddStep("reset variables", () => + { + addedObject = null; + removedObject = null; + }); + + addUndoSteps(); + AddAssert("hitobject added", () => addedObject.StartTime == expectedObject.StartTime); // Can't compare via equality (new hitobject instance) + AddAssert("no hitobject removed", () => removedObject == null); + } + + [Test] + public void TestRemoveObjectThenUndoThenRedo() + { + HitObject addedObject = null; + HitObject removedObject = null; + HitObject expectedObject = null; + + AddStep("bind removal", () => + { + editorBeatmap.HitObjectAdded += h => addedObject = h; + editorBeatmap.HitObjectRemoved += h => removedObject = h; + }); + + AddStep("add hitobject", () => editorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 })); + AddStep("remove object", () => editorBeatmap.Remove(expectedObject)); + addUndoSteps(); + + AddStep("reset variables", () => + { + addedObject = null; + removedObject = null; + }); + + addRedoSteps(); + AddAssert("hitobject removed", () => removedObject.StartTime == expectedObject.StartTime); // Can't compare via equality (new hitobject instance after undo) + AddAssert("no hitobject added", () => addedObject == null); + } + + private void addUndoSteps() + { + AddStep("press undo", () => + { + InputManager.PressKey(Key.LControl); + InputManager.PressKey(Key.Z); + }); + + AddStep("release keys", () => + { + InputManager.ReleaseKey(Key.LControl); + InputManager.ReleaseKey(Key.Z); + }); + } + + private void addRedoSteps() + { + AddStep("press redo", () => + { + InputManager.PressKey(Key.LControl); + InputManager.PressKey(Key.LShift); + InputManager.PressKey(Key.Z); + }); + + AddStep("release keys", () => + { + InputManager.ReleaseKey(Key.LControl); + InputManager.ReleaseKey(Key.LShift); + InputManager.ReleaseKey(Key.Z); + }); + } + } +} From 575b061dd76a59fd39079904f53193bb524ea261 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 9 Apr 2020 22:00:56 +0900 Subject: [PATCH 206/655] Add change state support to more editor components --- .../Components/PathControlPointPiece.cs | 17 +++++++++++++++- .../Sliders/SliderSelectionBlueprint.cs | 20 +++++++++++++++++-- .../Compose/Components/BlueprintContainer.cs | 14 +++++++++++++ .../Compose/Components/SelectionHandler.cs | 9 ++++++++- .../Timeline/TimelineHitObjectBlueprint.cs | 12 +++++++++-- 5 files changed, 66 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index af4da5e853..092a13cca5 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -33,6 +34,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private readonly Container marker; private readonly Drawable markerRing; + [Resolved(CanBeNull = true)] + private IEditorChangeHandler changeHandler { get; set; } + [Resolved(CanBeNull = true)] private IDistanceSnapProvider snapProvider { get; set; } @@ -137,7 +141,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override bool OnClick(ClickEvent e) => RequestSelection != null; - protected override bool OnDragStart(DragStartEvent e) => e.Button == MouseButton.Left; + protected override bool OnDragStart(DragStartEvent e) + { + if (e.Button == MouseButton.Left) + { + changeHandler?.BeginChange(); + return true; + } + + return false; + } protected override void OnDrag(DragEvent e) { @@ -158,6 +171,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components ControlPoint.Position.Value += e.Delta; } + protected override void OnDragEnd(DragEndEvent e) => changeHandler?.EndChange(); + /// /// Updates the state of the circular control point marker. /// diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 001100d3ce..b7074b7ee5 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -38,6 +38,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders [Resolved(CanBeNull = true)] private EditorBeatmap editorBeatmap { get; set; } + [Resolved(CanBeNull = true)] + private IEditorChangeHandler changeHandler { get; set; } + public SliderSelectionBlueprint(DrawableSlider slider) : base(slider) { @@ -92,7 +95,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private int? placementControlPointIndex; - protected override bool OnDragStart(DragStartEvent e) => placementControlPointIndex != null; + protected override bool OnDragStart(DragStartEvent e) + { + if (placementControlPointIndex != null) + { + changeHandler?.BeginChange(); + return true; + } + + return false; + } protected override void OnDrag(DragEvent e) { @@ -103,7 +115,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected override void OnDragEnd(DragEndEvent e) { - placementControlPointIndex = null; + if (placementControlPointIndex != null) + { + placementControlPointIndex = null; + changeHandler?.EndChange(); + } } private BindableList controlPoints => HitObject.Path.ControlPoints; diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index c81c6059cc..ad16e22e5e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -37,6 +37,9 @@ namespace osu.Game.Screens.Edit.Compose.Components private SelectionHandler selectionHandler; + [Resolved(CanBeNull = true)] + private IEditorChangeHandler changeHandler { get; set; } + [Resolved] private IAdjustableClock adjustableClock { get; set; } @@ -164,7 +167,11 @@ namespace osu.Game.Screens.Edit.Compose.Components return false; if (movementBlueprint != null) + { + isDraggingBlueprint = true; + changeHandler?.BeginChange(); return true; + } if (DragBox.HandleDrag(e)) { @@ -191,6 +198,12 @@ namespace osu.Game.Screens.Edit.Compose.Components if (e.Button == MouseButton.Right) return; + if (isDraggingBlueprint) + { + changeHandler?.EndChange(); + isDraggingBlueprint = false; + } + if (DragBox.State == Visibility.Visible) { DragBox.Hide(); @@ -354,6 +367,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private Vector2? movementBlueprintOriginalPosition; private SelectionBlueprint movementBlueprint; + private bool isDraggingBlueprint; /// /// Attempts to begin the movement of any selected blueprints. diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index fc46bf3fed..e212979433 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -40,6 +40,9 @@ namespace osu.Game.Screens.Edit.Compose.Components [Resolved(CanBeNull = true)] private EditorBeatmap editorBeatmap { get; set; } + [Resolved(CanBeNull = true)] + private IEditorChangeHandler changeHandler { get; set; } + public SelectionHandler() { selectedBlueprints = new List(); @@ -152,8 +155,12 @@ namespace osu.Game.Screens.Edit.Compose.Components private void deleteSelected() { + changeHandler?.BeginChange(); + foreach (var h in selectedBlueprints.ToList()) - editorBeatmap.Remove(h.HitObject); + editorBeatmap?.Remove(h.HitObject); + + changeHandler?.EndChange(); } #endregion diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 8f12c2f0ed..16ba3ba89a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -254,14 +254,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Colour = IsHovered || hasMouseDown ? Color4.OrangeRed : Color4.White; } - protected override bool OnDragStart(DragStartEvent e) => true; - [Resolved] private EditorBeatmap beatmap { get; set; } [Resolved] private IBeatSnapProvider beatSnapProvider { get; set; } + [Resolved(CanBeNull = true)] + private IEditorChangeHandler changeHandler { get; set; } + + protected override bool OnDragStart(DragStartEvent e) + { + changeHandler?.BeginChange(); + return true; + } + protected override void OnDrag(DragEvent e) { base.OnDrag(e); @@ -301,6 +308,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline base.OnDragEnd(e); OnDragHandled?.Invoke(null); + changeHandler?.EndChange(); } } } From e208251fc6f1729eac62cfa2e7e756b651eedbc4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 9 Apr 2020 23:18:43 +0900 Subject: [PATCH 207/655] Wait for timeline to also load --- osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs index abaa373cac..dd1b6cf6aa 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs @@ -9,6 +9,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK.Input; namespace osu.Game.Tests.Visual.Editor @@ -29,7 +30,8 @@ namespace osu.Game.Tests.Visual.Editor LoadScreen(editor = new Screens.Edit.Editor()); }); - AddUntilStep("wait for editor to load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + AddUntilStep("wait for editor to load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true + && editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); AddStep("get beatmap", () => editorBeatmap = editor.ChildrenOfType().Single()); } From f40bdcd34e835731855d48b1ddf9af4574331320 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Thu, 9 Apr 2020 18:47:28 +0300 Subject: [PATCH 208/655] Initial rewrite, moving API logic to SongSelect --- osu.Game/Screens/Select/BeatmapCarousel.cs | 35 +++------- .../Select/Carousel/CarouselBeatmapSet.cs | 16 ++--- osu.Game/Screens/Select/SongSelect.cs | 65 +++++++++++++++++++ 3 files changed, 79 insertions(+), 37 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c7221699de..5a62184ad8 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -34,8 +34,6 @@ namespace osu.Game.Screens.Select private const float bleed_top = FilterControl.HEIGHT; private const float bleed_bottom = Footer.HEIGHT; - protected readonly Bindable RecommendedStarDifficulty = new Bindable(); - /// /// Triggered when the loaded change and are completely loaded. /// @@ -122,6 +120,7 @@ namespace osu.Game.Screens.Select protected List Items = new List(); private CarouselRoot root; + public SongSelect.DifficultyRecommender DifficultyRecommender; public BeatmapCarousel() { @@ -145,10 +144,10 @@ namespace osu.Game.Screens.Select private BeatmapManager beatmaps { get; set; } [Resolved] - private IAPIProvider api { get; set; } + private Bindable decoupledRuleset { get; set; } [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuConfigManager config, Bindable decoupledRuleset) + private void load(OsuConfigManager config) { config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm); config.BindWith(OsuSetting.SongSelectRightMouseScroll, RightClickScrollingEnabled); @@ -162,27 +161,6 @@ namespace osu.Game.Screens.Select beatmaps.BeatmapRestored += beatmapRestored; loadBeatmapSets(GetLoadableBeatmaps()); - - decoupledRuleset.BindValueChanged(UpdateRecommendedStarDifficulty, true); - } - - protected void UpdateRecommendedStarDifficulty(ValueChangedEvent ruleset) - { - if (api.LocalUser.Value is GuestUser) - { - RecommendedStarDifficulty.Value = 0; - return; - } - - var req = new GetUserRequest(api.LocalUser.Value.Id, ruleset.NewValue); - - req.Success += result => - { - // algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505 - RecommendedStarDifficulty.Value = Math.Pow((double)(result.Statistics.PP ?? 0), 0.4) * 0.195; - }; - - api.PerformAsync(req); } protected virtual IEnumerable GetLoadableBeatmaps() => beatmaps.GetAllUsableBeatmapSetsEnumerable(); @@ -608,7 +586,12 @@ namespace osu.Game.Screens.Select b.Metadata = beatmapSet.Metadata; } - var set = new CarouselBeatmapSet(beatmapSet, RecommendedStarDifficulty); + BeatmapInfo recommender(IEnumerable beatmaps) + { + return DifficultyRecommender?.GetRecommendedBeatmap(beatmaps, decoupledRuleset.Value); + } + + var set = new CarouselBeatmapSet(beatmapSet, recommender); foreach (var c in set.Beatmaps) { diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 064840d99a..1b715cad02 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -13,13 +13,13 @@ namespace osu.Game.Screens.Select.Carousel { public class CarouselBeatmapSet : CarouselGroupEagerSelect { - private readonly Bindable recommendedStarDifficulty = new Bindable(); + private Func, BeatmapInfo> getRecommendedBeatmap; public IEnumerable Beatmaps => InternalChildren.OfType(); public BeatmapSetInfo BeatmapSet; - public CarouselBeatmapSet(BeatmapSetInfo beatmapSet, Bindable recommendedStarDifficulty) + public CarouselBeatmapSet(BeatmapSetInfo beatmapSet, Func, BeatmapInfo> getRecommendedBeatmap) { BeatmapSet = beatmapSet ?? throw new ArgumentNullException(nameof(beatmapSet)); @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Select.Carousel .Select(b => new CarouselBeatmap(b)) .ForEach(AddChild); - this.recommendedStarDifficulty.BindTo(recommendedStarDifficulty); + this.getRecommendedBeatmap = getRecommendedBeatmap; } protected override DrawableCarouselItem CreateDrawableRepresentation() => new DrawableCarouselBeatmapSet(this); @@ -37,14 +37,8 @@ namespace osu.Game.Screens.Select.Carousel { if (LastSelected == null) { - return Children.OfType() - .Where(b => !b.Filtered.Value) - .OrderBy(b => - { - var difference = b.Beatmap.StarDifficulty - recommendedStarDifficulty.Value; - return difference >= 0 ? difference * 2 : difference * -1; // prefer easier over harder - }) - .FirstOrDefault(); + var recommendedBeatmapInfo = getRecommendedBeatmap(Children.OfType().Where(b => !b.Filtered.Value).Select(b => b.Beatmap)); + return Children.OfType().Where(b => b.Beatmap == recommendedBeatmapInfo).First(); } return base.GetNextToSelect(); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 895a8ad0c9..fda5872f3b 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -36,6 +36,9 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Game.Overlays.Notifications; using osu.Game.Scoring; +using osu.Game.Online.API; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Online.API.Requests; namespace osu.Game.Screens.Select { @@ -80,6 +83,7 @@ namespace osu.Game.Screens.Select protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value); protected BeatmapCarousel Carousel { get; private set; } + private DifficultyRecommender difficultyRecommender; private BeatmapInfoWedge beatmapInfoWedge; private DialogOverlay dialogOverlay; @@ -107,6 +111,8 @@ namespace osu.Game.Screens.Select // initial value transfer is required for FilterControl (it uses our re-cached bindables in its async load for the initial filter). transferRulesetValue(); + AddInternal(difficultyRecommender = new DifficultyRecommender()); + AddRangeInternal(new Drawable[] { new ResetScrollContainer(() => Carousel.ScrollToSelected()) @@ -156,6 +162,7 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.Both, SelectionChanged = updateSelectedBeatmap, BeatmapSetsChanged = carouselBeatmapsLoaded, + DifficultyRecommender = difficultyRecommender, }, } }, @@ -780,6 +787,64 @@ namespace osu.Game.Screens.Select return base.OnKeyDown(e); } + public class DifficultyRecommender : Component + { + [Resolved] + private IAPIProvider api { get; set; } + + [Resolved] + private RulesetStore rulesets { get; set; } + + private Dictionary recommendedStarDifficulty = new Dictionary(); + + private int pendingAPIRequests = 0; + + [BackgroundDependencyLoader] + private void load() + { + updateRecommended(); + } + + private void updateRecommended() + { + if (pendingAPIRequests > 0) + return; + if (api.LocalUser.Value is GuestUser) + return; + rulesets.AvailableRulesets.ForEach(rulesetInfo => + { + var req = new GetUserRequest(api.LocalUser.Value.Id, rulesetInfo); + + req.Success += result => + { + // algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505 + recommendedStarDifficulty[rulesetInfo] = Math.Pow((double)(result.Statistics.PP ?? 0), 0.4) * 0.195; + pendingAPIRequests--; + }; + + req.Failure += _ => pendingAPIRequests--; + + pendingAPIRequests++; + api.Queue(req); + }); + } + + public BeatmapInfo GetRecommendedBeatmap(IEnumerable beatmaps, RulesetInfo currentRuleset) + { + if (!recommendedStarDifficulty.ContainsKey(currentRuleset)) + { + updateRecommended(); + return null; + } + + return beatmaps.OrderBy(b => + { + var difference = b.StarDifficulty - recommendedStarDifficulty[currentRuleset]; + return difference >= 0 ? difference * 2 : difference * -1; // prefer easier over harder + }).FirstOrDefault(); + } + } + private class VerticalMaskingContainer : Container { private const float panel_overflow = 1.2f; From caa404f8fa6024b1fadff3d61ad46339bc50c34f Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Thu, 9 Apr 2020 18:48:13 +0300 Subject: [PATCH 209/655] Remove test for now --- .../SongSelect/TestSceneBeatmapCarousel.cs | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 5a199885c0..8d207be216 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -656,34 +656,6 @@ namespace osu.Game.Tests.Visual.SongSelect checkVisibleItemCount(true, 15); } - [Test] - public void TestSelectRecommendedDifficulty() - { - void setRecommendedAndExpect(double recommended, int expectedSet, int expectedDiff) - { - AddStep($"Recommend SR {recommended}", () => carousel.RecommendedStarDifficulty.Value = recommended); - advanceSelection(direction: 1, diff: false); - waitForSelection(expectedSet, expectedDiff); - } - - createCarousel(); - AddStep("Add beatmaps", () => - { - for (int i = 1; i <= 7; i++) - { - var set = createTestBeatmapSet(i); - carousel.UpdateBeatmapSet(set); - } - }); - waitForSelection(1, 1); - setRecommendedAndExpect(1, 2, 1); - setRecommendedAndExpect(3.9, 3, 1); - setRecommendedAndExpect(4.1, 4, 2); - setRecommendedAndExpect(5.6, 5, 2); - setRecommendedAndExpect(5.7, 6, 3); - setRecommendedAndExpect(10, 7, 3); - } - private void loadBeatmaps(List beatmapSets = null, Func initialCriteria = null) { createCarousel(); @@ -886,8 +858,6 @@ namespace osu.Game.Tests.Visual.SongSelect { public new List Items => base.Items; - public new Bindable RecommendedStarDifficulty => base.RecommendedStarDifficulty; - public bool PendingFilterTask => PendingFilter != null; protected override IEnumerable GetLoadableBeatmaps() => Enumerable.Empty(); From 35f97dfc7521a7952dad16896babc6cc649353c0 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Thu, 9 Apr 2020 18:59:18 +0300 Subject: [PATCH 210/655] Style changes --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs | 1 - osu.Game/Screens/Select/BeatmapCarousel.cs | 2 -- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 5 ++--- osu.Game/Screens/Select/SongSelect.cs | 5 +++-- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 8d207be216..76a8ee9914 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Text; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 5a62184ad8..3e619a1f80 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -23,9 +23,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Input.Bindings; using osu.Game.Screens.Select.Carousel; -using osu.Game.Online.API; using osu.Game.Rulesets; -using osu.Game.Online.API.Requests; namespace osu.Game.Screens.Select { diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 1b715cad02..e7a18e15c7 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Beatmaps; using osu.Game.Screens.Select.Filter; @@ -13,7 +12,7 @@ namespace osu.Game.Screens.Select.Carousel { public class CarouselBeatmapSet : CarouselGroupEagerSelect { - private Func, BeatmapInfo> getRecommendedBeatmap; + private readonly Func, BeatmapInfo> getRecommendedBeatmap; public IEnumerable Beatmaps => InternalChildren.OfType(); @@ -38,7 +37,7 @@ namespace osu.Game.Screens.Select.Carousel if (LastSelected == null) { var recommendedBeatmapInfo = getRecommendedBeatmap(Children.OfType().Where(b => !b.Filtered.Value).Select(b => b.Beatmap)); - return Children.OfType().Where(b => b.Beatmap == recommendedBeatmapInfo).First(); + return Children.OfType().First(b => b.Beatmap == recommendedBeatmapInfo); } return base.GetNextToSelect(); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index fda5872f3b..d6bc20df39 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -795,9 +795,9 @@ namespace osu.Game.Screens.Select [Resolved] private RulesetStore rulesets { get; set; } - private Dictionary recommendedStarDifficulty = new Dictionary(); + private readonly Dictionary recommendedStarDifficulty = new Dictionary(); - private int pendingAPIRequests = 0; + private int pendingAPIRequests; [BackgroundDependencyLoader] private void load() @@ -811,6 +811,7 @@ namespace osu.Game.Screens.Select return; if (api.LocalUser.Value is GuestUser) return; + rulesets.AvailableRulesets.ForEach(rulesetInfo => { var req = new GetUserRequest(api.LocalUser.Value.Id, rulesetInfo); From 2201e9b4ae00154d14a4b0ccb45b881550c7bdfb Mon Sep 17 00:00:00 2001 From: Fire937 Date: Thu, 9 Apr 2020 18:12:15 +0200 Subject: [PATCH 211/655] Add stereo shifted hitsound playback support There is now a setting in the general settings called "Positional hitsounds". If the setting is enabled, the hitsounds playback will be shifted according to their position on the beatmap. --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ .../Sections/Gameplay/GeneralSettings.cs | 5 ++++ .../Objects/Drawables/DrawableHitObject.cs | 26 ++++++++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 41f6747b74..ab5a652a94 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -88,6 +88,7 @@ namespace osu.Game.Configuration Set(OsuSetting.ShowProgressGraph, true); Set(OsuSetting.ShowHealthDisplayWhenCantFail, true); Set(OsuSetting.KeyOverlay, false); + Set(OsuSetting.PositionalHitSounds, true); Set(OsuSetting.ScoreMeter, ScoreMeterType.HitErrorBoth); Set(OsuSetting.FloatingComments, false); @@ -176,6 +177,7 @@ namespace osu.Game.Configuration LightenDuringBreaks, ShowStoryboard, KeyOverlay, + PositionalHitSounds, ScoreMeter, FloatingComments, ShowInterface, diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 2d2cd42213..ef03c0622a 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -57,6 +57,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay LabelText = "Always show key overlay", Bindable = config.GetBindable(OsuSetting.KeyOverlay) }, + new SettingsCheckbox + { + LabelText = "Positional hitsounds", + Bindable = config.GetBindable(OsuSetting.PositionalHitSounds) + }, new SettingsEnumDropdown { LabelText = "Score meter type", diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 0011faefbb..ed9efba89f 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -12,11 +12,13 @@ using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Threading; +using osu.Framework.Audio; using osu.Game.Audio; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; +using osu.Game.Configuration; using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables @@ -31,6 +33,11 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public readonly Bindable AccentColour = new Bindable(Color4.Gray); + /// + /// The stereo balance of the samples if the Positional hitsounds setting is set. + /// + private readonly BindableDouble positionalSoundAdjustment = new BindableDouble(); + protected SkinnableSound Samples { get; private set; } protected virtual IEnumerable GetSamples() => HitObject.Samples; @@ -84,8 +91,14 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public JudgementResult Result { get; private set; } + /// + /// The stereo balance of the samples played if Positional hitsounds is set. + /// + protected virtual float PositionalSound => (HitObject is IHasXPosition position) ? (position.X / 512f - 0.5f) * 0.8f : 0; + private BindableList samplesBindable; private Bindable startTimeBindable; + private Bindable userPositionalHitSounds; private Bindable comboIndexBindable; public override bool RemoveWhenNotAlive => false; @@ -101,10 +114,11 @@ namespace osu.Game.Rulesets.Objects.Drawables protected DrawableHitObject([NotNull] HitObject hitObject) { HitObject = hitObject ?? throw new ArgumentNullException(nameof(hitObject)); + positionalSoundAdjustment.Value = PositionalSound; } [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager config) { var judgement = HitObject.CreateJudgement(); @@ -113,6 +127,16 @@ namespace osu.Game.Rulesets.Objects.Drawables throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); loadSamples(); + + userPositionalHitSounds = config.GetBindable(OsuSetting.PositionalHitSounds); + userPositionalHitSounds.BindValueChanged(positional => + { + if (positional.NewValue) + Samples?.AddAdjustment(AdjustableProperty.Balance, positionalSoundAdjustment); + else + Samples?.RemoveAdjustment(AdjustableProperty.Balance, positionalSoundAdjustment); + }); + userPositionalHitSounds.TriggerChange(); } protected override void LoadComplete() From 116b952dfe973218621de51532c8620c0f65e015 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 Apr 2020 01:20:43 +0900 Subject: [PATCH 212/655] Change param to hitobject rather than result --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index db8a47e4a2..9c066c367b 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.Osu.UI private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) { - missAllEarlier(result); + missAllEarlier(result.HitObject); if (!judgedObject.DisplayResult || !DisplayJudgements.Value) return; @@ -131,14 +131,14 @@ namespace osu.Game.Rulesets.Osu.UI /// /// Misses all s occurring earlier than the start time of a judged . /// - /// The of the judged . - private void missAllEarlier(JudgementResult result) + /// The marker , which all s earlier than will get missed. + private void missAllEarlier(HitObject hitObject) { - if (!causesNoteLockMisses(result.HitObject)) + if (!causesNoteLockMisses(hitObject)) return; // The minimum start time required for hitobjects so that they aren't missed. - double minimumTime = result.HitObject.StartTime; + double minimumTime = hitObject.StartTime; foreach (var obj in HitObjectContainer.AliveObjects) { From b8d7b78b55a3022e8556110a44bc4d40c977c86e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 Apr 2020 01:21:37 +0900 Subject: [PATCH 213/655] Remove unnecessary null set --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 9c066c367b..9011f21fd5 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -78,12 +78,7 @@ namespace osu.Game.Rulesets.Osu.UI bool result = base.Remove(h); if (result) - { - DrawableOsuHitObject osuHitObject = (DrawableOsuHitObject)h; - osuHitObject.CheckHittable = null; - - followPoints.RemoveFollowPoints(osuHitObject); - } + followPoints.RemoveFollowPoints((DrawableOsuHitObject)h); return result; } From deaf24f1419f25f26297271e819caf73445bac4b Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Thu, 9 Apr 2020 19:30:40 +0300 Subject: [PATCH 214/655] Fix oversight on null --- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index e7a18e15c7..99ded4c58e 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -37,7 +37,8 @@ namespace osu.Game.Screens.Select.Carousel if (LastSelected == null) { var recommendedBeatmapInfo = getRecommendedBeatmap(Children.OfType().Where(b => !b.Filtered.Value).Select(b => b.Beatmap)); - return Children.OfType().First(b => b.Beatmap == recommendedBeatmapInfo); + if (recommendedBeatmapInfo != null) + return Children.OfType().First(b => b.Beatmap == recommendedBeatmapInfo); } return base.GetNextToSelect(); From f115fecb23c13b7a2fbfc99e38b1eef458866199 Mon Sep 17 00:00:00 2001 From: Alchyr Date: Thu, 9 Apr 2020 09:34:40 -0700 Subject: [PATCH 215/655] Fix formatting --- osu.Game/Beatmaps/ControlPoints/ControlPoint.cs | 6 +++--- osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs | 3 ++- osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs | 3 ++- osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs | 3 ++- osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 6 +++--- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index 411a4441de..9599ad184b 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -28,10 +28,10 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// Whether this control point results in a meaningful change when placed after another. /// - /// Another control point to compare with. - /// The time this timing point will be placed at. + /// An existing control point to compare with. + /// The time this control point will be placed at if it is added. /// Whether redundant. - public abstract bool IsRedundant(ControlPoint other, double time); + public abstract bool IsRedundant(ControlPoint existing, double time); public bool Equals(ControlPoint other) => Time == other?.Time && EquivalentTo(other); } diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 44522dc927..dc856b0a0a 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -29,6 +29,7 @@ namespace osu.Game.Beatmaps.ControlPoints public override bool EquivalentTo(ControlPoint other) => other is DifficultyControlPoint otherTyped && otherTyped.SpeedMultiplier.Equals(SpeedMultiplier); - public override bool IsRedundant(ControlPoint other, double time) => EquivalentTo(other); + + public override bool IsRedundant(ControlPoint existing, double time) => EquivalentTo(existing); } } diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index 8066c6b577..d050f44ba4 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -38,6 +38,7 @@ namespace osu.Game.Beatmaps.ControlPoints public override bool EquivalentTo(ControlPoint other) => other is EffectControlPoint otherTyped && KiaiMode == otherTyped.KiaiMode && OmitFirstBarLine == otherTyped.OmitFirstBarLine; - public override bool IsRedundant(ControlPoint other, double time) => !OmitFirstBarLine && EquivalentTo(other); + + public override bool IsRedundant(ControlPoint existing, double time) => !OmitFirstBarLine && EquivalentTo(existing); } } diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index cf7c842b24..38edbe70da 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -71,6 +71,7 @@ namespace osu.Game.Beatmaps.ControlPoints public override bool EquivalentTo(ControlPoint other) => other is SampleControlPoint otherTyped && SampleBank == otherTyped.SampleBank && SampleVolume == otherTyped.SampleVolume; - public override bool IsRedundant(ControlPoint other, double time) => EquivalentTo(other); + + public override bool IsRedundant(ControlPoint existing, double time) => EquivalentTo(existing); } } diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index d14ac1221b..316c603ece 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -52,8 +52,8 @@ namespace osu.Game.Beatmaps.ControlPoints other is TimingControlPoint otherTyped && TimeSignature == otherTyped.TimeSignature && BeatLength.Equals(otherTyped.BeatLength); - public override bool IsRedundant(ControlPoint other, double time) => - EquivalentTo(other) - && other.Time == time; + public override bool IsRedundant(ControlPoint existing, double time) => + EquivalentTo(existing) + && existing.Time == time; } } From ea1bec85ae7ef875f133add73c5051a6fa9b5a4c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 Apr 2020 01:40:20 +0900 Subject: [PATCH 216/655] Simplify code/language --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 65 +++++++++--------------- 1 file changed, 24 insertions(+), 41 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 9011f21fd5..f4009a281c 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Extensions.IEnumerableExtensions; using osuTK; using osu.Framework.Graphics; @@ -87,10 +86,10 @@ namespace osu.Game.Rulesets.Osu.UI { DrawableHitObject lastObject = osuHitObject; - // Get the last hitobject that contributes to note lock + // Get the last hitobject that can block future hits while ((lastObject = HitObjectContainer.AliveObjects.GetPrevious(lastObject)) != null) { - if (contributesToNoteLock(lastObject.HitObject)) + if (canBlockFutureHits(lastObject.HitObject)) break; } @@ -108,7 +107,9 @@ namespace osu.Game.Rulesets.Osu.UI private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) { - missAllEarlier(result.HitObject); + // Hitobjects that block future hits should miss previous hitobjects if they're hit out-of-order. + if (canBlockFutureHits(result.HitObject)) + missAllEarlierObjects(result.HitObject); if (!judgedObject.DisplayResult || !DisplayJudgements.Value) return; @@ -127,12 +128,8 @@ namespace osu.Game.Rulesets.Osu.UI /// Misses all s occurring earlier than the start time of a judged . /// /// The marker , which all s earlier than will get missed. - private void missAllEarlier(HitObject hitObject) + private void missAllEarlierObjects(HitObject hitObject) { - if (!causesNoteLockMisses(hitObject)) - return; - - // The minimum start time required for hitobjects so that they aren't missed. double minimumTime = hitObject.StartTime; foreach (var obj in HitObjectContainer.AliveObjects) @@ -140,50 +137,36 @@ namespace osu.Game.Rulesets.Osu.UI if (obj.HitObject.StartTime >= minimumTime) break; - performMiss(obj); - - foreach (var n in obj.NestedHitObjects) + switch (obj) { - if (n.HitObject.StartTime >= minimumTime) + case DrawableHitCircle circle: + miss(circle); break; - performMiss(n); + case DrawableSlider slider: + miss(slider.HeadCircle); + break; } } + + static void miss(DrawableOsuHitObject obj) + { + // Hitobjects that have already been judged cannot be missed. + if (obj.Judged) + return; + + obj.MissForcefully(); + } } - private void performMiss(DrawableHitObject obj) - { - if (!(obj is DrawableOsuHitObject osuObject)) - throw new InvalidOperationException($"{obj.GetType()} is not a {nameof(DrawableOsuHitObject)}."); - - // Hitobjects that have already been judged cannot be missed. - if (osuObject.Judged) - return; - - if (!causesNoteLockMisses(obj.HitObject)) - return; - - osuObject.MissForcefully(); - } - /// - /// Whether a is contributes to note lock. - /// Future contributing s will not be hittable until the start time of the last contributing is reached. + /// Whether a can block hits on future s until its start time is reached. /// /// The to test. - /// Whether causes note lock. - private bool contributesToNoteLock(HitObject hitObject) + /// Whether can block hits on future s. + private bool canBlockFutureHits(HitObject hitObject) => hitObject is HitCircle || hitObject is Slider; - /// - /// Whether a can be missed and causes other s to be missed when hit out-of-order during note lock. - /// - /// The to test. - /// Whether contributes to note lock misses. - private bool causesNoteLockMisses(HitObject hitObject) - => hitObject is HitCircle && !(hitObject is SliderTailCircle); - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos); private class ApproachCircleProxyContainer : LifetimeManagementContainer From 518acf03e9b8319a0e533652f7cacadc7a2afa96 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 9 Apr 2020 19:41:35 +0300 Subject: [PATCH 217/655] Remove BeatmapSearchSmallFilterRow component --- .../TestSceneBeatmapSearchFilter.cs | 5 ++- .../BeatmapListingSearchSection.cs | 8 ++--- .../BeatmapListing/BeatmapSearchFilterRow.cs | 5 +-- .../BeatmapSearchSmallFilterRow.cs | 32 ------------------- 4 files changed, 9 insertions(+), 41 deletions(-) delete mode 100644 osu.Game/Overlays/BeatmapListing/BeatmapSearchSmallFilterRow.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapSearchFilter.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapSearchFilter.cs index 7b4424e568..fac58a6754 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapSearchFilter.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapSearchFilter.cs @@ -20,8 +20,7 @@ namespace osu.Game.Tests.Visual.UserInterface public override IReadOnlyList RequiredTypes => new[] { typeof(BeatmapSearchFilterRow<>), - typeof(BeatmapSearchRulesetFilterRow), - typeof(BeatmapSearchSmallFilterRow<>), + typeof(BeatmapSearchRulesetFilterRow) }; [Cached] @@ -43,7 +42,7 @@ namespace osu.Game.Tests.Visual.UserInterface { new BeatmapSearchRulesetFilterRow(), new BeatmapSearchFilterRow("Categories"), - new BeatmapSearchSmallFilterRow("Header Name") + new BeatmapSearchFilterRow("Header Name") } }); } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs index 501abbf2c8..3f9cc211df 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs @@ -47,8 +47,8 @@ namespace osu.Game.Overlays.BeatmapListing private readonly BeatmapSearchTextBox textBox; private readonly BeatmapSearchRulesetFilterRow modeFilter; private readonly BeatmapSearchFilterRow categoryFilter; - private readonly BeatmapSearchSmallFilterRow genreFilter; - private readonly BeatmapSearchSmallFilterRow languageFilter; + private readonly BeatmapSearchFilterRow genreFilter; + private readonly BeatmapSearchFilterRow languageFilter; private readonly Box background; private readonly UpdateableBeatmapSetCover beatmapCover; @@ -104,8 +104,8 @@ namespace osu.Game.Overlays.BeatmapListing { modeFilter = new BeatmapSearchRulesetFilterRow(), categoryFilter = new BeatmapSearchFilterRow(@"Categories"), - genreFilter = new BeatmapSearchSmallFilterRow(@"Genre"), - languageFilter = new BeatmapSearchSmallFilterRow(@"Language"), + genreFilter = new BeatmapSearchFilterRow(@"Genre"), + languageFilter = new BeatmapSearchFilterRow(@"Language"), } } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs index 467399dd20..bc0a011e31 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs @@ -17,6 +17,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests; using osuTK; using osuTK.Graphics; +using Humanizer; namespace osu.Game.Overlays.BeatmapListing { @@ -55,8 +56,8 @@ namespace osu.Game.Overlays.BeatmapListing { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Font = OsuFont.GetFont(size: 10), - Text = headerName.ToUpper() + Font = OsuFont.GetFont(size: 13), + Text = headerName.Titleize() }, CreateFilter().With(f => { diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchSmallFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchSmallFilterRow.cs deleted file mode 100644 index 6daa7cb0e0..0000000000 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchSmallFilterRow.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics.UserInterface; - -namespace osu.Game.Overlays.BeatmapListing -{ - public class BeatmapSearchSmallFilterRow : BeatmapSearchFilterRow - { - public BeatmapSearchSmallFilterRow(string headerName) - : base(headerName) - { - } - - protected override BeatmapSearchFilter CreateFilter() => new SmallBeatmapSearchFilter(); - - private class SmallBeatmapSearchFilter : BeatmapSearchFilter - { - protected override TabItem CreateTabItem(T value) => new SmallTabItem(value); - - private class SmallTabItem : FilterTabItem - { - public SmallTabItem(T value) - : base(value) - { - } - - protected override float TextSize => 10; - } - } - } -} From 10e849d19616d3fa1314e8fa81ea10e12111e1da Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 Apr 2020 02:02:09 +0900 Subject: [PATCH 218/655] Separate into separate class --- .../Objects/Drawables/DrawableHitCircle.cs | 2 +- .../Objects/Drawables/DrawableOsuHitObject.cs | 2 +- osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs | 104 ++++++++++++++++++ osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 76 +------------ 4 files changed, 111 insertions(+), 73 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 5776c64c86..d73ad888f4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables var result = HitObject.HitWindows.ResultFor(timeOffset); - if (result == HitResult.None || CheckHittable?.Invoke(this) == false) + if (result == HitResult.None || CheckHittable?.Invoke(this, Time.Current) == false) { Shake(Math.Abs(timeOffset) - HitObject.HitWindows.WindowFor(HitResult.Miss)); return; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 13829dc2f7..fe23e3729d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// Whether this can be hit. /// If non-null, judgements will be ignored (resulting in a shake) whilst the function returns false. /// - public Func CheckHittable; + public Func CheckHittable; protected DrawableOsuHitObject(OsuHitObject hitObject) : base(hitObject) diff --git a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs new file mode 100644 index 0000000000..ddaf714e5b --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs @@ -0,0 +1,104 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Osu.UI +{ + /// + /// Ensures that s are hit in-order. + /// If a is hit out of order: + /// + /// The hit is blocked if it occurred earlier than the previous 's start time. + /// The hit causes all previous s to missed otherwise. + /// + /// + public class OrderedHitPolicy + { + private readonly HitObjectContainer hitObjectContainer; + + public OrderedHitPolicy(HitObjectContainer hitObjectContainer) + { + this.hitObjectContainer = hitObjectContainer; + } + + /// + /// Determines whether a can be hit at a point in time. + /// + /// The to check. + /// The time to check. + /// Whether can be hit at the given . + public bool IsHittable(DrawableHitObject hitObject, double time) + { + DrawableHitObject lastObject = hitObject; + + // Get the last hitobject that can block future hits + while ((lastObject = hitObjectContainer.AliveObjects.GetPrevious(lastObject)) != null) + { + if (canBlockFutureHits(lastObject.HitObject)) + break; + } + + // If there is no previous object alive, allow the hit. + if (lastObject == null) + return true; + + // Ensure that either the last object has received a judgement or the hit time occurs at or after the last object's start time. + // Simultaneous hitobjects are allowed to be hit at the same time value to account for edge-cases such as Centipede. + if (lastObject.Judged || time >= lastObject.HitObject.StartTime) + return true; + + return false; + } + + /// + /// Handles a being hit to potentially miss all earlier s. + /// + /// The that was hit. + public void HandleHit(HitObject hitObject) + { + if (!canBlockFutureHits(hitObject)) + return; + + double minimumTime = hitObject.StartTime; + + foreach (var obj in hitObjectContainer.AliveObjects) + { + if (obj.HitObject.StartTime >= minimumTime) + break; + + switch (obj) + { + case DrawableHitCircle circle: + miss(circle); + break; + + case DrawableSlider slider: + miss(slider.HeadCircle); + break; + } + } + + static void miss(DrawableOsuHitObject obj) + { + // Hitobjects that have already been judged cannot be missed. + if (obj.Judged) + return; + + obj.MissForcefully(); + } + } + + /// + /// Whether a blocks hits on future s until its start time is reached. + /// + /// The to test. + private bool canBlockFutureHits(HitObject hitObject) + => hitObject is HitCircle || hitObject is Slider; + } +} diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index f4009a281c..2f222f59b4 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Extensions.IEnumerableExtensions; using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -11,7 +10,6 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Skinning; @@ -22,6 +20,7 @@ namespace osu.Game.Rulesets.Osu.UI private readonly ApproachCircleProxyContainer approachCircles; private readonly JudgementContainer judgementLayer; private readonly FollowPointRenderer followPoints; + private readonly OrderedHitPolicy hitPolicy; public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); @@ -53,6 +52,8 @@ namespace osu.Game.Rulesets.Osu.UI Depth = -1, }, }; + + hitPolicy = new OrderedHitPolicy(HitObjectContainer); } public override void Add(DrawableHitObject h) @@ -67,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.UI base.Add(h); DrawableOsuHitObject osuHitObject = (DrawableOsuHitObject)h; - osuHitObject.CheckHittable = checkHittable; + osuHitObject.CheckHittable = hitPolicy.IsHittable; followPoints.AddFollowPoints(osuHitObject); } @@ -82,34 +83,10 @@ namespace osu.Game.Rulesets.Osu.UI return result; } - private bool checkHittable(DrawableOsuHitObject osuHitObject) - { - DrawableHitObject lastObject = osuHitObject; - - // Get the last hitobject that can block future hits - while ((lastObject = HitObjectContainer.AliveObjects.GetPrevious(lastObject)) != null) - { - if (canBlockFutureHits(lastObject.HitObject)) - break; - } - - // If there is no previous object alive, allow the hit. - if (lastObject == null) - return true; - - // Ensure that either the last object has received a judgement or the hit time occurs at or after the last object's start time. - // Simultaneous hitobjects are allowed to be hit at the same time value to account for edge-cases such as Centipede. - if (lastObject.Judged || Time.Current >= lastObject.HitObject.StartTime) - return true; - - return false; - } - private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) { // Hitobjects that block future hits should miss previous hitobjects if they're hit out-of-order. - if (canBlockFutureHits(result.HitObject)) - missAllEarlierObjects(result.HitObject); + hitPolicy.HandleHit(result.HitObject); if (!judgedObject.DisplayResult || !DisplayJudgements.Value) return; @@ -124,49 +101,6 @@ namespace osu.Game.Rulesets.Osu.UI judgementLayer.Add(explosion); } - /// - /// Misses all s occurring earlier than the start time of a judged . - /// - /// The marker , which all s earlier than will get missed. - private void missAllEarlierObjects(HitObject hitObject) - { - double minimumTime = hitObject.StartTime; - - foreach (var obj in HitObjectContainer.AliveObjects) - { - if (obj.HitObject.StartTime >= minimumTime) - break; - - switch (obj) - { - case DrawableHitCircle circle: - miss(circle); - break; - - case DrawableSlider slider: - miss(slider.HeadCircle); - break; - } - } - - static void miss(DrawableOsuHitObject obj) - { - // Hitobjects that have already been judged cannot be missed. - if (obj.Judged) - return; - - obj.MissForcefully(); - } - } - - /// - /// Whether a can block hits on future s until its start time is reached. - /// - /// The to test. - /// Whether can block hits on future s. - private bool canBlockFutureHits(HitObject hitObject) - => hitObject is HitCircle || hitObject is Slider; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos); private class ApproachCircleProxyContainer : LifetimeManagementContainer From b54bbc5f6a217352a03ed77eb05eb20e50a948fa Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 Apr 2020 02:41:37 +0900 Subject: [PATCH 219/655] Improve commenting + refactor --- osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs | 79 +++++++++++++------- 1 file changed, 54 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs index ddaf714e5b..0a09b5be7c 100644 --- a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; @@ -35,22 +34,27 @@ namespace osu.Game.Rulesets.Osu.UI /// Whether can be hit at the given . public bool IsHittable(DrawableHitObject hitObject, double time) { - DrawableHitObject lastObject = hitObject; + DrawableHitObject blockingObject = null; - // Get the last hitobject that can block future hits - while ((lastObject = hitObjectContainer.AliveObjects.GetPrevious(lastObject)) != null) + // Find the last hitobject which blocks future hits. + foreach (var obj in hitObjectContainer.AliveObjects) { - if (canBlockFutureHits(lastObject.HitObject)) + if (obj == hitObject) break; + + if (canBlockFutureHits(obj)) + blockingObject = obj; } - // If there is no previous object alive, allow the hit. - if (lastObject == null) + // If there is no previous hitobject, allow the hit. + if (blockingObject == null) return true; - // Ensure that either the last object has received a judgement or the hit time occurs at or after the last object's start time. - // Simultaneous hitobjects are allowed to be hit at the same time value to account for edge-cases such as Centipede. - if (lastObject.Judged || time >= lastObject.HitObject.StartTime) + // A hit is allowed if: + // 1. The last blocking hitobject has been judged. + // 2. The current time is after the last hitobject's start time. + // Hits at exactly the same time as the blocking hitobject are allowed for maps that contain simultaneous hitobjects (e.g. /b/372245). + if (blockingObject.Judged || time >= blockingObject.HitObject.StartTime) return true; return false; @@ -62,6 +66,7 @@ namespace osu.Game.Rulesets.Osu.UI /// The that was hit. public void HandleHit(HitObject hitObject) { + // Hitobjects which themselves don't block future hitobjects don't cause misses (e.g. slider ticks) if (!canBlockFutureHits(hitObject)) return; @@ -72,33 +77,57 @@ namespace osu.Game.Rulesets.Osu.UI if (obj.HitObject.StartTime >= minimumTime) break; - switch (obj) + // If the parent hitobject cannot cause a miss, neither can any nested hitobject. + if (!canBlockFutureHits(obj)) + continue; + + applyMiss(obj); + + foreach (var nested in obj.NestedHitObjects) { - case DrawableHitCircle circle: - miss(circle); + if (nested.HitObject.StartTime >= minimumTime) break; - case DrawableSlider slider: - miss(slider.HeadCircle); - break; + if (canBlockFutureHits(nested)) + applyMiss(nested); } } - static void miss(DrawableOsuHitObject obj) - { - // Hitobjects that have already been judged cannot be missed. - if (obj.Judged) - return; + static void applyMiss(DrawableHitObject obj) => ((DrawableOsuHitObject)obj).MissForcefully(); + } - obj.MissForcefully(); - } + /// + /// Whether a blocks hits on future s until its start time is reached. + /// + /// + /// Must only be used when iterating through top-most drawable hitobjects. + /// + /// The to test. + private static bool canBlockFutureHits(DrawableHitObject hitObject) + { + // Judged hitobjects can never block hits. + if (hitObject.Judged) + return false; + + // Special considerations for slider tails aren't required since only top-most drawable hitobjects are being iterated over. + return hitObject is DrawableHitCircle || hitObject is DrawableSlider; } /// /// Whether a blocks hits on future s until its start time is reached. /// + /// + /// Must only be used when iterating through nested hitobjects. + /// /// The to test. - private bool canBlockFutureHits(HitObject hitObject) - => hitObject is HitCircle || hitObject is Slider; + private static bool canBlockFutureHits(HitObject hitObject) + { + // Unlike the above we will receive slider tails, but they do not block future hits. + if (hitObject is SliderTailCircle) + return false; + + // All other hitcircles continue to block future hits. + return hitObject is HitCircle; + } } } From 42b3ff805b60c740fbd85948a8db366f8e91952b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 Apr 2020 02:57:31 +0900 Subject: [PATCH 220/655] Rename methods + fix incorrect method usage --- osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs | 33 ++++++++------------ 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs index 0a09b5be7c..cfb850b785 100644 --- a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.UI if (obj == hitObject) break; - if (canBlockFutureHits(obj)) + if (drawableCanBlockFutureHits(obj)) blockingObject = obj; } @@ -66,29 +66,26 @@ namespace osu.Game.Rulesets.Osu.UI /// The that was hit. public void HandleHit(HitObject hitObject) { - // Hitobjects which themselves don't block future hitobjects don't cause misses (e.g. slider ticks) - if (!canBlockFutureHits(hitObject)) + // Hitobjects which themselves don't block future hitobjects don't cause misses (e.g. slider ticks, spinners) + if (!hitObjectCanBlockFutureHits(hitObject)) return; double minimumTime = hitObject.StartTime; foreach (var obj in hitObjectContainer.AliveObjects) { - if (obj.HitObject.StartTime >= minimumTime) - break; - - // If the parent hitobject cannot cause a miss, neither can any nested hitobject. - if (!canBlockFutureHits(obj)) + if (obj.Judged || obj.HitObject.StartTime >= minimumTime) continue; - applyMiss(obj); + if (hitObjectCanBlockFutureHits(obj.HitObject)) + applyMiss(obj); foreach (var nested in obj.NestedHitObjects) { - if (nested.HitObject.StartTime >= minimumTime) - break; + if (nested.Judged || nested.HitObject.StartTime >= minimumTime) + continue; - if (canBlockFutureHits(nested)) + if (hitObjectCanBlockFutureHits(nested.HitObject)) applyMiss(nested); } } @@ -100,15 +97,11 @@ namespace osu.Game.Rulesets.Osu.UI /// Whether a blocks hits on future s until its start time is reached. /// /// - /// Must only be used when iterating through top-most drawable hitobjects. + /// This will ONLY match on top-most s. /// /// The to test. - private static bool canBlockFutureHits(DrawableHitObject hitObject) + private static bool drawableCanBlockFutureHits(DrawableHitObject hitObject) { - // Judged hitobjects can never block hits. - if (hitObject.Judged) - return false; - // Special considerations for slider tails aren't required since only top-most drawable hitobjects are being iterated over. return hitObject is DrawableHitCircle || hitObject is DrawableSlider; } @@ -117,10 +110,10 @@ namespace osu.Game.Rulesets.Osu.UI /// Whether a blocks hits on future s until its start time is reached. /// /// - /// Must only be used when iterating through nested hitobjects. + /// This is more rigorous and may not match on top-most s as does. /// /// The to test. - private static bool canBlockFutureHits(HitObject hitObject) + private static bool hitObjectCanBlockFutureHits(HitObject hitObject) { // Unlike the above we will receive slider tails, but they do not block future hits. if (hitObject is SliderTailCircle) From 15a92d1451c9fe0fb916f4c4e392c4da2411dcf3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 Apr 2020 02:57:35 +0900 Subject: [PATCH 221/655] Rename test scene --- .../{TestSceneNoteLock.cs => TestSceneOutOfOrderHits.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Rulesets.Osu.Tests/{TestSceneNoteLock.cs => TestSceneOutOfOrderHits.cs} (99%) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs similarity index 99% rename from osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs index 2c69540951..d6858f831e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs @@ -25,7 +25,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Tests { - public class TestSceneNoteLock : RateAdjustedBeatmapTestScene + public class TestSceneOutOfOrderHits : RateAdjustedBeatmapTestScene { private const double early_miss_window = 1000; // time after -1000 to -500 is considered a miss private const double late_miss_window = 500; // time after +500 is considered a miss From 6988df30bd9cdfe7be77a21f6de63b11ca462a45 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 Apr 2020 03:12:13 +0900 Subject: [PATCH 222/655] Rename variable, add comment --- osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs index cfb850b785..dfca2aff7b 100644 --- a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs @@ -66,15 +66,16 @@ namespace osu.Game.Rulesets.Osu.UI /// The that was hit. public void HandleHit(HitObject hitObject) { - // Hitobjects which themselves don't block future hitobjects don't cause misses (e.g. slider ticks, spinners) + // Hitobjects which themselves don't block future hitobjects don't cause misses (e.g. slider ticks, spinners). if (!hitObjectCanBlockFutureHits(hitObject)) return; - double minimumTime = hitObject.StartTime; + double maximumTime = hitObject.StartTime; + // Iterate through and apply miss results to all top-level and nested hitobjects which block future hits. foreach (var obj in hitObjectContainer.AliveObjects) { - if (obj.Judged || obj.HitObject.StartTime >= minimumTime) + if (obj.Judged || obj.HitObject.StartTime >= maximumTime) continue; if (hitObjectCanBlockFutureHits(obj.HitObject)) @@ -82,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.UI foreach (var nested in obj.NestedHitObjects) { - if (nested.Judged || nested.HitObject.StartTime >= minimumTime) + if (nested.Judged || nested.HitObject.StartTime >= maximumTime) continue; if (hitObjectCanBlockFutureHits(nested.HitObject)) From 91b3aa2914427cfa0af3cd1dbc58ea4b25ad9919 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 9 Apr 2020 22:56:10 +0300 Subject: [PATCH 223/655] Implement interval list --- osu.Game/Lists/IntervalList.cs | 128 +++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 osu.Game/Lists/IntervalList.cs diff --git a/osu.Game/Lists/IntervalList.cs b/osu.Game/Lists/IntervalList.cs new file mode 100644 index 0000000000..493b6b6e72 --- /dev/null +++ b/osu.Game/Lists/IntervalList.cs @@ -0,0 +1,128 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections; +using System.Collections.Generic; +using osu.Framework.Lists; + +namespace osu.Game.Lists +{ + /// + /// Represents a list of intervals that can be used for whether a specific value falls into one of them. + /// + /// The type of interval values. + public class IntervalList : ICollection>, IReadOnlyList> + { + private static readonly IComparer type_comparer = Comparer.Default; + + private readonly SortedList> intervals = new SortedList>((x, y) => type_comparer.Compare(x.Start, y.Start)); + + /// + /// The index of the nearest interval from last call. + /// + protected int NearestIntervalIndex; + + /// + /// Whether the provided value is in any interval added to this list. + /// + /// The value to check for. + public bool IsInAnyInterval(T value) + { + if (intervals.Count == 0) + return false; + + // Clamp the nearest index in case there were intervals + // removed from the list causing the index to go out of range. + NearestIntervalIndex = Math.Clamp(NearestIntervalIndex, 0, Count - 1); + + if (type_comparer.Compare(value, this[NearestIntervalIndex].End) > 0) + { + while (type_comparer.Compare(value, this[NearestIntervalIndex].End) > 0 && NearestIntervalIndex < Count - 1) + NearestIntervalIndex++; + } + else + { + while (type_comparer.Compare(value, this[NearestIntervalIndex].Start) < 0 && NearestIntervalIndex > 0) + NearestIntervalIndex--; + } + + var nearestInterval = this[NearestIntervalIndex]; + + return type_comparer.Compare(value, nearestInterval.Start) >= 0 && + type_comparer.Compare(value, nearestInterval.End) <= 0; + } + + /// + /// Adds a new interval to the list. + /// + /// The start value of the interval. + /// The end value of the interval. + public void Add(T start, T end) => Add(new Interval(start, end)); + + #region ICollection> + + public int Count => intervals.Count; + + bool ICollection>.IsReadOnly => false; + + /// + /// Adds a new interval to the list + /// + /// The interval to add. + public void Add(Interval interval) => intervals.Add(interval); + + /// + /// Removes an existing interval from the list. + /// + /// The interval to remove. + /// Whether the provided interval exists in the list and has been removed. + public bool Remove(Interval interval) => intervals.Remove(interval); + + /// + /// Removes all intervals from the list. + /// + public void Clear() => intervals.Clear(); + + public void CopyTo(Interval[] array, int arrayIndex) => intervals.CopyTo(array, arrayIndex); + + public bool Contains(Interval item) => intervals.Contains(item); + + public IEnumerator> GetEnumerator() => intervals.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion + + #region IReadOnlyList> + + public Interval this[int index] + { + get => intervals[index]; + set => intervals[index] = value; + } + + #endregion + } + + public readonly struct Interval + { + /// + /// The start value of this interval. + /// + public readonly T Start; + + /// + /// The end value of this interval. + /// + public readonly T End; + + public Interval(T start, T end) + { + bool startLessThanEnd = Comparer.Default.Compare(start, end) < 0; + + Start = startLessThanEnd ? start : end; + End = startLessThanEnd ? end : start; + } + } +} From 38ee5f310302e3d05b240c4d55e1bb4e1bd5f8e7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 9 Apr 2020 22:57:54 +0300 Subject: [PATCH 224/655] Add tests covering most cases of interval list checking --- osu.Game.Tests/Lists/IntervalListTest.cs | 141 +++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 osu.Game.Tests/Lists/IntervalListTest.cs diff --git a/osu.Game.Tests/Lists/IntervalListTest.cs b/osu.Game.Tests/Lists/IntervalListTest.cs new file mode 100644 index 0000000000..1bc1483e15 --- /dev/null +++ b/osu.Game.Tests/Lists/IntervalListTest.cs @@ -0,0 +1,141 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Lists; + +namespace osu.Game.Tests.Lists +{ + [TestFixture] + public class IntervalListTest + { + // this is intended to be unordered to test adding intervals in unordered way. + private static readonly (double, double)[] test_intervals = + { + (-9.1d, -8.3d), + (-3.4d, 2.1d), + (50.0d, 9.0d), // intentionally reversing interval. + (5.25d, 10.50d), + }; + + [Test] + public void TestCheckValueInsideSingleInterval() + { + var list = new IntervalList { { 1.0d, 2.0d } }; + + Assert.IsTrue(list.IsInAnyInterval(1.0d)); + Assert.IsTrue(list.IsInAnyInterval(1.5d)); + Assert.IsTrue(list.IsInAnyInterval(2.0d)); + } + + [Test] + public void TestCheckValuesInsideIntervals() + { + var list = new IntervalList(); + + foreach (var (start, end) in test_intervals) + list.Add(start, end); + + Assert.IsTrue(list.IsInAnyInterval(-8.75d)); + Assert.IsTrue(list.IsInAnyInterval(1.0d)); + Assert.IsTrue(list.IsInAnyInterval(7.89d)); + Assert.IsTrue(list.IsInAnyInterval(9.8d)); + Assert.IsTrue(list.IsInAnyInterval(15.83d)); + } + + [Test] + public void TestCheckValuesInRandomOrder() + { + var list = new IntervalList(); + + foreach (var (start, end) in test_intervals) + list.Add(start, end); + + Assert.IsTrue(list.IsInAnyInterval(9.8d)); + Assert.IsTrue(list.IsInAnyInterval(7.89d)); + Assert.IsTrue(list.IsInAnyInterval(1.0d)); + Assert.IsTrue(list.IsInAnyInterval(15.83d)); + Assert.IsTrue(list.IsInAnyInterval(-8.75d)); + } + + [Test] + public void TestCheckValuesOutOfIntervals() + { + var list = new IntervalList(); + + foreach (var (start, end) in test_intervals) + list.Add(start, end); + + Assert.IsFalse(list.IsInAnyInterval(-9.2d)); + Assert.IsFalse(list.IsInAnyInterval(2.2d)); + Assert.IsFalse(list.IsInAnyInterval(5.15d)); + Assert.IsFalse(list.IsInAnyInterval(51.2d)); + } + + [Test] + public void TestCheckValueAfterRemovedInterval() + { + var list = new IntervalList { { 50, 100 }, { 150, 200 }, { 250, 300 } }; + + Assert.IsTrue(list.IsInAnyInterval(75)); + Assert.IsTrue(list.IsInAnyInterval(175)); + Assert.IsTrue(list.IsInAnyInterval(275)); + + list.Remove(list[1]); + + Assert.IsFalse(list.IsInAnyInterval(175)); + Assert.IsTrue(list.IsInAnyInterval(75)); + Assert.IsTrue(list.IsInAnyInterval(275)); + } + + [Test] + public void TestCheckValueAfterAddedInterval() + { + var list = new IntervalList { { 50, 100 }, { 250, 300 } }; + + Assert.IsFalse(list.IsInAnyInterval(175)); + Assert.IsTrue(list.IsInAnyInterval(75)); + Assert.IsTrue(list.IsInAnyInterval(275)); + + list.Add(150, 200); + + Assert.IsTrue(list.IsInAnyInterval(175)); + } + + [Test] + public void TestCheckIntervalIndexOnChecks() + { + var list = new TestIntervalList { { 1.0d, 2.0d }, { 3.0d, 4.0d }, { 5.0d, 6.0d }, { 7.0d, 8.0d } }; + + Assert.IsTrue(list.IsInAnyInterval(1.5d)); + Assert.IsTrue(list.NearestIntervalIndex == 0); + + Assert.IsTrue(list.IsInAnyInterval(5.5d)); + Assert.IsTrue(list.NearestIntervalIndex == 2); + + Assert.IsTrue(list.IsInAnyInterval(7.5d)); + Assert.IsTrue(list.NearestIntervalIndex == 3); + } + + [Test] + public void TestCheckIntervalIndexOnOutOfIntervalsChecks() + { + var list = new TestIntervalList { { 1.0d, 2.0d }, { 3.0d, 4.0d }, { 5.0d, 6.0d }, { 7.0d, 8.0d } }; + + Assert.IsFalse(list.IsInAnyInterval(4.5d)); + Assert.IsTrue(list.NearestIntervalIndex == 1 || + list.NearestIntervalIndex == 2); // 4.5 in between 3.0-4.0 and 5.0-6.0 + + Assert.IsFalse(list.IsInAnyInterval(9.0d)); + Assert.IsTrue(list.NearestIntervalIndex == 3); // 9.0 goes above 7.0-8.0 + + Assert.IsFalse(list.IsInAnyInterval(0.0d)); + Assert.IsTrue(list.NearestIntervalIndex == 0); // 0.0 goes below 1.0-2.0 + } + + private class TestIntervalList : IntervalList + { + public new int NearestIntervalIndex => base.NearestIntervalIndex; + } + } +} From 9a29797a5bee661a767d43584af541d6619d5ea5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 9 Apr 2020 23:00:16 +0300 Subject: [PATCH 225/655] Use IntervalList for tracking break periods --- .../Visual/Gameplay/TestSceneAutoplay.cs | 2 +- .../Visual/Gameplay/TestSceneBreakTracker.cs | 4 -- osu.Game/Screens/Play/BreakTracker.cs | 50 ++++++------------- 3 files changed, 16 insertions(+), 40 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index 4b1c2ec256..0be949650e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep($"seek to break {breakIndex}", () => Player.GameplayClockContainer.Seek(destBreak().StartTime)); AddUntilStep("wait for seek to complete", () => Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= destBreak().StartTime); - BreakPeriod destBreak() => Player.ChildrenOfType().First().Breaks.ElementAt(breakIndex); + BreakPeriod destBreak() => Beatmap.Value.Beatmap.Breaks.ElementAt(breakIndex); } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs index 91d6f2f143..a6f996c30d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs @@ -97,8 +97,6 @@ namespace osu.Game.Tests.Visual.Gameplay loadBreaksStep("multiple breaks", testBreaks); seekAndAssertBreak("seek to break start", testBreaks[1].StartTime, true); - AddAssert("is skipped to break #2", () => breakTracker.CurrentBreakIndex == 1); - seekAndAssertBreak("seek to break middle", testBreaks[1].StartTime + testBreaks[1].Duration / 2, true); seekAndAssertBreak("seek to break end", testBreaks[1].EndTime, false); seekAndAssertBreak("seek to break after end", testBreaks[1].EndTime + 500, false); @@ -174,8 +172,6 @@ namespace osu.Game.Tests.Visual.Gameplay private readonly ManualClock manualClock; private IFrameBasedClock originalClock; - public new int CurrentBreakIndex => base.CurrentBreakIndex; - public double ManualClockTime { get => manualClock.CurrentTime; diff --git a/osu.Game/Screens/Play/BreakTracker.cs b/osu.Game/Screens/Play/BreakTracker.cs index 64262d52b5..c2eb069ee6 100644 --- a/osu.Game/Screens/Play/BreakTracker.cs +++ b/osu.Game/Screens/Play/BreakTracker.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps.Timing; +using osu.Game.Lists; using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Play @@ -20,22 +21,24 @@ namespace osu.Game.Screens.Play /// public IBindable IsBreakTime => isBreakTime; - protected int CurrentBreakIndex; - private readonly BindableBool isBreakTime = new BindableBool(); - private IReadOnlyList breaks; + private readonly IntervalList breakIntervals = new IntervalList(); public IReadOnlyList Breaks { - get => breaks; set { - breaks = value; - - // reset index in case the new breaks list is smaller than last one isBreakTime.Value = false; - CurrentBreakIndex = 0; + breakIntervals.Clear(); + + foreach (var b in value) + { + if (!b.HasEffect) + continue; + + breakIntervals.Add(b.StartTime, b.EndTime - BreakOverlay.BREAK_FADE_DURATION); + } } } @@ -49,34 +52,11 @@ namespace osu.Game.Screens.Play { base.Update(); - isBreakTime.Value = getCurrentBreak()?.HasEffect == true - || Clock.CurrentTime < gameplayStartTime + var time = Clock.CurrentTime; + + isBreakTime.Value = breakIntervals.IsInAnyInterval(time) + || time < gameplayStartTime || scoreProcessor?.HasCompleted == true; } - - private BreakPeriod getCurrentBreak() - { - if (breaks?.Count > 0) - { - var time = Clock.CurrentTime; - - if (time > breaks[CurrentBreakIndex].EndTime) - { - while (time > breaks[CurrentBreakIndex].EndTime && CurrentBreakIndex < breaks.Count - 1) - CurrentBreakIndex++; - } - else - { - while (time < breaks[CurrentBreakIndex].StartTime && CurrentBreakIndex > 0) - CurrentBreakIndex--; - } - - var closest = breaks[CurrentBreakIndex]; - - return closest.Contains(time) ? closest : null; - } - - return null; - } } } From c17e47026623afde64aaf11b8334b5ebcd221696 Mon Sep 17 00:00:00 2001 From: Fire937 Date: Fri, 10 Apr 2020 00:01:35 +0200 Subject: [PATCH 226/655] Fix PositionalSound calculation implementation The position used to calculate the stereo balance is now the position of the drawable (as opposed to the position specified in the beatmap file previously). --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index ed9efba89f..30a9106ddc 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// The stereo balance of the samples played if Positional hitsounds is set. /// - protected virtual float PositionalSound => (HitObject is IHasXPosition position) ? (position.X / 512f - 0.5f) * 0.8f : 0; + protected virtual float PositionalSound => (Position.X / 512f - 0.5f) * 0.8f; private BindableList samplesBindable; private Bindable startTimeBindable; @@ -114,7 +114,6 @@ namespace osu.Game.Rulesets.Objects.Drawables protected DrawableHitObject([NotNull] HitObject hitObject) { HitObject = hitObject ?? throw new ArgumentNullException(nameof(hitObject)); - positionalSoundAdjustment.Value = PositionalSound; } [BackgroundDependencyLoader] @@ -377,7 +376,11 @@ namespace osu.Game.Rulesets.Objects.Drawables /// Plays all the hit sounds for this . /// This is invoked automatically when this is hit. /// - public virtual void PlaySamples() => Samples?.Play(); + public virtual void PlaySamples() + { + positionalSoundAdjustment.Value = PositionalSound; + Samples?.Play(); + } protected override void Update() { From ee7e2b0854a8096dd55c1e3472b8964311df2897 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 Apr 2020 13:29:46 +0900 Subject: [PATCH 227/655] Fix editor beatmap potentially not updating hitobjects --- osu.Game/Screens/Edit/EditorBeatmap.cs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 7f04a7a58d..efffde54b3 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -63,6 +63,7 @@ namespace osu.Game.Screens.Edit trackStartTime(obj); } + private readonly HashSet pendingUpdates = new HashSet(); private ScheduledDelegate scheduledUpdate; /// @@ -74,15 +75,27 @@ namespace osu.Game.Screens.Edit private void updateHitObject([CanBeNull] HitObject hitObject, bool silent) { scheduledUpdate?.Cancel(); - scheduledUpdate = Scheduler.AddDelayed(() => + + if (hitObject != null) + pendingUpdates.Add(hitObject); + + scheduledUpdate = Schedule(() => { beatmapProcessor?.PreProcess(); - hitObject?.ApplyDefaults(ControlPointInfo, BeatmapInfo.BaseDifficulty); + + foreach (var obj in pendingUpdates) + obj.ApplyDefaults(ControlPointInfo, BeatmapInfo.BaseDifficulty); + beatmapProcessor?.PostProcess(); if (!silent) - HitObjectUpdated?.Invoke(hitObject); - }, 0); + { + foreach (var obj in pendingUpdates) + HitObjectUpdated?.Invoke(obj); + } + + pendingUpdates.Clear(); + }); } public BeatmapInfo BeatmapInfo From 41caa378565d807853dd53ec6b0727d60139bd33 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 Apr 2020 13:29:49 +0900 Subject: [PATCH 228/655] Add tests --- .../Beatmaps/TestSceneEditorBeatmap.cs | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs b/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs index d367d9f88b..2d4587341d 100644 --- a/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs +++ b/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore.Internal; using NUnit.Framework; @@ -162,5 +163,69 @@ namespace osu.Game.Tests.Beatmaps Assert.That(editorBeatmap.HitObjects.Count(h => h == hitCircle), Is.EqualTo(1)); Assert.That(editorBeatmap.HitObjects.IndexOf(hitCircle), Is.EqualTo(1)); } + + /// + /// Tests that multiple hitobjects are updated simultaneously. + /// + [Test] + public void TestMultipleHitObjectUpdate() + { + var updatedObjects = new List(); + var allHitObjects = new List(); + EditorBeatmap editorBeatmap = null; + + AddStep("add beatmap", () => + { + updatedObjects.Clear(); + + Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + + for (int i = 0; i < 10; i++) + { + var h = new HitCircle(); + editorBeatmap.Add(h); + allHitObjects.Add(h); + } + }); + + AddStep("change all start times", () => + { + editorBeatmap.HitObjectUpdated += h => updatedObjects.Add(h); + + for (int i = 0; i < 10; i++) + allHitObjects[i].StartTime += 10; + }); + + // Distinct ensures that all hitobjects have been updated once, debounce is tested below. + AddAssert("all hitobjects updated", () => updatedObjects.Distinct().Count() == 10); + } + + /// + /// Tests that hitobject updates are debounced when they happen too soon. + /// + [Test] + public void TestDebouncedUpdate() + { + var updatedObjects = new List(); + EditorBeatmap editorBeatmap = null; + + AddStep("add beatmap", () => + { + updatedObjects.Clear(); + + Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + editorBeatmap.Add(new HitCircle()); + }); + + AddStep("change start time twice", () => + { + editorBeatmap.HitObjectUpdated += h => updatedObjects.Add(h); + + editorBeatmap.HitObjects[0].StartTime = 10; + editorBeatmap.HitObjects[0].StartTime = 20; + }); + + AddAssert("only updated once", () => updatedObjects.Count == 1); + } } } From 4a87ac784061318026fe650a48e42ca455e7e7f9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 Apr 2020 13:53:09 +0900 Subject: [PATCH 229/655] Add support for sample changes --- .../Screens/Edit/Compose/Components/SelectionHandler.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index e212979433..764eae1056 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -212,6 +212,8 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The name of the hit sample. public void AddHitSample(string sampleName) { + changeHandler?.BeginChange(); + foreach (var h in SelectedHitObjects) { // Make sure there isn't already an existing sample @@ -220,6 +222,8 @@ namespace osu.Game.Screens.Edit.Compose.Components h.Samples.Add(new HitSampleInfo { Name = sampleName }); } + + changeHandler?.EndChange(); } /// @@ -228,8 +232,12 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The name of the hit sample. public void RemoveHitSample(string sampleName) { + changeHandler?.BeginChange(); + foreach (var h in SelectedHitObjects) h.SamplesBindable.RemoveAll(s => s.Name == sampleName); + + changeHandler?.EndChange(); } #endregion From 1001fcfb94d28787d62fa9e185499188620b4522 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 10 Apr 2020 15:48:00 +0300 Subject: [PATCH 230/655] Revert nearest index accessibility back to private Implementation details should not be tested. --- osu.Game/Lists/IntervalList.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/osu.Game/Lists/IntervalList.cs b/osu.Game/Lists/IntervalList.cs index 493b6b6e72..6a71d94ea8 100644 --- a/osu.Game/Lists/IntervalList.cs +++ b/osu.Game/Lists/IntervalList.cs @@ -17,11 +17,8 @@ namespace osu.Game.Lists private static readonly IComparer type_comparer = Comparer.Default; private readonly SortedList> intervals = new SortedList>((x, y) => type_comparer.Compare(x.Start, y.Start)); + private int nearestIndex; - /// - /// The index of the nearest interval from last call. - /// - protected int NearestIntervalIndex; /// /// Whether the provided value is in any interval added to this list. @@ -34,20 +31,20 @@ namespace osu.Game.Lists // Clamp the nearest index in case there were intervals // removed from the list causing the index to go out of range. - NearestIntervalIndex = Math.Clamp(NearestIntervalIndex, 0, Count - 1); + nearestIndex = Math.Clamp(nearestIndex, 0, intervals.Count - 1); - if (type_comparer.Compare(value, this[NearestIntervalIndex].End) > 0) + if (type_comparer.Compare(value, this[nearestIndex].End) > 0) { - while (type_comparer.Compare(value, this[NearestIntervalIndex].End) > 0 && NearestIntervalIndex < Count - 1) - NearestIntervalIndex++; + while (type_comparer.Compare(value, this[nearestIndex].End) > 0 && nearestIndex < intervals.Count - 1) + nearestIndex++; } else { - while (type_comparer.Compare(value, this[NearestIntervalIndex].Start) < 0 && NearestIntervalIndex > 0) - NearestIntervalIndex--; + while (type_comparer.Compare(value, this[nearestIndex].Start) < 0 && nearestIndex > 0) + nearestIndex--; } - var nearestInterval = this[NearestIntervalIndex]; + var nearestInterval = this[nearestIndex]; return type_comparer.Compare(value, nearestInterval.Start) >= 0 && type_comparer.Compare(value, nearestInterval.End) <= 0; From b7ed17dfbd16a78561da25516757bc19170e9af2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 10 Apr 2020 15:49:06 +0300 Subject: [PATCH 231/655] Throw if interval with reversed values added May reduce in subtle bugs. --- osu.Game.Tests/Lists/IntervalListTest.cs | 38 ++++-------------------- osu.Game/Lists/IntervalList.cs | 7 +++-- 2 files changed, 10 insertions(+), 35 deletions(-) diff --git a/osu.Game.Tests/Lists/IntervalListTest.cs b/osu.Game.Tests/Lists/IntervalListTest.cs index 1bc1483e15..0958f0fa7c 100644 --- a/osu.Game.Tests/Lists/IntervalListTest.cs +++ b/osu.Game.Tests/Lists/IntervalListTest.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 NUnit.Framework; using osu.Game.Lists; @@ -14,7 +15,7 @@ namespace osu.Game.Tests.Lists { (-9.1d, -8.3d), (-3.4d, 2.1d), - (50.0d, 9.0d), // intentionally reversing interval. + (9.0d, 50.0d), (5.25d, 10.50d), }; @@ -103,39 +104,12 @@ namespace osu.Game.Tests.Lists } [Test] - public void TestCheckIntervalIndexOnChecks() + public void TestReversedIntervalThrows() { - var list = new TestIntervalList { { 1.0d, 2.0d }, { 3.0d, 4.0d }, { 5.0d, 6.0d }, { 7.0d, 8.0d } }; + var list = new IntervalList(); - Assert.IsTrue(list.IsInAnyInterval(1.5d)); - Assert.IsTrue(list.NearestIntervalIndex == 0); - - Assert.IsTrue(list.IsInAnyInterval(5.5d)); - Assert.IsTrue(list.NearestIntervalIndex == 2); - - Assert.IsTrue(list.IsInAnyInterval(7.5d)); - Assert.IsTrue(list.NearestIntervalIndex == 3); - } - - [Test] - public void TestCheckIntervalIndexOnOutOfIntervalsChecks() - { - var list = new TestIntervalList { { 1.0d, 2.0d }, { 3.0d, 4.0d }, { 5.0d, 6.0d }, { 7.0d, 8.0d } }; - - Assert.IsFalse(list.IsInAnyInterval(4.5d)); - Assert.IsTrue(list.NearestIntervalIndex == 1 || - list.NearestIntervalIndex == 2); // 4.5 in between 3.0-4.0 and 5.0-6.0 - - Assert.IsFalse(list.IsInAnyInterval(9.0d)); - Assert.IsTrue(list.NearestIntervalIndex == 3); // 9.0 goes above 7.0-8.0 - - Assert.IsFalse(list.IsInAnyInterval(0.0d)); - Assert.IsTrue(list.NearestIntervalIndex == 0); // 0.0 goes below 1.0-2.0 - } - - private class TestIntervalList : IntervalList - { - public new int NearestIntervalIndex => base.NearestIntervalIndex; + Assert.Throws(() => list.Add(50, 25)); + Assert.Throws(() => list.Add(new Interval(50, 25))); } } } diff --git a/osu.Game/Lists/IntervalList.cs b/osu.Game/Lists/IntervalList.cs index 6a71d94ea8..15d9a349dc 100644 --- a/osu.Game/Lists/IntervalList.cs +++ b/osu.Game/Lists/IntervalList.cs @@ -116,10 +116,11 @@ namespace osu.Game.Lists public Interval(T start, T end) { - bool startLessThanEnd = Comparer.Default.Compare(start, end) < 0; + if (Comparer.Default.Compare(start, end) >= 0) + throw new ArgumentException($"Invalid interval, {nameof(start)} must be less than {nameof(end)}", nameof(start)); - Start = startLessThanEnd ? start : end; - End = startLessThanEnd ? end : start; + Start = start; + End = end; } } } From 5966c9af9ccdd05d84a92921eae77b3cff787712 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 10 Apr 2020 15:50:07 +0300 Subject: [PATCH 232/655] Limit generic type to IConvertible structures (common number types) --- osu.Game/Lists/IntervalList.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Lists/IntervalList.cs b/osu.Game/Lists/IntervalList.cs index 15d9a349dc..5a5866ca91 100644 --- a/osu.Game/Lists/IntervalList.cs +++ b/osu.Game/Lists/IntervalList.cs @@ -13,6 +13,7 @@ namespace osu.Game.Lists /// /// The type of interval values. public class IntervalList : ICollection>, IReadOnlyList> + where T : struct, IConvertible { private static readonly IComparer type_comparer = Comparer.Default; @@ -103,6 +104,7 @@ namespace osu.Game.Lists } public readonly struct Interval + where T : struct, IConvertible { /// /// The start value of this interval. From bf124a5cc6d9d99566f099e405f2c03bcdc6ac3f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 10 Apr 2020 15:50:40 +0300 Subject: [PATCH 233/655] Simplify IntervalList implementation enough to work for its usages --- osu.Game/Lists/IntervalList.cs | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/osu.Game/Lists/IntervalList.cs b/osu.Game/Lists/IntervalList.cs index 5a5866ca91..580015bb96 100644 --- a/osu.Game/Lists/IntervalList.cs +++ b/osu.Game/Lists/IntervalList.cs @@ -12,7 +12,7 @@ namespace osu.Game.Lists /// Represents a list of intervals that can be used for whether a specific value falls into one of them. /// /// The type of interval values. - public class IntervalList : ICollection>, IReadOnlyList> + public class IntervalList : IEnumerable> where T : struct, IConvertible { private static readonly IComparer type_comparer = Comparer.Default; @@ -20,6 +20,11 @@ namespace osu.Game.Lists private readonly SortedList> intervals = new SortedList>((x, y) => type_comparer.Compare(x.Start, y.Start)); private int nearestIndex; + public Interval this[int i] + { + get => intervals[i]; + set => intervals[i] = value; + } /// /// Whether the provided value is in any interval added to this list. @@ -58,12 +63,6 @@ namespace osu.Game.Lists /// The end value of the interval. public void Add(T start, T end) => Add(new Interval(start, end)); - #region ICollection> - - public int Count => intervals.Count; - - bool ICollection>.IsReadOnly => false; - /// /// Adds a new interval to the list /// @@ -82,25 +81,9 @@ namespace osu.Game.Lists /// public void Clear() => intervals.Clear(); - public void CopyTo(Interval[] array, int arrayIndex) => intervals.CopyTo(array, arrayIndex); - - public bool Contains(Interval item) => intervals.Contains(item); - public IEnumerator> GetEnumerator() => intervals.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - #endregion - - #region IReadOnlyList> - - public Interval this[int index] - { - get => intervals[index]; - set => intervals[index] = value; - } - - #endregion } public readonly struct Interval From 7fba29113466d5fe6aa43eef0cd284323f1c969a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Apr 2020 13:14:34 +0900 Subject: [PATCH 234/655] Change inheritance of taiko hit pieces to better allow for skinning --- .../TestSceneDrawableHit.cs | 54 ++++++++++++++++++ .../Objects/Drawables/DrawableCentreHit.cs | 10 +--- .../Objects/Drawables/DrawableDrumRoll.cs | 10 ++-- .../Objects/Drawables/DrawableDrumRollTick.cs | 3 +- .../Objects/Drawables/DrawableRimHit.cs | 10 +--- .../Objects/Drawables/DrawableSwell.cs | 16 +++--- .../Objects/Drawables/DrawableSwellTick.cs | 4 ++ .../Drawables/DrawableTaikoHitObject.cs | 14 ++--- .../Drawables/Pieces/CentreHitSymbolPiece.cs | 50 +++++++++++------ .../Drawables/Pieces/RimHitCirclePiece.cs | 55 +++++++++++++++++++ .../Drawables/Pieces/RimHitSymbolPiece.cs | 39 ------------- .../Drawables/Pieces/SwellSymbolPiece.cs | 50 +++++++++++------ .../Objects/Drawables/Pieces/TickPiece.cs | 6 +- 13 files changed, 209 insertions(+), 112 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneDrawableHit.cs create mode 100644 osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/RimHitCirclePiece.cs delete mode 100644 osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrawableHit.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrawableHit.cs new file mode 100644 index 0000000000..b927f0294b --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrawableHit.cs @@ -0,0 +1,54 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Objects.Drawables; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + [TestFixture] + public class TestSceneDrawableHit : TaikoSkinnableTestScene + { + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] + { + typeof(DrawableHit), + typeof(DrawableCentreHit), + typeof(DrawableRimHit), + }).ToList(); + + [BackgroundDependencyLoader] + private void load() + { + AddStep("Centre hit", () => SetContents(() => new DrawableCentreHit(createHitAtCurrentTime()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + })); + AddStep("Rim hit", () => SetContents(() => new DrawableRimHit(createHitAtCurrentTime()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + })); + } + + private Hit createHitAtCurrentTime() + { + var hit = new Hit + { + StartTime = Time.Current + 3000, + }; + + hit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + return hit; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs index 4979135f50..22d62442cf 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs @@ -1,8 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; -using osu.Game.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; namespace osu.Game.Rulesets.Taiko.Objects.Drawables @@ -14,13 +13,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public DrawableCentreHit(Hit hit) : base(hit) { - MainPiece.Add(new CentreHitSymbolPiece()); } - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - MainPiece.AccentColour = colours.PinkDarker; - } + protected override CompositeDrawable CreateMainPiece() => new CentreHitCirclePiece(); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 5806c90115..0627eb95fd 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -34,17 +34,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private Color4 colourIdle; private Color4 colourEngaged; + private ElongatedCirclePiece elongatedPiece; + public DrawableDrumRoll(DrumRoll drumRoll) : base(drumRoll) { RelativeSizeAxes = Axes.Y; - MainPiece.Add(tickContainer = new Container { RelativeSizeAxes = Axes.Both }); + elongatedPiece.Add(tickContainer = new Container { RelativeSizeAxes = Axes.Both }); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - MainPiece.AccentColour = colourIdle = colours.YellowDark; + elongatedPiece.AccentColour = colourIdle = colours.YellowDark; colourEngaged = colours.YellowDarker; } @@ -84,7 +86,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables return base.CreateNestedHitObject(hitObject); } - protected override TaikoPiece CreateMainPiece() => new ElongatedCirclePiece(); + protected override CompositeDrawable CreateMainPiece() => elongatedPiece = new ElongatedCirclePiece(); public override bool OnPressed(TaikoAction action) => false; @@ -101,7 +103,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables rollingHits = Math.Clamp(rollingHits, 0, rolling_hits_for_engaged_colour); Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1); - MainPiece.FadeAccent(newColour, 100); + (MainPiece as IHasAccentColour)?.FadeAccent(newColour, 100); } protected override void CheckForResult(bool userTriggered, double timeOffset) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index 25b6141a0e..fea3eea6a9 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; @@ -19,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public override bool DisplayResult => false; - protected override TaikoPiece CreateMainPiece() => new TickPiece + protected override CompositeDrawable CreateMainPiece() => new TickPiece { Filled = HitObject.FirstTick }; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs index 5a12d71cea..6dad7af907 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs @@ -1,8 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; -using osu.Game.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; namespace osu.Game.Rulesets.Taiko.Objects.Drawables @@ -14,13 +13,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public DrawableRimHit(Hit hit) : base(hit) { - MainPiece.Add(new RimHitSymbolPiece()); } - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - MainPiece.AccentColour = colours.BlueDarker; - } + protected override CompositeDrawable CreateMainPiece() => new RimHitCirclePiece(); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index fa39819199..3a2e44038f 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -9,11 +9,11 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -34,8 +34,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private readonly CircularContainer targetRing; private readonly CircularContainer expandingRing; - private readonly SwellSymbolPiece symbol; - public DrawableSwell(Swell swell) : base(swell) { @@ -107,18 +105,22 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables }); AddInternal(ticks = new Container { RelativeSizeAxes = Axes.Both }); - - MainPiece.Add(symbol = new SwellSymbolPiece()); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - MainPiece.AccentColour = colours.YellowDark; expandingRing.Colour = colours.YellowLight; targetRing.BorderColour = colours.YellowDark.Opacity(0.25f); } + protected override CompositeDrawable CreateMainPiece() => new SwellCirclePiece + { + // to allow for rotation transform + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + protected override void LoadComplete() { base.LoadComplete(); @@ -182,7 +184,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables .Then() .FadeTo(completion / 8, 2000, Easing.OutQuint); - symbol.RotateTo((float)(completion * HitObject.Duration / 8), 4000, Easing.OutQuint); + MainPiece.RotateTo((float)(completion * HitObject.Duration / 8), 4000, Easing.OutQuint); expandingRing.ScaleTo(1f + Math.Min(target_ring_scale - 1f, (target_ring_scale - 1f) * completion * 1.3f), 260, Easing.OutQuint); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs index ce875ebba8..5a954addfb 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -28,5 +30,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } public override bool OnPressed(TaikoAction action) => false; + + protected override CompositeDrawable CreateMainPiece() => new TickPiece(); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 5f892dd2fa..397888bb11 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -4,7 +4,6 @@ using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osuTK; using System.Linq; using osu.Game.Audio; @@ -108,19 +107,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } } - public abstract class DrawableTaikoHitObject : DrawableTaikoHitObject - where TTaikoHit : TaikoHitObject + public abstract class DrawableTaikoHitObject : DrawableTaikoHitObject + where TObject : TaikoHitObject { public override Vector2 OriginPosition => new Vector2(DrawHeight / 2); - public new TTaikoHit HitObject; + public new TObject HitObject; protected readonly Vector2 BaseSize; - protected readonly TaikoPiece MainPiece; + protected readonly CompositeDrawable MainPiece; private readonly Container strongHitContainer; - protected DrawableTaikoHitObject(TTaikoHit hitObject) + protected DrawableTaikoHitObject(TObject hitObject) : base(hitObject) { HitObject = hitObject; @@ -132,7 +131,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE); Content.Add(MainPiece = CreateMainPiece()); - MainPiece.KiaiMode = HitObject.Kiai; AddInternal(strongHitContainer = new Container()); } @@ -169,7 +167,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables // Normal and clap samples are handled by the drum protected override IEnumerable GetSamples() => HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP); - protected virtual TaikoPiece CreateMainPiece() => new CirclePiece(); + protected abstract CompositeDrawable CreateMainPiece(); /// /// Creates the handler for this 's . diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs index 7ed61ede96..0509841ba8 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs @@ -1,36 +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 osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osuTK; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces { - /// - /// The symbol used for centre hit pieces. - /// - public class CentreHitSymbolPiece : Container + public class CentreHitCirclePiece : CirclePiece { - public CentreHitSymbolPiece() + public CentreHitCirclePiece() { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; + Add(new CentreHitSymbolPiece()); + } - RelativeSizeAxes = Axes.Both; - Size = new Vector2(CirclePiece.SYMBOL_SIZE); - Padding = new MarginPadding(CirclePiece.SYMBOL_BORDER); + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.PinkDarker; + } - Children = new[] + /// + /// The symbol used for centre hit pieces. + /// + public class CentreHitSymbolPiece : Container + { + public CentreHitSymbolPiece() { - new CircularContainer + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + RelativeSizeAxes = Axes.Both; + Size = new Vector2(SYMBOL_SIZE); + Padding = new MarginPadding(SYMBOL_BORDER); + + Children = new[] { - RelativeSizeAxes = Axes.Both, - Masking = true, - Children = new[] { new Box { RelativeSizeAxes = Axes.Both } } - } - }; + new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new[] { new Box { RelativeSizeAxes = Axes.Both } } + } + }; + } } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/RimHitCirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/RimHitCirclePiece.cs new file mode 100644 index 0000000000..3273ab7fa7 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/RimHitCirclePiece.cs @@ -0,0 +1,55 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces +{ + public class RimHitCirclePiece : CirclePiece + { + public RimHitCirclePiece() + { + Add(new RimHitSymbolPiece()); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.BlueDarker; + } + + /// + /// The symbol used for rim hit pieces. + /// + public class RimHitSymbolPiece : CircularContainer + { + public RimHitSymbolPiece() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + RelativeSizeAxes = Axes.Both; + Size = new Vector2(SYMBOL_SIZE); + + BorderThickness = SYMBOL_BORDER; + BorderColour = Color4.White; + Masking = true; + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + }; + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs deleted file mode 100644 index e4c964a884..0000000000 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osuTK; -using osuTK.Graphics; -using osu.Framework.Graphics.Shapes; - -namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces -{ - /// - /// The symbol used for rim hit pieces. - /// - public class RimHitSymbolPiece : CircularContainer - { - public RimHitSymbolPiece() - { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - RelativeSizeAxes = Axes.Both; - Size = new Vector2(CirclePiece.SYMBOL_SIZE); - - BorderThickness = CirclePiece.SYMBOL_BORDER; - BorderColour = Color4.White; - Masking = true; - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - } - }; - } - } -} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs index 0ed9923924..a8f9f0b94d 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs @@ -1,36 +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 osu.Framework.Allocation; using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces { - /// - /// The symbol used for swell pieces. - /// - public class SwellSymbolPiece : Container + public class SwellCirclePiece : CirclePiece { - public SwellSymbolPiece() + public SwellCirclePiece() { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; + Add(new SwellSymbolPiece()); + } - RelativeSizeAxes = Axes.Both; - Size = new Vector2(CirclePiece.SYMBOL_SIZE); - Padding = new MarginPadding(CirclePiece.SYMBOL_BORDER); + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.YellowDark; + } - Children = new[] + /// + /// The symbol used for swell pieces. + /// + public class SwellSymbolPiece : Container + { + public SwellSymbolPiece() { - new SpriteIcon + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + RelativeSizeAxes = Axes.Both; + Size = new Vector2(SYMBOL_SIZE); + Padding = new MarginPadding(SYMBOL_BORDER); + + Children = new[] { - RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.Solid.Asterisk, - Shadow = false - } - }; + new SpriteIcon + { + RelativeSizeAxes = Axes.Both, + Icon = FontAwesome.Solid.Asterisk, + Shadow = false + } + }; + } } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TickPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TickPiece.cs index 83cf7a64ec..0648bcebcd 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TickPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TickPiece.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics.Shapes; namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces { - public class TickPiece : TaikoPiece + public class TickPiece : CompositeDrawable { /// /// Any tick that is not the first for a drumroll is not filled, but is instead displayed @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces FillMode = FillMode.Fit; Size = new Vector2(tick_size); - Add(new CircularContainer + InternalChild = new CircularContainer { RelativeSizeAxes = Axes.Both, Masking = true, @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces AlwaysPresent = true } } - }); + }; } } } From ca2df77c7684899cc107d72f646e90461244a8e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Apr 2020 13:14:41 +0900 Subject: [PATCH 235/655] Add default skin test resources --- .../metrics-skin/taikohitcircle@2x.png | Bin 0 -> 13140 bytes .../metrics-skin/taikohitcircleoverlay@2x.png | Bin 0 -> 38217 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taikohitcircle@2x.png create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taikohitcircleoverlay@2x.png diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taikohitcircle@2x.png b/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taikohitcircle@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..043bfbfae1a77317aec68051b2d917a42d143fb1 GIT binary patch literal 13140 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 R|M{Pbx-w?>O~E?q{{RqUO*sGn literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taikohitcircleoverlay@2x.png b/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taikohitcircleoverlay@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4233d9bb6e6aa88c7da5c1998439789cc5f9c7b2 GIT binary patch literal 38217 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 literal 0 HcmV?d00001 From 78db83fd0e88ad1217f96ff83cb077c800ad9494 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Apr 2020 13:33:19 +0900 Subject: [PATCH 236/655] Remove TaikoPiece class and localise kiai for now --- ...SymbolPiece.cs => CentreHitCirclePiece.cs} | 0 .../Objects/Drawables/Pieces/CirclePiece.cs | 19 ++++++++----- .../Objects/Drawables/Pieces/TaikoPiece.cs | 28 ------------------- 3 files changed, 12 insertions(+), 35 deletions(-) rename osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/{CentreHitSymbolPiece.cs => CentreHitCirclePiece.cs} (100%) delete mode 100644 osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CentreHitCirclePiece.cs similarity index 100% rename from osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs rename to osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CentreHitCirclePiece.cs diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs index d9c0664ecd..ce2882656a 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs @@ -10,6 +10,7 @@ using osuTK.Graphics; using osu.Game.Beatmaps.ControlPoints; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Effects; +using osu.Game.Graphics.Containers; namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces { @@ -20,21 +21,23 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces /// for a usage example. /// /// - public class CirclePiece : TaikoPiece + public class CirclePiece : BeatSyncedContainer { public const float SYMBOL_SIZE = 0.45f; public const float SYMBOL_BORDER = 8; private const double pre_beat_transition_time = 80; + private Color4 accentColour; + /// /// The colour of the inner circle and outer glows. /// - public override Color4 AccentColour + public Color4 AccentColour { - get => base.AccentColour; + get => accentColour; set { - base.AccentColour = value; + accentColour = value; background.Colour = AccentColour; @@ -42,15 +45,17 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces } } + private bool kiaiMode; + /// /// Whether Kiai mode effects are enabled for this circle piece. /// - public override bool KiaiMode + public bool KiaiMode { - get => base.KiaiMode; + get => kiaiMode; set { - base.KiaiMode = value; + kiaiMode = value; resetEdgeEffects(); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs deleted file mode 100644 index 8067054f8f..0000000000 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Graphics; -using osuTK.Graphics; -using osu.Game.Graphics.Containers; -using osu.Framework.Graphics; - -namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces -{ - public class TaikoPiece : BeatSyncedContainer, IHasAccentColour - { - /// - /// The colour of the inner circle and outer glows. - /// - public virtual Color4 AccentColour { get; set; } - - /// - /// Whether Kiai mode effects are enabled for this circle piece. - /// - public virtual bool KiaiMode { get; set; } - - public TaikoPiece() - { - RelativeSizeAxes = Axes.Both; - } - } -} From 009b1383648dd267e76ee13f72daac2ee1bb1458 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Apr 2020 13:41:14 +0900 Subject: [PATCH 237/655] Prepare for skinnable versions --- .../Objects/Drawables/DrawableCentreHit.cs | 4 +++- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs | 4 +++- .../Objects/Drawables/Pieces/CirclePiece.cs | 2 ++ osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs | 2 ++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs index 22d62442cf..f3f4c59a62 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -15,6 +16,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { } - protected override CompositeDrawable CreateMainPiece() => new CentreHitCirclePiece(); + protected override CompositeDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.CentreHit), + _ => new CentreHitCirclePiece(), confineMode: ConfineMode.ScaleToFit); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs index 6dad7af907..463a8b746c 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -15,6 +16,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { } - protected override CompositeDrawable CreateMainPiece() => new RimHitCirclePiece(); + protected override CompositeDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.RimHit), + _ => new RimHitCirclePiece(), confineMode: ConfineMode.ScaleToFit); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs index ce2882656a..70fe4b7bb2 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs @@ -71,6 +71,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces public CirclePiece() { + RelativeSizeAxes = Axes.Both; + EarlyActivationMilliseconds = pre_beat_transition_time; AddRangeInternal(new Drawable[] diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs index 6d4581db80..babf21b6a9 100644 --- a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs +++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs @@ -6,5 +6,7 @@ namespace osu.Game.Rulesets.Taiko public enum TaikoSkinComponents { InputDrum, + CentreHit, + RimHit } } From dc56be0a1d946d08acede69fc6619dcc47298e3c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Apr 2020 14:20:09 +0900 Subject: [PATCH 238/655] Add support for skinned hits --- .../TestSceneDrawableHit.cs | 2 + osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs | 62 +++++++++++++++++++ .../Skinning/TaikoLegacySkinTransformer.cs | 8 +++ 3 files changed, 72 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrawableHit.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrawableHit.cs index b927f0294b..f2198031db 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrawableHit.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrawableHit.cs @@ -11,6 +11,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Skinning; namespace osu.Game.Rulesets.Taiko.Tests { @@ -22,6 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Tests typeof(DrawableHit), typeof(DrawableCentreHit), typeof(DrawableRimHit), + typeof(LegacyHit), }).ToList(); [BackgroundDependencyLoader] diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs new file mode 100644 index 0000000000..bb76eac865 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs @@ -0,0 +1,62 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Animations; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Skinning; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Taiko.Skinning +{ + public class LegacyHit : CompositeDrawable, IHasAccentColour + { + private readonly TaikoSkinComponents component; + + private Drawable backgroundLayer; + + public LegacyHit(TaikoSkinComponents component) + { + this.component = component; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + InternalChildren = new Drawable[] + { + backgroundLayer = skin.GetAnimation("taikohitcircle", true, false), + skin.GetAnimation("taikohitcircleoverlay", true, false), + }; + + // animations in taiko skins are used in a custom way (>150 combo and animating in time with beat). + // for now just stop at first frame for sanity. + foreach (var c in InternalChildren) + { + (c as IFramedAnimation)?.Stop(); + c.Anchor = Anchor.Centre; + c.Origin = Anchor.Centre; + } + + AccentColour = component == TaikoSkinComponents.CentreHit + ? new Color4(235, 69, 44, 255) + : new Color4(67, 142, 172, 255); + } + + private Color4 accentColour; + + public Color4 AccentColour + { + get => accentColour; + set + { + if (value == accentColour) + return; + + backgroundLayer.Colour = accentColour = value; + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs index 78eec94590..9cd625c35f 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs @@ -32,6 +32,14 @@ namespace osu.Game.Rulesets.Taiko.Skinning return new LegacyInputDrum(); return null; + + case TaikoSkinComponents.CentreHit: + case TaikoSkinComponents.RimHit: + + if (GetTexture("taikohitcircle") != null) + return new LegacyHit(taikoComponent.Component); + + return null; } return source.GetDrawableComponent(component); From 96bf86099c89444e5328adaa3c169a60d47854c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Apr 2020 14:43:57 +0900 Subject: [PATCH 239/655] Fix scaling of strong hits --- .../TestSceneDrawableHit.cs | 16 +++++++++++++++- osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs | 16 +++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrawableHit.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrawableHit.cs index f2198031db..301295253d 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrawableHit.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrawableHit.cs @@ -34,17 +34,31 @@ namespace osu.Game.Rulesets.Taiko.Tests Anchor = Anchor.Centre, Origin = Anchor.Centre, })); + + AddStep("Centre hit (strong)", () => SetContents(() => new DrawableCentreHit(createHitAtCurrentTime(true)) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + })); + AddStep("Rim hit", () => SetContents(() => new DrawableRimHit(createHitAtCurrentTime()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, })); + + AddStep("Rim hit (strong)", () => SetContents(() => new DrawableRimHit(createHitAtCurrentTime(true)) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + })); } - private Hit createHitAtCurrentTime() + private Hit createHitAtCurrentTime(bool strong = false) { var hit = new Hit { + IsStrong = strong, StartTime = Time.Current + 3000, }; diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs index bb76eac865..af10944ee9 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Skinning; +using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Taiko.Skinning @@ -20,12 +21,14 @@ namespace osu.Game.Rulesets.Taiko.Skinning public LegacyHit(TaikoSkinComponents component) { this.component = component; + + RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] private void load(ISkinSource skin) { - InternalChildren = new Drawable[] + InternalChildren = new[] { backgroundLayer = skin.GetAnimation("taikohitcircle", true, false), skin.GetAnimation("taikohitcircleoverlay", true, false), @@ -36,6 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning foreach (var c in InternalChildren) { (c as IFramedAnimation)?.Stop(); + c.Anchor = Anchor.Centre; c.Origin = Anchor.Centre; } @@ -45,6 +49,16 @@ namespace osu.Game.Rulesets.Taiko.Skinning : new Color4(67, 142, 172, 255); } + protected override void Update() + { + base.Update(); + + // not all skins (including the default osu-stable) have similar sizes for hitcircle and hitcircleoverlay. + // this ensures they are scaled relative to each other but also match the expected DrawableHit size. + foreach (var c in InternalChildren) + c.Scale = new Vector2(DrawWidth / 128); + } + private Color4 accentColour; public Color4 AccentColour From bf938a37e3d374075e0b9f8e2231e41dc2297949 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Apr 2020 14:51:27 +0900 Subject: [PATCH 240/655] Add old skin test resources (with "animation") --- .../Resources/old-skin/taikobigcircle.png | Bin 0 -> 3079 bytes .../old-skin/taikobigcircleoverlay-0.png | Bin 0 -> 17018 bytes .../old-skin/taikobigcircleoverlay-1.png | Bin 0 -> 18837 bytes .../Resources/old-skin/taikohitcircle.png | Bin 0 -> 6028 bytes .../old-skin/taikohitcircleoverlay-0.png | Bin 0 -> 20284 bytes .../old-skin/taikohitcircleoverlay-1.png | Bin 0 -> 20333 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taikobigcircle.png create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taikobigcircleoverlay-0.png create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taikobigcircleoverlay-1.png create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taikohitcircle.png create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taikohitcircleoverlay-0.png create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taikohitcircleoverlay-1.png diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taikobigcircle.png b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taikobigcircle.png new file mode 100644 index 0000000000000000000000000000000000000000..63504dd52db36e9da6b3b40a08a9d3ec6e1e34f9 GIT binary patch literal 3079 zcmZXWX*d*$7sh8F#x}MrSw<*Zj9r#76GG8dcEU)uByRS?jG3Y&L>Z(@xi?F;Yl&ja z$R#3%8f(VL&8`_ClNkT{_J5xH;XUv3p65L0!#Q7mcU;ceiy-6?002P5(ZTloPbdD8 z0PJV&#sm)hG)O4U83zE=_nAU7mw#r#Xb0~Y06?_+mw<1gDlh$P%Ej7w#kxj?#3lq@ z3kJ9ZhF^`9SLN^_4ac+tJ>yMLag6+gJaNoA|QMXqG8nw!R z(&_@2bKlWXm3>E0r=%Vb8#V4`9yxhQuowB^pN6Avm1R*_Tkj(fC>SPy>I(j-q9L4_ zS&2>!SbB6pDT-yaL&fZDov1MX{#oQ>%#UwX)pd1s)ph`RpjRCRdp|3pv=&*(w^=0n*?J=65!qZVDI8yKzyA&}eeYPQ_ce4qO3l>9?S36G=;X;;(ak zKdEWCZ0iDsI(O2B$-ZfZKM3C5ezi@Z`3+U-od{7-6iinJ5jG*ZDGj+(czap+hK!l$ z1t=fjHvN}TOZPlGBw8X`^SUvkP#7oci9}7|dk1)aL@mA&H&#=jG+1GnTXg2W5AKEv zGixxfKSv_v}BmTe>*osjH6v}N78ht9`hW$(3Xvdycd2{OEd={8Jv zqF?m~fr_-p#P$s7=VvpAtSKQ>!}%8MT~_3{wFRBGTwivmU2!Rj(OQAoTt;UTd z%k9Zoj^h@@XrmJR`Q)<5d5;&RQ*;@4J>&;@6({o{#eFhvde<--G9gg&A<5l(kJO2t zfGlP2ySrD%abMaeR^+Go4}Q@TtV_ZkyQ5cgi^F=!aUT9`N>u96WH{4s7dvtCGp&n& z0L!UOsc1(YkkMV`$Ed~UpBEW}wu8V%U+$Mv3qe;?W(1c3tLaF!1#Kn=ZVmHm{ko_< zK`R57XPakFQEeOs$2T@`c!rvfF=7d46x!u?-oh9N9%uW)+v;Y#*QxVWDu_Mva8kT3 zT$sgc6Yn@ou8ny{w@H4FZW7d6-;g)(+WFqQP7^7<%^WWJ1WnyImgQJKceytDkiLsm zvUyUcfD<*-#2R6irt2hGeI-YQZ1l8Z?rDZziREU{9=CP7s0873?RZQmTOMk(JJ`u2 z-YKAfaz%CN7+cVQ3xE-w~g=z4qSPzI-_^(@e7BdZ*X~u36X}+CziB+Q`D-}dvoA(1A;js~w5Rjx< z%dYAxPwRO&4gbfSC;cR&1lN}n!CPJzvOf5?(3+=CnYP0-Pc>tU_jjwD*95wwzJkhd ziAw0A_^&>wcl?CS@mj~`5Dl(x>X_?bJ`)uFZ8eu+!K}D66f)7V7GV7MLInLd@84ew zEZ$57+_WsTV2d~RuL+EOp9BA%oY1_(m+*`DJ-cuHG(2zQ%9009{46XPv^oKk2J#|7LBS=Et6D#1f?{3h^OurDaOPW;l+Fb>>7O1~_PJ9$V6L8dSSa1Klc2-j4kCVm z)ad0#9m9sgh!|m=L512Yh(dI=ee}C4N;vZvA>Qx!ms?@Mu5`cRq~eIrQh<856ENFd z&&fgMtRcw#WPKo0kf(DS8EpY!XMF@Um?)-8Rw@oAJSeCht_D6^Vrbx-<6YJtm5{6aeZl9Imy4kfu}sllQO*K-MSmc4E8$@tcpwVXS@bcKEBr ze0vKopOgnJW+W$=m6T2uCOffF1+j$q_QAu$v!M%h*Xl+WuDMaOtlhtLf8~TbN%Z%|1uLzy8RXn(M~VsMU1opIP=B~aB5rK5)QD&Syo>Ao zo7->rVnwgGK2W^hcm+vJQ(2-~|N7YHi{C)se-mhb28aNAJA{L~9WXZb zv6=-cJL6%0a%59J{naG%AQ!NNY&2o-2alGGmw#n^!JuZ7h|77liv_oKFzrpao9P32 zrc9b$`|7y^8y|Wf`w6?hkwqIXAK5j%5Ky|SECE=BP({2NEq&yq5(@*@U|)R?40XU? zU^>!41<`7^jr0JzJuD37<i zj}AMmkqfl(D1|^5MfH{~Je4!M%5wgWQP6SO%Ol_Y)?$?T{CYx{^2q^ zKW^9Up%N^llPIU@A13=dDfuM3@w#U8a6rLIX`m;*gl?loxn-5>3`#xw8w-}lY4c=y zTTvDjG|-kw_9hZrt>r!8Go~Qlbf)!b4I>w#Z#NlRa;&7CdtG*Xnm+hiD`uv&#~LsH z^3<9RuM>$;c9im;~)@lDc)S6M!L>zDY=QU-da z9uJeIJB6w>fYK&~#&P-L&t$$bwinh(e~2 z!D)hj%^9RVK^6LkDjpr*yQ)tpMz&crO!XyT-INJ@(NnDh833^5y8ovAarKTeU)y;Y z3;RX!pOj8AeVYX+_Uxz;n`&Vm?pd3}EC#<*osdeF_Zs^%>I`X|V-B=@H-A$@yUkE} z!DGK}MYqbTzI-I^V+JZuUm|DXk@eq2l25d#=HC?{=*+O~`=+3s&h-3tSqFm@7PzEf za#Ao%DDDPT+5VzR`h8og439Q*c;XLt&*ByS5qxdWEjrQrz>Tkw<}gKf3bMk()t-i{ zy{vaXjS@CU-eY>L#`6gR)&#Ajj6{TV^Z@%l=x+Axg&6+Hc;dVMgs>xzHu*>P@ z>(Cz_?5r!-b0>YL>Zcp@| z?W4e^ghW~Z(1sjs3eY~kX>Zf5CXZpH5F07NBxUCk^U ztURISRyKCdVl-!MT{KWTOEDTfJ{3+CS7|F-I|YAtD=mLjZ3}+~3n5Dy32~^XuP_3E zla;3#)Yr+;*+bY@jOJf>g%Rz4E_2X8{{`adAVwqk4?(EDiaJ!<#oY?Z$Ij1Y!Nn^G z6%binbGNh>)|8R^H!Z|3F&bM> zPgh|M4j&&Mb{`&g7k3*DE+HWV4sH%^ZZ-r2n}?sXreW00}(uyD6?^|W(w zhW>-m%-qGxQ;Y_|>3@mfp!rrwl1D79=0z3H>m$}`+p&TP_2r}e`x$~d2w?34+#%XS#N|G z|5nKV7TQDG&((@U)5^oe%iY3C)*Hbl?LX4E3QN0NnR&XnYrD8O{`Wws|2NA}ZiLj( zS1M)}cFz9@!Sp|Mv63f-r(Rp8xr}qO`QSyNk7*BjSdKro0qXQC6CtONgJJjhmh8U+k)=2rD{! zc$zs|SSiYg(IC{rZf9qSutouM0c!;3)`C1jY`i=?0&IfZW?XDS*8GCp-27(z{DKz$ z-CxGV!s{O+`0xIf|3CYyx!WNop_$|VF`j>>=0DaWtYGJX7+1f4PaZ8Rw|}=B?V$fM z7-2Jue`D&)WJ{ZAA8zrhh+G)p&Knz7u zMpE1N=Px5vQ|+jY+_=ReLZ~m-|ovwD=j{n&k72@$2mn5;6jd(Vy z=aV7_a{9Y2J+k#mhG7vmLMcbfD|I+NLK;mNAcdt*Qibo5Fj=#ZT5h*zHlHPf(#%v{>d! zP@z+Vn-(e_8EFd?CP10+uSR)arlasVm9|<_E^&&9(AXfYy+e-uSD6k=sHeSbl^i=c zO7ME=hu0sON(%RTrFjEhm=|?M&pHIpFj*1La=S5v7*`7tljHff>v{H3=O|r#(WLQz zTbZ<4q%-u{HZ8`pTB%^BY{!Tf-A-QcbB!wfE&5aiZuUeF1$uGhzCs7(`|e})9CX?6 zUayL@SHhy;ep%VVb9J$!tjzG_fd2f{Vq@3!=vTtrFR#zCLglc@W2Wzm3+syH3O_Wh zgbpN<#-F~a$Ngz9oXKZFMkeA3YP?_Ml0UAuiMkXu{j*k_F_CQ6z1? zdz2e6aQ{SBDkz!Yrw$|PcGrOBGk!$A?VHXAp#L_&R*wqf#O+s&k_ZX|DD?E)&z<{m ziJE{kU(Ua>Qj4_J-u6-|c>&p&k%LQo0O0u>EhxNG@Xt`aj8df*srnG(kuR?Yhb>Bg z*^xJqEl44JWdX!1)mi~7Egd+gJIA|#XYaMEM|O%prXh!}o3+M?cSBr|`<~-*ia=MK z=LG$ilUPG|QG8fY$eE!@{f;w4TNwGs;*f>Iy~tN-Trj{oKd7DvIQv7eL(VTxmubD` zqtcfOx}G_EJ5C4^xFAWhKq0{UbMNUvyele4)_G{FR}UUF8D@NZBsLkGF@Yl9W+Eek zc-;@xxXzKuxXqt%t=1W9t=1bW$Z?m}_wey{3;A+?lT?dwyN)(;8+iE6Rnw7ozE{ZK z^9UB)M!~DcNEMDG>{Dn^WGp=-qfd`lXDXuI?PY|iF|v+6Q(hWrul*IlVmN@BZ2j&a zO2a??h;X_sKV65VUs64SY^+yZET0wt2he zhZ=l2r+0U9I3p+_fRmo~aVsXq{jH8pbY*3wo0F51n7zGyZI5YpT3T9jd;3e1Hg7B& zYong79KjHrCmV^izekm@)WP$dOPlSWcPWRQTMOfnxyw|%kzihIQu`{5uZ&cd8R#oZ!c6uI#L=SO-|MbqX2}W|&{@pupb^L2N@dT}NKB5Gdi@LyN~< zY#8dr{DN`6&uCU`%9e$jPZ#zg>X&Ft;`{a7(qDl3Qvgn=5w|Q?KYB5Zig^;B8!L{+oC7BC<0=wb2 z$>jKibwUgbnpYC9;j_gueo9ggGaHR*%rYS_ouF{g+xq;|9Jj>rAVD@hw62FO3_%u_ zOBY7Zbjz~xfv$(&JjNH@kAiP22gFRHaBojgiW^%#S?Y&v_EhK$jhEgkeiE_2t4(<3 z0cQx@^RL^bFsjbJvi#hQYBQv-$m(aV|ACSUJYXc}PZkugOx|2yoLX_4bwLdQHR`k8 z^}fg0uo8{2=)KwyyED^B{GzW>vm3h6i8MNlR!4RHS|yqx^l9*g>Ce<*+u3tNUZiPF z6I~@)BHU)!fKOJ%?RT-B1|>C>fts$r-0W;@1!9MJzoB#t1KZLYK@oO3vsQ#ic(dNs zj95oKk;k(w`9Pxhm*OkWdGhc#JntCQNF;dKxwlZ&-(RETLI&i6Nf>Fb4BCUn?6Xr+ zE*T-lwpRw{8~U4SQxMjXt-Rm|yonqkq}Tr5gGM3^NLaWs%b_Ik`J{`*cg?@H<>F_b zEg+m`CkO2KgYib_0&mb!KBx=au6^%9Iz?lCZakd1n2>m0IocM%qvlwADjMTR!aqMp zbqSY1-|ToR7!DF2=?9sB^1gxEyFu~^MeAV_!#;*7XtN~`ri0koYfS>DD^tX%Vf;`! zsMgdZQ@Ak~o_giZQI594a*HZm?A%74f{9=b;I6ELc;g@TmHp$w}z_F)h zDW4cG0ORYIO`$yk$TNnqX_Mr70Qy=MQoIBOop8i{3lO@&Yl;R_*!JUB*=WX0x+3Vt z4B02b0Nn;d?{0lZgR$CMq{xwiM) z?5isxTH$wSU00C@sGAs*=lP2Aa%=m*UMz;OH{))`hJY=}ypHFV-~D4F=j^OI+D#)B+{Z~r zmTZemLNtmPp&;{>jHy*;GSG>CaOADijEjn@+OJ2JPuwKK=&VamAbxmZiU%+R$1ryf%@J4# z0+SV_w&Jltd&Y0qzjJN+{|q^hBRo*=b0FghT$G0aWTio(HwV(!6S;+R5Z8^%cg7rD zcTzNOcj$ZdbEIOTH|66x>^MuUY;5=puVl7)s>*T0?<*Qk>;<)QV|{VARHOm;ZzJo_ zxf&v-Tq#W8++}vd8MVRd;@KqRNEE{p&ab2MjYPI24=%b^9F0to;&?Pj=>&=p6hGTjG?Vd0piVmGI!scb7C-J(GAQu7o@@{ zc+-t2t(Yg|=Fx5SanDs00a{pFtaCmF5q?^dJ`2-8eZTxK_+d;s0XB=kudiD)e=p_B3BI)(~0sRUnL>lq)_dJNtl3=azA z3p8~mGgWClVnqnhUL-c%}`ZnKuY zzEPA+q{@4|$A9@{W@7{H?8)+y3%y^gEh|sI%lqePPFuT66yWS^aT8D6_xW8t%}nR6B!5> z>-;Gf*a;*|`ynpfvSVBN6y( zHwpNayZV(>?7lug5O-rP-Q)&>a8os_sc05WDfshhSDuIA0#$|!Rk;&i=SM3liRE>z zp0VS#SDC?gBK^yLQt+?qU9kD~D%JKDe)Nc{{Q@WZ;~Z0gM2G%`^C7b^H@&W#o26bT zE5^~Vczxhu_@|lA)M^_rfN3f@YvIHQm9Hs!vmfiHHF`z{NkT2ggh3RXy_;iy@r{`N zw>)*+XT01yxgm1g%?vOi-y-AefY{-D@DfAR~!jE|3n89~XBCUy`Bx?r3x6>5z?~m2i#a+GxUi2c{PUZMCha&+`P23X=Svzix?aJi5 zVpxr)>jt}M6JjyHcRk-^4T@1YbK(p`s#gAJ3gw&TC%2NUZDEDsnxptWgIg>F74R zYw+ruj7y*YV)U!QewTKL@Fk?y_kt!}9|R^5alf0fM8(l~-1(I4d!Mx|`a&G_d0*e5 zd``U`Uc)45J#)S7WdSmEVEQ3+Pw{teNSxP8pQbfr1A46x`c9b!3X*7Q$P z0bZQCL^Jyi?}?|RFC&YB{wBO)(~WyEXEX_OvMII?zTD?6V}$MU+2WiVM|G%wjEw6F zS{_UG3M!t{cT2jRlomxJ%cvp+!`XD3TKJVe|FKpW$p2y?9}lyT!}dW(1u2sW{6rhb zkG7C2D90aYh)sYzHn9rAbKxf|@!@rmj@!Ro?e#X;2J}8vNCjIQ64GTbukuee6jmNg z^{1%Id8+YVek9?2r4c<{RAQ@XKP17xg+ve2VcPnEftwPH&GkMVPsR9&d9ZTDG+3e^H$^np8GH<-pb-Z zsD73lD2cIJy;oXu%%wdK>|}IOHm8il8P0d$Y+Q_iRq6dugE11RgeQoaom{&@Vam!^ zUQsb|uqI6MX?lL<2f!c6-`~aByapaMlih-hb8S5CMbrB~{fHp@rb??dE@b(mWAX&0 zg0@>iNCiU++|ecYjq+nMh@-TE6;l~FJ)@jkfBiY{q{IO%gQ3o$^3t-!>b#q9hCxj# zWgrnL&IgVJ+}bv;k$&rlsidNqTdK$OBlSk-75D0ER<#Ll8+7Xd&5q=9_IfmlGmxyI zG39=3H>Wx(i=>+~YAGE@Zc`?<|5vS?I)jVRa;<}6TrA}@@YX?8?RFR{c4|B&S9YJJ zV0Q?hydyw_Mu@cJTJd_pL7QqM49PDUGX#<+bCQ~t=HK`!%%;<_x}m44F775!>xn*w z-%KQu>2krJgeQ@9J1y_S?(783>z*Zzmlmx@!^)d=Me>|=b35LKFrt^XF$tJmE<+n3 zuPjW3^Ml0@183O{Ma87Z2>E-hNI_YcOuRT<3ZBtNxcaW$59e=QJJpqJj4F6s&|9*L zVhA7!fCc6mY!^6<|MUY(*E=u2JpLNYjVtiQV05i&Yi!aV^&PJwZFU&+z7Y(AQFHU| z*c#X?nHsEXPnREW!r;Ys##p$CnlJF*(OkDKq;5eIzFdXuu5&a9Ts?8$R+wzO@$g$a zj!3zGhxxe+2A;FQ(WpmT5?f4mM4f%`Ev9g3E)DS(JCqzn6ZhZY^tI19Oj2IEW%!X+nN1K_=4pkXzjB{r$R*!=CXA7=9IP_~t7{g#TOQ3qeBFBQUrbPj~mWzRaK8t z!E_2r3i$}zbFXRkzfz4u1%%yL$UmfGFXtmAuD@%Yls7g!0{l%7f0DWj6{pblv1K`xfxyC?@As#iY|Fhu3>COe&i@2RdZe38FNRH!96`-)oLN`m4Wcy*5V zRUUxqbEdqH42S6T45=x{MaxHXxg~(3&~b{QuyJlNG=3xkfVRv!$?*;&1B@XWL<=FA zbT>aO?~+)?kVg0x@D|*0zc4xB+k5C?SB@m9&p^&8tx1tH4Bk6OhO~={+Ardg^mf=* zp)7jxnNk0g-zuU}f9dIs3X+L<{}Y#ttUN8+0EC^w>f5&5d|&obzF(4 z$4irsiDc4c%u>iU!Or;huN{FFgrRP8cmwDzYj6Ju*Ng1OAX%1YSZX<-@@Fe4e~bx3 zu2yQ4V3tZm1XUBjkQuvIQhZ0q`RvR?joa93#=b+{p4mBRyW_V6g1I6#%ws2ZOcS!$ zuSSpsO50U3*ycc{${usF3qZp-QfR6oUR@HersU;UgNx>OZ90YbQd z714u7YK6)qC%q5FL!8!0^<{~oSJ1J-!w4X&iO~gtVdj2+H?DeOvS@ha>koAsG51+) z0V1_38>-z&kIAy|EtFSM$|`SgM+_1$oM|xFjoaa-KEMC3p7 zz6(oKRnB->89-xDc_&wsbaQX=;xxPuVlHsY!Aszxj2ri0YqY#Vz%o=^a1FN;jIvgQcgl4qC6=Nm!o%D(lW z`v#F)9J%0Q+3l7h^qKxM4e4_sM)iw}uvdy(o~UY(ZJByWJrxpWv0a{&p57lxh_IVeLIoXY$J_`vn6sBXX}YVU*1nX-p^CY@T>WzI5jxd8 z=YMS;0H{FH^F$_my1~j65quFJh2e*+2#w!Ex!P@0wJcX<2`o8A_(Dk1sMk93zcWwKnjI{N$Oy^?TLk$Z1YsnZh8a7^ANZX zP^##@WSC0kg~s)dr{T&C{jP!au?bPN@3WjspfkX0z~0ikQ*ZQa2_K@*^o-MFX!eG3 zmn*nwfd#a{`Xgw(R3mBz&5#U`m-dz?OThfw@O7zLu2qFlE@|(Z_%dvejU+wt(m`WS zovm*+K|BbwQjk`-ofo|K&0&RYhVF%1gA`5SRNqY@M!aNWUVnY*`F?1AmekpXGy4>J zFBRyOyL;X>G)IypMO#9G+0JpME|#u$3AhHDaOrLwwN(*UQi{2_o1(4F<7f_Zw}`qt5uXgKKOZ{chzzbOwnL*B{IO_}}9XMjozUS34q#T&l%@;o3vri5FLvU1i6fVbD zK6xNRuY`6KTJeQFjoxk+)e`F2;L#%>g9dGBfZ%eVxuVl-5C}8_djgOs>m2yfZ(qM& z>UPEbto5tkS5bX?ow?nAj%Z0)VG_W~F-ovfg+)?PaV{21JZ1mtCfo?KJY)w3sMh4d z%y0|A%_rA?#`DK|+FR6PpUJ!MyvY;k{+11a9|6KZt(htDg|bg&E*Wv5+1oQ*NOG?% zH7ln(&X3hGG7!rk1%R1|bPOuk77cBqbr()cx%OG?6A?)-clqw_n~o@=bMIDhXl*=~ zcDG|o+hT}~fJj38O0hlV8t85v0+i|(%b{Ufq@oXiTi^V( zq7#&!d$=!_?qTEdH;4FmiDRC}FQx1~vN27qX&)%67k6 z71xt7^-F2@Pf9)>CKoP`Ni4P+Z&a00E4?j*)ayi>-j)C`)5TU#e-forhg z1x3*fF{(^Smvz!kE6WU-XZ(h*7@qYp7eQt@R6@41v;0=r;_)<&QXfFfr zYd)w^lvVlqAqf+R(EG5#p+9Pdxb+Gpa0Sut$cuCRdjNpAp3YhyQ>Yjl4GqD_E6%3@3*Tt{v6N!u&AaFoEGOY z*!7vG7155L(9v|Avqn9avT$^3(z;w(`XCa~>q_7K!3TKz$0W zCXoZ+3>5GI+vzFM!sFAFp7yYCe~=%`Dls)oDcsv|@6YAdz&c zMKp}v`oQCOeqGLw!!o|^jt42@*5hQf*x)r>nY(caY*KB7u%rd>rz4_fICCJo`FyTg z3J|UGjg1nwW2fY^fLEup3%miPjvm`mnrTg;%1m2w6;*L2 z15?>Sf<7nq)wdD@2f$rNX#3k@;szgrfv(fJW1ZjMlw6)lH_H!%}i=wTYv zltPZUry#O!CLrvwDG4AY!7c4Yu^|x6vUtEC%&r{$mOWj;x4~$Yuc=_@Rz?#~f>Au~ z?#I&@JuK(k2+y>(IC8Kyujn!-dQ%Pcb$_6dn)3(Uu=`dP`oN_rHHlNvNi<+{sK-#I z&rh`Rs*7*{ZA2>+jz~4jAI~RI=XY>ayYB{3M@kiw9fD=)EY>(Ce8H*_v<_jP`eXfbTT=mKFRb!zuZ`nBf%Z zcJ;NxVJkH(8hRcEEaNIWPEY^!_7%q})s%eA9}VV@uJw@m<$;bdU)+vCQ(xHDnIt1I)3g!HySh4rSrY!C6lWX77?9tRCyw8Zv>~+>|IA9h9H?z;$ zCyn_lH;S`%_ji0OCh&<}F)Q`n$UG>Utt`zcW^|_+Wehv%= z-Yne!DF0iwKl)7gSp<`nb1%{@#MKgxEA`&#-c46T&Es~8fN+~Qm0rTRVcpWA^njkG z35M$_JM1Vra~_DE?T=~3?_ggUSCbc7a%IjqPmVluwlh{nIrxAGorL{t>5V*Z0UX!A zvrO5z_|a{D@IaGQ6JUSeeUrhbcF zS`|6O*l`_V#j)tbJ9;Ccz8Z6eXI09u5XuG=k#!LcI%$p&A?- zx8yXi)K>sIdK#thQb^KN2AJelAwIrSK$rGjgO<4Go#L4Lr*LGPsB|USb5<^g-51jJ za(~A{uQHH}DTZ!Q%EPkg0D4BqoPwwmou|%_yNk#(H|??nq9$))yyE1TJiw+2?Fsas z5R)KV!KpPvMsceV?5nE)vH-ZYh|~3Y=Ym$Qn2tm3b)3Pv1XGZcL5=ZoBz>g#SG&M1 zU+&1sWIp&j9Srkj-iwVhZCXKUIMzT7ePZHkN*)F-ORossI$9%YAwkX0cjt}91(hb* z!k%;kkw<4NMwGL3z_N3POc{~fx)V2Iy5GCRsCBCJpAX+k@~(P_Iy>X*WvH&@fu*W?hpf*at&n0h4Kpm4ogC8 z)sbj$L2;UG9iLU<&f}u#cwbN1(YncEj3fsPl}*~n07Io~iQ+5ot%`YFR?XG7sR0j* zO!?k5NjSqY>QZlcpMEu&tYQI+Uu_3uL+?B+UWY4we^(91R3t_d9*3^J9G zVZA+yYbGIeGDzxLw)i{obg^aLSS-Pe9L;OZCT}!C_Q!IgMf_;9Fwjx>hyF?=@WXXi z2_r%T_%!xho(+PUfr6@V{m4^s_hA&C!I~WyE_-=8uEfc=gcO}H3 z;>NuXadTP6)&-$#%E^Pu(kSYc{-AS$u`FAXWRh37f)lb@0Lu~hYqt7h>4X7GG-26A zh?SW#4cd3xnT=vjY$^|VIR0R8!F7bN&v{)x>JPd%{_*y5wB)cgY<(`YMGfWOKRmB4 zdJo@eo%dtxuUfsQwlkH$!%Q;*OB1?B@)z*W0O&-5vjAakG_Rg37voWD1NK!BQ8!he zfD~$Dmh^1&xe6_^YUEcPQhXJ-rh~HXxQaqb6&f!=&r@Eug6~q%FtL%sz%B0^#yA~` z6wtC608h`NlCBi^rrcQ<_~i4O*v3P;iTVtCeCU6SJLK?B1o6wXilR6>nsk{&lvzR+ z7r>29@6vReym_d)-Q_N28XHmkLJ{GG1EBUt!S@$gVa9^k00Ceag*m=#^Vk> z6&O84^EVypmHL0+HdKUdC*((Q5+F~Or%hDQ(eO-x=tPD>m-n~a^|X5V?^Cu%8_sGY zxTzXjK`mjX)whh8N5ehQB-Whdup0c6&)onjq42Td=uFcdJ#V z%O@&ReQuXZ1xuunx_ZT}EuqRrn7+7$6zJ++qu*q;F(d^ZeDj4{mN@leyVg*$&rkZ% zmd`)ICyEKs#uMRL$Kd{D&pR0LmkLJ)`MItQ_lIXh#g(UVq%r_+4Ettvc!<*ox(G-h zmFf)vfTuRi&^ceQCj&HI$WE_L;5-MM84KV`@ru11`MA4k+`vP5Yg~IR38{^@xNkRyHvZQkE>S+ z=Bib+1d={#PsZ;>N|ASb*z-Dutangc}Lc@vNj&CW?B3!mfc=K@$@VsqeYNNBBar0GN`*eEA=NKj;?3Q!6Hp>*T*Kf=2Z|tA zPhQ{X z-JR>YRBLLrb4Ij0h$>13`Xag@8uWumm6$f7K!~lN=UabnPgtM}Nq!qo{J9N!UTV4n z=156Or{k~=pUX1nw6&0AX!B%=2kI-tQ1b2+xCot!3ZVTn-ppW{mJ+46x!yf6-WRjB!iAUb+$A)@q z7%sL3lWGYAV|g>3PAV?r6tm@eN+?pJ7--G6|F;!;M~K7#RpYF- zrxiFQU}{0%7x;;#hQ>oSD8*J5raz63QLfXzHXIz}(V65kcX_6?oEa@XEy3k7X* ziTxoPWxLH(ZMqhNbBg_7{0pW?+myoWBWw3Wd<1eK+uvC%! zhUHs+)-{ZT??*;PT;=>FR=$0sqPUj~yPVqmOMx_f|M&K60hJGtaYMm538sj!#=MGw zOg&QJ!mnry*!hK7!6t&i;~mlv6re+Shbi-qmuBLZTBtq@}q4df1bWRSQfMk{|%KJc#j>t0U#IwfcCDe3GZ0~dB4($xbG#1!1r%*ARGbdFc0 z!`)-ws@MItfwAMc+R0_|>Pac!yAHGLos>V;2 zU#4!E+AAmZY83l|ZxnD$LkO!x7Bk)5WN*&O&UYlWJ&)AI(1RtSes~1SXPe5!q0Y%< zLE%HWLDgmxjN5;t0_^z{jhf{Ky}&CNoV<5N$pCZFDT3-Re_wwsSTL!(4)k?uMKPZ4 zU+0UftDy2M{gzwrqd3f$y~gj#1q$xkZ&Ay~&KiE%xG9sUBNz9en zzj2h>7hj7TKQT!B9rQplK0(vi8fV#Riw1l_h6^t{BqldJ8Pi#cj|eUIUM@s1imy;t z&G_*6>T+g5V|TdbgAk5rY5Qf=AMap*2U~D<1FH+I^(O;024k5xM}Z_RtH`+ zc{c%r~03iYA+<#u51=MSRBAzHql~8ASPKq1Y5ug*PepC;9&Fu0Kt%Asw|m zt$rx-fKsK&IRkz?-5zTiHn^6AC5!eIM_W=<8b>4fdx4W0foZ6vupg%>2#N@i$-Q@B z8uOy-E_X?yf7XOwOdsMo+HJ1J^?QD}Rlh!4{YE|#3aTxObQ(@?S5=G)#<$$#^S-j3 zB|zxQy$_O3!skxc>h-%%D7pKzk$|l**E3}^#tH}i(Ok4$d-PoNS=grHVxKt{cAyhw z!3Kj@kW+W(HQLVDQV%bPj~sXub&Z6LZNuOb>oW5v7)DDK(^hc>f$T0^k5JP%F^xwd zp(S5{%HuS)gC$KB3 zhGo7Ae)`|h%rYUKY2Yc)GzXxYq_m@E~=8b zBLVPnYvpurB~~jA`4c|u9Cd~8z2n^?wz$NnEb4FdjV~vR&No6111Jsul&xnD>K4u= ziX9=y`r0lbIoFvVJ|5)!TAoVo#~@B#b6K`x?iG6w*|1At*kG1U`z#I2VcHoj4R(tw z|Hc9$LFaaPGIrq?qz7dAHS!o5;hFgVUG|JExJqKYe7N3RTYo42y(1uWPAy#R2=v}w zM`%Lqn~?il5l)d#B7o8h3sF&Yap!-#Sc@%od1YPiH>36Rcz@+LL*(F`1*K^?q%__; z1bx%oCwLBffPSn$ekZ8#Hoxm3x~=TRZl!Nj{Yq-P+naV`L6LJzio43{=dCTCf+(3W#YxSTf~myHRsJ^(`0dW z-7n3J@-p!0(`Wj`ns=B^fSzO@bI>QfYtF?+G(ENa)9J_;hzr=kt3Dq zf(d6II=?hhwe{rD*uF+6q@4c zIV7FGEW*Pi49dlYTTWT~C6^8;yPpzR^S*E_pnNw_i1vqr2c|f1<|)$Yc`4 zhoci;B}Y(|+j(?ItWTgpzEE9)PwI33nd5qb;lsv~L5Nk*j?j&Fk3ErvmKVU#i!EOW zyusEmUXz z-}KhVqWdj;@I`%_`4ZAd`kGJT`Zq)V!Y)E!sY))Kh&_WNhtqpjUgq4g zFe;Ua`h=c{g-Lp%g-!aH`2a2u;u-|PawUcs^^;e$gDdBjQ~l9B- z7e~$r8i_1qouR(=RwSw^WB-!%8+O&~Dy-e4r^j zwxC~W>4+_IUa?HD=cSLTLlOyB;vqI1Am9xUm7R4>GG4LG>JVOMUucKpQ1i{~dHp@D6zyY7n zR$~^u(ifK#qVY9GEZ*espP@^#KUI(i)o)6rR*s(&$rW4q4i!s1C$h!gpS5}b7v;nb zt+CBFruYas{}ODOmikCNTza@<(NhO-eOwLZXe~!`-T!qtSt#Nx|2-1>1D3+^eU;`* zM{2{5!&q8+A8e6GZX>KPd-DR@Q4P&R9gS!n#3*IXW#1$F^7u--;h8lXe)bk5neF`X zg|NWmd{g*3t&UcHbo?N1&Fp5B^ngkhj`uyHH;Hr3=f~YeN@1^*s$v+Dz9wg=KU)3` zNci52^sa-aRJB(=;pG5B$|02`IJu&veur%B=BGomiM>l}vOJL|!HeaN${~JdjeH@# zmW5oQrm?t8aW8bTMGmY-S~O0lAC`!keWdBXuO!DhK%+IfF-XVX8-^`W>u{o&!_T(A zN8-%=}{zW!uBPyqT4T*K)a*I*ga@1%@cY zm4qxj4*00?e8o#ZS$GUU*?QsXm*4=0c)Exr!HfCmhnW>a(Irg6weYvkTlp%_5z9f> zAZK>A*r)Y*OkR{^tJ;DJ^gIb4?6ai~a*pT@!*FG4^b_BAPhGpK^d~?EwL)OTQCV45Lpk# z;plKdQ&o&A>BxCYJ3J~0siF@}2QTY%Tp4JWIh#l+&(eOv_EUCl9%dG(0-~`s37}#o3z8J8@3N4OD`68!hx3my1!NIK`c+gpRZSV ze4jhtc}8-(F)9ACFjq*f%fKxX^1!mppdKJ+YJ~m%l_K>MnKL(qX0%QG4j|F#OpzHhIV;~^ZVqfIKBFiXfxT0?1mzQL$7Fb(F9Qn z{3aq}zaHIFx%<`BxH%!d6q`sP6q@+_fFX_p9!3fXP1T}DCPYK;5gYl@9;%ij93A{{ z5nx~usXXoY?pnt{#$&CD^zLIEuvyO&93Pnv4);htV)kk)cqLiV7#PSopKeDmokDHj zKTyW`2%EI;{9smq*dFn(-EQ52g>RIu$oSgln=o+Z=nwdQl#rENy(*g*wLQ1Ts>6rT zF4}KIb*G(38nI5}D_Zkt-bXoR(Num=s-=e&O};o9cH`1^SV`u>O(e;bWUr);%ci^&?~ z#^*ZrDw)nYJiw6_hJl9r1*c%}U>1Ek>FGm3!LV@JZw>Wtc}RHd$skBR#V8$bOmdGd zLG%}qBsT4y4gU`%I_A+S_2rgqp#v&my z!!WX{^fy4-XM%UBfz}Y)+b(IpUkl&uBZ2c5mgV5O46qEQM%=-;AWx=E5p+ z3esK+I9Mgq+JD*+#ibzzYoXOvkY8yA^pE<#)f-D`U*Bj(u>@IYJC@3PBV0lWd3|fU z^EJ&iDA`vCGAlvoEtBzRunalZ!-YvgHju%#pLK?W-&dsB*OErS(TyQz9oUwFC{k@O z`<)(KNpQNI8uk+1QG&)YFtiKQ1L6(^4XgkO-2laGgk zn~w#;#lb1a&LP0g$<4~aDa64i#LEx)_Ye9i&C}XeNJ~cU-?CosM4|TH-tI!|?0$ZJ zY<}EqZk~4RoPvU{Jh<4oxL98)SiJ&Vy)FD%UA?IPn}dvvmzAf3ySIazE94)J7M5;4 z-lEV~P5;XT7x(|Lb@lqUn_eBp?r-7F&dJ8{k4gU`w6^*Wox6{x^S^{!Td~_X+ql@c zdV9Una{h9{f~_Qtu8Ju{}JKkE$jPg#=jl%zoqul z32?Vz*Rt_)^YOH@k@bC5llmWP+=Zk)Z7jUqJaydMod3I_H2zy<2-mCC5C&BXD+kwq zj9~nqs@TX_c-x3VU)zn7mE*P1_;fgUggCi{c=?z)1cf*_{)<%A&Dy~>;J-<^I9NG& zSUEX$IC+G4IfQsP{uk2Mps}{_w)lS~wzd+ob@Ozwcs1F<#lp^p-QCp=3i*#Cg{0k_ z-8^3fziP+*KR;KLme%levvqKOE%4Hkmx3tDO7n3F^6{~9v2p%OT~$>fMOQCx3s)-} zMHx}(t9#fS9IS;bE%31-<(od}2GqYS+%aok|Re>QsI7@~8#7;=np1-=B0a ziCnHp%{^Y}pUu-BS}ZoMb~aS3cE==$MYZgXh7 zx8G+Ss0<2XAfMp8nFJry5|#Gzw3!zi@PAwjec<9yD2Sh6+KN0oU93ahhkjCd)H0~{ zwUScVN&p8guR}gP*2%H`&L4M5P2*grLfR~$v+cN7>-=Oo`*5rpsG*i8`s0s1UnFn-G-7hb^TrhW;;>~IDV>WhbnQ&Y1>w-Vi^L8ii-c3`T7TfZ4L}jgk<~hyF54PIa+AZMlF9R2K7%3#b7da&zS(gA}+!n6qQe?Z{Q^Jco#tI+O@P|U>^ALTHWtzS1<%Jq9 zXvh}=1-fR-F@ic${N5nmFBF@QKI~>+3QGi^`N+GePmVN-Cf)i-dV;WBs4erpiT!%c*e&dgc6`sv^wuBbNUK0v05RiGEU!%$@cR+^zNVrqI726B3= zW^&p7N8lf4&q2SB(zt)6zQz4jVj-=Nb4fwNNcV2K2E6kaqwxHD>!FqSMSXB+ zP{GCqp|q@7#?jGH!NkM_-qFcP(Z`3k$$KBCw4?+=$ZkqwIKTUQT{`wx!)ZgJ-%2p< z+rOYRZZDzz8Sb<+FeSUXudCTKnbx$x^t6U*p`0i5dr2Mx@wIHs9!0|YkMV7a0Dfd_ zO5F8NnMC|3LDyk*N)CBSB=(vXk&`h+5nqserLT^=NK7e+B)9@^pjhHRMgvTjN&Kw-3S!{HnK5f0COXJrYN;wfL#WH7UZrf#TkOgq@(&Y(~_- zV-@<)szM9)U~>Kyh_8GcAbfNjnic$e9QgG6pg4!7#qSs@XlSUiHmf{$BIf^*p5Q$K zuM+L=VG+spmAbbqQn<-PxQfB!GTt(jk@r-obvjNHL7Kn&YO6o%+OcrhNOWD|fT5_d5 zm&5tVD{DY)v&7ZgZ!fjE?mnLnvuNxl3ne-pQ2**x5r%vxYTJ^PkEwe5@gw$&6-r`y zYKekkTo6T116Pp%%nBn)!!nLwuAX}T&qQot5na-(SxB_Co!!%R)C!c?(dpM|$0@xs z#m}6meV#(nbmX^Tgf#V{!-cjOBayPjsW~dANuC2|RFMlWR>&My_bx8h7lX~>Ec*wCiaDF)pRNY(;n$(K#FS`peGjn&$Xg*hzTujhUOYv{i(0+sq`af zoszmLomc ze(KJ;QWyGoa&8Vj=%c<)GzMli(QyI$!f*GskKc_OQnz+?o>3wQ(_0CaZY$4|dC`wa%C!1rAl)pNe(fA<1r~<49u#nkkNC)vfE>&i z>WX`Y02a6xy%v5fgIyqViX81xTJ@g8o|4r0e4Bv$&<{=i3?+LR3@>5lnPxh;6eiTCVl zT3ylCeuLibC(QS3ow}!xy?dVvn@Xfbruwk!;s7^blUwK}sRH(QZHKtO#74Zt(LZAS z%DB);(WXo|KM*o;Hg&}ea9@X)Az6!6a|@DBh0sAeMrEor@jj6924A~O(LFpKBf}1c zHs_18(cdDYEHGj0_0t)boY5-43B?O}EZKYBu5~$S6(5N&7``>{=@=oi6C5ITT&bC7 zdG9~Ul~_w6tx>Lk(6#dc1fT(|VF5a@6ac~B!f>xCZ~%z_$oLrJnb*-B#oSGZi;PA- zPF$BZ)FTQ%&h4zV$e)bc6h%LbT>qHT;JbCtz@eh0GYvJ+2fZ|=&3?$L@mq7#XWCr}I6%5qbvHt+-h#eza)vZ3y%beiJ=FWKj zu={JkN`uQdB}*XSwRaQ#Q?VKmi~30AlB*_IXbn%hOD<@%eXVrudbroXZ4-${V&T(ptOw`1PDL@ zydb~Z7)khKGZvwZu+05Jo3P!GQIH$6sY-On=w%k-kbRM342$(g0OR zFArxnr-bcPzC7BXoIU+<6BU2Fc?c=!xoXOH%`&JeR=FO)uJRkx`P;K1I%_^3z>Uhb zXj}5saiHBHpZ?yf%UBy#0dPx+`)a>=1uHyFVeyRfUV`W9uZurQA}6}B+*PfQI1{D# z&fP;|?opvY-~0*c7}reh3(S491OTa+L+#0OW6*$X)Hb5`Uz_Xfg^;jdQO{oze5Rqi zrXr|A-ET$=Gix>kO*w{~`-Msby^x1#1HvTXQeiU@VQ6~QM1zw7+RKC`;6Vc-4}6-r zvvYYGA(b&9>^CJ5Kp_G=PL+AXKwW}YLKHJz7;)6eXWOL`QvT8hKgJ(rZm36VVV4zkF&gu zMi4Kb#n8?O4JRFmaB@P=7aXgW2KuMTpo*}|ygo~%2z+JANZp|?-9H8>!#@pC_>roC z3|h~`8Z8K)Loh%FH!BMz*~_?C2)-Vd!RQwyXT{4xNqG2#rXOO>Vlf&QDSw8^LYr~au4 zN5|Bgst(i~w_Eh=fCEK?Mqk#}my;IPuD1YnNYGN376|{oN132@H~NNMLna2%ZGUGf z+};|eUn?-L(Q=HPk~RsV~lXxcER^O_*JLgCX>>3h({+A|W>^uj3pf`OBB7calbq zv;k;trHf81L&WwaESB9(Nyr-d?s(|Y#jwWddxJ8i)6L=j+K-Zwjg+gwSF|W-lQ>Cc zgbjEfg`#m@D!&!$vE^Le#i%bD;{wG@J62eO|DF$2;~bB8mFqXQg>^08Or5EbjMrOC z$O#2Lq@~d>V;%bo#X+ZyoXS@#N5yHY7>7YtGkvdbBDi)wf*d62P}?vklWldPg$5?A z+r3DiVd6qD0+R^2dY3pk-LwAksv@z6OYIkOI1P-mTj~V#-tod@wdEg*( zc3-cDl|ntK)x(u2rN@eM8Mzkt9IPa?kPNXR8-!HK#vr@HBfARKlj zBC;D|d0syT-1uQ#!=_5iu79ijit59k7vinO!kg?@@+s&X<-GP<&LGnDs>NO1?G?h#+cYgXK|?Zdd1 zzO#(MyCeKWz&)1oe$GW9Z}Wbqp)c%L{*N{CxwHac^XJC^R!bm``E)ha*c85q&efD% z;D*m>g^%(r*>g^<;ULQlZSCEH7DwK=2^eND2)WrBJKL-r;qcIaE{VG_JvJW--~Rou zn|m2dXUacNnYCLTtGi{A1b8_-p1o_bgH3NqE=LdVph#2^j>KPSfzNiD)mmqM5L9LO z4d4-BAaZ%#^E8ybtN7NyE>w}l?PUAXG&gjbElzmS43~+_v3b3(-#aY)t!KJ4rKNm3 zU#ZzVJUcG??7CDtZr<8c{R(%*yp+wvNZ%9DUt0D(h{Kr4V%EE64_$=E3;A-gF}98f zg~m}x(#%9&qY>Vq!5!%Q;0PaF6H2c08Gqvjt?2Tg?F|Z7cJMFmDAQ(uIB~HPW|7dZrX218PB;t~RI9?qi2B=Ab ze02k?a3eiKf)!J?WATMuiv|C<_Tl(U@Vt%7MWlT343WS`ZOzC2j*w=~Dunc>o7>lT z_#N2!kF@I;#q*);#_W9Tv3_BDniTZMrP`sO-{V(JZ$;BucW7tl^AnrAoLMe937>r@ z177f_g_`;Wtey^f{Yv+svxvs@P!9ZU23DH@a|SUqy;cit8_wx9)!7}Jd=eH!FGKJ& zL9TD7sg{;I0Y!%&iM5Px_!}gd?yc}7HPEj(4apyBsEm?adwKd2M6}ZKV;$;xm`)fv z6pI%~z_aHH**UhpT~c#qA*|NlJc#78aDEG4PXfreAdw?ob2oY*>^3D2WZ@crGT*J5 z0=8IOV17XXo3yoz06GOvuC|KxoL-B^kgn2lHe-|(2DP4nRq zkkyI<$N272ius`Uh=dubry8J?E#m!r?0TLdbQ`LR@$8A4L7i&^lXXazxLA7*`J$4} zLD8Q;{DEi_L=V^7*KZs%_tjoORWZ9zpLt04qalOPlCr4!jh-DUU3t?Er;}=u$*s=Z zNN1uSs)7wwYPKi{Ym69!cp?;|+fNqO;bfYfFD)G2;tq=MGc@m_a2pjzv7L^)s@6!+ z(1TIA=Y4G?YHX_2T6U~SEX8%Rh?H`+D&v94M8{I>tu|H#_l{F}tHH^1GX9215i($VKYnrgX@NW_tjTGH{ftjr_IYtezR_QgaupMg zm)=icv-d{g?{yx3E5_#~PGl}i_ze6|E4ZaN#)XEE_R;6&GHhqU+Ah6xFG03^X@fUq zy}qIc{n@xbf@am?r-isuMuLH*g`Xj0mMz5%R0^tJ8xEj>)%+dS@RTDvYUfz1^$FE5$w%&0DjF?;9m(2++ z^z=RoQaCiUG#mn8h@e;la2a)K81H&>)`vloh>ig(zXZ%g15m?_4YTk^^}RT;ldY-?Gc&9!@)^u}8G+p*0KN z#T2+Mudjv~LCk`m8I^svf5}Tkq)skH&3(O8dhn#^sJ0c z$s!$#z(nf&sfOa#rLLxi26w;DFp*|qa0~XlJDrfsE_)a3?>-2KOTLRxV~ns6l`_}r z(D+$&N(p(XcFr2a1ki`Ae60G=sgKW#MjBGdme$41yYz#Af#DZ*>06J&6hGkxcaUm; z$yYIZO-o}unqE0BfdqINnK>{8&|_kvjmJ|FJmM(n^>;krg)($*Q|pNu=x?RF&8%k` zxzjo2asJ5i({IpxufhTA2{%FlXAa}O51~txI^TS!)BPta^$}%SC9f=dOb{JLRC;El z1e;BG#hJiH6t2L1@OlZm9WS|OA0Xwo!t27El88pD5$&S)Ad*vRi*_=uFwMByN2*X< zxG3&k8950ZgT0;-KF8AI8;3*17>pxzB_$=}#ZXfQPW){dgL35wr?x$ivM!3Y+b-4< zNHYu2Uu3U&W~8C;NX4iEUrMy_VUOZ6VRSwt*BQ%CeGdbnBMj{^y2pIv>B)2J2)@ht z=TMDeJpy-U%CfNE1TUP-bUigGp#;=ef?k}HQwS5Mz$yy`o0wn_8=qhY_q*1S6kN4V z84u~K*O=Icc&^4)&6JZiKE1I1Pum5%>AUgon)xykp^ zU-eL)pPyfD3r8Y1@UbT`AmX_7FmqtuH?pV&y3U)pkun+m@?S;7zsBf$kT4{0Pr9xh zbJg`pNxlcsuyK)D*3=4vAyA&)kBN!MS;ol&bRo^=3{-!qgE+q1JDiI7D2%fij^G;> z-2H{KN6jlELRCMBZ5b$%X4iS5#w%*&ctp;Pmj^d!J=>QYdNu(;K{63+Gk> zS64pDq0_Lg`emixch_hGkj3-newy` z2l>ft!q={wl8Be8^n&yAm6qz^n@uWKhMzl{pjeq2*|8QX2wOQzNFL9y?k_!Y{-wA; zSJW3Q^#1Gpk6G5&bEX>JC&=)N=TFj;JedVcQQlPOtmto3em z&06LSJzUh@G!JQGf*#ewhlS8mGyTc>NdEcib*s;_{WIeU6?@*|;iFglkfAox5O3q| zsIR%j`QC$+S4xIeVac!ox)>yUaZ%n&rJOOMWn3n;V{X3}4 zUZW}b5J8bHX4WpOQ>-ZnhA31x6BcT&gqL;1hyErN9RN!sCP%=1xp>Yr=V-NRa{or& z*DFEP?BbG2%L)TVAv4v$>`g5md<#)oS zU&u2AhOSk=q9G9*hd|LIu8jNj3E6G&$g!xibU1~ERWNBrVSJ7|eHWn|-zy6xcM9bA z>F?_R>Vza1{W-x#BWS5e=mOLC37Q(OzTOB?rq6FcbTny9yR4qrg!QLck4KR z^uAnE)G95MW5lmr?){jLrYiX*N7hVzeKkCy9jEzyb6wmawtmEF>noJMF zP$$uSX)#^bio*_&G|_De5fqWX;SmZjF;|3d$pEsH z1d-MK({Xaq0$^}?4l&kj$8bK& z8lqlH6n}qz3B5|uvL7f7--sBn-$a+U!LwHyj_~~*=Z*Yc5^e0+p9WC6^L~V6j65|u zNo%ES{QiwQ@Fw%2NU$XlaEg{W^IpDQT@u8cDR@a56#!SL*unz{8~3rcK2t_bYjaO& z_VK4&4`Kx&C*AVDr%7U`X$jZ6zL)j(ifO*O`K{8mW-sa!C4BRC0w9~&jE1+fD>w{T z#j9b6RLMhNmY79-et4>vIa>X+jaNM@jAJJZmMBPM&o7#>XCx?NLcBg4`G#l0_DuEp zQ^XAVm4v0lPLlLmEC~4%A|H%wwLD(4Jk(kC)a{)}?^*flODDTSa1U(x7IcXN2DQL4 z)=eO$n|}HMVb@oWafOf>OwTRuyR4^fn59-eND0joHcmdG=cMf|IS^D{4(_VzSm^zO za4Xz!QSiMs6B!l}0A$aNFGDiIb9gKPvsg902TPM~ekO!GTDD0ZM60v_>a)`3kL-nTPISG%}38p)W=_B>6f( z*<3ey26h&>2+?f*^o;J5#(6QY(aKlp>!TG}DQ`!uj`9aCIGN~u{(LJ9`5A87&dc8^ zhJtwdB(QR(v^~7^wHh@FQS=p47m2rODx&Xx_?2A=ZM)4DgnteE^llf45#Pm_txiIc zKlU4FoJz5t&ArGg%7OQsXvDZ#?Is44XggbJibL^T10b7d93!J4;enYBe1GhTgWUbn zv>@-lcC$Kv&1v+r+jOoB1&Cu+5Sok6yDuf>2xsrWn_fRD%hf0}b+;;qJk2L4k>In@ zi*n`)56d%!|5cU2W8v5bobhp_IpBz9v@vrvY|2ex#&e zznpss*yS~AldHc8M9FDTuT#6KwV4gPx;>%lOvHhU?CI4o^6yG&k+6TaYg{osEE14R z(0IJSKsWeE8?bJazQ_WC`y1GLfdnK5n0_-HYlFeiO+^-`~4O7ci)42E{ zAEbaXTQMT!qa>9e&dHlM@?vuOZHraR($ z;IiKIL<_6obMIzh<>(AUuEfCGAaDjT32Z>fZwnw30JsGEIRMCajK8LG=ywe^nmSp% zjWcGW!?l#kb`|mTrR~k{B#lJv5QNjXO!khcBk1hkBW?TUMbbDB!`j9MN?hPwd=%3} zGYty$kq>+XPZ;f$Uq>!!$2!DtR$F{sM%QaPDwUPBc7G9b?tpCukAmEFCT#aj9R6S# zsiqL;pexii(rtabJ#rtUS zD`3!7qdE-9Jvz<&xA+RW8jDvfKpLq*0?;Ox_WR(E2JUzzYq{rkAT*908^qRDily;2 z0NvIH-T;ruv6B*Ro-qOKk28*9Z$!h4sGJO*7AGa?_hq?;j_-c_=+0`ltl+gW1^0s) zZ1Vn79F(jhu;Ac8bC{dXt!NLAnIY;dvulb^gag;Z-hP|{FZv(lyeKtf!vhJ4PpjzJ z9`1y$gOuHMkGzx9D)Hm^z603M1?N1zFAIFTLUBg^Gn%WrbWUO2EYjhCmu#Bs? zi+P;x2bW@5;JcT~RXYeq9L)|uVlGrMGDUR+3mk$hd8vdkMzO!Uum5u67+5U`z?pXW zfHI}gCv*A-`2no2FyH*z759 z&kY?X84G7Ljph?-rwZ(*d~-Vvi@_#xu=n%3)uO7MGdfgd8AxtHi=R;DZVuULxMfi410NXLvrW2)7@d zJUzKpfR)qJ=CttVw!`^3GZJmA$d5pk4I-f!cU~{RYzkoF_6bjX)s|O^8&utM7J2!5 z;y1NC9U}Y4IOA+FbJ1c90;6~ftC;nEIW7_=wY7XtND(t!1}%Wx#HPEd4ch8o9#BxO z3SD(um5Z20{k&2<-vr3g3Qf>s8!b+e6tjoBFDkAHY^&UfNCdD$u|z=H7(58eC_3Jc zfTei^5@h!TF>FM}=f|S8^JkOSG;dlpc?;0IebS+ki2sFk`2(;w8;6_<=;38MErril z_DUMk2$*HYk>JpSRWujU{tBo)kNu1eFqwYnv&+E|WF2`UPCPvgGA9c{D_g#}lorBH z#U_=-q!im51dZp%Q+h43`F|-iKVX%WFeMr^TylpiCHZN>6VQWa&(pT87=qKyy>()sB6U+7&{NUrOg2YcH zQEfCsYUuGp?7=%xNv*+calw*UzZoZ*`q8z#lR4^0dz?(+S&(7rigj4V%-rMa+*?46 zCwQ+0{PG5L%gly@kpwbT0|d|$ZOoCd8we6&%el+gdvwNp-2HCU5FZWm&P3W)DQ~3k zqd*#WxFU%@q7OQ_sVpq7;t#mV;*aaIY`0_rG?QoUJjn)8$}%ahD`7h_<*m)Oap-T& zvjB0nOkTX<>aK}C)FX-_3KLMIP&|y$b(Z)7=8?i`!v>6}xL6*^Ozi{KpNj^SxUBAV zCi5`nq&2*&HUF#%J^+MN?Rel%9XNUNaHWpVhsyO>Xb~ePbhVJtaLnV)`8q#I_%q{W ziNJ9S1D9~2j-LTZbJ&hGQKRKk(fH2I*MYo$>v7i9FSN%qd1yQFWC@Al+d9?~BLSbA zKd(h6-5yrzArRu5LZlHdX~p)Igg-}q%9p0J}U}GrsF`QnPY$qk6yx!;Tnc& zn?ZzY*7mVC&Ct#~_>S5@Y4SGF;|KWdN#`~_(W19lxo1*(@$$Z@oBqM{0cV|boB zmyG&`7WO1_?9m6xSqhB(-&tYFYfCF`xpwknwkT)nIH9RLXIyYJKG#vT9WN?}NC3`1 zun(2@E8NzB=jS~aioP_6Q%jk&W|PZUOm&p|=*kf6eG}*XRwS8>NN^e+ za>(q;9y;|*PUih1e{KJ=l)+8}(RaL%^ilw*xSZp{uz|Lk>(o0`KrM36-b5)zrfCOe zl=Oq7#AydbgR-nXNZ%-zI+Hi&rvZB4*%}as6fE}&sFQVuf96!Y6?hTFD947qUSF9URgATGPMO*V@!FRV_Wjm8Brq`V~ zu4+!2qOFv>y#nn~og)R3l2=%aeV0c?E#Z z|HLE*w zKDOLtnE_sU^j+7SutOgaKURf+rGy&W9D(QMYwGe-7n3)YNI7>9w6W`*xeyNlDS&5iy zw|CIBGUPQiHy`9-`1Ohgw-sc$Ug|*Ty+C5rI7CRG{h`b!;gEiGxx2icEH?&$r(r56w(qswO*}bLOLxx;GUof6~EZARO*(;g< z6T)E3apUl$B$B2}<0nTlA<@Ua-JE(NOup|}5NllhLaMA$0MA~TUg5X`-*9aSB^X%k2^toZM@u9v4Zh=epBU?b^OWy*$Ffy5n)26dWEe5( zEJ|Js9krwur+Y!U?obJ59Ub@$%lyT>;#)UIMEeGg19Y`4o+Ki4pGvyeB)BD7Wt&i} zlVRZ6D7h!rE*5-_4`?*>2?hOdx^f0&KY3W1t}fepxHq3ET^)h>l& zmc>Gz@dUE&#QY)$qzCy3^ERElS}g;h$?p zI0Rz|!zshzr31P|ZX;`^c)fmprmhbk!GWPr!vCGh5Ca{~_ryt?I{@W)iZFhOV(v_m zRhV<{wkTmMl3$03zKFbk^7(En0YTAYPAHFOOr|ngGMOL%d zt-(S*NRa-pKF=p82p2y}s2DZ~4rRy*nmpm%&Gi0YEUXsxAb9Bz1FA%ZRkcvrQmMr< z$ad%2hyCp?`Plv3(iSx-zj)TaO_AwBdvauFuDX5(nyP@e}$b5fr*=SXmJf4+;Z_5d%TT2ua5*bMk8y9 z{7BfdFLu4u^US%)BSxKp^z%0g&x`_3BgJ2ma%th#yyTgJD~vPWA7q(jPNY5hej$|1 z!yTnsVn%K=Tyy)mA-`a-lCjsrgG}+oF|sF)LRB?9czp3Q;|QL$Ij)})&c;rB^|4#?W0 z&fGo%EVwk^g9M&>FxLj(b!LXZeJ{ogAHg6}8VxUMfQxHtW*&RP-=wYiGe1Amy6^`2 zOdj&}SRP|kK6-!lM)7oiu|*%t9-SWU@^tZm3w?O+5KKXK8KwX@5UusB(ah`Gx-3P3fq)oQ@NyPZ|+6BA8g8p4N=@UO$G#;&>9TA?@A4s41vSTK|o_IaN=B{ z#;)woT6uY1@G}ey`&7_fZbZkZs(dWwd)E2oD<)3jV%02Fc;CE)Lj> z&VHT2NF?HC5mKBvE(@h$g-=%0d%+Xj+~&-gIa(rU_HzKe7kaFZa}X^YP5zYc^E{Xw z;n3%G8FG-qMcVdqj|s$RAamYC-CK7PtSC_n`dWf+|I^lpcv|lbrrtCl9zc)90rav< zh+xGvv!ehlI!vb`U*8{ruN(7-cqLQ~d3?X6oYeU_?8_#vP)Aoh)#{^P5aQ%h^WW@$ z`jdtlEFot`|B6-Fn41`}9PCALNwz9j`SIl=F0N*y8jVglwBzY-`5T%wgvxYo;uAZ^ zb{;}dokLhjNV5SA*3njV1}qTnOb`Anx^C2+)>tHrWYltLsW=UX%U=~`!btPa;bw4E z&}mgHf-3MHA5Q2?s}+DT1_w5}&o2+t6au&C1&&;jR1h65j(_Kb%M`~nwm}KJ8|XY+ zmaE1$lc;q^jE}tzU&m$ze}VseoYD1o=*mHmdF04#ys&1<86aiq@5=k;T2;8LVDe30 z;`38K=H4O_BGxaMq(VghE9hcNv61IJV+!45SxV9#nsIgcCyCE;zWsHjEO&`-XsV6I zzECVr&3YaHm_Lo`nu6NP$ONQ$iNaP88p!J2Z_B>UI3uDzzMsWi>voU>G+)FGn0oIt z=1|S!rsH%{T0P*S-FpifUb<%}+dovalFP{uZ$R^LG!Oj~LiH#yrVBj0$EPjoc397c ziAhiOVWD!-RxkzAj*q`H@^|&H=L-nTqa!%JIdLPDXN)rCPD<)kkpu30vrrg)@nQW0l4VTRG%OI?( z5;UrV6tn!gu=GG=!1OM>y!>^V(pp3C^Di3Y>tXKRt)#%k!x;9aW=CH1hs6hsgsb=S zye%eSmyd4pc_T9MvX|1A5`|Rv5JW0DsH^l+q(PiPlt1!0gC+h3LhoVREH*Q?pL5#& zGPM$QenEs4czyc(70_wz0EiQ=vznEu`d~`k_8JMSxaK|EAco&aL;VSP-`2LQS8Ull z3KU2W<8Fp?l$+~rEJascANoGMqJfTu1Gn6XMDqh)?S1^|!D@N5H(}YP7t)_(pGZrh zpnk0O_u^tHsUw7*YQVQTu5vf^ILwM~T=GZ5M@_l09b4oo`r!b($qL&|hZWVC4u{QM z8Hzpm-sL0h_Ddfn%cUR&?gUM0xz10ocm@mU*oL_Eo|vu*-2P=Yj6Fy73Fx)md-N|X zuj>k_Gb75hdfB_HJMs1HOGTDP_`03*!<}j6XFh_ez97E$;h*K<%faEd$x zKr?rJY%368-Lc7EvdqG3zi`m`ZfC^E52dPwq+X)_>8XsXn)nb#ug6hvs-1R+W-L;T z;n^ONP!u`f3-odbWpU$shGI)CX+AYI2^L~0KW+5&Gp9nsaK3Bqh+K|ajwJV?t#=W; z8jIjJ;skzmH&Ozd;dHyaR`+hctI6B}jw2!sFw`DZ%wvjb3x)oSZpGR^shq{xZw8c$ zf)haDKl&F59QuOa5PF4hwA7C8d%T1KzK7Vyel(D0casR{dc{*AEp|L0`W`I-gNPc; z=+3A+j>AoaeLF`_8+)?JS9|Cwzen=jzG0VxI~M#_;y!Tyjh#BvHG~` zX;Zd{F{1~$nM>4 zcJ$1c*gt^4vpH$3=H3Ejf}U60sC@JR55gqO)%j}@CO=)DTn&qks|w+w6B=q}IKNI+ zjjnE8haMbqW~V9cD2#(|X?i^pDG9m7{B$L&8VO;Lz=)5RQpVkSxw|9a*^{+1W9`B1 zIWl4uLH_z>5LM8`7yV$$Kx6ICLOFG3*Ky+0_8a&8qb_&6M*@9WDpK)37Q^ZTwtEZ} zq61#^lO1mN{qz(;({}T?30;%)6fR&jkiY!|JX?+R`qNogvro18^HNjY&7ywVJ4Jpo zH!g=q11(*X?Mg;->gdehw_J9-zO59X>OZ_9I7;xY(V?COfXbY}xzN z_!*Fik6;+P&eV0oVp~0_uq}j>d-O}KSM!#fOtC?R z<<9D5rOh-jaNF~AM!h2H9~3y+D+>JSXsV)nidnytnZ12ny*TyLgYZ`c7x1#~<9v+z zQajZEoi;^Q&+7TrTo@cs0COZe&yms83C!HQ|81jK`@HTQ+VZBlRY>|skymVkwS-)b z%S+&Tz<$b54osK9;Qk1+@)hXKW`;;v5!7+q^PX@N$LH9(Kn(an5`F?tCN?&ib=8%w zB8cNk9|kgTtt>zCl*U=pVw^+gUH5-f*Ep-Ma3T-?Zrq&e!)4e8|B+5Wrd;UZw>)Kk z+s){rD_XlD+)pRC14)$X=hZ{=kty|il?;({MIm{K0Yv*~r801`W&W>G^3{|-Vj7)3 zdgUI>sNMkl0}N?^_9ra2?@jL;4L`1*x|{^wUnSV(1SUrZYRPvYaRxq?gfPGEi4)pu zapMnaefX0xD+jYtrgS3bh&QMYW(*2_oQQqwPoOx*Jbu#Qoj@zXRMK+hxeI;pE4;$O zV;nPCW9jGBMX)Q<4H$S?0dbfr_1N1{#{}qZFsz={&YCmlu3=Xx;4{6;cZNA{KvwT18<%;K=)sNP{I#=V1>xh+fwqK+@i z!P)&VREZt_6n)_wqg2&F4s>MZQP|M;@6wCoOsM#jm2Kd{_rr4@_q()ap71ySQi8S9 z){upWFWF|x&o8E-;AcCKy~^vZ)v@rmn{vwX31kgP{og)jF|ep%U`-}r{i&eVe_C_# zi5O{4*mdSk1hj|wz|Eo8GmTmXenxH}EBvwT=-TwHA$=(Ulakz_$hX7v)_dwCSr&`M z)PGe}j9u)8<*^8v!=c;Yt8maa=cAH!GmWmPaH!F@s2^Vfzt^FQ?*2e`QO>~hr$*i7 zr|C3d@V+vO2OK8?-e!)RF}c}f@UiL--7IC2ak)&ge!md#yyw8tP(6FyH9{bIv8A+% zbkaup9QX>QMcf%_!nIY&p;P?${x0!HeY?`9*F6ZN70ox50fp1A&;sT(P@ctDxw(|2 z$(rEd3l;F58fdKmNO%DPTLV_g@Q-U{aUwGX-)_T?NRM^n4Q^wf&JYTG~C;stTEW9yG63$r;P9zfa z6CQ;FJ30df+KUIBIU@iUsDWDwuxtewIAVk^f%{MEh!>gaph4X3j|q$#4002Q{`@Y= z@86@)8`iI54em(ZcUH3Mkao-o2KAAQklpInx7D%FL*4JQMrh};Hhs_5Tin)SmFDd~ zXXYjiC2#B`t<0>(%Je5P`c9Jz2eP+aq_tSXO+567GrXH77>oqnyhv#sa+k?v#T$^QsU2DAA^jgp7&)$#T@T6d@E-FjFo zR+hoDch3|yrCUF%M@=Wb7J7A0-a7Zr8S#rBxVO!H_uK`4O9D)qFb?L@@I=VsDsDmRM6>DN<`M~3330r-nNeA~Mq@%k+j>s7SQ|+Py&;{m7U9*+?vC53er~{u&P|_~N2MvIe7} z*zi!&rGpm{q1snFv?6v6J}gLce$=p`(=&RfdrzM_1#Tz;3>`EOO~S;sw~QGxJ~A}; zm`vnnqXi>alz?i_ucHrN0vh(~ z)4>kvA5c|@zb89)?qroq+}V`+S9@427INXj1+s4KT9VN#jV;-xUcG(IzM~k1B+!PM z@LWcp^m(I(5B0fy+Ekc~ZFIvEpjS$F=-;;w^y}Mus7|HWt&)oC9Lt$m_(L5bY8z=! z89Qn@bD^!(Un}P@+=89y@xJyzu-Q(jy_BZPcXUFS|cBLdxdxIgLzG9v%|B zcF=%+TDlVpBPRGq5g?;aZ|I$#24TTL-sm7Mz>w#F7Ky;twj&8>T2q>=k|wC|X$vGZ zF_x@Y_Bh$Ka|bEN%VTBH|JRyR8srO7t{zeG zePE_W-FSihORxNIgB+JInc(XsZZM>Xlnc3 zJyOU$^XHM5UwVOT*}R#pP^KXzt$j=#NotEJFW^*3JH>r{sb+jN((Ad6k&Ll zopqiZJ9d=p+xH8ru|o%G-g)y)Rz!2gv?*k8MjsL%70&iUFaqK^X?2zQ)liI}GJ8Hg zwDsj$rR-?1M)_=HsJ<_1wYCwVA%Fycn*?x!0O%aCEG2ra24CyF!a{-)^jg(ItxC2V zRck&rc#W3&38)e5HE_fjN_Jx*7VU{Nvl*>@M3+s`*+>=o5~xvF3H9XIXr?s=XY?gQ z2WGH}ngjdxCTS^2th8E0Xb>x~L?e>GE}d8}?i5c-8ZxY}t2@s|aX5R_YC}~6bMudbU2dPz$qMH2{iExG% zKtSzaqFbXF8SF)o*t`Q>LM6h`i%zXlPn@PrrE?LqfMVwkUaXX=9bWA3F8Dqr4V{po zf~EsCRIRJ)$>tEc+Ftdv7SB_Ne;-ABpx3EqMuh2u@Si6VfL-rR0$gVTga&CLG9(m& zHENda5__s1RqYg=LbgUHm+sQYrKgY(c_^4m2#cmsFLhMRY`|sJJn8$5zSoFM=!{BC zjA-^E+bD3^My*2r7@nUdUJgX{&x9Qw>bTaM1o&?dKuHPUfduf_TWE zTe6hbUTDF8^uBM`_kGv*-@du#oaZ^u^Sgid^4#}*uInUPTbXjOim(Czz+rA?WJ4Rt z2iGAc+PzH&-Afx-h-PO<0Kmq3aDjl#Y#{((6v5j$1vsHjA~6JCMKqS+j#CWrCDPCU zprso^L}R>h0bqBWC*Dt6a%Qz!w*Q28a0i_>qty+LFI`k+k{2GE@@$3liY1EvbK?5bT7q1{)ImabOiiRR{*I ztPWPwP=u>0!<1ANzz7&z0}4}z!j&K}I1;9cR8a%}^^>G&^T&E1ZH!F*(nWjHmh=h; zAR?jA;NW1zU?oL@zb6!~p+Vz7KoJNC4FMsA`URjvAbupNzZr~hB#b|v7=S1Efe#qb z?u5VqZAqG?f4bmH{7vge`YTMdfI&mhL?~PlcHq)4AQtl*M-24$`K26-f#Q5{zBs=C z5)BLgjU{>!0th58!hb{kd;6ah&_ats{nqhsZSnQ}t%4L_97J>DuYmkpG|4WMh=bbT zNQ6Lt49+-+W~S7EH$%UC~BWPZOWl?Ah-tWMP6aOHBGeQU8 zv?Xch28Y0uAaGSXI2@_0j8uWi!!(dE*q=}o0gLwt{TqsaL14-dINT1dj8s-bDyjVw zl$JDDbO8E)1!FNt4}!lhn&vXz7ww6I68$_S!M_8EG$i;C{Ar45)+znty1AjDwLifF z??c-l*_axD&5aFJ;To!{5QHN9m$@hu(%g>}fcC@S%#E}qY2i`C;I5>P@4JBgQES@6X?G>`D^bV3jA+4t!o@if43{z!|#TN^P_b+ ze_A_Jv43L)06aqGM*4OkW6L?A5q9=>IyFH&baIfVrH-qkcdjvDkK8)ODi?$1b|&IM zrj_w44yLAf$%2AwKAfLr8)Z2zG`Ni;Cphyin;R)&2t+ z3q7$x5i?+n&fLF-evDqQYt&`$;oj!c4Z>Oab=1!8IZl8aa-v%L#E2!?E*}b`6QWOM zUSW)4(_;VFqrzQxc<1)d2r(FI9cveZBBSE1sT`mh0dmWp$dQhn5bjE&>toDioTP68 zZGmulj6|^#y65CmiqPphdG^4Z9;gE3#ID7x#dPwz+h^9F1{bcW>SeQ#7aQ&5!Q5s{ z-@7Kh^R0m@m`iRMjAljfNdJg_Kn-Ad3L?uzza2Ak^HK$_H)wV zAbX;22|O_5?51sPIeh%_$f0ItCX3a3H7w*y_S=g1At;m8;Lk4l2*@l)#)eaZ_YiPs z>}zDSCs|65Jdc{ATE2Q}o>Hg^Qw9nL9)f}a2{qopDJKej6MYm%A>ezK4=B8suGm)j zt=d_RK|KBR$%#~kHjrNKcH%~CvBlK02e!x7OodPXkzd^4B@39%xPsei2YLn7K8>WU zl_nEqmTvnX*a583V<}?e$-$kI8F^{1El-(^(lf*XWWFc_QNDu0==5#ol~qPyb?nm7 zb}_(ary1}|H|M=6V)_HyA0v<=lW+p~n6Xa^Esyl>`SKk9c!c4o1al>m-i^NLM=+c) zV)+Js-qPSAH--uLv=GcV&zt2ONh+v8T(~lLdWBG zUUYzeN*KX?BNTw^0Q-&dMDxmf65O?S$4&SF|802$(%!r0sK-b8c{4#RolL+CC|4bb zG91)t=dW%r0rXb(QWBCfRb=)#?lBfg`NJ*_0ebTJqHAg0;cbHgpRUy;GSOZAxw^Qrla=FX_Y0x&9ho>IidEWlTUSs%~CmU>gI;;FHnc7L;E`=oGEYqvJ z7pzil-}$ipBP)B*n^LA>XH6ZmFLSDM(}DJ9ws?~ksvdHIYEEPW?^A{q$+SzrbzcrZ@y0Y&H3v4c-ElNnDeL({F7I;;f~_RVZcIIuDGoOBT)>SH;sZO9N3j_T` z8I4*;NCShci!2dEoi`mE?AG>{*y;+x1(hMfSw+*<{MnQk+nQ|Wp#B)j32dF%$$=cR znKY?5rj|sC++?UPXgB<;UJdI#O{=EV5ViOsH^&zi!*(*Ly7U~)r?!rQTTNq$vS43c z7C;(&<1pil4$k<~>3J;;MpTXale0vMgEI%i6!D!M8-;?1`a>v1OvlHJt1i`X?GN6Y zr|tR2g{cwWdGa(LvO3t$bY!>qOjN_2x$Gn}&&wFo0n1zy5K3EUt4J{ariaz6jW>dG zG^RH*g3~1DtfGC(g&P0;E9-W5ZVOxN1nNl8Af4{RAD_smea^|=sIz@25T&6N|9qmM z6%>4A&8ShcL=8ZK>jPsWoEU#bHMv%brS~SfqhubWu_GA)HmkB2kKvHb*!|arxX&i8 z5p_c6ELg4Vxd%dXdu3ANip4_{u5MINwIA!qC8SKW&T=ikoR8ncJ~;N`edsvkt)2Ja zvydjRHPMSTBS)!gsFVk8;%5aOovF<3OIH4aqJlNiuxF#WIZ?D;?CB}eDzM|z?f%N<{L#zbN5aosbmA}4 z)ye4Txvsr@{%c%*F5o4F=^r;#y)GvoY9(U!5+*!n2T^~>dY>R<mOTELs+2bdYX-_Yi({|=qmN^7fJq85G+G=41parGUq zEif`N0t*{Cl$~j>24FRmRU;Y{#ZI}!u#bIWB0r^`Pw417divr;Zo|hC1zBr3l-P$O ze6JsVlY3g8H{QP}YC7|Z1qd27pcJjWJ1!9~7Jq}gh3yN2p60Y#LeQt9r_t!6ZT?>v z^*pX7jg~pQP+cQ^xKa)fy#$;yV=p!ByOa#+>lRpT8fb$C#dn~sviow1a_)=QThO8w zEoG!IR&S1cke_%c{Rv7zujV_u*Z>r6`EvzX`+WU!J!qQF4S_Bs%lD+L5AHNr2fE)f zv=hl>=WMwbtDSzN5Z&2|vQy^AJ+I@$N&>%~vwn)}0roTVg-@7a-yK(fsDf_IOz7sX~0WGV#6x^JuFD-DEFHZ@g z;?>E+_bT0onBxvH%=wP@;4*Srr*<6}(p3|tP^j;eo9(fsaDi`F5uA4ZeQ+=r2e8zfiK|Y7os4AK~rc|Xc zSlNyn=zctBOM&dsvc23h84u4j_E-zIe7MXkbaB^BxvXZW`;psB86{Ss_*$(%UvYgs z@_pyf?x8yCdI7W}gnlp`K}Vm zWe?KEP+t03l<8LwwNy0HKYVc4CO>N64gtAejaL*%A6S~p8qmeGQ+<3gDY z1PyWy<(br)iw6b<{;VTN9M&c3YHNCSeMnHc44$}makC`t;jaI&(b3Vk@Reot&tBNO#8p>G7E&S_3UY>&$#F~f4RgbC@9=I=dCdM`gK)guZK&f zQkJHlNH88t8kU7kteaZxoL#qeap-7?p?m5(+4pjJylY^f zxUldF?{O=m4~_2;K2w}I!!3qf&pd7>qmEIb+$>t>*$l#@OlmT*K0T(@H->A#(wsx7 z9{0Lk+lJpz`4`Xd$RrtPTI|0P+i~*=v-Nwe-UzbJBowq$4j3- z&raX}V#>6&YMH8-L0e91ZIv?NZIXaT{&dYW;caoKOr*lV&^v^^w%3oGb0!X_7hRvJ zQu?~yDXSf3CvkIZX|C;9QuoTRC~d?2$ql7NkszNbfrgQQ>12;L=yu#-q1P5`is=`& zyS$bd;?f<+anQ6K+lYn z^;kU_Pa&w~mGKv~#$VX4zRS+1y_U6pPSY2@ZP%txFCqp8=}w0JK&2dEi}XSJ`a&Kz z*OPk@!>6KFZ%0BsX>D_MQ&^7QpAL@wUf|TnmXN;ilCAO;olairsB1aaZNKSwjo#J% zo;mFX_`HTW6f~U@y1?5c^<6{(sOSRUrSR$&vNNp|mcPD>*ZVK{-p^2p6xk2i*KRu7coik8Rzd0vqNzEq3r+3MQmtHjq$YIfjZ;`ax7M;| zSm07?YwaZL?1``=Yo6ewJR!)bTOA$807nqKv3R+h^5Mx`Z{70z=|as@HQuGQ)3(cU zXYh&#V${7zVbzaFKXHwiEI*-_qwjXREJ!&~+{U(xcSaKh^DbGb4H%e?B?|GRVXr)k zzL@ZBYx@NfxwszkwhpbGy0^kzzW8ppka6|GU}M>y=LyxNGrFs98jh1(Bcclm4DOfi zooz1>zeeigN?`?J3NGhxV_sr%r1 zJ-<(g>pd{$t`=Uhtl7DHeU{%u!PGVS?ohT{^~{jD4P+TcmmZ-5?X<9b*R;%h2m0gI zw@nw1p8Ge2NIxEZWUr3{?fw`nc05coFUPyQXMW{k@Wz5}-&_OX&1n}Gzmb)X$Ec0e z7ugse9|eFb^{$jU%jHovT94NfKYT#id`V(F>TS>W)n?4ZSk1$84R;_}YNU#Ma%QH8 zl&fB8oRs+l3fHIu zUimJ=m^6rsfljm_>1&W}hv$Q~gt5Jc#8`qXz;J}w!HvBjer$Y5XIp=4*^(uppcT9vvZq?gM+tEbPiBa6{AMK73Zu z0F+Clx&U$gY85=*wDsaMP<;;vw;)JdR1nbbxECj6x~U48eN}q+;)^|<{NZoyTUked zn5u;3Xz^Fu6GJC?&7;~GUuIDC@N6|HFv7LNWTp%w{-K%Aq3vJF<_lN$uP)Xs8cFwk zD4-4((UZZxgAOLtm0Ks}y<4~RRVvtr>edk%(n+b3x>*8%NL|u0H_l_0+-!DjkgvRu z`D4od@im|TH~u2cVY{Cd;2;ljnwh@86mB(kNQH?n;?Wlmmz$r)R}wOJUk_Pt_wxW8 zDRI*6k?k$iHfjT+b~^FiU?U>p2ltw6ftM8O!ea-f>2sTa9z9sPE9Z9NV!~~g@LHq!zygrVq*} zq#jSE-w5_5+Zb)e4m>!5I5MQLDI-iR6$~qLD*YzFMt JRBqrF^Iu$8TZ{kz literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taikohitcircleoverlay-0.png b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taikohitcircleoverlay-0.png new file mode 100644 index 0000000000000000000000000000000000000000..272c6bcaf75c18a985b971284b11162a13a2cca0 GIT binary patch literal 20284 zcmV*WKv}KLZ*U+5Lu!Sk^o_Z5E4Meg@_7P6crJiNL9pw)e1;Xm069{HJUZAPk55R%$-RIA z6-eL&AQ0xu!e<4=008gy@A0LT~suv4>S3ILP<0Bm`DLLvaF4FK%)Nj?Pt*r}7;7Xa9z9H|HZjR63e zC`Tj$K)V27Re@400>HumpsYY5E(E}?0f1SyGDiY{y#)Yvj#!WnKwtoXnL;eg03bL5 z07D)V%>y7z1E4U{zu>7~aD})?0RX_umCct+(lZpemCzb@^6=o|A>zVpu|i=NDG+7} zl4`aK{0#b-!z=TL9Wt0BGO&T{GJWpjryhdijfaIQ&2!o}p04JRKYg3k&Tf zVxhe-O!X z{f;To;xw^bEES6JSc$k$B2CA6xl)ltA<32E66t?3@gJ7`36pmX0IY^jz)rRYwaaY4 ze(nJRiw;=Qb^t(r^DT@T3y}a2XEZW-_W%Hszxj_qD**t_m!#tW0KDiJT&R>6OvVTR z07RgHDzHHZ48atvzz&?j9lXF70$~P3Knx_nJP<+#`N z#-MZ2bTkiLfR>_b(HgWKJ%F~Nr_oF3b#wrIijHG|(J>BYjM-sajE6;FiC7vY#};Gd zST$CUHDeuEH+B^pz@B062qXfFfD`NpUW5?BY=V%GM_5c)L#QR}BeW8_2v-S%gfYS= zB9o|3v?Y2H`NVi)In3rTB8+ej^> zQ=~r95NVuDChL%G$=>7$vVg20myx%S50Foi`^m%Pw-h?Xh~i8Mq9jtJloCocWk2Nv zrJpiFnV_ms&8eQ$2&#xWpIS+6pmtC%Q-`S&GF4Q#^mhymh7E(qNMa}%YZ-ePrx>>xFPTiH1=E+A$W$=bG8>s^ zm=Bn5Rah$aDtr}@$`X}2l~$F0mFKEdRdZE8)p@E5RI61Ft6o-prbbn>P~)iy)E2AN zsU20jsWz_8Qg>31P|s0cqrPALg8E|(vWA65poU1JRAaZs8I2(p#xiB`SVGovRs-uS zYnV-9TeA7=Om+qP8+I>yOjAR1s%ETak!GFdam@h^# z)@rS0t$wXH+Irf)+G6c;?H29p+V6F6oj{!|o%K3xI`?%6x;DB|x`n#ibhIR?(H}Q3Gzd138Ei2)WAMz7W9Vy`X}HnwgyEn!VS)>mv$8&{hQn>w4zwy3R}t;BYlZQm5)6pty=DfLrs+A-|>>;~;Q z_F?uV_HFjh9n2gO9o9Q^JA86v({H5aB!kjoO6 zc9$1ZZKsN-Zl8L~mE{`ly3)1N^`o1+o7}D0ZPeY&J;i;i`%NyJ8_8Y6J?}yE@b_5a zam?eLr<8@mESk|3$_SkmS{wQ>%qC18))9_|&j{ZT zes8AvOzF(F2#DZEY>2oYX&IRp`F#{ADl)1r>QS^)ba8a|EY_^#S^HO&t^Rgqwv=MZThqqEWH8 zxJo>d=ABlR_Bh=;eM9Tw|Ih34~oTE|= zX_mAr*D$vzw@+p(E0Yc6dFE}(8oqt`+R{gE3x4zjX+Sb3_cYE^= zgB=w+-tUy`ytONMS8KgRef4hA?t0j zufM;t32jm~jUGrkaOInTZ`zyfns>EuS}G30LFK_G-==(f<51|K&cocp&EJ`SxAh3? zNO>#LI=^+SEu(FqJ)ynt=!~PC9bO$rzPJB=?=j6w@a-(u02P7 zaQ)#(uUl{HW%tYNS3ItC^iAtK(eKlL`f9+{bJzISE?u8_z3;~C8@FyI-5j_jy7l;W z_U#vU3hqqYU3!mrul&B+{ptt$59)uk{;_4iZQ%G|z+lhASr6|H35TBkl>gI*;nGLU zN7W-nBaM%pA0HbH8olyl&XeJ%vZoWz%6?Y=dFykl=imL}`%BMQ{Mhgd`HRoLu6e2R za__6DuR6yg#~-}Tc|Gx_{H@O0eebyMy5GmWADJlpK>kqk(fVV@r_fLLKIeS?{4e)} z^ZO;zpECde03c&XQcVB=dL;k=fP(-4`Tqa_faw4Lbua(`>RI+y?e7jKeZ#YO-C zL}^JxK~#9!?0t85T*cM?d*+tyRjXCEtYXVmZrI=g1PrEy5_%2kl_aEpd>{1-A?24s zLK4zQfiDn4LLhW7#x}0lVBF;<%c`r@R&C#V@67Lym29tzBp1LC=6TLzNnTyeo%7C_ zGpCXe0w0e5+`&Fvg8%Fx13)+wJRuJwVo+omOk&u)c@qNN0R(z`5e|n@KYBD|ML{ec zN4T#Kl~q-!Z)n8Ejc>!__F(hI4X7GbgT7E07PA=)Kq8q&Z*LD88XD2n*$J1+4FQ4I zm%WbDW}gaT410F(!l;^BDI5*ko13RNHf`SQjm2X1K2J$qE}!!!Qweu6k*w(N@Av2O zIj^p1woE#0%IEVc=bRA{S5;NBSgkolRWquhL>zWUz-qMy><(wt>2!u7v1pqt%ezZU zOCt`OEe@jOuHCzIo5hB*@^UD$f^;ec&N<@oII2cfp>;dDZhq~qdMslRML0Nzi7h(JV;W!a%=TD7k0<)Kihw!5cmLO!3b>FVsP?&;|n zo9IvY4MT$vfDi&i3<3ZnK$0X7(Y|M<(`h6V2^bI{0D_BwzXM22g3IlRl$82*L?hu2 ztJ&J=E%9#Ab-j&q-Yd&Whb+teL?qs~65ymirx=4INs=tf?o2i_$}o(kj=k-Z!;$ce zK%i%QEE=s!^!H1#SR7`P3RP9XWEqSIK!GoU8vp=Z2xyvCc(4$_01$`>j8K5UzTace zG!4Ch9&cA?`)P*3Pm?7Xo{|!jl=`~tHv4ui_?s59Ws}8hX;D>m4`Wh{F{XbAfD^-b zVvISm*=${ZJU%fT3e5@h^h^u)1!r&Dx{1n}W@4UTj zZS&gp?3vNo-flBEhs|mMlOIXQ(H}=Tl|p}i0=Zlc z5P(Tl0f~VCyAQb9D%eL*<)4CghKmhG~_aYXH zAeYNQB7(!=1Oey?^r#)}?X#UO=j=eB=laRblU}ts+>2c<=MqVldjMoU1i(Q`ouVi* z5mg6zdd}M2y62J=%a@%M=<0F-fZgtZ)8&Si*O1AiVK$jjRau3xW5;3glx8%I8;6OL zo8d3>!(Uo<;uc0xP+3`t%F4>3pNB#r^aX?1x@8Ntw`{}aO&hUo>lXC(1(8mrA z4m%8PVC&W`<(oHczQJGazkJTzxl1Z4E1x5xmtX3jblP2X=$CPQ)P+MDjQk|jY<>e?ZFULpDIt$lacLO$U+K45uzKmr{mtxn>-N z*sNBVi4YElO^X*VK7ZTxmU*YoJM)F|vhqI*AyyMn^gjjwvMduZHl}6UwrgK~<>gPc zw6xR;!QpbbUbsIo7D=N&4xf%xBZEQk6pF?tv64fdFrVTmz7mKtSCwg5g8v? z01y!vV~$X$Z|=5jTfe#JrI+SMqhWG5U65rN=~N0tgo%?Ug(qvqI`BixFh3Bwv;oq@k(?;a;IXIk7aL%z~$M*8>?ym2gHUHex8yf0= zB7}JD-IfOL3IG^m0MY1eTesfyuP6WUm95*hmO5+>l$7|8%jJ+xCQ(y68dqF(6|TGC zMl>{z`M}uPYwJei^I!ThF1X|pJn`tGc>M2wN9*pLkR%yiuNT>D2G2b8&)J(ctpD?Q z=U@0>Wo6YvvLf$1&{FwM4(aZ@??xt_I$^t9VhqFx!C(-{L=xFtPEMs#(^_}!y6<-D$Ye9H*&Mj+@+)!Y|K5vhufG9aZ^?;!yFpLqzQr8Ww+^?sL;c_J z50}%0IdkV>_S|_O0qot|hW`FIOePa777K#CLDQxU8|HaRN+vWkG_*w`;T`~n$KyeH zc{vQDV4Q*gOeRxdQys^!+3fGE1Q@W{+e3Y!3pQ?e`<|!%`OooAmlJNc2ia^Ef^$ro z+>FnC;mf%E>Z_s3CvGYNfDj@FA+iLK2Pj1l8X%$qQVK#a0tbYEpaG^6qRb?!()#=GQYdKvtdf} z)VmtSj`=qc#ol*EfGo=(Ciz-gw%+{EAAWyVdq;=E=PQ9MOUR_tu-fgogwuG0J{kgA|VJd5=fGvbqLOPLXtWlj9w77Lr+IRYBwpiG=v~%SwLtYk^wRU3Iw!_ z6+){Ns?!dxHwvO!()3Xvxe2aL2 zA}b&|A&gZ*&?7?6{e&{%pMt5cK~h=?2*J?v1OgNVBt(JFTn9)3m{Pz4*8y;V7(g<> z7{E0^a6mE*{2l>X9z>LfqJ{w4O@gn0p*;rOSOrGifRO=7M1ZXzPL6&|m^cwLXPt(g zt}g6p-Hn{4!R_%Nn@;1UmtL7}Xj)}`W8+qv-4+r;Ks|vvz%c`0aJLU!@4y&Cpr>cT zuYdK+dw>4(U*2f9Sx{2qLw`IDMNx6hbvNLLKl#Z%3)<0&c|(X4INwT!_B4#luY{rf z7))IP@E$U<9Fhf)WDo>kXgHw&00D|x5Z*!nBmhtk7+V1?^AAYM+t71qK*)lT4SAm%o1Sic+^7 z4u=boNErT7Kfe86x8c@rd=u;_&d7om&oki0He~u2Ld$+nDCS3riyk5XY~UrG}gm`-Pq&*xFlKMEUjq-3`{_5&cFJ37adrfG;pqfk@@ilSC8S-kk3 z`+oG}8+>jD91bTUkqD})tMET}-i1$o=5t5A=Y^s7h*W$bwA_7^PyY$B8Wge$GB}9D zNelo0iV0+BQISb37Y1Jk895Nj2+W0{G{u7uGI7#mR8>`B^Tti+>gt5k>4ep!V)?2! z8WcsUEb*1BYi->f?d<4;!|no+1TC*Y)ASLM$Ll%1TL5DW0w`IreEBW+e(#=7cpO$Z zoKA#8AyidW;r6@#7uQ~U{ZUGLo)EgW7FzcAhy@;pw^HC;vWT5{LkI(it%s3a4eWi$+jhR*wI={f?t%zR^ST=|Dua<``!PGQFZl*MWe9UY;ZfQ_`&z@ z{nWa(Z+wx6T*spjvV%4H;g!HbfM!d}whQn7=}+!S_4iY$zZCKQ1e`86zW(j+;HHm% zVx-nO05G_oU(Zvq+mKCt7mV%32XQc|1NqEdJQe%8(DG{pkRNGI&Lx*!hX1+!PLx+v zARdpy<#Ivj9Dn@7@4mBZ$Bru*W9pz37%3GQg7W#1g{u|9tBKn8?wz)9;ot6QZ|`zc zRaPLCO2K4O@wqR25ug9!mqxlTzy&f!I`KMkng1qky!pNr_ai?yA#%BY2p-!kZ1%gD zZ2l;~>?8TrtFOHlkw^$X`u_KkOeW#=l^_`GRi1w8$-A4UOl=!AYV>PD2!6CnC<8!8 zd;5sQzOL)&>FKF`;koDUSoHGZ@nvN`aiIJrHjUCst{|Fc~BpKQsP4@okCqhJ#M@G4*1K;Mzs5duBY^9=n0Wc-zxjc`thGUh_Zt7?;+K%35)$! zV&vcNr#QTwz-G7Mf9|{sd)wQvb@LY3Y*ys+8vgdk|9!r?y5_CA#>T%Ziee~=GH}p} z;g5jY`nn;nd+~UjmMmJ-@WS)YePj3TJ*ED#Qe?AP*zFE{<*Q%EtkX^%5%amBCyi+M zaUP9)i`nh}+05U+!34DY4-6g$b9lcFLMa=@PB6A<9KQLj@8CP%{N}!!K`_{3Tl(7K zZ{&4t(>doZ*woNCMo6*@!Nu^m0K+hjNSt#(z|q>e`}${|dg?5P!w!I87zQrA>|^-& zCqFsDGhZZmvi}LAKXNN1Ir<^z57G;We#kTFUxSONfEd>6ck!hk!}T}bxKCQR-EO?{ z@=Mb@_O^W*0$yF$q2=??w8K-e9iWgwc?b!?!8vDbZLL$Ec>M8CrP3*~TCK>W)0i~5 z8Ml7@np3(jl@2{0D$MShmg>7&@yRk+q|jliN_xM^w!OrM>(A? zXmd>5+P2}3WE3}WzmZXCjFNs_=M z2{gd?z}~%(6a{p^&rY7}ePOOlR?ykeR`d>o?=iGR1p5>=QcXb->%E8T`L!59V(B>x z&co$bTn%n;Fp^-lnDFYNSI&Cvm6sRv_H>&2qY=cTp+l$$0OBx2#L&~zQ~R$cpS+>H zz1{2ac#uk^P*Yon&wTz1P(~8N$F**u=YA;|)*Fl<`okdNAW1-I85qHCaK>Qthk=3X zCWJsoTN^q%yV2Rvfu62z?AY)&J~L$;0>M6XceEqc-Bn2c9(Hlx!DCz6&-7qd2l_fX z5DZ0d_Jk&Ee*10obQk8|)!u>5wl+YB!ut_H7zV&OVCV%W(%`>DAXx^8ZRh>5`-HCT z8Hw5WQ=j=P#!i@kR3;6F(}`ej(Du@cFI?N(8?0lD4TL-$Ob6TobVD5E$6&~&Gs>#f ztIu4vZ0Xr1RVkQlIpL~nuEwvPiGeb z;RqJYKOZ(#Mvf6|I!C37hTE2|1vf$<=JTW=?|IpF{v04rd82_{h1Cg#iHurgNk?$4f6g1Ak2g%KcS9Uk|vZ z0U~@rjRFDKOpxuPAd*RlcxF1mQH*O;;KBc?FosAtQoVT5tCw|k1#D&h5+oBzOq|?|Yp=g? zM7ADbXlq4({8zA=c>%-Xu-3%^uOc?R`6jMA_d=uj^-$proRrU~)CYiInjmNkU&&C&(}WRmIJ-ry&*Z2N7U% zd*JYTkp@Rt2V8C!6o>)<3=SU*T>a6Dk=KBy7rlT|vy4)Y8%&Z=WVIVqz^UMV=7F}~ z@Dl$NW|2PzKpAR^AG_*GJp0U3Sg~w5oK6PNi5q??KWSsMK*dUw}X` z6hc=ZfQzoY0n@#1bh7~`5e;pYwtGBBusOw`F60W%LQuOBYc;&H&Fl*Yh zegA)WjZ%&YUHdiXdZ*7F8>#$l)Sh;+~84DJi|B>3-+E%;G zoHGuyDHs3*gT4DG6bhoFqusY=&6))rT>*#N>qa`A!njEj@zF~z9l_S;SrHDtKzn!n z6OxQ0Id}ws4I4LN>b&{5>Jy*Bs4@?ZrDPou!R~g$?(srWRD5w_9ln10EW{#VL;~Ff zrST{OD2cHG<9oUg4o7hP>>0RnY&B#>fy3*C!{a@sP%I$?oK7c3SNO2tx=-V+^>2d- z4jBT{!6Z0jixt?jb(!evdP;C(*uc#N=bevfGo~S(PQ&GJA{2>9E0!-?5Q&B>IWM;N z6anBsja5-p>gx+OFI&3wY=sCy0For*qKiH{qTLToYej$TFOXG!B%N1P6m)cUV*b@P zVRX404!a#EqFx~p!RGcrQBC+la}&NcV*=u#5MsT(h3K6^irNvxJnj2Tq&I+QUmvcU zJQi0@Y=CMq!QuAcM5Sv9Ay8XZg43`26!vy>g30pXZ84k!k_;Z|{wwmit-}eMvhs3V zdg*1bn61!t16H$y)vH#YwRP*(Q+oS?6bSZWkcy4~hr@Az$4_(Y+PUK+E!(%%I6Zb` zQW?}YG~)aVFB-wg&r_7_f0ne|YA6a0r=N4fKqM4Jsy_~wO&wO;AFE}}<|+iDYG*I^=b9Z5v|Yh47mjz}S!q;rw&Y#ozw+2-dOZwVx;r~vt5>W#tD>Uf zHLJ}U;+!8@36vxS(P(t^h7B9$W;1y>9CqZh8s?pG2F8sWH~dTGvpYn*?~xa*v*%;; z#*L6jg24sWuYYsS8E2i@Xtmo84}e%S4iKQo3ft4#*1T!MhFKPi3{BHeTIR>u^XEe~ z4V@lm2%Zx8?8}l6o1rL&-Iandq|zxkomMcb6*2AIONX~Q_lKl^e$r?V5q`IRGp?A} z1R(@$PAA^&A%uX{YJpqRk?d%PFe#7*qIM4%`OY3Vy)VOJn+i#C4JG}~I(t5z{KpgM z>gY~n=_8_-}sWY#-1c&h(-q2;@WTjMFs(@0E}xI2fNK)zJ2?)@ro=%7zSJ}7n+)yMxd~WUJ`iaL+-o_ z!)5~<4lpD4G7LR1+{dR(odJJYIdolzswn8`?i$-4k5wIF3CwAEmPjQ=MI+HFS(3py z$Eey`jGs7hSgAyMrjrug8;KbpXal5w$gFol0OV7Hv;*AOIh?jtQ#%S373JXEfMPPC zHxTfJLy?I#tJPctfSy3W5$fxk+!yL&s>uW{1j@=QQC?Ot+?-NEHti z?-;Xb%T`~JAt)M+dV_&Lwagfdf%AV{)A*6-rAcA3wu{RBM+K84v~SvgZ`2V=tE-Bv zS{z`1*wuC15gXrZs-mcFh!de|_U4aWT0Pqle#dxyp1V*5q$ifcKi9567zf0p6F?~e8NhLy-dc6e{;pX_CO#5g8fMEQC%|%W##4A zxnnyRV=#0>O(qg$MF8mT?k*LEQSf&Yp|s3@NS*wHk0K#NP5|w&x%T(nQB~|%zaAH# ze;F$Lj(xhos$f5G0$Zo__VgWg+pg~}h~ya~1j6w!e|2x{9S1;MH6czpWDO3%7)X+_ zPw^K5LKwSA@H7$GkMuS^UkQ9ZA7~)Y)6g|^baplr0U#cad4=Er2#g429j%0EorP2Xg-*$ud0N5|~ue(B>ad znk02s@sDK~OhItKZ24XeK1@+60?@%6aDtzdu8Z^hwtYu{1RNTTLqpgeTPYeSTs^u1b{>$S;iQH7`O&nEyLI5ClXl_yh|Lk&>TRLBv|Ra?MAb+ zJBZqhctRR=o zKvEQB(pg6l0P;W4q z13=j9fW;1&iqxMhh~!8GvCJY7qc(*{p>z3A(23GXxF4xRM50 z)(=q*B?T}zz~B2NZSU$qUFD(1tU4nHP1%1$%`yutPfI5a2u>^YlJ5Wm3IUL{G$1md z12TId1VObEhLN^5n`3~d0*nz1LsyCbkWQyfj0%MYWm$&BI$R(m0VIeHF6%-JiZDTb6PTgFDF6pQISecZtAgGmq>j~D&FD&}-*YK2v9eTR zN^v4XOeOGG2ncoo+5v#p^MIoiP=?mo6h(owFFlW77zQf>0Ox#Pod7oC`m=P{Lskah z2jDqLwo>&$>DuWb%n?;q>xe-xufm1pJ@FGhUoY~q0-h5^f>l6DMu2#z7y$V!NFUzw z${5?9hgq=UP!Rx#h!3IzM~X^4L~M%pUD!ujAO1Kn(R-VEE*~bVQE&hjH()P0L;?sV z4a)~E_`ZCDLh9ju4Sldkl$K7X0TlE%eI()9i~u_HuCJ;p>fM0LZF=7^9{_w76GEAh z)lNRA(u+mikrOt@tE=2pL(mW0TCq?9e|h;i)J~dQ%=|$eKplRawY+vfI+%bYNxbNY zv|6nB!N_3U(2&iIpuMd?VM2p8YF!7kJSxXe#$B(iLp&IK-!cC{5WE-T@vhjRvR44) z^Z*_o;4Ud9Pbq{f4|`4_m7^BA2L&W-kRCGV3 zV-TSpD|BPnrWA=}68U^?Uz-^wv0?ytyxv~UbqE3sA&|{xhd%)XNrFiZK@tuj%|Q92 zX}D|YIwV3Nyx&KkVIY~yzhe^NjG9A@Rq=2F4=j5PqnoD!TK;eh0GUlbNMs%E`O$C~ znx=y>2F^LGR$HP704}GihlqiQ!7y}0V-e_@KFs}E*r4Uc!N}uqL|lW!0ONpU$FGd_ z~09V^RNVm(<6u} zL)8@ITp${az~K6TH;}{Q@$?h{fXPyilw8VYGf1YAFt|~u_Ra_%Z^_;w05pwh3NjG*K-sq5 z-ad49cMrW7j7$JkBOU`1{f8w=B_QSV(DV8sg&C$+mtkyW>HGFr5W{>wlF?M*Kdc6- zNE)fCP}*2>j@MYf^3kEDiw?b&6nBH#{w*#{TTOXYlx{oeG zczRh04veK7u*f2p2NVLx&<{2I6q^U_WKl<9h=5Q4=eK7HC;c|E=lO`k7MWdmJx_SatJ+F%QBG`95DiiP+soD-T!_H|8?2}*zTTUV3v>UTz|gn_`}1A2*J)c9(?sBRE=(cNF;}` z@ABznw$~1KE$Isekxr+$mg@z-MSm0PzXMs zZ>UMTNs?!S^OtbMj9>vWfa>OE-1FQsxcQ>XVDMJYM~b4P)Y>}aFQlKl3YWrQ!by!j+X7nq%x2s z1|c{qD=W4)jU5{-0)Qkd37^lu#q0HoOeReLp*IjfUtjN#005Cq$jU4ltgtoIoFxgW z#ez(FAo>_$-&T|$;1ju%hVes<9bJ9L_4{X3`#@kt{3Rk}(gsWxOQA%aVGM0>5P(ok zQ$fr=)WTn>^XqkV5XTDEN) zUYBBOy^x&Kkr#kQN2aWS>e@yueEHw#=?WCNocRC{_+jXSHpWN{r>cO725T}Cy1Rl{ zxMDe~$21kHc@qsyKouj*WWbP4i>}U21Ohz(W5{H(C@=HJ>gpP{n636qkp$@K?t-Rk zfzhMux3_HFTE!ScBoao;wyneK0gRPHHJ?YBe~jOJTMT zTes)!^=}~%=!T-m$fQ&7msjj`dp(`%0RcaS^zd8`7K^20Y*W)ZMOFX^Xqtvi8#WaX zG_>;KfXQ^KaM_q}J0RTlBaj2aWd|HKj6Y*8URt#no!xzfQ1pR`9Em+)Sw?Zd(kh!hwckw_FTEnS3~QKMirnZZX=&Xx#>r%oe_WeQ>F$dw&C zcVgqFO#|j%1FG4K*>mQsayi|-;c&QE0$8mUn9U~P_xrbxX=-{inNGuOHX|79#rkz? zM=&o|14X?I*))*LjexQsJr9T3gk(}5Q3%@A?}88Eo!a?_+x^Krhuv<6)8heCWI$rW z6A4h6{{m(MURdwV++ML{Z^Moo21hr{mNRaIT3H;rp5mH+|*hGD?xE8R15 z*6g**SFV`>Fr-sStayDnuDSjOR8&+B_4h1rm7awer;iu8)V7gy05w3UX`FzUpMMfx zIsbC_t9`?UqFqw&M_$wLVn_6SDfS1M-_sGqq7|>9reSPB5tK)G&e;uh*Kdcrj(s=`?&7QNYs;at0RZWoPgLD9^*#?tphR;_TojPs$8zpX+kxceO zk`-*(vKbpUY#e@Ws(G|4c%qKr;f<^w=77Q@dMy?}8|V?n88!FiTS4o}H`K(H%7)P!NY(k)wC z@YcFF0YS)Svv66>nA|+&Et|vMXBY+!sT4{Bxn%;vD61&jFm?L0wM!N+In7sUM{l4P zOBTP1`SZ_#tPB}{?qaI?QJ74B=5Kr?^_iv+U_SOj5hQUhlkqhy99 z?Zui zuT3|OGXC5y3h*A9#yKjDaf zlmP@uogie}jWGJ|8+i1{%%HLir`v^OA`gd)j-_<`zEt`{Z0;4_;mqYlzFWs)H${|_XG61x+>^JyGk`%A2t6MgL3UA3=P_0*y$@T&ywtl2OQb9oJs9OB~xhL@Xi>^ZX2=sw> zd(>5y!)D~bgp9OOn6!m!FqndgHhEY(e_wYHKYIKT)K8djlwC{&NN81DwOlVTY1p{B zWlLYf=1p5*wc3zLCs9^jo<42%X-ix#SBP_d2znR*G*6nekETqXEauIfyZ+SKvlnNz zLZV16pTl#{K8=o!Vf#Fju-L|mlJXl1=F9^dG|-W0AZ4j(@+AE3nS}^+hE7;>hWC09 z5&AlV_~G9lM#F>&!*&Ec=H&05;r zJmn3u*=!I1MpV?ohXI9H{y~x?2_+@o-Z^vTy;5H0%f@0c*sL~e+PD!fyzuPsb7ss+ zcGtz+?mAD`vJjF4vRFW7(-33^OeScuiZN%NjR)UY@qqvV5h2(S#1CIvgvK+@fuWc{ zoPmiE5F{Yh5As!=#yq9hNRl*cJ^RIr7GcdBZ@_G^AdyJGo=@b?|`Y1X(I)^dw08CQq$>zRQ=vw7I zB#(d!{s4yYGiKm-&pnRb_b~_xt>uFF(aX7@?QN)^HVvGMBIdJ-YH&$Lu5}mSDTiUULrdq7 zOXpy?yx?t}FrqO?Rdw^_s`}3{Ss6C*ptGYBfBy4dkWQyyHk*-7r!j5X^wo3b%zMV= zaHS-Yph!v)6#+nSaS(ABhJiA_zwM%nE_$q@vb;YYi@{>Gpmpa?{OOOs9hovCE2GHk z`<7rdUJHkSl1gxs9m3)Qca|cvV>gU+3c758uz89bCJlV;A61Kn=4SkU%?kAF4Wd65 zJ1K!cMCfhrz|U5#!RY2G@YU27y~hF9tiq4=JO;a~h{1$h#tKSbK3@Z~_1lo-I*cF{ z;o*n>gsmGkz-qIhzdsJIuO#=8v(J9Q=kx703`20v58-jqHPBt+IqYdJ$x{BT`RBa4 za@DGrAO7>7uQa#;CNuo|sej?D`RCxmi!L7C>m#evxz&3o@9z34WTmfgGhhIdAo6)Y za0ohbKnd4%2q7?L$`m~P_M1p#v$$&3Ttv+(9B$`vHK&LtlCanv$Hnw$EQbEPgsw;y zPp@8%#;MbRR02hfvX7`O9Y*WVq+P6e>i31&awZ#r0b}Kg<@o#G9)+NRz>-W33(mjr z^>Y_2c(%H_CZi9N9aRVj8Ek}HL<~fP%IfOQYp%Qgk7Js~cE@8eSS?mWB2oPM7r#KL zw|6AhSe4lvA61&B-VH5pIpl>$suu`>iqWGnc5*YG-?j;_E_n%^Jt0J+(W90Kk!TG4 z{Ruqw%O4?;NFWl8AsmSu9pfVrbOfS!<+*?3;iW6^+Rm+*Fl{;n=Q!qK$+;{^^UwPx ztEjmIz_69NGU*h4b^p)N)71s5)rwd=hU!tHVpm*!&0ngjYxWRh;=rDe2U3v)IP$s+ z2qF0B(WBqEg!OYRbb`z7NlZfoPO$Dm=pkBW zxtGiGEs&TFE`D%4=PlQDkPsL>b}W!fE_nhHg#v{WIx!ej%JWbjN1j3hv{ z0@)~P%RGQp20{UpRaF9Im4Mxcr`BzSo=E~m4rF)25CBOkSS|}L5%yg^kAXFfV}MKoFtUKdGcY}+@GOZ2;`j@jLg;x2O*{S|R&EfpJMK_x>u({m zWkk&H=;*+YfA|CR_VmDNw;~?vM_GAA?sK32!u^w*n>Q0667l$uCv0h{f4>fp&ud4Z zJhXx21hdIxG>#ej@(nlL{2LO4OfCzv#e!{Hx8T0--GfA;|A@NH;JFD9AWME>v40%` zcM4rQAz3+wu7eNmLe7DK@42oQJZ`!^@EK#sohoz#hOR^CI)tG^*L4^Wg*ZH22R90I zT)VwkDl)Rz?vh70-a;0eZzSE8xMAQ2-@6BIzPT1Avk7`$Lnf2KO&`DM;qenDJgvxb zo`}G?QPeE@U@B4qhd-VQ0VCWnVvS?RJaolXSG^XE#32UE$geJX1wa1&eIxeU5kx67 zo4=-xoAfi}awjIi{{3YpgvjN*!~vELY4M~VDE^9DB(te>q{8t2pZyrmKJzprR#4Vs zu{h2-XTkdV`o;$!MDSpf?~$nJ2(XN#K{62(RmHs1&)i*8Gy0y^*4D;1)~u>2FE2+j znZjTH`d3s`RpCot`O2^wG=TtuN2;i~QFy&BB%=4gWNGrM0K-v_Vn0!Wf` zq{qnL{_hd|_BRhAo6W)La3Ijzi!o!zMsNPaC+}`(XxJhQh-@ZzR0k^?aNrD2M35v2 z7K@dSs;yml+2xnt<@I{=p->;}c01Cke*EeeKgZ*bJv#D@z?h9$93PY2<$n-{ap7UB zjeL*>=McKSK&~J6km9erjF>dyhQ%*D_Z)um!}|~ph2d~G5RFD*GO4)v6QBCuF=NKO zqN*z2XVeqJ(@`A(#qYpj7=|{ssp-kjed)_TWU`ECEDE>Vh2B69?z{JUc>1ZQMh1Wa znV721k!zEp-1uM~35M>JT;6XphxfP4?mPn^6Qhw;9C>ZgtGMUR+p)K;4KB9} ziDUxlOa}k;-?u+7b=vg*?h1ZX65!~stO7!a_{>?eetX;Pcl@D0nL&Rt3Ae|C zy={AN@7;Ie#TTACs(Fy4j*^`v-ypl^K?r^}_yn~`I*N6F z{q?1|>-O8QecM*JJRW4TSwx}$k$CkE9E%q%!aaBV4>oSx2)D-rUDFTJ9)uEul1B?+&W;0Ypg)GakTCJhl+Rh*V;%nq*}rWOWWWuOvO+O^Ol~07!5M$pqpC1QE#KAXzRLYm9s$ z?oKv=m;`7!2$DdO0@Bk!J_DqaKsF7St$@u@uo9|P2t5Z9I$*LCu*zgGT`N>Z9k2wF z7>Ge72Fj)i9zK%^;KB#cO{A*-4N1L$N%BM@G9N8d9)09teDAJ1(7Jm!Tpl;LVW2x0 z#>XzZc-@UR-t^tt(RHisb~|UpjyZPHWH#>yKsuc|rei=1IE4l0FsW*9)41`QU}&|= zSFLGOBnFq$iAXezwQsCOG#tT5Jfs zk6pUz#*g3pUuFKX<*LbKIP8uioA8eU0P=~EcmkYrUQt=O{tI9F^0zG(OZs=e`@`kB zuEXQ;Ael(u55NB{cJJAP+wZs&0g<GBoK=y@$nn4 zTXglc*WK>%maNk?%{Yv@@> z5XKfj*$#*Zgy;j1f#3u~FF1H|nZmz@J{YSfAh;DmlmemzKqG`Gfof_XMiYowB|uI= zF$QvVgX|e!C(+piv{=I_k&-I9XIZNm&?_bOeEit zTnQ3FAk-H;E(u`hI{M>rfH9a&CdjIcjc>n=)}6cDJ9qB7eDmf__bh*XS+(2chRJL~ zCX<0g45!SRg)e{YRxCL0{1biw6N2XfkrY4*Txdj;1}G!A#s({Jk*+~VGDuayB?ZJ} zE(Es_gtbtC6INnOWrEBAOeeB_&5C8O32Fq>K$$FCxn0`QLe14e944~6F={9dp^5p(aWFjXy0qIJM6I8 zEQo}{`2BAm#M0MZ!}T}Zgv+nG3gs0QCrZE_!aEw`>N)gp9^VLsLU`t>r||H@f5Dbb z8v!9;wc3!+=a5JwP+3`-pTA(i!zFI-17-fQ%{{$6{M|^8d&j#w3L&8D8aNj%<0nqI zuX*Z}buYd6!Z(<Xsq;8imd701=^O>lWPmzjxueXP?H^*IkbV=bw*?imLa$B{&=k zVbP+OvGCDH@y6=aNT<`VS}gz(`uqD~Hd}Do>^Uu;{LH6+noJ~~ZQrvupbrGXz8m9R zYwtPWrw-Rt*E~^Q->}(cw}1NWx8Ayb&z{|-j2IkFCp0aO70Z`n!-h@x$Kwle@g74UTb@lc4o;G{Vl9el0e0t^U%g*Tw^_f*k zfz9rKVd!{$>FZeg#yULq=wtZEM?QkH=AVNJlP1CEJ9!IC$D$Ez+tz}muPw&nMT@Xy z(-x#N37AYK*z7hKx`F<99Nto&K4aRwQ-;<2!_oTt^fHVkI zR961Gv2o1C>gt;F-+pV|RXetCKRwtNloVNk!)b@E8+d#DTiCpD1OD~oKQLwLG|ZVh z4^yVkz__OIaC_YEIn$H<{n);J2R6R_7FH~O9qZS>jn0k^_SI-2No|{gsPfRm^5h;CQO=$DO0ASrgjvnt7~Aj zzVn5@bGaNkJ37$W*@?H;zlD}9Td-yGCbaKuLo%5FNP?m&uv#t1=5k1;5-2Gt!Gwv; zEB&Ru=kxjezx;mx-f&+irJtzS!uKNpifrJzo)JQ9FR!TRoG@|nGrp42voo3Wf?Ydz zex$pr)162ppsFg&Rx1p{z^2wCsNCcf7?da_6LSJ7HJGO5}>&~6%4)h?<-HmiQ4Pu21Ym3c> zY&M5nE(?I*FY~9yjUWHI-R68{Qgid7a9?mwXGcfkWMKSB1%Q421R+v3n|-^tq-3w6 zls)fsIcGav?s?qM=e4%(ni2>EVY8YG4kZrghJn3pZD`-qhBwz1^5U%)E8Jc$%FD{( zEA_!JN(#T|T@cTQPlqlczLK*K*p7_3PK4-m$l>UK2X< z`5XX(suXJ99P_%xVp*3?r`A_gly9r3 zs0gd7N#~q{pQI5^GEU;bIoCPoAw^X}K96sm)nfCclIigiCr+#jN5gZXk?4d}GSL(a z2EEZp1VRXy%;tS2(!m%8&N(uf4ASXzLDLrdE0yoJXz!1CV2pt=2}}rZDtzAH9J!nZ zMg*tZjgpemxWnn#Ugr02A2)vd^4{Ly?z85f)6%x5EwpD(Yfje-c?Ad7$~J=3O6*|e*5cSl88S+Jp@ zp(h-U7$#N4!FBjf_95eaHU_}J8DoI~dfVxAFP<{B*_uwLOD$Gg9U#j5e!st~tGgi@ z4cEn^v5Isi;}D#yx^5_%mX|r_ObEe_CKeY$w~i<#NAGST;!htNwpP}NyVI8El4?;xERe?Ie199d6`(b zI9S-Z7)aSzSb3RQc$it)8Ch8QSh)B&xk>-^AqS7aJGGO_%l(ti<}oBfB**~8WTzXmrqW45rj zaIkQ6cLUS1{)g7t+R5F?&D!aI!}>p$|EB@Krd3e*&lvwpSsWbxGlZMFgeO>ye;MR| zN$sZY<7~mKYT@SO;c8|f;RzO#;va3C`NUi;Ox&GZ)t#K||LrK{e~V1Y2G*LCM#03) z#_=B|X#a;U7UCxE76Rm8zp*m1fStyz&ce&b4)z-hJ-ETb^53KiPUbe2KK~|VV_{_B zU}R-gXXOCX@v-y#Po&_WF*k8H`Try~H{-K(a&<5Pt8C+7Vr9YX>}W+!`X58`i8#f*)WmDB9s{gqs8z&D|Z z{r_)1|J<7YL=T^|jT_jnKL5IT)GS>7)v~uC{jY%GGco(;Mi3x3`zHb{%*p?C+U9>@ zf&V)V{+q3rwFQ{z{~<2^o6OD0(%s9%)k4GyY_0!`$YcKBiSK6O`F|Gv|IU5?v+)1h zNd5mO{J%_VW^LkVWdY8t%;f*5!~9PU`ftZD|G$0qU)TPJeD!Z~a1#8d`Je0y{_~&w zZQ%%J?F!D$vG?nB0DuggjJSxp_v*O;w4eI$Z4NHf?gbO2^c677bdV^j9ossuMU4;v zlZI(fS2qePeVZ6a1{ICy;x)C=`S>7oOFk(wVgUE{W>=l#6#s*N@#XEy*+wVgVwEBEZ~!mxR~$*m+si}W zGiUkE$d1_6)w}<4r`s38mwOy-wm_zLUxEt#S|qM2BiJm|@pPsz+GMJp$J6eR%(<>_ zf8^47>OM#3^;_lilE1$1hTWPh=R(vNKQJ^#?n$0He3;bV7sBuH!}!m1Z_P(ySjCbE zoV52E-L1_P$o+D2&<(gL@k##H8%r=B>)}?X@ot6%t-QIQeWA z!;YMV@$~-uR<~b$&&#L@1#3uhd<8o0ZbL{O&w>ToJ-1)d$!J+oO!LXf7&vf6 zLqbSd$%s{xlFP!n86nb`4S+;zO<_(r%=0=tPH``}@BV!9xB|BJkH3kyoD{dXY2Uhno_q<| z%{05?gGnfp~L1f#p>YZH+JrMv+rd+cXkWL#a~zR zQ5j<+0nfYL$8N!;K}_s3OSCWn)puKWU9EYJ*N@`BqiFNI?qgpVUCzv^Vi@-MvcVtr zl`GT=1cM&-YX^&=wFTVyX}FwD2NMT!2UFbdwzKIcc3Ra>@c@z#0DU^6+F#on%8kZy zO|Bmto)gZP&%YgszIZQqd7#N7yJs*VtO>Vcv(J`(Mv%qCg~x@qtoSt;=S3fri_60FKuPYAAB_PRQIQKO_hQs zkk1dMMoQTO&Mn5v^ms{84qI(u=UdI;M1tO+H}|L9r>Mt)Ca1Nioa*7rN>e!;X4AP} zik2a;cNQhjIvDY76O*Hgl(+`NaMf02;kuC3N{_;Yb2I8V@H$H?D#2U>1ziGdPTAKB z^IfM)RSmk$mL9PmaJf(r0qgDMLnjOL4mcB80_ejlVmSe`3{hXJJdrhut1D>{dS>S% z@Jy=N2R5Z*FN1-X^Y83jrbot2<};!Xr-?bsoJvXud*_p;W@Z`UVKnf=}@|hmfCXn5?g^N zYkDLfoxLm|Umi@r(P1$h_>$Tg@D}y?7iI0oVB+`hV?apY@+Gwr8{zlm8WE!!$@}$; zuu`(QrCH2ysF?$nS+UC8-Ug2O%#c`%-iW-As)?Thd#6stik^Zub35M&y7e9}sd|`& zh`v#8Zq`^mqKrFaaU&CO)qLo<54$$nm9clOa%N&ZxC8Iq{W&`H&m44eh&NWe>@@4M zma6NQGqfMHc+fF0R(`HEXcJZK5-n(k#BrqbK@{>ZG*s2?h6beCY$i()DwLoaBurk;0c zn)=D(IWbBRj^ZJOY<$Jo*8G7F%$+F;KJzkQ3*4+rizSdeF5s*T@1qXP8uIeDXR&SJ z%4sq_U92*hddsfu>l1zO;ww~zza2J#kJVXh-LRVIn@%|1bxTZ?&HhH51e%^N2q153 zC75n1@p@TQXv{$&emX?o=E48Eyywxib!E`(QXsV{oGzcs+SQ;EQ{$Dew$LWQZr_5; zz(E=Mh7ni7ooIhq^|L~3jVs^Ircf_S2V+HrA|Y0IO2}_yTgn;(Ss2;4#;5;VATdOj zSZd(wQT2nJCc{TlsZ?i*E`A7Bma2_EHa9+yfP_yjIegE(3Wt+F zT+;kJ{VTsutgdIQm{$J+M29qfemNPJIV+u-i{Cm4R*n(cq!9@-s@kuQ_v#3E`#$-T zHKdhqId3?Z(H4@x{u4fWj7P)r(@cTmW|vDRqb&&E*X#221$}$ejF~6!pyA=U(GC`w z=uUz(%ti)!RWCEz5nW=D&+mcuc}Xf$oa&{;Rq85OdEnMBt;dZvxJQaD2+<<=ERqzi z8QQRA-1~xQ&)f?-yJrJy>(`VIlR9b(xBNB+-kafev>+ezb$9gPRQ<(IK5W>e8i5i6 zYo3xKlnT9HD2a%cQ}}Otn;Xwd)dqiG&~i&yNc{YW37oW>zoN7W@^UT+yC2ga$puSw zXG~_ibNb#=?NUVQot^V8*RVd}kC-Rfbrd>NPs!lG)%Dc^wU(o>irQIas+a<*e)AZH zU>(@h1k3wPE*ML&A)CkW(T~kogdICdM_I~ia;R@pB5JF=Gf^e#V@%OU98NuJmyy$w z1u^RI7$I5CBv{+&@Us?G=E)|jC$-jFRdx96m09S@&TfSO@Q!T~vVKC3%D^wKH9M?W zJKB?4@ANP0=|y07!N>Ve;&T}b5%DQkVh;*VAmqQpKCue}x3~WGAk<%BZ6%KY;6c@`Plhn05C9p|81nHj8J)v@D6vT4Yv&>@n2IOQ5 zWG?lGFPvY8Eng+_Gj~q7tn(`-ZK;pMsT@YTLcjx-PlKkWfMm=sd#I$af$7PShrPFB zSN`x_@!ePmw^`&X*jIitK8D+3R~-vC8b+Odqu)L~i^jOm5{491VzGYm_tGN z2mYLJ(Yr*M+a))jR)x@4d(^_^wa+V8>7>GF$ei9A>qy(TY$vWA_GD978^fpO0VSn1 zc86S=!=s8wQM}VvHl`yb2E#Fgp0_)1z6#19IvZMAnsC#?VoI%efKO|yEQJvBC#3|W zN5`!o*vHG@sbp&B`!TF=UiRV~_1?KeyTb^994g6^vhB{vfN9t4w z+-5s2eGJj#mMZHI_yZr1><(r?#|u>|#qcFRXF~XVPZvFYZ7C7KQX)Kc--M4u(0F6K zOWLSs0X9E%ZeQ<7Nq%W^&@)oQ8i+(;)3?4UsEVfe;(2xiE1f;HLn+bR(osRCFAqZC zECV6`ZKf2K1ddFHY$MEFT{aN=nPgLcMby!7z?hOt*LJ)6AsY@khn{8J?`4N#$m2D1 zvcAHOdPD-J;*_5Vl_*=F3ne{xeOS9hQTcN%FvG&OggK${GE^W3shbbQ^K1n6&d+{Z zTt;)1%0{vdBV6=gH+UyU1mymzNF}Y7?@;J-Oz)x{U1>`gWGO0e(#j4wUm*@WvFFfh zvmfo_Je>NufQSHBMhsDA3W>RYSU{Fh^)S~P67u7`!nI*uLn35KTS@Bf@$q7_=#GaO zlVIGMTjsT;BBeH|(3;amlkSxQJ^>qcHil=rP7M}8HIiGfwUDl>FVbu{qrEGhAIzdK$wKd#D2Fo5 zY_sEJ8C{uQo^V>an?hRldWxI^K*km6wE#XL?6PRP(~_#)W%kvKhjL%DsPo}$wfAgB zf>JM9I#)w1wehV%;w;u~Nl*&C1O^sHOgh0efud*pa0+X8AsT1<@Dwqf*{~sp%i_cb z`IT0|JYm~LpIj(s&wT9ub=TUks)FVtf$}ajmI0A~j`4a+1T^ox?KUf%hUqwR_~&fs zMFz*aOPzp@KaJ6l941Dl?CatP^p<5_(vNYsaa4r*um!`aN-n{8AO{}3lFHG21UOw#xjfG4{7dI5O+Si*4t zG7*tWDQ8qDXed;8^{Ll@GAjd|aLf%|{6Xx}JSMxv5(wYjH*KE}L#lClSs5O6T#PDu z`jN0eP6)Ou9mHFWmh}k@!R?^$jCyon|j^N>lW< z+)n!8;0HSCMOsG}A}}+o^YG6qI6AgH!^HkwYqA$r7!0mv>3`g6MgaT{>f#>%@nFJ@%Vh{=xpYWzH zR~AXQe4k#rHE9_{S4>`{3+oWbq(HC1uaCjf3E#h$wVP~w|3lTb3Y^ql$vw$)30&l! zOF?no!A+;1loe7(B`#4zB}V!{%O!+hQxi^0t|A|w())eu_pK-DL(RQH_ni?1VvyFO zAtjb7{$S!q*W|~&UBP+>e~J;re%_KM+CDr8Sr6^zX6BrpLD~QLPG-$AmOtOFygXtL zB{A1*jf;VgO*OJqFDQu}p*24^M1nT*8QMv7TL`Ew46u!U;kK3A3_TY?r};xCmEI=gTjS)`x{I3br2*mN6RD$nCNht&U zIX8d2YNm99?dar+NCT`jC%c7VED`6fskC&28H6q9$2#H2s1ME!lrTg`te@Z2j|q-O z_FsxfF15`Hzn}?HX2jrs82R%i%B0Hep+nis-1aGd49!Le)5yv_hX7m>Nhc>6sDve> z2q7|{!jf^n_4N=;%3{JNpyI^=Q!oREa9)exNV%vs$|*mYnvqKY;Z{byKRsn)l8u*! z0-!DMg`ejUZ>nBVW=lYP9r8CIdrJ%a%;JPmf_g4aY&RyCh~I1NZo?@f5z3P-88i{G z0kdqQID z-@1*~zmcX1NFjgjFLZ0gx5twRxGP0Ot3<}eg*IPnQnDzZWM<+eB>gD4p38gNJFA$u zr4fh>798SRwLdSz8XG0v5YSOQv$T;fvE9k)aSaZ{Ny<=81k^J7Z68;b8v_7~ob$cn zYLI6O3)^Z6SVvAppB^}`-o69eeoT@g6MXqhg|BKx4#J|$6Go5_ea(cTd1plUnJy}; z4n-*;C5*5s<%MW)R9=L@*Z}yhjT>a4fLv3VhrsoOUd?x|Sofb0! zQEt;pzxoT`Jj+~{3w6N=K+4qQJmGQWMR7b#q7RMErrKO?eDT`8s+eM!p(*4?lB4iP zL4M?YXEs_y`GhQzE~t#+3Y8lge3Lt0wcP^<2WPr$t?=DY#kbY(6&}$_+Tz|Vsy9*# zo>KTHm!z?X!`X3A-nUFq%1^ULsEL3nCw&NRdm24t4QuoKPxlJ3Jqvx99}Z9klVAZk z*tkJ3*f1<>gFqO(c~CD5KFi`F9NTU%DI*=yEc))1kGBd@pgM57HKRvN)*h))QbN>L zw1eiZsS#o|Z`xNIuSc4|?8x4X2EAuv0+)XxCO0S(o`ZucxOnJ!yAZ-SWa`js4#SjW z6GV<#mjV@6Sc3YL$-uRq#ys*xCOQ_gN=07LT%aw0>EP2e9lh-5)2&~J=i!eRZBmk< zV&2v)MW?AEaex4&oG}QUf`FUmg8tHPah}MWtY_9`zs8#!Rw~Xy132RTgir>J9BikP z!u#B#6h@H0GN3HHByYt>brwdGArKRk=hYe(L1paO!tW7I8uOd}2%$yds-R6WX=oHQ zryLT)U&X6Hg&q&JMFRy)-PDsYGDu^7)lze?)*`>lrnxg*{6_wjoQ^^mgF?XCyb#d= z3Arav61hhWv{V~H+Q0d3h`#+I5%4&ZJ(3e_C{n^U6GS0IWT{C-m2X!P-UP6Pa-8~2 zq-It?ZdXh-D5C0TkGBu!e1B8KrvKQ1l%3A>eJb;MQYmH4%E6HmAKO8%S+(r(7HBk5 zM0u9ld8b(^ef8&hf1f#j+o91;6oVb|$Uaxg0+<4uXcA$9jB8N@n|VExkR5>!(Fsc> zT_z4tiO&6!o`#BOV?FP|T7`Vxps^onNIZs~+Xx|$#X8@}0<(|G&+7@-((|i_q9qD- zA8}odd3!FI+4$F(R)%GE#>iKX(>`|n$9umAD!4u;0|*EE zP|Jg7<*9Wz{HznLEPw?mIj&8gw4A|+tW4;{K!&N^4|0GVyiwY z6Aza~&P><0Wa!7aeO(-p0Nc@moKUJ&mr^cRdhfG)<$+`=!)=d`s*Aco#KmLf+RtJ{ zmL&#RoOVaOhF#uy`ar9%V=xem#$r?m50!H)pIhvta(;ZQ-aI zZG*NFHxm5LIWT>UU^Ax}69iiSqKuGY*0KB7__Je0ZYy}>+HDo5W@K7eRt(#gm)77J zDQx&ZJ|LUrP#c2l??G_9K<|xi>Rs^6x5fRvLTW2}<6_3W`m;u-@SfAbosGO|ArBK7 z=E2Lg6Y!*4=?N*eYxzs|G^Wz#m^SQ*?)TSYD4Id#Jzer|SvDe`u59LesJ)cD33xcr znedU*D%~&*OB(&e)x{0l<#GTkb~S685@^X1_`WGZhyz~&^LVF@XsIP(X_=>#sqt$Z zOdALhRQg}ay1Xln90u!63)IYtIeeS5!-r`6H*M75nO^#o5ojETsppz5s}zw6(vZ%qcSa>BXC(*v6yOlW)=5`yKP^zgPm}TA5y4XH$>p| zQ9F-6>eJIv4s@JahL-oo1<_)SBT*vMH@@#YF$DD%ayUv~0uN$_is}!ycQfm6puF#I zc%Qlh;FWUtxqf^xdv3zdKeeJsvpila{}LS;r%8UG_7tTQVXaSh_Ayoy;0MQQ!WfqC zXC%=itZQ4QJrU&!M;jnk^_q?`MAYVd(C~L5I(>UAtQlR@kUtOx;=Ovi?C_5yxDx0u zK9s|WABcUV=-2ER%hqI+r<_o)CXFPeffW|`?!k#S_p{;8So>|v513dhz} zB9VcjD?Q;3CUjpK?F0T)Nmvy33UAZ_x|C8oQRwO^k=t&jAyHd3-WFdqwGr~=QIW^v zCgUXAVC@fl&7T+K8m}dkpAxr+RR!ddDVE}kA3Xv7SekqJ_FPpoA8a{=MFY<4NJ1GGQ1iv$<>t+Tp`h7i(<1e*8D#JWaOE#`IiQ4`C z%Jh#yW-aEhH+sxPQitDPj2NEPK9d6X3vnpu2UF8!jOddlCr`Px_h9Y^pon#K)T`s% zPQ1#s9-jYPk@*0<4ZBL7`wwEx>?ahwW$DQugT|`6z80}OQLxxtD3H-=D@aQ~)*zvc z3C!<8D8PK>sjR%TzU>(tx`_=*wfG*=s8gJr>Jt@R`6r7Hh3ivS$zhwI?=vVZkh*v4 z586^XJccGYXg^(2YAQJy{-v0r#vX+zVlsS1BbGTK&9E1=yNfywfSvQ zAA6K3kc^D1LMnY~5KE6r{S`lw#cL=A+DegdDTSgZFXZJujp0YLj2e-+hloxw#Wxfa z+R@P{xCDAk^Gf5HfF4t$w;;+eN8`yv#*-=M`ue-(#=42-S`M700gMvYGzZ>gy-b{z z6@zrz93ut zo+>KBq~OGRTL0mr-e1DD172^}+o(}RUlXpvil{*^uRC>Z1{@5qitz%^*1HzI-d*Jl zb1VqQ5UUJb&JH4`$lS-G-PE<$WTf}(0>?-xHDv)C%zEDT^$i^34NVQkP1xA^(_66Q z&}aiQt8Dc?VqUsm)lZq}{0a<_iM&>Ibj{aN;B>Fubhh1eEZoa4TL=S<#K<6K)uMtZ zx9iz-b6<*_JspP^Nd{KMDAmS}374zm563LF+Q!hpn}UCcH~5+Yg$E-?o(ir%mU04B zsdnfS(nr_5>*nT(`9~H}8h^+V##kB|R(Rn!qCCD?MwG+Q88qz#{CW~L6Q zE1kLQ=h2!+1T@0TP`-Aq=Vv-2on`F|_67{s!v-6k%=Nrlnx2zg47JWrk~S1fc5Bu# zpUd2{==h`8-N*>9AN72>PSv`Gy+nW<1wKF{N`O+b)=FS5$QAi>w<=~A8?S($b55h} zVrb^9EmH%8edPotc;DrVWE=oo&(E^3v1F(VMe1o{6f#jF_SNd@3x^LA4fw5yjRV85 zqktE>Z|&`WPsmdaXkeM471La|FSkT_$kwEBf1yL@;&(nW_8_Y0p!Cw>>8>Ov(81M& zryRgu0C{iJ%l$mz)!u4uVtd)PB4-rtHxZ+5ua8mSruEPMEe$Fb2T?ypyJ5M`eF!V! ze{Qe9hHB`iBLiS9Uo4eNU0={z!$sW!qj7$xQ}k#VDe7yrwW8}p8Qq-^nwsTf{r%86 zdY{B$Q=4X#!Rke*BU?UYTHaG=W?KkKqZ-V$(?8m%YL87V8fe1rYo=$3REOWn5lJ^*l+y6LXLil22 zZ&Z7Tal;;zeh`o4E!fItMas;au0Z%jSDt`*W7v58DDh<UAOr}GZNKgbkdq>w!j zMJbWG)VAM>*~MaoWU-eP46h1z?F)3Y1ynNLje&%wFAb*thO3sh@F80mnF@1yx$(s) zMvT57QE9nRgjrjbK~D;y4lcFYEdE}2u*$MU=zdk6ZVVcSVM`e#>nY@Nm*61(vK{8+ z!T?H63)yEk+|H-dlNh)q%;J1ItrLhZk#uSPO;kOGmaDa-cvxcXa_ni-a36*KM?W9R zM#T;)CD`={X!I0BhMpT2CWE(p>dQs!+4RX?NL6gb#HrND>4oM!z0zY-3SiaYXyGi9 zk(m_|-Ac<72spxz4_MndxG>&xurkqfFkOr62jSTrnENJo^4Li9UFri}aJ~P^X9H52 zGt&3szIkQjbL6Ftr(rskXw(ttj$6hWj-usm{9^2sYt|~5&SNXx!h|qS0=_iEZ^uKG zc)dLRl|#$dt6YTt5jT3U?WO{M`MV6MROdo7XsSwxYV-3N#c9|Bhc~ zY-VBjEtI?YOrOvp+m8dd-Osb49e$}_+ahBDCA^?dwn+t|NSe=!%Doj5_$x=m4f`VH zNg`Jt3032r+-}m;~ zkkQE@w!5*i<5(bwXrG~aGA6zE=G|1NA&jDi9!7{$>8uBhnf;G`CNd3j1FQUK12 zeyMWC6kP6lc$%qCf{p# zUi!jHKv^5-eV}Yo^`KJrP#-*N1be@K@jc7>vZ7!6Ef z`~gcNrrHum%BU_40`ICt61XaRa@%-YeL0oq1x`zjVfK;(6Uun}jzDa7ffsB#c`(U4 z%SH(NI6CF`Gafcp#ab{3ZoeHD&FdN$NUq;Mh11rH=J@7@pSIm3)<5QvA$0IdKVhMP zrib)#bm=2!d1G0TH`4quf!dgMO{M22N?VOKA)fw-6f|Kv$XIY)SM)W?B*qYif!lwG zh{%NW$RMf=TPMDAm1xNWQtCq*C7K8!tf3R60fZDZNb+B7>2_e&EmSE1c!5PdZW81W zeB=;a!oBID9m`El`ub-TkoM&^ERimr1ku`D1xk7rrgs92vHQw+5P}g#%5S* z0;X9}eO|SV@DY6T)grKn4%S8}$Q(1suMS^oerbUO_SvZqZ}juGIU-^!@%R%^PX z0j%n=H)#|AhdjeF2>tWaIY7g>WGY-w#=$^c)GN-R zcR0_=*iQm%wOANgd9NsK&D>W0Lu@uy!8q~s?KjHO^1TDo*zZ{l%GWWtD-lY#)#H4$AkEdm1?+Tpg57g z{V9R2hE0<5d~o4d^Ybo18X%MSO}inF|H`kn1bQr4SdpgPvaQ+G#oOM-v3JExjF-l~ zVi!gtwwHHpZGBXrCO<%<*oGdxiyWvP24%6d%G;))2CFCJ=B8$dr=W>H)6m3nxK8BY zR7W7Uam-J#MFoNpZrGxzrTNy9RLYOQ=HYz@#DosEmP&fwhem~%UiR``LwqW%mBK9o z&0#wZBq$JcsmTK}2jVTLGNR-vN^GTk;8Y`guc5j)R7*3Ue6oohAF!>!&uYYJh5d6z zI-*;j=gnVCo;VnuSlp@}Vbu|Uee6Yc?$9`zZN)Kk(3P`Xy`~~cwk7sK7>)jpf0`gN z#u3_%n+~SCy9eJSlOrbQDhvIF7u+;t_*}_hz#uE2^vuC}Pq~5z#rY_=<^F=i1z`1; zr^l*N&_@)iMi@99jJk@c6De)XLqAsj5qAYI4+CGVjCk~s2Yt|`03`fE-Tas_J<|Ie z*V*X9Wt$Czm5T8`!iFrL7s!PL*u|V1 zSTG{51cB~;Sx_-}4R<9UvEd;LX0UebeoW({cJzpNUxho0itxI0G5L0J^|yL&dJCuU z)aN{lftQ3xG}U4Q#o|h-AkkI(z#LwOEkQ| zyW^Gf;LM;nTzC#{R^u%u!@>!DVan&+%CYd^KOioVGdZJqVA&;wFg$upI`Y|4r*HIu zn;v&%bD2ZEB)u-TV@H{s+mZyqGv~P!Yv`xZ>Ax9J)}SpXlM;YV6X`i_bcQ81ujk-yrSOBwZ)_dDKk>LiTk<@$9S zA;eG&epirSp+WnoGA>8%r|9_j@{VoxDT`a)W5lo&9#qvmddaWo1N;B zR&uCbv0+v(KZDr1t78DjvT)Pu`dSP((+jhxZRve|S0NY8= zi{rYX+j%tl<6nRO{&Os(&v+gE@Sgh3L9YzRKs+Ta8R6f-_J60os z#uM>F%SoU`$jSumm=4A@4QO5LTR^uUBEfQ zH=o{YOu>*g=|9z~ru&X)Tv4*kC#N)Pf=7ELsGL}&ed)`xqFyNqby3J)@S88|WbMspVq)9;sp|`%qiUlIpPf zw86s*1cNv$Twb^;iBD~8BdT(ZkZodZ`!f?2EbPvPXrWdCk*43Mq>BDO_I~c!sV=d(d0`p$8TJF z=FM1)=mH)$Q01p5G#9FhGTq20Z9`KzU&`AFAp(}`MlFweAgF1a*2e~v8F<@n);`OI zhWynQQB#==uK4#cij9_rev5=lPZ_1iT1+6GntPj+Sg| zTs}e*)3LQJ%QqHgDo7P+K&Sa!p^6IMcVQ5!)VM%L+0!labk8WiNhZv0zzOGn0u8)> z1>WV~(2Iz0#eMjwO)n^x%kxX1!)Nh2e7F9qm&Snf*LF74ApG%?J&B^{x}dwHTD#3W zzcI7c7z}noYYW)ShK6~jS3%_YK#+rnNW)g)t;;7{J*48$gvQ-AHXgD3Bft1Y>pDAvgJ*Y4Oe(yZt0tt|n|Pq6hYXXc zWAyx-x3YD?n);k^FSi}PC@S!u3?0v_V^m6bSxI>w+|ds91E>+PXECyBpwu5Doi^hO zxqGOkM2;pAr)4p66B}2$lK^`T3dZ?A;Twc;=AhxH`&R)!YSo^EeBk&W0$BUoSejk; zt@PkK&N9hY*4y2}Ii2={h;*}5Ho~8)6f;kkDvo{c(v9}DU_Wa9`YyKw7`#FFf88iE zs1r`Xj;zot;Gtxw`ZO}BfA;`P^g<&;7ZFB>T5kbZ(gCW{mQg>*f%23)6vdhCS3n{$UU4ea`^(l~mQ9foDL;I~bm@SMT*$RSZTgr&ISl zd)OHdH<8>;^DL8o1*(&Y|KcLxvL=<2msj3Lx#?)w{*jQCONUt)telV!cp{SF>+tr| zW5`^cUEOA;W%zO@iMb;dY9tHNJdiXlDu+g{LEcr&0X)nTgbshg+Uq6So}E*=s<^4N zcB`vjnUCCyrVk~BDA|4-$%72syza*?@BFewhvxQjpm6o|O8C#PK)aOgZn}bdqjN`z z0wVm%M!Q!^CO!(jH;@B~DczRS-)26HR zctGG@p{lW$qS*Y5fpO&Cmb2a7r}I2Y!v1VzWC2H|vTL7Sgj80V-0O(<2$TrTh{Xuc z2&Rb52$cw%h?NN52Zw?h}4K$t)V4*jjhcFM8jV~eVw653*fI3g4^f1=(!6D* z`Z9)beC^UT{}}pK8>eY`?A`eWSAK$Oob;3St{(>FrVY|h1REtX;#K~0qRF?REq@6Q zAUiwzz&+^O(Qma@p;Z^ZlfuaE%{I@4@3I2|Exs?6KgHg%qv25C)L-roDfZ;=?|J+y z@$=V&H+wXclm>@L4b#V`3p);KI7URF98i7so*StQ z_?jMzZSxwkCzSP~8GmqV?cA{_6@x0K<)kBP{)qYoA>OK?>`Vp4dqd>m#EE?RiLdcl zc#f~Bc<0pD260v>5>OhgvC^XX9Gu*U7rykRC);9|$%rq_B4MS&50O8_{*7er~X z9Ew-re9UaD$M={NnV3U#tVmKiwkVI|<+y*4Q<~V8eakAae*PVk`Q-g%{QTmfgb<1! z0d+`+zq2(((rdhuVJ>7EIn}38IV9;-(;YTCB=F3XQEyzcZ_%m=ZdD+5V{a27a#2+V z;kx_~RyBl!W#NQjbZ`iB`e8X(G5Lp=%v;CRaUan_nI#sw1C1RXBBS9yJ7e#71dwJG zurcSBH@^GAPgkoyku@+UIAL93nmYv(wREiOjok&Tat_Q6Xk&H-IJiV_y}rG8#v&oY ztNUW0A)wK`Ml{2tViRhA8@W>UzBn}#xF5gn&TB$X7B7A9J489xmY0uVD>ePAuX$oA zxQm6r35TXle$*q2N*WM92qkhgh;f;a%f^jr)QE?Tfs!?!5~s%jd#{0zYJ@BI}IiU}oAh29B|=e1e3DWHT4;I$bd5r7sm^c^GyTw9mYyZWmuCaqI3MuEpeNc6;WuBq%Sq)VUwQ<})DGuP6R-B4X`t+`_t%{L*6e}bh@+w@66r6)|Ntj0X zLQ9$7QdDFVsD#_Z0<4m$eg&awGWra? z_W{5AG(c&Ur*Cy$x`K19)_@?aF+>g}ZhZWK0pX^W5*T5Io0yA!1qY7~dYCfvLqmb3 z&h!6#v9lIL`l<&zL*PQ9(KN8}Tm7t8>^mM@^4byNc-zU9)VD=2ic?=~m)oFV+UvXI z#Rjuo;ZK6f%!Wh6$~zsRJIbFY1gso%a%wFyPni)|aC6O78j>K(14#t`b~=7Xz&&C1i$H1!Aw<&d z-hM(-2B20Y@q4?MW0tL%t*SXl41^U&i>b0&X9KA=hP&NjucO;1i&y4V8{AR{HB!7Q z1>G|(f%MS!8ULF=!@?FpnZFzhW~E&u4@g?r`ExL44R`(5hhn5~_{F{jZPNbVlZz?L z$A6p-ujaODBb?CEoXX!-ciJ-vw4_13TtgO*6Gg)z4BW_tB@p63sHZ64fv8b%%mW&d z9~DBDSi}d4zsl;1M$rt}t#>AeLgvH!-zx}93scJ9I|(mR_u1);IxSC$Oa2b%E!CH1 zyd!9&oUGSiXj5SiqLI;3FXXcU^9FB96ZKo633Lch<&$M}5a{iJ@#bGZJ}U zm?M>o={F&RLU<(>6U?U(M5kP07D;?0Dtn)i*)}&BD+!n#J6~%;gY>#%P`|3z`xUnd zhLYN%UY!(;l^+IN-TY<$N59X4JB$O-l+rPgrPsH;Z+q4|LnQ6twzaMGT`IP|ZZq@= zG97>Sxi6APja@MPWFeV^2oO3PLm1G9B~}$VsUaybahN{?9EgABkU3rqm@y;x z>_Sg2q)>=N){Gdii*ShEiA;!`iLEohABmIh%5g}*?IM_>&ipzwEm>pub03bzOpygR}X42+C0W)FBxq z(rcp_!#VjN;6fHBXRhon*V#=G;utb?jEs4!&M-ob5KCU4q`oEH)e2q@)`7_T&+?<) z4xr4}YvML=9glegnu!r5G@oDE`wdAn`)g1*0*!#1mZRt|OL(#$Xt8KH6SfPemys8-7l{|C7p<;9!oYNZ3@a(`TUIs)(+M0iT-aDc^-@1< zMUXF%iwU!bvO>ySLJ*`Q|Lwhtf}*T?)S%w~JA#aB#^WjjMIH?_9M|`WFqltX-7fTSfaGR1ek=K)2s;RkRrrGCywbVjV4e^ zOl*;CN;3T~_*rO~nPzD~>Zi}3giQSog)irT?>R7uWWCEzh8o$!)m2xMGn{Ow(3hV# zAvdDEl^W;}F0Vu2wqK+hY54qj7mU=K6u1MsML2%TafeOMDi3V)k0inW+8I za#9T$ff4oAKBa@cFRDRfwv--z(zC+^?&WX&51gV)oUFBbB7yYPA1QdrV#f0h99m3f!LKw?<^;QmccsNZ5BNS|*p1aYTBR(Mq2!Vwm#@3{b0Emb7fBUcun8jxk+yV~;?PpB{+j&UZ^W21D`q zKJDKSBCvj0qroG?%Hc35yV9VJ&=d|9;aQ(wq(TdDzAmAWo^kGHzpo_7$*h990=^43 zE~To!WpP*0%iU4-$7B26oy`tjUIo1&Pj_p_L(}R71&v`mdeW>%25ZgrWrX}whc3{(!<3^lj)924t5+#9;hAyjxY_-d zJ;GrNL=Ed|qd?w1@XKFOcDyUvQKg2lC#_aLUeC32td#uJjh+NU(gVPWGt(5DH_6CE zXt(o{l9GTbDk}JT+)nsb4z`pt5=;FvtEv-%9$v1-)B#^~v8IIeO~>RYKbfx_3sAb` zk;ntk$8zZW%0|@;fXYwY0=0qp3WS`+zcZG~LVh1_ z6NgIvcAWw5zzD$DZV0?^M9{jJQz~KR-+1Zevpcqo>iD}7JixIv|9%4SZRC? zvc_>{JB9~u@+AH$oGC-kEz~se=>3ytMfO{E43v(k9uF901r<8R9`Xjm=zqWQ?|Nzp z{9t;ikZ>zj%EoD8{`^(hmpOy|bpM=S>8#q66*p)w zUnM0IDmbN|l@BsB1`4%BG+`70RGkxDJAhh zq0hYVs>#XaP=7~B`GaP2uS`u%@&2CxY73S02@>Mt!2k8DdgDfoxJr>(+IPU-%3Qo@ z8V*h#KlbjxuS`;jz~`cc{D{xXn$4v|TUUty*juUBQtz!)__vS$IRwZ`Gi01}A}1@l ziO-S!A-&+r<%_IE<3`x?ON4J)oZkIKzuw-rZ{KK_tLGq9g5? zRV4z@&KjuBNv-oJV3wT(EG`h(^IIRE07ZGmyrgh4lNMeaI$ajY*rjvo1$HL6>O z8PqPG-X(vZ((?xN>;3f1?95AHV^ zKYFAm-7KvzCYykkn%i5Mv1N-FvFz-e4`YECJdkAxQpqC}@(Mmb^AjOA`L98M#3&Hb z5?Tnd{1pN(yN9=$Ob+gcJf}=5Wt%o^5M4botjXY`b^X+ygZlS85)2U$;VM3lz3YEHdD3ZhJT+ye(EbCZ8y1ZiHk1Vg2C`}$f{=ql zL!clnIL1kfz83PcT{&7Zot^nVB!Jl4t?>L`aRqrN1eqj|eW0o@J))zc*yM>5S?RtF zqP4H5%_t>r?8sp^u3Wm%!uyez+Jk#{;q|K*e=PzeB^G-JVPT;V8xswop}{U|SFbo^ zRI+O|8HQva8tZGbcAvIlXV3gzT@sL1;CP--MT7Ab$4Sqiu{Z|>(-H}w#|kow*V26~ z0_5kCz>RpWumzG}JX-U6aBTMz1iU`phagn%acN|ElW>JXCLDbidU&;B4{t&J4_}y&KKou;fr!7AOYI2oV2=JZng;$ zK$~SH=&E#G zXiXlSUl1s6U!=4}Tgj1^o5wb9T+ck*UB%u`Z6wa?*HjnVY4e8lTQ8nJXHP47{CuAR z4zYg^0wg5H!Bx*ouzUMf{aMqejqlu{T`WykQaxhw5Ga?x693(&dslYtswevs4(ynG zA$HDG+E)*?M3}&HF9n`|%*%>=us8b%xtTsJGsQ>9$?#$LH!qX;h@W$^msEb0DsXbN zHzE_VP|iiwl=VBu!+ZDHkS_)>+Od^{EtZW(OP^Bjnlz}FK4<2PnfGqpGP!i&JOl>3 z{`(QY^U6iov2`@JQf-f`X}%} zDEUvZ7b;Zv%a4CVMn$rW_K@X$4w=WMyi?dUfl} z_U+!m!oxzvB#6J$A(X7r{psK#_C=pw%+}mYd@fq7LC;C?uUjQ8%|)o=93;TN6ltP9CDFwxa6scTQ# zK@*Kd1J=IHr)!c4+7ti-M*&2)V)ZN`E`r zS;+?V>vvZ%pX9TzWL z@Kuj4pWi_uaJ06KDqB-Pgor?Dk_sI)tn11~4 zLqG0g6Go3j3%)+uHF~0;%?m$J(ZLC;_HlIG8HRGGv^JdL*S+VR}4HULI zLZe>2dTJ#6r3>bl4FUgX0-y$f)2B}2>oVB3^@m!M#*M8rb<+5`eR_7gZD(a6)2^u@ zPAC;`Yp*Agf|4L;Qlv%G#%#ivQEc<~>)Flg*I7tNuvi!We{mEQ6^Ru%@7}q?wr<(X zrc4+wPS@Je(m8ESarqntb1ZQ?9wwq1T)AwC){d<|)EPf|)YM^vzBt>wkw+#OOynC=FCxQ< z48>A1&}Zl~vmRYKv1wB#GrSf%aqKtt@cun9v`T>%+A-t9f{i@O%gbZ2F)_^B>mfUJ z;)GaudFJ$KtWUSDtXU%u(UqkP9h&c;P&XB1B9F$PBuS=@lU-ilUfn!L3>iFo?dnwx z`}XQ-go4&7LCq53tBfBr8t&h}1OI9QOu$B1{_SEwR|)3MnyI;H{#?7s6UPnxyj|O! zs5W0vFQV*MLu_0N+Wy2qPYftgk2BWR6315xrchd1tL9Bv|6V=Wq;X?VU|%y-wQSp# zEuwcp8!6L1de^R85qH$Qim#XOZ`xYzr$dL>4kQunu`~OdX>8K?v1~x!K4QK7y3USb z3bv_%o|yh-T(SW}X;oEH@uiDsKWW-HXy~8;KQ37?fArdwE9*3C(!>mVyeSG>zntJz z(IW!10pLFZlq0}_efwb6H`8JM>{&4Lo3GWs=-bzL#`LdRfAjT}=~E|8_^D;nCK0r| z3B?bn(aA=jNE&$%;+{At4C;l(;sy!Y#ZgDp6hsc1Wvl1vByObK499BPPL;-N`kUT& zwYL@bO{DKreI&|qqVtHGEs(H{k$5Ga(Y1*Pbbhi`S~qVRGjiC_qm#yun>}>Uz)n57 zb#bw@urNbGli>Br_xRN-S6~~=g#QSjfB@f2n+pAV_lCK1X2AM2t7~rGyv2O<&|w`W zj2<<Ev2baES_A(eO~(D@%9DqE`|i1z<#SP$g7^Q8|c$#?MQ8 zwYY~;$$7-BbntU3l_M^}Mv~NZams4Zw9%73-Mbu_KYPaH;e!YE95;4MLu+emSM2T1 znwpxHC~T^Rt5G_}&&KB(@byBK%*Txy z*{E-iZaw;R@3wIC@S!`t=-bP)QGNGl2Wtx{B~e%!7q3~M9!=JGc@$4hnToHaDE*AS zUrGWoxdqSHz`agfw@w{y4(QWs7aDX+7tNjBuYaFjExLZ*p}v8>J{A6?b)trdS|vhP z6WzI8yWpFzzk(k&ZG!&@@DT_wZp27fJbxZ6m^VxOP5)j!V9fC0np-xmH(aw~S)Gx? zhIAV_Z19-bGp5ZOGH}4&E}yr%)}l$nR}I`Hz$V_CtIrw+Ay1T(WZ@*X)j%R zzaD;!{qyI{S~zLK*l_~~^y&HK#PJQj=-1cQ#o5`acJ12c zdU|?h+S=NNCDweI&_|CL4l}2J1=}`ng1-d;{=>T%J9?yA=g&Xa96x@vPS2j5bh~%& pu7$$YDiL Date: Sat, 11 Apr 2020 15:01:09 +0900 Subject: [PATCH 241/655] Add support for taikobigcircle and fix exception on missing layers --- osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs index af10944ee9..43d45ea1c9 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs @@ -6,6 +6,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -26,13 +28,24 @@ namespace osu.Game.Rulesets.Taiko.Skinning } [BackgroundDependencyLoader] - private void load(ISkinSource skin) + private void load(ISkinSource skin, DrawableHitObject drawableHitObject) { - InternalChildren = new[] + Drawable getDrawableFor(string lookup) { - backgroundLayer = skin.GetAnimation("taikohitcircle", true, false), - skin.GetAnimation("taikohitcircleoverlay", true, false), - }; + const string normal_hit = "taikohit"; + const string big_hit = "taikobig"; + + string prefix = ((drawableHitObject as DrawableTaikoHitObject)?.HitObject.IsStrong ?? false) ? big_hit : normal_hit; + + return skin.GetAnimation($"{prefix}{lookup}", true, false) ?? skin.GetAnimation($"{normal_hit}{lookup}", true, false); + } + + // backgroundLayer is guaranteed to exist due to the pre-check in TaikoLegacySkinTransformer + AddInternal(backgroundLayer = getDrawableFor("circle")); + + var foregroundLayer = getDrawableFor("circleoverlay"); + if (foregroundLayer != null) + AddInternal(foregroundLayer); // animations in taiko skins are used in a custom way (>150 combo and animating in time with beat). // for now just stop at first frame for sanity. From 3d5a622db7b0dbf8e4984c1ea57dba56c1d7393f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Apr 2020 15:04:58 +0900 Subject: [PATCH 242/655] Tidy up comments --- osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs index 43d45ea1c9..80bf97936d 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs @@ -37,18 +37,20 @@ namespace osu.Game.Rulesets.Taiko.Skinning string prefix = ((drawableHitObject as DrawableTaikoHitObject)?.HitObject.IsStrong ?? false) ? big_hit : normal_hit; - return skin.GetAnimation($"{prefix}{lookup}", true, false) ?? skin.GetAnimation($"{normal_hit}{lookup}", true, false); + return skin.GetAnimation($"{prefix}{lookup}", true, false) ?? + // fallback to regular size if "big" version doesn't exist. + skin.GetAnimation($"{normal_hit}{lookup}", true, false); } - // backgroundLayer is guaranteed to exist due to the pre-check in TaikoLegacySkinTransformer + // backgroundLayer is guaranteed to exist due to the pre-check in TaikoLegacySkinTransformer. AddInternal(backgroundLayer = getDrawableFor("circle")); var foregroundLayer = getDrawableFor("circleoverlay"); if (foregroundLayer != null) AddInternal(foregroundLayer); - // animations in taiko skins are used in a custom way (>150 combo and animating in time with beat). - // for now just stop at first frame for sanity. + // Animations in taiko skins are used in a custom way (>150 combo and animating in time with beat). + // For now just stop at first frame for sanity. foreach (var c in InternalChildren) { (c as IFramedAnimation)?.Stop(); @@ -66,8 +68,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning { base.Update(); - // not all skins (including the default osu-stable) have similar sizes for hitcircle and hitcircleoverlay. - // this ensures they are scaled relative to each other but also match the expected DrawableHit size. + // Not all skins (including the default osu-stable) have similar sizes for "hitcircle" and "hitcircleoverlay". + // This ensures they are scaled relative to each other but also match the expected DrawableHit size. foreach (var c in InternalChildren) c.Scale = new Vector2(DrawWidth / 128); } From e206df479b1636496a95f96711b6ccfa6a52696f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Apr 2020 15:13:20 +0900 Subject: [PATCH 243/655] 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 aaac6ec427..5b200ee104 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3e2c2b1599..7cf1272611 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,7 +22,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 7903d964ce..c58a431e80 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -79,7 +79,7 @@ - + From 12c21cba7e0150d0d14c3a5d5906b5e21409b132 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Apr 2020 15:20:27 +0900 Subject: [PATCH 244/655] Add missing masking specification --- .../Edit/Blueprints/HoldNoteSelectionBlueprint.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index f1750f4a01..d569d68b59 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -45,6 +45,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints new Container { RelativeSizeAxes = Axes.Both, + Masking = true, BorderThickness = 1, BorderColour = colours.Yellow, Child = new Box From eb1fbdacde77c9f7f29634d9f8953d7eb0e55dd7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Apr 2020 15:29:52 +0900 Subject: [PATCH 245/655] Remove unintentional edge effect --- osu.Game/Overlays/Music/CollectionsDropdown.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Music/CollectionsDropdown.cs b/osu.Game/Overlays/Music/CollectionsDropdown.cs index 4f59b053b6..5bd321f31e 100644 --- a/osu.Game/Overlays/Music/CollectionsDropdown.cs +++ b/osu.Game/Overlays/Music/CollectionsDropdown.cs @@ -29,14 +29,8 @@ namespace osu.Game.Overlays.Music { public CollectionsMenu() { + Masking = true; CornerRadius = 5; - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.3f), - Radius = 3, - Offset = new Vector2(0f, 1f), - }; } [BackgroundDependencyLoader] From a843793957583694bd12bcb765068da79c9388eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Apr 2020 16:41:11 +0900 Subject: [PATCH 246/655] Un-nest class --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 +- .../Screens/Select/DifficultyRecommender.cs | 75 +++++++++++++++++++ osu.Game/Screens/Select/SongSelect.cs | 62 --------------- 3 files changed, 78 insertions(+), 63 deletions(-) create mode 100644 osu.Game/Screens/Select/DifficultyRecommender.cs diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 3e619a1f80..555c74fb44 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -117,8 +117,10 @@ namespace osu.Game.Screens.Select private readonly Stack randomSelectedBeatmaps = new Stack(); protected List Items = new List(); + private CarouselRoot root; - public SongSelect.DifficultyRecommender DifficultyRecommender; + + public DifficultyRecommender DifficultyRecommender; public BeatmapCarousel() { diff --git a/osu.Game/Screens/Select/DifficultyRecommender.cs b/osu.Game/Screens/Select/DifficultyRecommender.cs new file mode 100644 index 0000000000..d89d505f61 --- /dev/null +++ b/osu.Game/Screens/Select/DifficultyRecommender.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; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Rulesets; + +namespace osu.Game.Screens.Select +{ + public class DifficultyRecommender : Component + { + [Resolved] + private IAPIProvider api { get; set; } + + [Resolved] + private RulesetStore rulesets { get; set; } + + private readonly Dictionary recommendedStarDifficulty = new Dictionary(); + + private int pendingAPIRequests; + + [BackgroundDependencyLoader] + private void load() + { + updateRecommended(); + } + + private void updateRecommended() + { + if (pendingAPIRequests > 0) + return; + if (api.LocalUser.Value is GuestUser) + return; + + rulesets.AvailableRulesets.ForEach(rulesetInfo => + { + var req = new GetUserRequest(api.LocalUser.Value.Id, rulesetInfo); + + req.Success += result => + { + // algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505 + recommendedStarDifficulty[rulesetInfo] = Math.Pow((double)(result.Statistics.PP ?? 0), 0.4) * 0.195; + pendingAPIRequests--; + }; + + req.Failure += _ => pendingAPIRequests--; + + pendingAPIRequests++; + api.Queue(req); + }); + } + + public BeatmapInfo GetRecommendedBeatmap(IEnumerable beatmaps, RulesetInfo currentRuleset) + { + if (!recommendedStarDifficulty.ContainsKey(currentRuleset)) + { + updateRecommended(); + return null; + } + + return beatmaps.OrderBy(b => + { + var difference = b.StarDifficulty - recommendedStarDifficulty[currentRuleset]; + return difference >= 0 ? difference * 2 : difference * -1; // prefer easier over harder + }).FirstOrDefault(); + } + } +} diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index d6bc20df39..9897515615 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -36,9 +36,6 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Game.Overlays.Notifications; using osu.Game.Scoring; -using osu.Game.Online.API; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Game.Online.API.Requests; namespace osu.Game.Screens.Select { @@ -787,65 +784,6 @@ namespace osu.Game.Screens.Select return base.OnKeyDown(e); } - public class DifficultyRecommender : Component - { - [Resolved] - private IAPIProvider api { get; set; } - - [Resolved] - private RulesetStore rulesets { get; set; } - - private readonly Dictionary recommendedStarDifficulty = new Dictionary(); - - private int pendingAPIRequests; - - [BackgroundDependencyLoader] - private void load() - { - updateRecommended(); - } - - private void updateRecommended() - { - if (pendingAPIRequests > 0) - return; - if (api.LocalUser.Value is GuestUser) - return; - - rulesets.AvailableRulesets.ForEach(rulesetInfo => - { - var req = new GetUserRequest(api.LocalUser.Value.Id, rulesetInfo); - - req.Success += result => - { - // algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505 - recommendedStarDifficulty[rulesetInfo] = Math.Pow((double)(result.Statistics.PP ?? 0), 0.4) * 0.195; - pendingAPIRequests--; - }; - - req.Failure += _ => pendingAPIRequests--; - - pendingAPIRequests++; - api.Queue(req); - }); - } - - public BeatmapInfo GetRecommendedBeatmap(IEnumerable beatmaps, RulesetInfo currentRuleset) - { - if (!recommendedStarDifficulty.ContainsKey(currentRuleset)) - { - updateRecommended(); - return null; - } - - return beatmaps.OrderBy(b => - { - var difference = b.StarDifficulty - recommendedStarDifficulty[currentRuleset]; - return difference >= 0 ? difference * 2 : difference * -1; // prefer easier over harder - }).FirstOrDefault(); - } - } - private class VerticalMaskingContainer : Container { private const float panel_overflow = 1.2f; From 7f753f6b4d79a2bbee5c169cdd10e39f4dd158e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Apr 2020 16:43:09 +0900 Subject: [PATCH 247/655] Remove current ruleset from function call --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- osu.Game/Screens/Select/DifficultyRecommender.cs | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 555c74fb44..7139b804b0 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -588,7 +588,7 @@ namespace osu.Game.Screens.Select BeatmapInfo recommender(IEnumerable beatmaps) { - return DifficultyRecommender?.GetRecommendedBeatmap(beatmaps, decoupledRuleset.Value); + return DifficultyRecommender?.GetRecommendedBeatmap(beatmaps); } var set = new CarouselBeatmapSet(beatmapSet, recommender); diff --git a/osu.Game/Screens/Select/DifficultyRecommender.cs b/osu.Game/Screens/Select/DifficultyRecommender.cs index d89d505f61..fb67d63818 100644 --- a/osu.Game/Screens/Select/DifficultyRecommender.cs +++ b/osu.Game/Screens/Select/DifficultyRecommender.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Game.Beatmaps; @@ -22,6 +23,9 @@ namespace osu.Game.Screens.Select [Resolved] private RulesetStore rulesets { get; set; } + [Resolved] + private Bindable ruleset { get; set; } + private readonly Dictionary recommendedStarDifficulty = new Dictionary(); private int pendingAPIRequests; @@ -57,9 +61,9 @@ namespace osu.Game.Screens.Select }); } - public BeatmapInfo GetRecommendedBeatmap(IEnumerable beatmaps, RulesetInfo currentRuleset) + public BeatmapInfo GetRecommendedBeatmap(IEnumerable beatmaps) { - if (!recommendedStarDifficulty.ContainsKey(currentRuleset)) + if (!recommendedStarDifficulty.ContainsKey(ruleset.Value)) { updateRecommended(); return null; @@ -67,7 +71,7 @@ namespace osu.Game.Screens.Select return beatmaps.OrderBy(b => { - var difference = b.StarDifficulty - recommendedStarDifficulty[currentRuleset]; + var difference = b.StarDifficulty - recommendedStarDifficulty[ruleset.Value]; return difference >= 0 ? difference * 2 : difference * -1; // prefer easier over harder }).FirstOrDefault(); } From a84fe2525ba17ac331c198e5c1ec80c061f1066f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 11 Apr 2020 16:53:45 +0900 Subject: [PATCH 248/655] Fix nested hitobjects potentially indirectly masked away --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 0011faefbb..8fa0c041d4 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -375,7 +375,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } } - protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => AllJudged && base.ComputeIsMaskedAway(maskingBounds); + public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => AllJudged && base.UpdateSubTreeMasking(source, maskingBounds); protected override void UpdateAfterChildren() { From abea7b5299a5fc38c12742aad9d741dec3be7f3a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Apr 2020 16:58:13 +0900 Subject: [PATCH 249/655] Tidy up function passing, naming, ordering etc. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 15 +++---- .../Select/Carousel/CarouselBeatmapSet.cs | 13 +++--- .../Screens/Select/DifficultyRecommender.cs | 42 +++++++++++-------- osu.Game/Screens/Select/SongSelect.cs | 8 ++-- 4 files changed, 42 insertions(+), 36 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 7139b804b0..3e3bb4dbc5 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -49,6 +49,11 @@ namespace osu.Game.Screens.Select /// public BeatmapSetInfo SelectedBeatmapSet => selectedBeatmapSet?.BeatmapSet; + /// + /// A function to optionally decide on a recommended difficulty from a beatmap set. + /// + public Func, BeatmapInfo> GetRecommendedBeatmap; + private CarouselBeatmapSet selectedBeatmapSet; /// @@ -120,8 +125,6 @@ namespace osu.Game.Screens.Select private CarouselRoot root; - public DifficultyRecommender DifficultyRecommender; - public BeatmapCarousel() { root = new CarouselRoot(this); @@ -586,12 +589,10 @@ namespace osu.Game.Screens.Select b.Metadata = beatmapSet.Metadata; } - BeatmapInfo recommender(IEnumerable beatmaps) + var set = new CarouselBeatmapSet(beatmapSet) { - return DifficultyRecommender?.GetRecommendedBeatmap(beatmaps); - } - - var set = new CarouselBeatmapSet(beatmapSet, recommender); + GetRecommendedBeatmap = beatmaps => GetRecommendedBeatmap?.Invoke(beatmaps) + }; foreach (var c in set.Beatmaps) { diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 99ded4c58e..92ccfde14b 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -12,13 +12,13 @@ namespace osu.Game.Screens.Select.Carousel { public class CarouselBeatmapSet : CarouselGroupEagerSelect { - private readonly Func, BeatmapInfo> getRecommendedBeatmap; - public IEnumerable Beatmaps => InternalChildren.OfType(); public BeatmapSetInfo BeatmapSet; - public CarouselBeatmapSet(BeatmapSetInfo beatmapSet, Func, BeatmapInfo> getRecommendedBeatmap) + public Func, BeatmapInfo> GetRecommendedBeatmap; + + public CarouselBeatmapSet(BeatmapSetInfo beatmapSet) { BeatmapSet = beatmapSet ?? throw new ArgumentNullException(nameof(beatmapSet)); @@ -26,8 +26,6 @@ namespace osu.Game.Screens.Select.Carousel .Where(b => !b.Hidden) .Select(b => new CarouselBeatmap(b)) .ForEach(AddChild); - - this.getRecommendedBeatmap = getRecommendedBeatmap; } protected override DrawableCarouselItem CreateDrawableRepresentation() => new DrawableCarouselBeatmapSet(this); @@ -36,9 +34,8 @@ namespace osu.Game.Screens.Select.Carousel { if (LastSelected == null) { - var recommendedBeatmapInfo = getRecommendedBeatmap(Children.OfType().Where(b => !b.Filtered.Value).Select(b => b.Beatmap)); - if (recommendedBeatmapInfo != null) - return Children.OfType().First(b => b.Beatmap == recommendedBeatmapInfo); + if (GetRecommendedBeatmap?.Invoke(Children.OfType().Where(b => !b.Filtered.Value).Select(b => b.Beatmap)) is BeatmapInfo recommended) + return Children.OfType().First(b => b.Beatmap == recommended); } return base.GetNextToSelect(); diff --git a/osu.Game/Screens/Select/DifficultyRecommender.cs b/osu.Game/Screens/Select/DifficultyRecommender.cs index fb67d63818..47838ebd6d 100644 --- a/osu.Game/Screens/Select/DifficultyRecommender.cs +++ b/osu.Game/Screens/Select/DifficultyRecommender.cs @@ -33,10 +33,33 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader] private void load() { - updateRecommended(); + calculateRecommendedDifficulties(); } - private void updateRecommended() + /// + /// Find the recommended difficulty from a selection of available difficulties for the current local user. + /// + /// + /// This requires the user to be online for now. + /// + /// A collection of beatmaps to select a difficulty from. + /// The recommended difficulty, or null if a recommendation could not be provided. + public BeatmapInfo GetRecommendedBeatmap(IEnumerable beatmaps) + { + if (!recommendedStarDifficulty.ContainsKey(ruleset.Value)) + { + calculateRecommendedDifficulties(); + return null; + } + + return beatmaps.OrderBy(b => + { + var difference = b.StarDifficulty - recommendedStarDifficulty[ruleset.Value]; + return difference >= 0 ? difference * 2 : difference * -1; // prefer easier over harder + }).FirstOrDefault(); + } + + private void calculateRecommendedDifficulties() { if (pendingAPIRequests > 0) return; @@ -60,20 +83,5 @@ namespace osu.Game.Screens.Select api.Queue(req); }); } - - public BeatmapInfo GetRecommendedBeatmap(IEnumerable beatmaps) - { - if (!recommendedStarDifficulty.ContainsKey(ruleset.Value)) - { - updateRecommended(); - return null; - } - - return beatmaps.OrderBy(b => - { - var difference = b.StarDifficulty - recommendedStarDifficulty[ruleset.Value]; - return difference >= 0 ? difference * 2 : difference * -1; // prefer easier over harder - }).FirstOrDefault(); - } } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 9897515615..f164056ede 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -80,7 +80,8 @@ namespace osu.Game.Screens.Select protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value); protected BeatmapCarousel Carousel { get; private set; } - private DifficultyRecommender difficultyRecommender; + + private DifficultyRecommender recommender; private BeatmapInfoWedge beatmapInfoWedge; private DialogOverlay dialogOverlay; @@ -108,10 +109,9 @@ namespace osu.Game.Screens.Select // initial value transfer is required for FilterControl (it uses our re-cached bindables in its async load for the initial filter). transferRulesetValue(); - AddInternal(difficultyRecommender = new DifficultyRecommender()); - AddRangeInternal(new Drawable[] { + recommender = new DifficultyRecommender(), new ResetScrollContainer(() => Carousel.ScrollToSelected()) { RelativeSizeAxes = Axes.Y, @@ -159,7 +159,7 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.Both, SelectionChanged = updateSelectedBeatmap, BeatmapSetsChanged = carouselBeatmapsLoaded, - DifficultyRecommender = difficultyRecommender, + GetRecommendedBeatmap = recommender.GetRecommendedBeatmap, }, } }, From 310cf830d47a4618f33b2bc1fe646aa516823ea8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Apr 2020 17:07:08 +0900 Subject: [PATCH 250/655] Simplify api request logic --- .../Screens/Select/DifficultyRecommender.cs | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Select/DifficultyRecommender.cs b/osu.Game/Screens/Select/DifficultyRecommender.cs index 47838ebd6d..595bfd6122 100644 --- a/osu.Game/Screens/Select/DifficultyRecommender.cs +++ b/osu.Game/Screens/Select/DifficultyRecommender.cs @@ -15,7 +15,7 @@ using osu.Game.Rulesets; namespace osu.Game.Screens.Select { - public class DifficultyRecommender : Component + public class DifficultyRecommender : Component, IOnlineComponent { [Resolved] private IAPIProvider api { get; set; } @@ -28,12 +28,10 @@ namespace osu.Game.Screens.Select private readonly Dictionary recommendedStarDifficulty = new Dictionary(); - private int pendingAPIRequests; - [BackgroundDependencyLoader] private void load() { - calculateRecommendedDifficulties(); + api.Register(this); } /// @@ -48,7 +46,6 @@ namespace osu.Game.Screens.Select { if (!recommendedStarDifficulty.ContainsKey(ruleset.Value)) { - calculateRecommendedDifficulties(); return null; } @@ -61,11 +58,6 @@ namespace osu.Game.Screens.Select private void calculateRecommendedDifficulties() { - if (pendingAPIRequests > 0) - return; - if (api.LocalUser.Value is GuestUser) - return; - rulesets.AvailableRulesets.ForEach(rulesetInfo => { var req = new GetUserRequest(api.LocalUser.Value.Id, rulesetInfo); @@ -74,14 +66,27 @@ namespace osu.Game.Screens.Select { // algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505 recommendedStarDifficulty[rulesetInfo] = Math.Pow((double)(result.Statistics.PP ?? 0), 0.4) * 0.195; - pendingAPIRequests--; }; - req.Failure += _ => pendingAPIRequests--; - - pendingAPIRequests++; api.Queue(req); }); } + + public void APIStateChanged(IAPIProvider api, APIState state) + { + switch (state) + { + case APIState.Online: + calculateRecommendedDifficulties(); + break; + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + api.Unregister(this); + } } } From 7aac0e59a8c02ab765b53b196177a79f43bb294e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Apr 2020 17:08:07 +0900 Subject: [PATCH 251/655] Reduce dictionary lookups --- osu.Game/Screens/Select/DifficultyRecommender.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/DifficultyRecommender.cs b/osu.Game/Screens/Select/DifficultyRecommender.cs index 595bfd6122..20cdca858a 100644 --- a/osu.Game/Screens/Select/DifficultyRecommender.cs +++ b/osu.Game/Screens/Select/DifficultyRecommender.cs @@ -44,16 +44,16 @@ namespace osu.Game.Screens.Select /// The recommended difficulty, or null if a recommendation could not be provided. public BeatmapInfo GetRecommendedBeatmap(IEnumerable beatmaps) { - if (!recommendedStarDifficulty.ContainsKey(ruleset.Value)) + if (recommendedStarDifficulty.TryGetValue(ruleset.Value, out var stars)) { - return null; + return beatmaps.OrderBy(b => + { + var difference = b.StarDifficulty - stars; + return difference >= 0 ? difference * 2 : difference * -1; // prefer easier over harder + }).FirstOrDefault(); } - return beatmaps.OrderBy(b => - { - var difference = b.StarDifficulty - recommendedStarDifficulty[ruleset.Value]; - return difference >= 0 ? difference * 2 : difference * -1; // prefer easier over harder - }).FirstOrDefault(); + return null; } private void calculateRecommendedDifficulties() From c0c1f2c0235c9e49c8c44541ad1d266a182bb017 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Apr 2020 17:17:18 +0900 Subject: [PATCH 252/655] Add test coverage --- .../SongSelect/TestSceneBeatmapCarousel.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 76a8ee9914..f68ed4154b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -54,6 +54,35 @@ namespace osu.Game.Tests.Visual.SongSelect this.rulesets = rulesets; } + [Test] + public void TestRecommendedSelection() + { + loadBeatmaps(); + + AddStep("set recommendation function", () => carousel.GetRecommendedBeatmap = beatmaps => beatmaps.LastOrDefault()); + + // check recommended was selected + advanceSelection(direction: 1, diff: false); + waitForSelection(1, 3); + + // change away from recommended + advanceSelection(direction: -1, diff: true); + waitForSelection(1, 2); + + // next set, check recommended + advanceSelection(direction: 1, diff: false); + waitForSelection(2, 3); + + // next set, check recommended + advanceSelection(direction: 1, diff: false); + waitForSelection(3, 3); + + // go back to first set and ensure user selection was retained + advanceSelection(direction: -1, diff: false); + advanceSelection(direction: -1, diff: false); + waitForSelection(1, 2); + } + /// /// Test keyboard traversal /// From 73a3f1fe65d6eb17ca9eef1a5b3cd8a843f0e3eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Apr 2020 17:30:34 +0900 Subject: [PATCH 253/655] Remove unnecessary DI --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 3e3bb4dbc5..a8225ba1ec 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -23,7 +23,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Input.Bindings; using osu.Game.Screens.Select.Carousel; -using osu.Game.Rulesets; namespace osu.Game.Screens.Select { @@ -146,9 +145,6 @@ namespace osu.Game.Screens.Select [Resolved] private BeatmapManager beatmaps { get; set; } - [Resolved] - private Bindable decoupledRuleset { get; set; } - [BackgroundDependencyLoader(permitNulls: true)] private void load(OsuConfigManager config) { From 832822858ca2c17eac82ab669f4c10634978c58c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Apr 2020 17:47:51 +0900 Subject: [PATCH 254/655] Add basic request / response support --- .../Online/TestDummyAPIRequestHandling.cs | 39 +++++++++++++++++++ osu.Game/Online/API/APIRequest.cs | 2 + osu.Game/Online/API/DummyAPIAccess.cs | 7 ++++ 3 files changed, 48 insertions(+) create mode 100644 osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs diff --git a/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs b/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs new file mode 100644 index 0000000000..bf3e1204d7 --- /dev/null +++ b/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs @@ -0,0 +1,39 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Online +{ + public class TestDummyAPIRequestHandling : OsuTestScene + { + public TestDummyAPIRequestHandling() + { + AddStep("register request handling", () => ((DummyAPIAccess)API).HandleRequest = req => + { + switch (req) + { + case CommentVoteRequest cRequest: + cRequest.TriggerSuccess(new CommentBundle()); + break; + } + }); + + CommentVoteRequest request = null; + CommentBundle response = null; + + AddStep("fire request", () => + { + response = null; + request = new CommentVoteRequest(1, CommentVoteAction.Vote); + request.Success += res => response = res; + API.Queue(request); + }); + + AddAssert("got response", () => response != null); + } + } +} diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 6a6c7b72a8..1f0eae4965 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -30,6 +30,8 @@ namespace osu.Game.Online.API /// This will be scheduled to the API's internal scheduler (run on update thread automatically). /// public new event APISuccessHandler Success; + + internal void TriggerSuccess(T result) => Success?.Invoke(result); } /// diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index a1c3475fd9..fa5ad115d2 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -30,6 +31,11 @@ namespace osu.Game.Online.API private readonly List components = new List(); + /// + /// Provide handling logic for an arbitrary API request. + /// + public Action HandleRequest; + public APIState State { get => state; @@ -55,6 +61,7 @@ namespace osu.Game.Online.API public virtual void Queue(APIRequest request) { + HandleRequest?.Invoke(request); } public void Perform(APIRequest request) { } From 415adecdf68c07d8882ca7ecdb2c099d6749243a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Apr 2020 18:02:43 +0900 Subject: [PATCH 255/655] Add support for Result fetching --- osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs | 8 ++++++-- osu.Game/Online/API/APIRequest.cs | 8 +++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs b/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs index bf3e1204d7..5b169cccdf 100644 --- a/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs +++ b/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs @@ -10,7 +10,8 @@ namespace osu.Game.Tests.Online { public class TestDummyAPIRequestHandling : OsuTestScene { - public TestDummyAPIRequestHandling() + [Test] + public void TestGenericRequestHandling() { AddStep("register request handling", () => ((DummyAPIAccess)API).HandleRequest = req => { @@ -33,7 +34,10 @@ namespace osu.Game.Tests.Online API.Queue(request); }); - AddAssert("got response", () => response != null); + AddAssert("response event fired", () => response != null); + + AddAssert("request has response", () => request.Result == response); + } } } } diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 1f0eae4965..34b69b3c09 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using Newtonsoft.Json; using osu.Framework.IO.Network; using osu.Framework.Logging; @@ -98,10 +99,15 @@ namespace osu.Game.Online.API { if (cancelled) return; - Success?.Invoke(); + TriggerSuccess(); }); } + internal void TriggerSuccess() + { + Success?.Invoke(); + } + public void Cancel() => Fail(new OperationCanceledException(@"Request cancelled")); public void Fail(Exception e) From c96df9758674b58df599ca8f9153f9f5c1d3e206 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Apr 2020 18:02:49 +0900 Subject: [PATCH 256/655] Add support for non-generic requests --- .../Online/TestDummyAPIRequestHandling.cs | 29 +++++++++++++++++++ osu.Game/Online/API/APIRequest.cs | 15 ++++++---- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs b/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs index 5b169cccdf..b00b63f6d5 100644 --- a/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs +++ b/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs @@ -1,10 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using NUnit.Framework; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Chat; using osu.Game.Tests.Visual; +using osu.Game.Users; namespace osu.Game.Tests.Online { @@ -38,6 +41,32 @@ namespace osu.Game.Tests.Online AddAssert("request has response", () => request.Result == response); } + + [Test] + public void TestRequestHandling() + { + AddStep("register request handling", () => ((DummyAPIAccess)API).HandleRequest = req => + { + switch (req) + { + case LeaveChannelRequest cRequest: + cRequest.TriggerSuccess(); + break; + } + }); + + LeaveChannelRequest request; + bool gotResponse = false; + + AddStep("fire request", () => + { + gotResponse = false; + request = new LeaveChannelRequest(new Channel(), new User()); + request.Success += () => gotResponse = true; + API.Queue(request); + }); + + AddAssert("response event fired", () => gotResponse); } } } diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 34b69b3c09..6abb388c01 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -17,22 +17,27 @@ namespace osu.Game.Online.API { protected override WebRequest CreateWebRequest() => new OsuJsonWebRequest(Uri); - public T Result => ((OsuJsonWebRequest)WebRequest)?.ResponseObject; + public T Result { get; private set; } protected APIRequest() { - base.Success += onSuccess; + base.Success += () => TriggerSuccess(((OsuJsonWebRequest)WebRequest)?.ResponseObject); } - private void onSuccess() => Success?.Invoke(Result); - /// /// Invoked on successful completion of an API request. /// This will be scheduled to the API's internal scheduler (run on update thread automatically). /// public new event APISuccessHandler Success; - internal void TriggerSuccess(T result) => Success?.Invoke(result); + internal void TriggerSuccess(T result) + { + // disallow calling twice + Debug.Assert(Result == null); + + Result = result; + Success?.Invoke(result); + } } /// From 1c0ad13d82bec928abb9f0cdb9e826f0cf23c7f8 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Sat, 11 Apr 2020 17:20:37 +0800 Subject: [PATCH 257/655] Added ignore hit object --- osu.Game.Rulesets.Taiko/Objects/IgnoreHit.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko/Objects/IgnoreHit.cs diff --git a/osu.Game.Rulesets.Taiko/Objects/IgnoreHit.cs b/osu.Game.Rulesets.Taiko/Objects/IgnoreHit.cs new file mode 100644 index 0000000000..302f940ef4 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Objects/IgnoreHit.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Judgements; + +namespace osu.Game.Rulesets.Taiko.Objects +{ + public class IgnoreHit : Hit + { + public override Judgement CreateJudgement() => new IgnoreJudgement(); + } +} From 3ad36c7b84e920a13e48b160888cab9874bac20d Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Sat, 11 Apr 2020 17:20:52 +0800 Subject: [PATCH 258/655] Moved flying objects to use ignore hit judgements --- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs | 3 ++- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs index 86e885239f..4468c1e3fb 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; @@ -35,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } public DrawableFlyingCentreHit(double time, bool isStrong = false) - : base(new Hit { StartTime = time, IsStrong = isStrong }) + : base(new IgnoreHit { StartTime = time, IsStrong = isStrong }) { HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs index ad9872b21f..e5cfc0562b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } public DrawableFlyingRimHit(double time, bool isStrong = false) - : base(new Hit { StartTime = time, IsStrong = isStrong }) + : base(new IgnoreHit { StartTime = time, IsStrong = isStrong }) { HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); } From df76636ffc5be7d5d817a7a943ec56a505339855 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 11 Apr 2020 14:08:16 +0300 Subject: [PATCH 259/655] Implement "prefer no video" option --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ osu.Game/Overlays/Direct/PanelDownloadButton.cs | 10 ++++++---- .../Overlays/Settings/Sections/Online/WebSettings.cs | 5 +++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 41f6747b74..89eb084262 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -49,6 +49,7 @@ namespace osu.Game.Configuration }; Set(OsuSetting.ExternalLinkWarning, true); + Set(OsuSetting.PreferNoVideo, false); // Audio Set(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01); @@ -212,6 +213,7 @@ namespace osu.Game.Configuration IncreaseFirstObjectVisibility, ScoreDisplayMode, ExternalLinkWarning, + PreferNoVideo, Scaling, ScalingPositionX, ScalingPositionY, diff --git a/osu.Game/Overlays/Direct/PanelDownloadButton.cs b/osu.Game/Overlays/Direct/PanelDownloadButton.cs index 1b3657f010..f09586c571 100644 --- a/osu.Game/Overlays/Direct/PanelDownloadButton.cs +++ b/osu.Game/Overlays/Direct/PanelDownloadButton.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online; @@ -14,12 +15,12 @@ namespace osu.Game.Overlays.Direct { protected bool DownloadEnabled => button.Enabled.Value; - private readonly bool noVideo; + private readonly bool? noVideo; private readonly ShakeContainer shakeContainer; private readonly DownloadButton button; - public PanelDownloadButton(BeatmapSetInfo beatmapSet, bool noVideo = false) + public PanelDownloadButton(BeatmapSetInfo beatmapSet, bool? noVideo = null) : base(beatmapSet) { this.noVideo = noVideo; @@ -43,7 +44,7 @@ namespace osu.Game.Overlays.Direct } [BackgroundDependencyLoader(true)] - private void load(OsuGame game, BeatmapManager beatmaps) + private void load(OsuGame game, BeatmapManager beatmaps, OsuConfigManager osuConfig) { if (BeatmapSet.Value?.OnlineInfo?.Availability?.DownloadDisabled ?? false) { @@ -66,7 +67,8 @@ namespace osu.Game.Overlays.Direct break; default: - beatmaps.Download(BeatmapSet.Value, noVideo); + var minimiseDownloadSize = noVideo ?? osuConfig.GetBindable(OsuSetting.PreferNoVideo).Value; + beatmaps.Download(BeatmapSet.Value, minimiseDownloadSize); break; } }; diff --git a/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs index a8b3e45a83..da3176aca8 100644 --- a/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs @@ -21,6 +21,11 @@ namespace osu.Game.Overlays.Settings.Sections.Online LabelText = "Warn about opening external links", Bindable = config.GetBindable(OsuSetting.ExternalLinkWarning) }, + new SettingsCheckbox + { + LabelText = "Prefer no-video downloads", + Bindable = config.GetBindable(OsuSetting.PreferNoVideo) + }, }; } } From fc1d497a864c48adf3d702beb1c0b7c93d23ddcd Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 11 Apr 2020 14:21:28 +0300 Subject: [PATCH 260/655] Change PlaylistDownloadButton default --- osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs index ed3f9af8e2..d58218b6b5 100644 --- a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs @@ -212,7 +212,7 @@ namespace osu.Game.Screens.Multi private class PlaylistDownloadButton : PanelDownloadButton { - public PlaylistDownloadButton(BeatmapSetInfo beatmapSet, bool noVideo = false) + public PlaylistDownloadButton(BeatmapSetInfo beatmapSet, bool? noVideo = null) : base(beatmapSet, noVideo) { Alpha = 0; From f274ec297ce23e79eff1d2a64f745e1396b0c280 Mon Sep 17 00:00:00 2001 From: Fire937 Date: Sun, 12 Apr 2020 01:33:25 +0200 Subject: [PATCH 261/655] Add positional sound support for all rulesets The SamplePlaybackBalance is calculated in a way that the balance remains between -0.4 and 0.4. Positional sound is not supported in osu!taiko. --- .../Drawables/DrawableCatchHitObject.cs | 2 ++ .../Drawables/DrawableManiaHitObject.cs | 16 ++++++++++++ .../Objects/Drawables/DrawableOsuHitObject.cs | 2 ++ .../Objects/Drawables/DrawableHitObject.cs | 26 ++++++------------- 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs index 6844be5941..e726d6eff5 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs @@ -70,6 +70,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale; + protected override float SamplePlaybackBalance => 0.8f * HitObject.X - 0.4f; + protected DrawableCatchHitObject(CatchHitObject hitObject) : base(hitObject) { diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 5bfa07bd14..76e9695855 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -5,8 +5,10 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Rulesets.Mania.UI; namespace osu.Game.Rulesets.Mania.Objects.Drawables { @@ -24,6 +26,20 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables protected readonly IBindable Direction = new Bindable(); + protected override float SamplePlaybackBalance + { + get + { + CompositeDrawable stage = this; + while (!(stage is Stage)) + stage = stage.Parent; + + var columnCount = ((Stage)stage).Columns.Count; + var columnIndex = HitObject.Column; + return 0.8f * columnIndex / (columnCount - 1) - 0.4f; + } + } + protected DrawableManiaHitObject(ManiaHitObject hitObject) : base(hitObject) { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index a677cb6a72..c5a4491b2d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // Must be set to update IsHovered as it's used in relax mdo to detect osu hit objects. public override bool HandlePositionalInput => true; + protected override float SamplePlaybackBalance => (HitObject.X / 512f - 0.5f) * 0.8f; + protected DrawableOsuHitObject(OsuHitObject hitObject) : base(hitObject) { diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 30a9106ddc..ef32dc1560 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -33,11 +33,6 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public readonly Bindable AccentColour = new Bindable(Color4.Gray); - /// - /// The stereo balance of the samples if the Positional hitsounds setting is set. - /// - private readonly BindableDouble positionalSoundAdjustment = new BindableDouble(); - protected SkinnableSound Samples { get; private set; } protected virtual IEnumerable GetSamples() => HitObject.Samples; @@ -94,7 +89,9 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// The stereo balance of the samples played if Positional hitsounds is set. /// - protected virtual float PositionalSound => (Position.X / 512f - 0.5f) * 0.8f; + protected virtual float SamplePlaybackBalance => 0; + + private readonly BindableDouble samplePlaybackBalanceAdjustment = new BindableDouble(); private BindableList samplesBindable; private Bindable startTimeBindable; @@ -119,6 +116,7 @@ namespace osu.Game.Rulesets.Objects.Drawables [BackgroundDependencyLoader] private void load(OsuConfigManager config) { + userPositionalHitSounds = config.GetBindable(OsuSetting.PositionalHitSounds); var judgement = HitObject.CreateJudgement(); Result = CreateResult(judgement); @@ -126,16 +124,6 @@ namespace osu.Game.Rulesets.Objects.Drawables throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); loadSamples(); - - userPositionalHitSounds = config.GetBindable(OsuSetting.PositionalHitSounds); - userPositionalHitSounds.BindValueChanged(positional => - { - if (positional.NewValue) - Samples?.AddAdjustment(AdjustableProperty.Balance, positionalSoundAdjustment); - else - Samples?.RemoveAdjustment(AdjustableProperty.Balance, positionalSoundAdjustment); - }); - userPositionalHitSounds.TriggerChange(); } protected override void LoadComplete() @@ -179,7 +167,9 @@ namespace osu.Game.Rulesets.Objects.Drawables + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); } - AddInternal(Samples = new SkinnableSound(samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)))); + Samples = new SkinnableSound(samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s))); + Samples.AddAdjustment(AdjustableProperty.Balance, samplePlaybackBalanceAdjustment); + AddInternal(Samples); } private void onDefaultsApplied() => apply(HitObject); @@ -378,7 +368,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public virtual void PlaySamples() { - positionalSoundAdjustment.Value = PositionalSound; + samplePlaybackBalanceAdjustment.Value = userPositionalHitSounds.Value ? SamplePlaybackBalance : 0; Samples?.Play(); } From 162a85042a6e15be69f57388e50669e174f81792 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Sun, 12 Apr 2020 10:38:22 +0800 Subject: [PATCH 262/655] Removed un-needed using --- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs index 4468c1e3fb..b6f6f04821 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; -using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; From 7a9ee907bf4d862c1e49188a8c835a2412977fb9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 12 Apr 2020 07:34:58 +0300 Subject: [PATCH 263/655] Fix incorrect button state in some cases --- osu.Game/Overlays/OverlayScrollContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OverlayScrollContainer.cs b/osu.Game/Overlays/OverlayScrollContainer.cs index a6c687f28f..a8f2a0ce6c 100644 --- a/osu.Game/Overlays/OverlayScrollContainer.cs +++ b/osu.Game/Overlays/OverlayScrollContainer.cs @@ -60,7 +60,7 @@ namespace osu.Game.Overlays return; currentTarget = Target; - Button.State = Current > button_scroll_position ? Visibility.Visible : Visibility.Hidden; + Button.State = Target > button_scroll_position ? Visibility.Visible : Visibility.Hidden; } public class ScrollToTopButton : OsuHoverContainer From f8b728f9e86e7bb0c8e2a6686859201dc7b26f49 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Tue, 7 Apr 2020 16:39:41 +0800 Subject: [PATCH 264/655] =?UTF-8?q?Added=20=E2=80=9Cinstant=20fly=E2=80=9D?= =?UTF-8?q?=20variant=20of=20hit=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Objects/Drawables/DrawableCentreHit.cs | 17 +++++++++++++++++ .../Objects/Drawables/DrawableRimHit.cs | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs index 4979135f50..08df05e719 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs @@ -2,7 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; namespace osu.Game.Rulesets.Taiko.Objects.Drawables @@ -23,4 +26,18 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables MainPiece.AccentColour = colours.PinkDarker; } } + + public class DrawableFlyingCentreHit : DrawableCentreHit + { + protected override void CheckForResult(bool userTriggered, double timeOffset) + { + ApplyResult(r => r.Type = HitResult.Good); + } + + public DrawableFlyingCentreHit(double time) + : base(new Hit { StartTime = time }) + { + HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + } + } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs index 5a12d71cea..0c2c9fbdef 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs @@ -2,7 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; namespace osu.Game.Rulesets.Taiko.Objects.Drawables @@ -23,4 +26,18 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables MainPiece.AccentColour = colours.BlueDarker; } } + + public class DrawableFlyingRimHit : DrawableRimHit + { + protected override void CheckForResult(bool userTriggered, double timeOffset) + { + ApplyResult(r => r.Type = HitResult.Good); + } + + public DrawableFlyingRimHit(double time) + : base(new Hit { StartTime = time }) + { + HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + } + } } From a52130857bdefc35656ff4357ff8ee3ee1783174 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Tue, 7 Apr 2020 16:40:01 +0800 Subject: [PATCH 265/655] Added arbitrary hit handler to drum roll object --- .../Objects/Drawables/DrawableDrumRoll.cs | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 5806c90115..3e7b6dfd31 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -34,6 +34,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private Color4 colourIdle; private Color4 colourEngaged; + private bool judgingStarted; + + /// + /// A handler action for when the drumroll has been hit, + /// regardless of any judgement. + /// + public Action OnHit; + public DrawableDrumRoll(DrumRoll drumRoll) : base(drumRoll) { @@ -86,15 +94,27 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override TaikoPiece CreateMainPiece() => new ElongatedCirclePiece(); - public override bool OnPressed(TaikoAction action) => false; + public override bool OnPressed(TaikoAction action) + { + if (judgingStarted) + OnHit.Invoke(action); + + return false; + } private void onNewResult(DrawableHitObject obj, JudgementResult result) { if (!(obj is DrawableDrumRollTick)) return; + DrawableDrumRollTick drumRollTick = (DrawableDrumRollTick)obj; + if (result.Type > HitResult.Miss) + { + OnHit.Invoke(drumRollTick.JudgedAction); + judgingStarted = true; rollingHits++; + } else rollingHits--; @@ -113,8 +133,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables return; int countHit = NestedHitObjects.Count(o => o.IsHit); + if (countHit >= HitObject.RequiredGoodHits) + { ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Good); + } else ApplyResult(r => r.Type = HitResult.Miss); } From 81a514ee6a38b37cb1b9a64e947f2087eba287cd Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Tue, 7 Apr 2020 16:40:18 +0800 Subject: [PATCH 266/655] Added judgement forwarder to drumroll tick object --- .../Objects/Drawables/DrawableDrumRollTick.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index 25b6141a0e..9961cb6ea2 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -11,6 +11,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { public class DrawableDrumRollTick : DrawableTaikoHitObject { + /// + /// The action type that the user took which caused this tick to + /// have been judged as "hit" + /// + public TaikoAction JudgedAction; + public DrawableDrumRollTick(DrumRollTick tick) : base(tick) { @@ -49,7 +55,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } } - public override bool OnPressed(TaikoAction action) => UpdateResult(true); + public override bool OnPressed(TaikoAction action) + { + JudgedAction = action; + return UpdateResult(true); + } protected override DrawableStrongNestedHit CreateStrongHit(StrongHitObject hitObject) => new StrongNestedHit(hitObject, this); From fbcfc7d278a7ed779839c7ade8a395db9b4bc53a Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Tue, 7 Apr 2020 16:40:32 +0800 Subject: [PATCH 267/655] Added separate scrolling track to display drum roll notes --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 25 ++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index bde9085c23..b32e7b53da 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -42,6 +42,7 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Container hitExplosionContainer; private readonly Container kiaiExplosionContainer; private readonly JudgementContainer judgementContainer; + private readonly ScrollingHitObjectContainer drumRollHitContainer; internal readonly HitTarget HitTarget; private readonly ProxyContainer topLevelHitContainer; @@ -135,6 +136,14 @@ namespace osu.Game.Rulesets.Taiko.UI Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, Blending = BlendingParameters.Additive }, + drumRollHitContainer = new ScrollingHitObjectContainer + { + Name = "Drumroll hit", + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Stretch, + Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Width = 1.0f + } } }, overlayBackgroundContainer = new Container @@ -212,12 +221,28 @@ namespace osu.Game.Rulesets.Taiko.UI barlineContainer.Add(barline.CreateProxy()); break; + case DrawableDrumRoll drumRoll: + drumRoll.OnHit += onDrumrollArbitraryHit; + break; + case DrawableTaikoHitObject taikoObject: topLevelHitContainer.Add(taikoObject.CreateProxiedContent()); break; } } + private void onDrumrollArbitraryHit(TaikoAction action) + { + DrawableHit drawableHit; + + if (action == TaikoAction.LeftRim || action == TaikoAction.RightRim) + drawableHit = new DrawableFlyingRimHit(Time.Current); + else + drawableHit = new DrawableFlyingCentreHit(Time.Current); + + drumRollHitContainer.Add(drawableHit); + } + internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result) { if (!DisplayJudgements.Value) From c6d996030a83763d4b03ff1e5a819e6958d9f973 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Tue, 7 Apr 2020 17:25:47 +0800 Subject: [PATCH 268/655] Added logic to allow strong notes --- .../Objects/Drawables/DrawableCentreHit.cs | 4 ++-- .../Objects/Drawables/DrawableDrumRoll.cs | 6 +++--- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs | 4 ++-- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs index 08df05e719..86e885239f 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs @@ -34,8 +34,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables ApplyResult(r => r.Type = HitResult.Good); } - public DrawableFlyingCentreHit(double time) - : base(new Hit { StartTime = time }) + public DrawableFlyingCentreHit(double time, bool isStrong = false) + : base(new Hit { StartTime = time, IsStrong = isStrong }) { HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 3e7b6dfd31..64be870262 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// A handler action for when the drumroll has been hit, /// regardless of any judgement. /// - public Action OnHit; + public Action OnHit; public DrawableDrumRoll(DrumRoll drumRoll) : base(drumRoll) @@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public override bool OnPressed(TaikoAction action) { if (judgingStarted) - OnHit.Invoke(action); + OnHit.Invoke(action, HitObject.IsStrong); return false; } @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (result.Type > HitResult.Miss) { - OnHit.Invoke(drumRollTick.JudgedAction); + OnHit.Invoke(drumRollTick.JudgedAction, HitObject.IsStrong); judgingStarted = true; rollingHits++; } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs index 0c2c9fbdef..ad9872b21f 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs @@ -34,8 +34,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables ApplyResult(r => r.Type = HitResult.Good); } - public DrawableFlyingRimHit(double time) - : base(new Hit { StartTime = time }) + public DrawableFlyingRimHit(double time, bool isStrong = false) + : base(new Hit { StartTime = time, IsStrong = isStrong }) { HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index b32e7b53da..59cf9193b5 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -231,14 +231,14 @@ namespace osu.Game.Rulesets.Taiko.UI } } - private void onDrumrollArbitraryHit(TaikoAction action) + private void onDrumrollArbitraryHit(TaikoAction action, bool isStrong) { DrawableHit drawableHit; if (action == TaikoAction.LeftRim || action == TaikoAction.RightRim) - drawableHit = new DrawableFlyingRimHit(Time.Current); + drawableHit = new DrawableFlyingRimHit(Time.Current, isStrong); else - drawableHit = new DrawableFlyingCentreHit(Time.Current); + drawableHit = new DrawableFlyingCentreHit(Time.Current, isStrong); drumRollHitContainer.Add(drawableHit); } From 9d0d2ef68aaff860b62c2c3de247f2a34ecb2961 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Wed, 8 Apr 2020 12:12:59 +0800 Subject: [PATCH 269/655] Added content proxying to drull roll elements --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 59cf9193b5..e947795fe5 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -241,6 +241,7 @@ namespace osu.Game.Rulesets.Taiko.UI drawableHit = new DrawableFlyingCentreHit(Time.Current, isStrong); drumRollHitContainer.Add(drawableHit); + topLevelHitContainer.Add(drawableHit.CreateProxiedContent()); } internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result) From 78492dbba8a23fba4e07606afce6b3f1ae336227 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Sat, 11 Apr 2020 17:20:37 +0800 Subject: [PATCH 270/655] Added ignore hit object --- osu.Game.Rulesets.Taiko/Objects/IgnoreHit.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko/Objects/IgnoreHit.cs diff --git a/osu.Game.Rulesets.Taiko/Objects/IgnoreHit.cs b/osu.Game.Rulesets.Taiko/Objects/IgnoreHit.cs new file mode 100644 index 0000000000..302f940ef4 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Objects/IgnoreHit.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Judgements; + +namespace osu.Game.Rulesets.Taiko.Objects +{ + public class IgnoreHit : Hit + { + public override Judgement CreateJudgement() => new IgnoreJudgement(); + } +} From 940d85cfa6f22df86ce1a55a752959579998028b Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Sat, 11 Apr 2020 17:20:52 +0800 Subject: [PATCH 271/655] Moved flying objects to use ignore hit judgements --- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs | 3 ++- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs index 86e885239f..4468c1e3fb 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; @@ -35,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } public DrawableFlyingCentreHit(double time, bool isStrong = false) - : base(new Hit { StartTime = time, IsStrong = isStrong }) + : base(new IgnoreHit { StartTime = time, IsStrong = isStrong }) { HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs index ad9872b21f..e5cfc0562b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } public DrawableFlyingRimHit(double time, bool isStrong = false) - : base(new Hit { StartTime = time, IsStrong = isStrong }) + : base(new IgnoreHit { StartTime = time, IsStrong = isStrong }) { HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); } From 442992dc5bd9bb1e4fcdbeee0ab39a170af263f5 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Sun, 12 Apr 2020 10:38:22 +0800 Subject: [PATCH 272/655] Removed un-needed using --- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs index 4468c1e3fb..b6f6f04821 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; -using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; From c3f0475748f910172c5161db4dbd62afc3eb0c28 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 12 Apr 2020 17:40:22 +0900 Subject: [PATCH 273/655] Make CirclePiece abstract --- .../Objects/Drawables/Pieces/CirclePiece.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs index 70fe4b7bb2..6ca77e666d 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces /// for a usage example. /// /// - public class CirclePiece : BeatSyncedContainer + public abstract class CirclePiece : BeatSyncedContainer { public const float SYMBOL_SIZE = 0.45f; public const float SYMBOL_BORDER = 8; @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces public Box FlashBox; - public CirclePiece() + protected CirclePiece() { RelativeSizeAxes = Axes.Both; From c5d6c7728a512ab6a85857e391f7841afedb255f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 12 Apr 2020 18:29:25 +0900 Subject: [PATCH 274/655] 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 5b200ee104..723844155f 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 7cf1272611..0732e6090d 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index c58a431e80..d7006761be 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 63a1686dfbe8e984cc8e3e5ad32ce1bfa8931e41 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 12 Apr 2020 12:42:52 +0300 Subject: [PATCH 275/655] Scroll to screen middle --- 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 59dddc2baa..a5379e9649 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -632,7 +632,7 @@ namespace osu.Game.Screens.Select case DrawableCarouselBeatmap beatmap: { if (beatmap.Item.State.Value == CarouselItemState.Selected) - scrollTarget = currentY + beatmap.DrawHeight / 2 - DrawHeight / 2; + scrollTarget = currentY + beatmap.DrawHeight / 2 - (Parent.DrawHeight / 2 - Parent.Padding.Top); void performMove(float y, float? startY = null) { From 07dc2773218e4e65e6c388e7210a49a8a6a8f8ac Mon Sep 17 00:00:00 2001 From: TheWildTree Date: Sun, 12 Apr 2020 14:55:42 +0200 Subject: [PATCH 276/655] Remove unused changelog comments class --- .../Online/TestSceneChangelogOverlay.cs | 1 - osu.Game/Overlays/Changelog/Comments.cs | 79 ------------------- 2 files changed, 80 deletions(-) delete mode 100644 osu.Game/Overlays/Changelog/Comments.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs index 864fd31a0f..22d20f7098 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs @@ -24,7 +24,6 @@ namespace osu.Game.Tests.Visual.Online typeof(ChangelogListing), typeof(ChangelogSingleBuild), typeof(ChangelogBuild), - typeof(Comments), }; protected override bool UseOnlineAPI => true; diff --git a/osu.Game/Overlays/Changelog/Comments.cs b/osu.Game/Overlays/Changelog/Comments.cs deleted file mode 100644 index 4cf39e7b44..0000000000 --- a/osu.Game/Overlays/Changelog/Comments.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Online.API.Requests.Responses; -using osuTK.Graphics; - -namespace osu.Game.Overlays.Changelog -{ - public class Comments : CompositeDrawable - { - private readonly APIChangelogBuild build; - - public Comments(APIChangelogBuild build) - { - this.build = build; - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - Padding = new MarginPadding - { - Horizontal = 50, - Vertical = 20, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - LinkFlowContainer text; - - InternalChildren = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 10, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.GreyVioletDarker - }, - }, - text = new LinkFlowContainer(t => - { - t.Colour = colours.PinkLighter; - t.Font = OsuFont.Default.With(size: 14); - }) - { - Padding = new MarginPadding(20), - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - } - }; - - text.AddParagraph("Got feedback?", t => - { - t.Colour = Color4.White; - t.Font = OsuFont.Default.With(italics: true, size: 20); - t.Padding = new MarginPadding { Bottom = 20 }; - }); - - text.AddParagraph("We would love to hear what you think of this update! "); - text.AddIcon(FontAwesome.Regular.GrinHearts); - - text.AddParagraph("Please visit the "); - text.AddLink("web version", $"{build.Url}#comments"); - text.AddText(" of this changelog to leave any comments."); - } - } -} From ecd25e567d8803c548e273b11aac15f333e55da6 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 12 Apr 2020 16:00:05 +0300 Subject: [PATCH 277/655] Present selected difficulty --- osu.Game/OsuGame.cs | 12 ++++++++---- osu.Game/Overlays/BeatmapSet/Header.cs | 3 ++- osu.Game/Overlays/Direct/PanelDownloadButton.cs | 10 +++++++++- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1b2fd658f4..113e9bbe24 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -315,8 +315,12 @@ namespace osu.Game /// The user should have already requested this interactively. /// /// The beatmap to select. - public void PresentBeatmap(BeatmapSetInfo beatmap) + /// Predicate used to find a difficulty to select + public void PresentBeatmap(BeatmapSetInfo beatmap, Predicate findPredicate = null) { + // Use this predicate if non was provided. This will try to find some difficulty from current ruleset so we wouldn't have to change rulesets + findPredicate ??= b => b.Ruleset.Equals(Ruleset.Value); + var databasedSet = beatmap.OnlineBeatmapSetID != null ? BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID) : BeatmapManager.QueryBeatmapSet(s => s.Hash == beatmap.Hash); @@ -334,13 +338,13 @@ namespace osu.Game menuScreen.LoadToSolo(); // we might even already be at the song - if (Beatmap.Value.BeatmapSetInfo.Hash == databasedSet.Hash) + if (Beatmap.Value.BeatmapSetInfo.Hash == databasedSet.Hash && findPredicate(Beatmap.Value.BeatmapInfo)) { return; } - // Use first beatmap available for current ruleset, else switch ruleset. - var first = databasedSet.Beatmaps.Find(b => b.Ruleset.Equals(Ruleset.Value)) ?? databasedSet.Beatmaps.First(); + // Find first beatmap that matches our predicate. + var first = databasedSet.Beatmaps.Find(findPredicate) ?? databasedSet.Beatmaps.First(); Ruleset.Value = first.Ruleset; Beatmap.Value = BeatmapManager.GetWorkingBeatmap(first); diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index 29c259b7f8..a60b9e04b1 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -277,7 +277,8 @@ namespace osu.Game.Overlays.BeatmapSet downloadButtonsContainer.Child = new PanelDownloadButton(BeatmapSet.Value) { Width = 50, - RelativeSizeAxes = Axes.Y + RelativeSizeAxes = Axes.Y, + CurrentBeatmap = Picker.Beatmap.GetBoundCopy() }; break; diff --git a/osu.Game/Overlays/Direct/PanelDownloadButton.cs b/osu.Game/Overlays/Direct/PanelDownloadButton.cs index 1b3657f010..eae2f3353c 100644 --- a/osu.Game/Overlays/Direct/PanelDownloadButton.cs +++ b/osu.Game/Overlays/Direct/PanelDownloadButton.cs @@ -1,7 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; @@ -16,6 +18,8 @@ namespace osu.Game.Overlays.Direct private readonly bool noVideo; + public Bindable CurrentBeatmap = new Bindable(); + private readonly ShakeContainer shakeContainer; private readonly DownloadButton button; @@ -62,7 +66,11 @@ namespace osu.Game.Overlays.Direct break; case DownloadState.LocallyAvailable: - game?.PresentBeatmap(BeatmapSet.Value); + Predicate findPredicate = null; + if (CurrentBeatmap.Value != null) + findPredicate = b => b.OnlineBeatmapID == CurrentBeatmap.Value.OnlineBeatmapID; + + game?.PresentBeatmap(BeatmapSet.Value, findPredicate); break; default: From ed28e8c8f5f6d92f7f06034800fd9df82f688ef7 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 12 Apr 2020 19:38:09 +0300 Subject: [PATCH 278/655] Rename param --- osu.Game/OsuGame.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 113e9bbe24..5e93d760e3 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -315,11 +315,14 @@ namespace osu.Game /// The user should have already requested this interactively. /// /// The beatmap to select. - /// Predicate used to find a difficulty to select - public void PresentBeatmap(BeatmapSetInfo beatmap, Predicate findPredicate = null) + /// + /// Optional predicate used to try and find a difficulty to select. + /// If omitted, this will try to present the first beatmap from the current ruleset. + /// In case of failure the first difficulty of the set will be presented, ignoring the predicate. + /// + public void PresentBeatmap(BeatmapSetInfo beatmap, Predicate difficultyCriteria = null) { - // Use this predicate if non was provided. This will try to find some difficulty from current ruleset so we wouldn't have to change rulesets - findPredicate ??= b => b.Ruleset.Equals(Ruleset.Value); + difficultyCriteria ??= b => b.Ruleset.Equals(Ruleset.Value); var databasedSet = beatmap.OnlineBeatmapSetID != null ? BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID) @@ -338,13 +341,13 @@ namespace osu.Game menuScreen.LoadToSolo(); // we might even already be at the song - if (Beatmap.Value.BeatmapSetInfo.Hash == databasedSet.Hash && findPredicate(Beatmap.Value.BeatmapInfo)) + if (Beatmap.Value.BeatmapSetInfo.Hash == databasedSet.Hash && difficultyCriteria(Beatmap.Value.BeatmapInfo)) { return; } // Find first beatmap that matches our predicate. - var first = databasedSet.Beatmaps.Find(findPredicate) ?? databasedSet.Beatmaps.First(); + var first = databasedSet.Beatmaps.Find(difficultyCriteria) ?? databasedSet.Beatmaps.First(); Ruleset.Value = first.Ruleset; Beatmap.Value = BeatmapManager.GetWorkingBeatmap(first); From 3b9e0fa67def20a8e9177088147cbfff4536cc07 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 12 Apr 2020 19:42:28 +0300 Subject: [PATCH 279/655] Use readonly IBindable --- osu.Game/Overlays/BeatmapSet/Header.cs | 7 ++++--- osu.Game/Overlays/Direct/PanelDownloadButton.cs | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index a60b9e04b1..4e57bfa688 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -274,12 +274,13 @@ namespace osu.Game.Overlays.BeatmapSet { case DownloadState.LocallyAvailable: // temporary for UX until new design is implemented. - downloadButtonsContainer.Child = new PanelDownloadButton(BeatmapSet.Value) + PanelDownloadButton panelButton; + downloadButtonsContainer.Child = panelButton = new PanelDownloadButton(BeatmapSet.Value) { Width = 50, - RelativeSizeAxes = Axes.Y, - CurrentBeatmap = Picker.Beatmap.GetBoundCopy() + RelativeSizeAxes = Axes.Y }; + panelButton.CurrentBeatmap.BindTo(Picker.Beatmap); break; case DownloadState.Downloading: diff --git a/osu.Game/Overlays/Direct/PanelDownloadButton.cs b/osu.Game/Overlays/Direct/PanelDownloadButton.cs index eae2f3353c..6fe174438b 100644 --- a/osu.Game/Overlays/Direct/PanelDownloadButton.cs +++ b/osu.Game/Overlays/Direct/PanelDownloadButton.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Direct private readonly bool noVideo; - public Bindable CurrentBeatmap = new Bindable(); + public readonly IBindable CurrentBeatmap = new Bindable(); private readonly ShakeContainer shakeContainer; private readonly DownloadButton button; From 1cf240b5fff1c269f9e668970a4046d963521b41 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 12 Apr 2020 20:04:25 +0300 Subject: [PATCH 280/655] Test new predicate behaviour --- .../Navigation/TestScenePresentBeatmap.cs | 57 +++++++++++++++---- 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs index 909409835c..27f5b29738 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs @@ -20,26 +20,30 @@ namespace osu.Game.Tests.Visual.Navigation public void TestFromMainMenu() { var firstImport = importBeatmap(1); + var secondimport = importBeatmap(3); + presentAndConfirm(firstImport); - - AddStep("return to menu", () => Game.ScreenStack.CurrentScreen.Exit()); - AddUntilStep("wait for menu", () => Game.ScreenStack.CurrentScreen is MainMenu); - - var secondimport = importBeatmap(2); + returnToMenu(); presentAndConfirm(secondimport); + returnToMenu(); + presentSecondDifficultyAndConfirm(firstImport, 1); + returnToMenu(); + presentSecondDifficultyAndConfirm(secondimport, 3); } [Test] public void TestFromMainMenuDifferentRuleset() { var firstImport = importBeatmap(1); + var secondimport = importBeatmap(3, new ManiaRuleset().RulesetInfo); + presentAndConfirm(firstImport); - - AddStep("return to menu", () => Game.ScreenStack.CurrentScreen.Exit()); - AddUntilStep("wait for menu", () => Game.ScreenStack.CurrentScreen is MainMenu); - - var secondimport = importBeatmap(2, new ManiaRuleset().RulesetInfo); + returnToMenu(); presentAndConfirm(secondimport); + returnToMenu(); + presentSecondDifficultyAndConfirm(firstImport, 1); + returnToMenu(); + presentSecondDifficultyAndConfirm(secondimport, 3); } [Test] @@ -48,8 +52,11 @@ namespace osu.Game.Tests.Visual.Navigation var firstImport = importBeatmap(1); presentAndConfirm(firstImport); - var secondimport = importBeatmap(2); + var secondimport = importBeatmap(3); presentAndConfirm(secondimport); + + presentSecondDifficultyAndConfirm(firstImport, 1); + presentSecondDifficultyAndConfirm(secondimport, 3); } [Test] @@ -58,8 +65,17 @@ namespace osu.Game.Tests.Visual.Navigation var firstImport = importBeatmap(1); presentAndConfirm(firstImport); - var secondimport = importBeatmap(2, new ManiaRuleset().RulesetInfo); + var secondimport = importBeatmap(3, new ManiaRuleset().RulesetInfo); presentAndConfirm(secondimport); + + presentSecondDifficultyAndConfirm(firstImport, 1); + presentSecondDifficultyAndConfirm(secondimport, 3); + } + + private void returnToMenu() + { + AddStep("return to menu", () => Game.ScreenStack.CurrentScreen.Exit()); + AddUntilStep("wait for menu", () => Game.ScreenStack.CurrentScreen is MainMenu); } private Func importBeatmap(int i, RulesetInfo ruleset = null) @@ -89,6 +105,13 @@ namespace osu.Game.Tests.Visual.Navigation BaseDifficulty = difficulty, Ruleset = ruleset ?? new OsuRuleset().RulesetInfo }, + new BeatmapInfo + { + OnlineBeatmapID = i * 2048, + Metadata = metadata, + BaseDifficulty = difficulty, + Ruleset = ruleset ?? new OsuRuleset().RulesetInfo + }, } }).Result; }); @@ -106,5 +129,15 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapSetInfo.ID == getImport().ID); AddAssert("correct ruleset selected", () => Game.Ruleset.Value.ID == getImport().Beatmaps.First().Ruleset.ID); } + + private void presentSecondDifficultyAndConfirm(Func getImport, int importedID) + { + Predicate pred = b => b.OnlineBeatmapID == importedID * 2048; + AddStep("present difficulty", () => Game.PresentBeatmap(getImport(), pred)); + + AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect); + AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapInfo.OnlineBeatmapID == importedID * 2048); + AddAssert("correct ruleset selected", () => Game.Ruleset.Value.ID == getImport().Beatmaps.First().Ruleset.ID); + } } } From b475316a4e0a34161450ab8eed126d4087866cab Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 12 Apr 2020 20:40:08 +0300 Subject: [PATCH 281/655] Simplify and comment --- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index a5379e9649..e13511a02c 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -632,7 +632,11 @@ namespace osu.Game.Screens.Select case DrawableCarouselBeatmap beatmap: { if (beatmap.Item.State.Value == CarouselItemState.Selected) - scrollTarget = currentY + beatmap.DrawHeight / 2 - (Parent.DrawHeight / 2 - Parent.Padding.Top); + // scroll position at currentY makes the set panel appear at the very top of the carousel in screen space + // move down by half of parent height (which is the height of the carousel's visible extent, including semi-transparent areas) + // then reapply parent's padding from the top by adding it + // and finally add half of the panel's own height to achieve vertical centering of the panel itself + scrollTarget = currentY - Parent.DrawHeight / 2 + Parent.Padding.Top + beatmap.DrawHeight / 2; void performMove(float y, float? startY = null) { From 3efb4aba25803c36a2f50401808a389f3b082f29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 12 Apr 2020 19:48:15 +0200 Subject: [PATCH 282/655] Use BindTarget --- osu.Game/Overlays/BeatmapSet/Header.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index 4e57bfa688..a03613f955 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -274,13 +274,12 @@ namespace osu.Game.Overlays.BeatmapSet { case DownloadState.LocallyAvailable: // temporary for UX until new design is implemented. - PanelDownloadButton panelButton; - downloadButtonsContainer.Child = panelButton = new PanelDownloadButton(BeatmapSet.Value) + downloadButtonsContainer.Child = new PanelDownloadButton(BeatmapSet.Value) { Width = 50, - RelativeSizeAxes = Axes.Y + RelativeSizeAxes = Axes.Y, + CurrentBeatmap = { BindTarget = Picker.Beatmap } }; - panelButton.CurrentBeatmap.BindTo(Picker.Beatmap); break; case DownloadState.Downloading: From 633b969017515da564a14036e7ad3eb0cbb5a141 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 12 Apr 2020 21:57:35 +0300 Subject: [PATCH 283/655] Apply review suggestions --- .../Visual/Online/TestSceneDirectDownloadButton.cs | 4 ++-- osu.Game/Overlays/Direct/PanelDownloadButton.cs | 13 ++++++------- .../Settings/Sections/Online/WebSettings.cs | 3 ++- osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs index 5b0c2d3c67..f612992bf6 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs @@ -149,8 +149,8 @@ namespace osu.Game.Tests.Visual.Online public DownloadState DownloadState => State.Value; - public TestDownloadButton(BeatmapSetInfo beatmapSet, bool noVideo = false) - : base(beatmapSet, noVideo) + public TestDownloadButton(BeatmapSetInfo beatmapSet) + : base(beatmapSet) { } } diff --git a/osu.Game/Overlays/Direct/PanelDownloadButton.cs b/osu.Game/Overlays/Direct/PanelDownloadButton.cs index f09586c571..51f5b2ae4f 100644 --- a/osu.Game/Overlays/Direct/PanelDownloadButton.cs +++ b/osu.Game/Overlays/Direct/PanelDownloadButton.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -15,16 +16,13 @@ namespace osu.Game.Overlays.Direct { protected bool DownloadEnabled => button.Enabled.Value; - private readonly bool? noVideo; - private readonly ShakeContainer shakeContainer; private readonly DownloadButton button; + private readonly BindableBool noVideoSetting = new BindableBool(); - public PanelDownloadButton(BeatmapSetInfo beatmapSet, bool? noVideo = null) + public PanelDownloadButton(BeatmapSetInfo beatmapSet) : base(beatmapSet) { - this.noVideo = noVideo; - InternalChild = shakeContainer = new ShakeContainer { RelativeSizeAxes = Axes.Both, @@ -53,6 +51,8 @@ namespace osu.Game.Overlays.Direct return; } + noVideoSetting.BindTo(osuConfig.GetBindable(OsuSetting.PreferNoVideo)); + button.Action = () => { switch (State.Value) @@ -67,8 +67,7 @@ namespace osu.Game.Overlays.Direct break; default: - var minimiseDownloadSize = noVideo ?? osuConfig.GetBindable(OsuSetting.PreferNoVideo).Value; - beatmaps.Download(BeatmapSet.Value, minimiseDownloadSize); + beatmaps.Download(BeatmapSet.Value, noVideoSetting.Value); break; } }; diff --git a/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs index da3176aca8..23513eade8 100644 --- a/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs @@ -23,7 +23,8 @@ namespace osu.Game.Overlays.Settings.Sections.Online }, new SettingsCheckbox { - LabelText = "Prefer no-video downloads", + LabelText = "Prefer downloads without video", + Keywords = new[] { "no-video" }, Bindable = config.GetBindable(OsuSetting.PreferNoVideo) }, }; diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs index d58218b6b5..d7dcca9809 100644 --- a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs @@ -212,8 +212,8 @@ namespace osu.Game.Screens.Multi private class PlaylistDownloadButton : PanelDownloadButton { - public PlaylistDownloadButton(BeatmapSetInfo beatmapSet, bool? noVideo = null) - : base(beatmapSet, noVideo) + public PlaylistDownloadButton(BeatmapSetInfo beatmapSet) + : base(beatmapSet) { Alpha = 0; } From 65b96079a05f46de50dd1f0f191bed8389cdf62f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Apr 2020 13:00:03 +0900 Subject: [PATCH 284/655] Move dampening to base implementation and change range to 0..1 --- .../Objects/Drawables/DrawableCatchHitObject.cs | 2 +- .../Objects/Drawables/DrawableManiaHitObject.cs | 2 +- .../Objects/Drawables/DrawableOsuHitObject.cs | 3 ++- .../Objects/Drawables/DrawableHitObject.cs | 16 +++++++++++----- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs index e726d6eff5..b12cdd4ccb 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale; - protected override float SamplePlaybackBalance => 0.8f * HitObject.X - 0.4f; + protected override float SamplePlaybackPosition => HitObject.X; protected DrawableCatchHitObject(CatchHitObject hitObject) : base(hitObject) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 76e9695855..ce56fd222c 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables protected readonly IBindable Direction = new Bindable(); - protected override float SamplePlaybackBalance + protected override float SamplePlaybackPosition { get { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 068d666c8e..8308c0c576 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Graphics.Containers; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables @@ -18,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // Must be set to update IsHovered as it's used in relax mdo to detect osu hit objects. public override bool HandlePositionalInput => true; - protected override float SamplePlaybackBalance => (HitObject.X / 512f - 0.5f) * 0.8f; + protected override float SamplePlaybackPosition => HitObject.X / OsuPlayfield.BASE_SIZE.X; /// /// Whether this can be hit. diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index d6e231424b..b14927bcd5 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -87,11 +87,15 @@ namespace osu.Game.Rulesets.Objects.Drawables public JudgementResult Result { get; private set; } /// - /// The stereo balance of the samples played if Positional hitsounds is set. + /// The relative X position of this hit object for sample playback balance adjustment. /// - protected virtual float SamplePlaybackBalance => 0; + /// + /// This is a range of 0..1 (0 for far-left, 0.5 for centre, 1 for far-right). + /// Dampening is post-applied to ensure the effect is not too intense. + /// + protected virtual float SamplePlaybackPosition => 0.5f; - private readonly BindableDouble samplePlaybackBalanceAdjustment = new BindableDouble(); + private readonly BindableDouble balanceAdjust = new BindableDouble(); private BindableList samplesBindable; private Bindable startTimeBindable; @@ -168,7 +172,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } Samples = new SkinnableSound(samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s))); - Samples.AddAdjustment(AdjustableProperty.Balance, samplePlaybackBalanceAdjustment); + Samples.AddAdjustment(AdjustableProperty.Balance, balanceAdjust); AddInternal(Samples); } @@ -368,7 +372,9 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public virtual void PlaySamples() { - samplePlaybackBalanceAdjustment.Value = userPositionalHitSounds.Value ? SamplePlaybackBalance : 0; + const float balance_adjust_amount = 0.4f; + + balanceAdjust.Value = balance_adjust_amount * (userPositionalHitSounds.Value ? SamplePlaybackPosition - 0.5f : 0); Samples?.Play(); } From cdff6060d3eccdf3ed36df6ce0d3aa26dc411a17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Apr 2020 13:01:27 +0900 Subject: [PATCH 285/655] Remove recursive hierarchy traversal for mania sample balance --- .../Objects/Drawables/DrawableManiaHitObject.cs | 16 +++++++++------- osu.Game/Rulesets/UI/Playfield.cs | 1 + 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index ce56fd222c..a708adb493 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -5,10 +5,10 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania.Objects.Drawables { @@ -26,17 +26,19 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables protected readonly IBindable Direction = new Bindable(); + [Resolved(canBeNull: true)] + private Playfield playfield { get; set; } + protected override float SamplePlaybackPosition { get { - CompositeDrawable stage = this; - while (!(stage is Stage)) - stage = stage.Parent; + var columns = (playfield as ManiaPlayfield)?.TotalColumns; - var columnCount = ((Stage)stage).Columns.Count; - var columnIndex = HitObject.Column; - return 0.8f * columnIndex / (columnCount - 1) - 0.4f; + if (columns == null) + return base.SamplePlaybackPosition; + + return (float)HitObject.Column / columns.Value; } } diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index c52183f3f2..fc6906560b 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -15,6 +15,7 @@ using osuTK; namespace osu.Game.Rulesets.UI { + [Cached] public abstract class Playfield : CompositeDrawable { /// From c51bad0e35e4a08d3e0e159e103884bbf709e85d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Apr 2020 13:42:21 +0900 Subject: [PATCH 286/655] Cache ManiaPlayfield instead --- .../Objects/Drawables/DrawableManiaHitObject.cs | 9 +++------ osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 2 ++ osu.Game/Rulesets/UI/Playfield.cs | 1 - 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index a708adb493..88888001b4 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.Mania.UI; -using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania.Objects.Drawables { @@ -27,18 +26,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables protected readonly IBindable Direction = new Bindable(); [Resolved(canBeNull: true)] - private Playfield playfield { get; set; } + private ManiaPlayfield playfield { get; set; } protected override float SamplePlaybackPosition { get { - var columns = (playfield as ManiaPlayfield)?.TotalColumns; - - if (columns == null) + if (playfield == null) return base.SamplePlaybackPosition; - return (float)HitObject.Column / columns.Value; + return (float)HitObject.Column / playfield.TotalColumns; } } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index c2eb48b774..2dec468654 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics.Containers; using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -14,6 +15,7 @@ using osuTK; namespace osu.Game.Rulesets.Mania.UI { + [Cached] public class ManiaPlayfield : ScrollingPlayfield { private readonly List stages = new List(); diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index fc6906560b..c52183f3f2 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -15,7 +15,6 @@ using osuTK; namespace osu.Game.Rulesets.UI { - [Cached] public abstract class Playfield : CompositeDrawable { /// From f38b64d20177c2010b415f8a3bae39fbf29e0303 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 13 Apr 2020 13:57:15 +0900 Subject: [PATCH 287/655] Fix placement blueprints handling double clicks --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index ea77a6091a..fb1eb7adbf 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -106,6 +106,9 @@ namespace osu.Game.Rulesets.Edit case ScrollEvent _: return false; + case DoubleClickEvent _: + return false; + case MouseButtonEvent _: return true; From e17d5bdbaf3a6ad45b4a60af9ab9a168cf0d0406 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 13 Apr 2020 13:57:40 +0900 Subject: [PATCH 288/655] Improve red slider control point placement logic --- .../Sliders/SliderPlacementBlueprint.cs | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index a780653796..be43515269 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -1,10 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; +using osu.Framework.Logging; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -23,6 +26,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private SliderBodyPiece bodyPiece; private HitCirclePiece headCirclePiece; private HitCirclePiece tailCirclePiece; + private PathControlPointVisualiser controlPointVisualiser; private InputManager inputManager; @@ -51,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders bodyPiece = new SliderBodyPiece(), headCirclePiece = new HitCirclePiece(), tailCirclePiece = new HitCirclePiece(), - new PathControlPointVisualiser(HitObject, false) + controlPointVisualiser = new PathControlPointVisualiser(HitObject, false) }; setState(PlacementState.Initial); @@ -91,17 +95,29 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders break; case PlacementState.Body: - switch (e.Button) - { - case MouseButton.Left: - ensureCursor(); + if (e.Button != MouseButton.Left) + break; - // Detatch the cursor - cursor = null; - break; + // Find the last non-cursor control point and the respective drawable piece + var lastPoint = HitObject.Path.ControlPoints.LastOrDefault(p => p != cursor); + var lastPiece = controlPointVisualiser.Pieces.Single(p => p.ControlPoint == lastPoint); + + if (lastPiece?.IsHovered == true) + { + Debug.Assert(lastPoint != null); + + segmentStart = lastPoint; + segmentStart.Type.Value = PathType.Linear; + + currentSegmentLength = 1; + } + else + { + ensureCursor(); + cursor = null; // Detatch the cursor } - break; + return true; } return true; @@ -114,16 +130,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders base.OnMouseUp(e); } - protected override bool OnDoubleClick(DoubleClickEvent e) - { - // Todo: This should all not occur on double click, but rather if the previous control point is hovered. - segmentStart = HitObject.Path.ControlPoints[^1]; - segmentStart.Type.Value = PathType.Linear; - - currentSegmentLength = 1; - return true; - } - private void beginCurve() { BeginPlacement(commitStart: true); @@ -169,6 +175,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders currentSegmentLength++; updatePathType(); + + Logger.Log("Set cursor"); } } From 99fa1458470fe7fc031e3ae4e422cf2f1ad0d73b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 13 Apr 2020 08:38:34 +0300 Subject: [PATCH 289/655] Add test for potential failing case --- .../UserInterface/TestSceneOverlayScrollContainer.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs index 4205d65100..fd3b6ed3ab 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.UserInterface [SetUp] public void SetUp() => Schedule(() => { - Add(scroll = new OverlayScrollContainer + Child = scroll = new OverlayScrollContainer { RelativeSizeAxes = Axes.Both, Child = new Container @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.UserInterface Colour = Color4.Gray } } - }); + }; invocationCount = 0; @@ -62,6 +62,10 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("scroll to start", () => scroll.ScrollToStart(false)); AddAssert("button is hidden", () => scroll.Button.State == Visibility.Hidden); + + AddStep("scroll to 250", () => scroll.ScrollTo(500)); + AddUntilStep("scrolled back to start", () => Precision.AlmostEquals(scroll.Current, 500, 0.1f)); + AddAssert("button is visible", () => scroll.Button.State == Visibility.Visible); } [Test] From 142cddfb10d46a1392dda590ac5fb1864c82bf7f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Apr 2020 15:13:35 +0900 Subject: [PATCH 290/655] Rename CurrentBeatmap to SelectedBeatmap --- osu.Game/Overlays/BeatmapSet/Header.cs | 2 +- osu.Game/Overlays/Direct/PanelDownloadButton.cs | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index a03613f955..11dc424183 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -278,7 +278,7 @@ namespace osu.Game.Overlays.BeatmapSet { Width = 50, RelativeSizeAxes = Axes.Y, - CurrentBeatmap = { BindTarget = Picker.Beatmap } + SelectedBeatmap = { BindTarget = Picker.Beatmap } }; break; diff --git a/osu.Game/Overlays/Direct/PanelDownloadButton.cs b/osu.Game/Overlays/Direct/PanelDownloadButton.cs index 6fe174438b..08e3ed9b38 100644 --- a/osu.Game/Overlays/Direct/PanelDownloadButton.cs +++ b/osu.Game/Overlays/Direct/PanelDownloadButton.cs @@ -18,7 +18,10 @@ namespace osu.Game.Overlays.Direct private readonly bool noVideo; - public readonly IBindable CurrentBeatmap = new Bindable(); + /// + /// Currently selected beatmap. Used to present the correct difficulty after completing a download. + /// + public readonly IBindable SelectedBeatmap = new Bindable(); private readonly ShakeContainer shakeContainer; private readonly DownloadButton button; @@ -67,8 +70,8 @@ namespace osu.Game.Overlays.Direct case DownloadState.LocallyAvailable: Predicate findPredicate = null; - if (CurrentBeatmap.Value != null) - findPredicate = b => b.OnlineBeatmapID == CurrentBeatmap.Value.OnlineBeatmapID; + if (SelectedBeatmap.Value != null) + findPredicate = b => b.OnlineBeatmapID == SelectedBeatmap.Value.OnlineBeatmapID; game?.PresentBeatmap(BeatmapSet.Value, findPredicate); break; From 2c20328a70cfd3f5f2722b226346feeabad633ec Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 13 Apr 2020 15:31:46 +0900 Subject: [PATCH 291/655] Rework control point placement for better progression --- .../Sliders/SliderPlacementBlueprint.cs | 69 +++++++++++++------ 1 file changed, 47 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index be43515269..9af972dbce 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -3,11 +3,11 @@ using System.Diagnostics; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; -using osu.Framework.Logging; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -77,11 +77,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders break; case PlacementState.Body: - ensureCursor(); - - // The given screen-space position may have been externally snapped, but the unsnapped position from the input manager - // is used instead since snapping control points doesn't make much sense - cursor.Position.Value = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position; + updateCursor(); break; } } @@ -98,12 +94,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders if (e.Button != MouseButton.Left) break; - // Find the last non-cursor control point and the respective drawable piece - var lastPoint = HitObject.Path.ControlPoints.LastOrDefault(p => p != cursor); - var lastPiece = controlPointVisualiser.Pieces.Single(p => p.ControlPoint == lastPoint); - - if (lastPiece?.IsHovered == true) + if (canPlaceNewControlPoint(out var lastPoint)) { + // Place a new point by detatching the current cursor. + updateCursor(); + cursor = null; + } + else + { + // Transform the last point into a new segment. Debug.Assert(lastPoint != null); segmentStart = lastPoint; @@ -111,11 +110,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders currentSegmentLength = 1; } - else - { - ensureCursor(); - cursor = null; // Detatch the cursor - } return true; } @@ -167,17 +161,48 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } } - private void ensureCursor() + private void updateCursor() { - if (cursor == null) + if (canPlaceNewControlPoint(out _)) { - HitObject.Path.ControlPoints.Add(cursor = new PathControlPoint { Position = { Value = Vector2.Zero } }); - currentSegmentLength++; + // The cursor does not overlap a previous control point, so it can be added if not already existing. + if (cursor == null) + { + HitObject.Path.ControlPoints.Add(cursor = new PathControlPoint { Position = { Value = Vector2.Zero } }); - updatePathType(); + // The path type should be adjusted in the progression of updatePathType() (Linear -> PC -> Bezier). + currentSegmentLength++; + updatePathType(); + } - Logger.Log("Set cursor"); + // Update the cursor position. + cursor.Position.Value = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position; } + else if (cursor != null) + { + // The cursor overlaps a previous control point, so it's removed. + HitObject.Path.ControlPoints.Remove(cursor); + cursor = null; + + // The path type should be adjusted in the reverse progression of updatePathType() (Bezier -> PC -> Linear). + currentSegmentLength--; + updatePathType(); + } + } + + /// + /// Whether a new control point can be placed at the current mouse position. + /// + /// The last-placed control point. May be null, but is not null if false is returned. + /// Whether a new control point can be placed at the current position. + private bool canPlaceNewControlPoint([CanBeNull] out PathControlPoint lastPoint) + { + // We cannot rely on the ordering of drawable pieces, so find the respective drawable piece by searching for the last non-cursor control point. + var last = HitObject.Path.ControlPoints.LastOrDefault(p => p != cursor); + var lastPiece = controlPointVisualiser.Pieces.Single(p => p.ControlPoint == last); + + lastPoint = last; + return lastPiece?.IsHovered != true; } private void updateSlider() From bde0b259c1c03d2022124c6125dd2cc45a292807 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 13 Apr 2020 15:31:54 +0900 Subject: [PATCH 292/655] Improve slider placement test scene --- .../TestSceneSliderPlacementBlueprint.cs | 267 ++++++++++++++++++ .../Visual/PlacementBlueprintTestScene.cs | 28 +- 2 files changed, 282 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderPlacementBlueprint.cs index 0522260150..9fc479953e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderPlacementBlueprint.cs @@ -1,18 +1,285 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using NUnit.Framework; +using osu.Framework.Utils; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Input; namespace osu.Game.Rulesets.Osu.Tests { public class TestSceneSliderPlacementBlueprint : PlacementBlueprintTestScene { + [SetUp] + public void Setup() => Schedule(() => + { + HitObjectContainer.Clear(); + ResetPlacement(); + }); + + [Test] + public void TestBeginPlacementWithoutFinishing() + { + addMovementStep(new Vector2(200)); + addClickStep(MouseButton.Left); + + assertPlaced(false); + } + + [Test] + public void TestPlaceWithoutMovingMouse() + { + addMovementStep(new Vector2(200)); + addClickStep(MouseButton.Left); + addClickStep(MouseButton.Right); + + assertPlaced(true); + assertLength(0); + assertControlPointType(0, PathType.Linear); + } + + [Test] + public void TestPlaceWithMouseMovement() + { + addMovementStep(new Vector2(200)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(400, 200)); + addClickStep(MouseButton.Right); + + assertPlaced(true); + assertLength(200); + assertControlPointCount(2); + assertControlPointType(0, PathType.Linear); + } + + [Test] + public void TestPlaceNormalControlPoint() + { + addMovementStep(new Vector2(200)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(300, 200)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(300)); + addClickStep(MouseButton.Right); + + assertPlaced(true); + assertControlPointCount(3); + assertControlPointPosition(1, new Vector2(100, 0)); + assertControlPointType(0, PathType.PerfectCurve); + } + + [Test] + public void TestPlaceTwoNormalControlPoints() + { + addMovementStep(new Vector2(200)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(300, 200)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(300)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(400, 300)); + addClickStep(MouseButton.Right); + + assertPlaced(true); + assertControlPointCount(4); + assertControlPointPosition(1, new Vector2(100, 0)); + assertControlPointPosition(2, new Vector2(100, 100)); + assertControlPointType(0, PathType.Bezier); + } + + [Test] + public void TestPlaceSegmentControlPoint() + { + addMovementStep(new Vector2(200)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(300, 200)); + addClickStep(MouseButton.Left); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(300)); + addClickStep(MouseButton.Right); + + assertPlaced(true); + assertControlPointCount(3); + assertControlPointPosition(1, new Vector2(100, 0)); + assertControlPointType(0, PathType.Linear); + assertControlPointType(1, PathType.Linear); + } + + [Test] + public void TestMoveToPerfectCurveThenPlaceLinear() + { + addMovementStep(new Vector2(200)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(300, 200)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(300)); + addMovementStep(new Vector2(300, 200)); + addClickStep(MouseButton.Right); + + assertPlaced(true); + assertControlPointCount(2); + assertControlPointType(0, PathType.Linear); + assertLength(100); + } + + [Test] + public void TestMoveToBezierThenPlacePerfectCurve() + { + addMovementStep(new Vector2(200)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(300, 200)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(300)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(400, 300)); + addMovementStep(new Vector2(300)); + addClickStep(MouseButton.Right); + + assertPlaced(true); + assertControlPointCount(3); + assertControlPointType(0, PathType.PerfectCurve); + } + + [Test] + public void TestMoveToFourthOrderBezierThenPlaceThirdOrderBezier() + { + addMovementStep(new Vector2(200)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(300, 200)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(300)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(400, 300)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(400)); + addMovementStep(new Vector2(400, 300)); + addClickStep(MouseButton.Right); + + assertPlaced(true); + assertControlPointCount(4); + assertControlPointType(0, PathType.Bezier); + } + + [Test] + public void TestPlaceLinearSegmentThenPlaceLinearSegment() + { + addMovementStep(new Vector2(200)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(300, 200)); + addClickStep(MouseButton.Left); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(300, 300)); + addClickStep(MouseButton.Right); + + assertPlaced(true); + assertControlPointCount(3); + assertControlPointPosition(1, new Vector2(100, 0)); + assertControlPointPosition(2, new Vector2(100)); + assertControlPointType(0, PathType.Linear); + assertControlPointType(1, PathType.Linear); + } + + [Test] + public void TestPlaceLinearSegmentThenPlacePerfectCurveSegment() + { + addMovementStep(new Vector2(200)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(300, 200)); + addClickStep(MouseButton.Left); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(300, 300)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(400, 300)); + addClickStep(MouseButton.Right); + + assertPlaced(true); + assertControlPointCount(4); + assertControlPointPosition(1, new Vector2(100, 0)); + assertControlPointPosition(2, new Vector2(100)); + assertControlPointType(0, PathType.Linear); + assertControlPointType(1, PathType.PerfectCurve); + } + + [Test] + public void TestPlacePerfectCurveSegmentThenPlacePerfectCurveSegment() + { + addMovementStep(new Vector2(200)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(300, 200)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(300, 300)); + addClickStep(MouseButton.Left); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(400, 300)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(400)); + addClickStep(MouseButton.Right); + + assertPlaced(true); + assertControlPointCount(5); + assertControlPointPosition(1, new Vector2(100, 0)); + assertControlPointPosition(2, new Vector2(100)); + assertControlPointPosition(3, new Vector2(200, 100)); + assertControlPointPosition(4, new Vector2(200)); + assertControlPointType(0, PathType.PerfectCurve); + assertControlPointType(2, PathType.PerfectCurve); + } + + private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position))); + + private void addClickStep(MouseButton button) + { + AddStep($"press {button}", () => InputManager.PressButton(button)); + AddStep($"release {button}", () => InputManager.ReleaseButton(button)); + } + + private void assertPlaced(bool expected) => AddAssert($"slider {(expected ? "placed" : "not placed")}", () => (getSlider() != null) == expected); + + private void assertLength(double expected) => AddAssert($"slider length is {expected}", () => Precision.AlmostEquals(expected, getSlider().Distance, 1)); + + private void assertControlPointCount(int expected) => AddAssert($"has {expected} control points", () => getSlider().Path.ControlPoints.Count == expected); + + private void assertControlPointType(int index, PathType type) => AddAssert($"control point {index} is {type}", () => getSlider().Path.ControlPoints[index].Type.Value == type); + + private void assertControlPointPosition(int index, Vector2 position) => + AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, getSlider().Path.ControlPoints[index].Position.Value, 1)); + + private Slider getSlider() => HitObjectContainer.Count > 0 ? (Slider)((DrawableSlider)HitObjectContainer[0]).HitObject : null; + protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSlider((Slider)hitObject); protected override PlacementBlueprint CreateBlueprint() => new SliderPlacementBlueprint(); } diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs index ce95dfa62f..dc67d28f63 100644 --- a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs @@ -4,8 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -15,13 +13,11 @@ using osu.Game.Screens.Edit.Compose; namespace osu.Game.Tests.Visual { [Cached(Type = typeof(IPlacementHandler))] - public abstract class PlacementBlueprintTestScene : OsuTestScene, IPlacementHandler + public abstract class PlacementBlueprintTestScene : OsuManualInputManagerTestScene, IPlacementHandler { - protected Container HitObjectContainer; + protected readonly Container HitObjectContainer; private PlacementBlueprint currentBlueprint; - private InputManager inputManager; - protected PlacementBlueprintTestScene() { Add(HitObjectContainer = CreateHitObjectContainer().With(c => c.Clock = new FramedClock(new StopwatchClock()))); @@ -45,8 +41,7 @@ namespace osu.Game.Tests.Visual { base.LoadComplete(); - inputManager = GetContainingInputManager(); - Add(currentBlueprint = CreateBlueprint()); + ResetPlacement(); } public void BeginPlacement(HitObject hitObject) @@ -58,7 +53,13 @@ namespace osu.Game.Tests.Visual if (commit) AddHitObject(CreateHitObject(hitObject)); - Remove(currentBlueprint); + ResetPlacement(); + } + + protected void ResetPlacement() + { + if (currentBlueprint != null) + Remove(currentBlueprint); Add(currentBlueprint = CreateBlueprint()); } @@ -66,10 +67,11 @@ namespace osu.Game.Tests.Visual { } - protected override bool OnMouseMove(MouseMoveEvent e) + protected override void Update() { - currentBlueprint.UpdatePosition(e.ScreenSpaceMousePosition); - return true; + base.Update(); + + currentBlueprint.UpdatePosition(InputManager.CurrentState.Mouse.Position); } public override void Add(Drawable drawable) @@ -79,7 +81,7 @@ namespace osu.Game.Tests.Visual if (drawable is PlacementBlueprint blueprint) { blueprint.Show(); - blueprint.UpdatePosition(inputManager.CurrentState.Mouse.Position); + blueprint.UpdatePosition(InputManager.CurrentState.Mouse.Position); } } From 0eaff00787293a93f945bdf0f5866b5285e2626f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 13 Apr 2020 09:45:49 +0300 Subject: [PATCH 293/655] Fix typo in test --- .../Visual/UserInterface/TestSceneOverlayScrollContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs index fd3b6ed3ab..3ef0adcd9d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("scroll to start", () => scroll.ScrollToStart(false)); AddAssert("button is hidden", () => scroll.Button.State == Visibility.Hidden); - AddStep("scroll to 250", () => scroll.ScrollTo(500)); + AddStep("scroll to 500", () => scroll.ScrollTo(500)); AddUntilStep("scrolled back to start", () => Precision.AlmostEquals(scroll.Current, 500, 0.1f)); AddAssert("button is visible", () => scroll.Button.State == Visibility.Visible); } From bdce79ed5b85b01de2f49e24e22aa8156a518f3c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 13 Apr 2020 09:57:05 +0300 Subject: [PATCH 294/655] Fix incorrect test step name --- .../Visual/UserInterface/TestSceneOverlayScrollContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs index 3ef0adcd9d..6a09fecc0a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("button is hidden", () => scroll.Button.State == Visibility.Hidden); AddStep("scroll to 500", () => scroll.ScrollTo(500)); - AddUntilStep("scrolled back to start", () => Precision.AlmostEquals(scroll.Current, 500, 0.1f)); + AddUntilStep("scrolled to 500", () => Precision.AlmostEquals(scroll.Current, 500, 0.1f)); AddAssert("button is visible", () => scroll.Button.State == Visibility.Visible); } From 9a65aa18d78407bbba9658e0fc98810fc2940c69 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 13 Apr 2020 16:13:14 +0900 Subject: [PATCH 295/655] Fix connections hidden due to overlapping controlpoints --- .../TestScenePathControlPointVisualiser.cs | 64 +++++++++++++++++++ .../PathControlPointConnectionPiece.cs | 16 +++-- .../Components/PathControlPointVisualiser.cs | 57 +++++++++-------- 3 files changed, 103 insertions(+), 34 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestScenePathControlPointVisualiser.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestScenePathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu.Tests/TestScenePathControlPointVisualiser.cs new file mode 100644 index 0000000000..cbe14ff4d2 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestScenePathControlPointVisualiser.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using Humanizer; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestScenePathControlPointVisualiser : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(StringHumanizeExtensions), + typeof(PathControlPointPiece), + typeof(PathControlPointConnectionPiece) + }; + + private Slider slider; + private PathControlPointVisualiser visualiser; + + [SetUp] + public void Setup() => Schedule(() => + { + slider = new Slider(); + slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + }); + + [Test] + public void TestAddOverlappingControlPoints() + { + createVisualiser(true); + + addControlPointStep(new Vector2(200)); + addControlPointStep(new Vector2(300)); + addControlPointStep(new Vector2(300)); + addControlPointStep(new Vector2(500, 300)); + + AddAssert("last connection displayed", () => + { + var lastConnection = visualiser.Connections.Last(c => c.ControlPoint.Position.Value == new Vector2(300)); + return lastConnection.DrawWidth > 50; + }); + } + + private void createVisualiser(bool allowSelection) => AddStep("create visualiser", () => Child = visualiser = new PathControlPointVisualiser(slider, allowSelection) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }); + + private void addControlPointStep(Vector2 position) => AddStep($"add control point {position}", () => slider.Path.ControlPoints.Add(new PathControlPoint(position))); + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs index 0fc441fec6..9c620ecb2f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs @@ -16,22 +16,25 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components /// public class PathControlPointConnectionPiece : CompositeDrawable { - public PathControlPoint ControlPoint; + public readonly PathControlPoint ControlPoint; private readonly Path path; private readonly Slider slider; + private readonly int controlPointIndex; private IBindable sliderPosition; private IBindable pathVersion; - public PathControlPointConnectionPiece(Slider slider, PathControlPoint controlPoint) + public PathControlPointConnectionPiece(Slider slider, int controlPointIndex) { this.slider = slider; - ControlPoint = controlPoint; + this.controlPointIndex = controlPointIndex; Origin = Anchor.Centre; AutoSizeAxes = Axes.Both; + ControlPoint = slider.Path.ControlPoints[controlPointIndex]; + InternalChild = path = new SmoothPath { Anchor = Anchor.Centre, @@ -61,13 +64,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components path.ClearVertices(); - int index = slider.Path.ControlPoints.IndexOf(ControlPoint) + 1; - - if (index == 0 || index == slider.Path.ControlPoints.Count) + int nextIndex = controlPointIndex + 1; + if (nextIndex == 0 || nextIndex == slider.Path.ControlPoints.Count) return; path.AddVertex(Vector2.Zero); - path.AddVertex(slider.Path.ControlPoints[index].Position.Value - ControlPoint.Position.Value); + path.AddVertex(slider.Path.ControlPoints[nextIndex].Position.Value - ControlPoint.Position.Value); path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero); } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index e293eba9d7..f6354bc612 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using Humanizer; using osu.Framework.Bindables; @@ -24,17 +25,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler, IHasContextMenu { internal readonly Container Pieces; + internal readonly Container Connections; - private readonly Container connections; - + private readonly IBindableList controlPoints = new BindableList(); private readonly Slider slider; - private readonly bool allowSelection; private InputManager inputManager; - private IBindableList controlPoints; - public Action> RemoveControlPointsRequested; public PathControlPointVisualiser(Slider slider, bool allowSelection) @@ -46,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components InternalChildren = new Drawable[] { - connections = new Container { RelativeSizeAxes = Axes.Both }, + Connections = new Container { RelativeSizeAxes = Axes.Both }, Pieces = new Container { RelativeSizeAxes = Axes.Both } }; } @@ -57,33 +55,38 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components inputManager = GetContainingInputManager(); - controlPoints = slider.Path.ControlPoints.GetBoundCopy(); - controlPoints.ItemsAdded += addControlPoints; - controlPoints.ItemsRemoved += removeControlPoints; - - addControlPoints(controlPoints); + controlPoints.CollectionChanged += onControlPointsChanged; + controlPoints.BindTo(slider.Path.ControlPoints); } - private void addControlPoints(IEnumerable controlPoints) + private void onControlPointsChanged(object sender, NotifyCollectionChangedEventArgs e) { - foreach (var point in controlPoints) + switch (e.Action) { - Pieces.Add(new PathControlPointPiece(slider, point).With(d => - { - if (allowSelection) - d.RequestSelection = selectPiece; - })); + case NotifyCollectionChangedAction.Add: + for (int i = 0; i < e.NewItems.Count; i++) + { + var point = (PathControlPoint)e.NewItems[i]; - connections.Add(new PathControlPointConnectionPiece(slider, point)); - } - } + Pieces.Add(new PathControlPointPiece(slider, point).With(d => + { + if (allowSelection) + d.RequestSelection = selectPiece; + })); - private void removeControlPoints(IEnumerable controlPoints) - { - foreach (var point in controlPoints) - { - Pieces.RemoveAll(p => p.ControlPoint == point); - connections.RemoveAll(c => c.ControlPoint == point); + Connections.Add(new PathControlPointConnectionPiece(slider, e.NewStartingIndex + i)); + } + + break; + + case NotifyCollectionChangedAction.Remove: + foreach (var point in e.OldItems.Cast()) + { + Pieces.RemoveAll(p => p.ControlPoint == point); + Connections.RemoveAll(c => c.ControlPoint == point); + } + + break; } } From 29dd2252054ed45fbac8be5f43318bdae45552f5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 13 Apr 2020 10:45:15 +0300 Subject: [PATCH 296/655] Make button protected --- .../UserInterface/TestSceneOverlayScrollContainer.cs | 9 +++++++-- osu.Game/Overlays/OverlayScrollContainer.cs | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs index 6a09fecc0a..e9e63613c0 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayScrollContainer.cs @@ -25,14 +25,14 @@ namespace osu.Game.Tests.Visual.UserInterface [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); - private OverlayScrollContainer scroll; + private TestScrollContainer scroll; private int invocationCount; [SetUp] public void SetUp() => Schedule(() => { - Child = scroll = new OverlayScrollContainer + Child = scroll = new TestScrollContainer { RelativeSizeAxes = Axes.Both, Child = new Container @@ -104,5 +104,10 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("invocation count is 1", () => invocationCount == 1); } + + private class TestScrollContainer : OverlayScrollContainer + { + public new ScrollToTopButton Button => base.Button; + } } } diff --git a/osu.Game/Overlays/OverlayScrollContainer.cs b/osu.Game/Overlays/OverlayScrollContainer.cs index a8f2a0ce6c..9af09f0f6a 100644 --- a/osu.Game/Overlays/OverlayScrollContainer.cs +++ b/osu.Game/Overlays/OverlayScrollContainer.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays /// private const int button_scroll_position = 200; - public ScrollToTopButton Button { get; } + protected readonly ScrollToTopButton Button; private float currentTarget; From b8ecc41667301cccff0d3e81bccdc95f6946a69f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 13 Apr 2020 10:52:34 +0300 Subject: [PATCH 297/655] Add comment --- osu.Game/Overlays/OverlayScrollContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/OverlayScrollContainer.cs b/osu.Game/Overlays/OverlayScrollContainer.cs index 9af09f0f6a..e95a6379f1 100644 --- a/osu.Game/Overlays/OverlayScrollContainer.cs +++ b/osu.Game/Overlays/OverlayScrollContainer.cs @@ -56,6 +56,7 @@ namespace osu.Game.Overlays return; } + // Clicking on button should immediately cause it's disappearance, so we don't want to override it's state until we have a new target. if (Target == currentTarget) return; From 1e3251e3e936f0ce0eca7a77dd7ae5dbffab27eb Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 13 Apr 2020 10:59:53 +0300 Subject: [PATCH 298/655] Remove excessive logic --- osu.Game/Overlays/OverlayScrollContainer.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game/Overlays/OverlayScrollContainer.cs b/osu.Game/Overlays/OverlayScrollContainer.cs index e95a6379f1..e7415e6f74 100644 --- a/osu.Game/Overlays/OverlayScrollContainer.cs +++ b/osu.Game/Overlays/OverlayScrollContainer.cs @@ -28,8 +28,6 @@ namespace osu.Game.Overlays protected readonly ScrollToTopButton Button; - private float currentTarget; - public OverlayScrollContainer() { AddInternal(Button = new ScrollToTopButton @@ -40,7 +38,6 @@ namespace osu.Game.Overlays Action = () => { ScrollToStart(); - currentTarget = Target; Button.State = Visibility.Hidden; } }); @@ -56,11 +53,6 @@ namespace osu.Game.Overlays return; } - // Clicking on button should immediately cause it's disappearance, so we don't want to override it's state until we have a new target. - if (Target == currentTarget) - return; - - currentTarget = Target; Button.State = Target > button_scroll_position ? Visibility.Visible : Visibility.Hidden; } From bb53f96c717e9034c5a2e1b2411c3474d076fc9a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Apr 2020 17:18:50 +0900 Subject: [PATCH 299/655] Store states as byte[] instead of Streams --- .../Editor/LegacyEditorBeatmapDifferTest.cs | 19 +++++++-------- osu.Game/Screens/Edit/EditorChangeHandler.cs | 18 ++++++++------ .../Screens/Edit/LegacyEditorBeatmapDiffer.cs | 24 +++++++------------ 3 files changed, 29 insertions(+), 32 deletions(-) diff --git a/osu.Game.Tests/Editor/LegacyEditorBeatmapDifferTest.cs b/osu.Game.Tests/Editor/LegacyEditorBeatmapDifferTest.cs index d70a112b7f..ecd3799cb1 100644 --- a/osu.Game.Tests/Editor/LegacyEditorBeatmapDifferTest.cs +++ b/osu.Game.Tests/Editor/LegacyEditorBeatmapDifferTest.cs @@ -315,26 +315,25 @@ namespace osu.Game.Tests.Editor differ.Patch(encode(current), encode(patch)); // Convert beatmaps to strings for assertion purposes. - string currentStr = Encoding.ASCII.GetString(encode(current).ToArray()); - string patchStr = Encoding.ASCII.GetString(encode(patch).ToArray()); + string currentStr = Encoding.ASCII.GetString(encode(current)); + string patchStr = Encoding.ASCII.GetString(encode(patch)); Assert.That(currentStr, Is.EqualTo(patchStr)); } - private MemoryStream encode(IBeatmap beatmap) + private byte[] encode(IBeatmap beatmap) { - var encoded = new MemoryStream(); - + using (var encoded = new MemoryStream()) using (var sw = new StreamWriter(encoded, leaveOpen: true)) + { new LegacyBeatmapEncoder(beatmap).Encode(sw); - - return encoded; + return encoded.ToArray(); + } } - private IBeatmap decode(Stream stream) + private IBeatmap decode(byte[] state) { - stream.Seek(0, SeekOrigin.Begin); - + using (var stream = new MemoryStream(state)) using (var reader = new LineBufferedReader(stream, true)) return Decoder.GetDecoder(reader).Decode(reader); } diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index 7e372926ba..22f076d939 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -16,7 +16,9 @@ namespace osu.Game.Screens.Edit public class EditorChangeHandler : IEditorChangeHandler { private readonly LegacyEditorBeatmapDiffer differ; - private readonly List savedStates = new List(); + + private readonly List savedStates = new List(); + private int currentState = -1; private readonly EditorBeatmap editorBeatmap; @@ -69,15 +71,17 @@ namespace osu.Game.Screens.Edit if (isRestoring) return; - var stream = new MemoryStream(); - - using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - new LegacyBeatmapEncoder(editorBeatmap).Encode(sw); - if (currentState < savedStates.Count - 1) savedStates.RemoveRange(currentState + 1, savedStates.Count - currentState - 1); - savedStates.Add(stream); + using (var stream = new MemoryStream()) + { + using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) + new LegacyBeatmapEncoder(editorBeatmap).Encode(sw); + + savedStates.Add(stream.ToArray()); + } + currentState = savedStates.Count - 1; } diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapDiffer.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapDiffer.cs index 8d2f577a1d..c62f21bb39 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapDiffer.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapDiffer.cs @@ -4,12 +4,13 @@ using System; using System.Collections.Generic; using System.IO; +using System.Text; using DiffPlex; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Formats; using osu.Game.IO; +using Decoder = osu.Game.Beatmaps.Formats.Decoder; namespace osu.Game.Screens.Edit { @@ -22,7 +23,7 @@ namespace osu.Game.Screens.Edit this.editorBeatmap = editorBeatmap; } - public void Patch(Stream currentState, Stream newState) + public void Patch(byte[] currentState, byte[] newState) { // Diff the beatmaps var result = new Differ().CreateLineDiffs(readString(currentState), readString(newState), true, false); @@ -36,7 +37,7 @@ namespace osu.Game.Screens.Edit foreach (var block in result.DiffBlocks) { - // Removed hitobject + // Removed hitobjects for (int i = 0; i < block.DeleteCountA; i++) { int hoIndex = block.DeleteStartA + i - oldHitObjectsIndex - 1; @@ -47,7 +48,7 @@ namespace osu.Game.Screens.Edit toRemove.Add(hoIndex); } - // Added hitobject + // Added hitobjects for (int i = 0; i < block.InsertCountB; i++) { int hoIndex = block.InsertStartB + i - newHitObjectsIndex - 1; @@ -74,18 +75,11 @@ namespace osu.Game.Screens.Edit } } - private string readString(Stream stream) + private string readString(byte[] state) => Encoding.UTF8.GetString(state); + + private IBeatmap readBeatmap(byte[] state) { - stream.Seek(0, SeekOrigin.Begin); - - using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8, true, 1024, true)) - return sr.ReadToEnd(); - } - - private IBeatmap readBeatmap(Stream stream) - { - stream.Seek(0, SeekOrigin.Begin); - + using (var stream = new MemoryStream(state)) using (var reader = new LineBufferedReader(stream, true)) return new PassThroughWorkingBeatmap(Decoder.GetDecoder(reader).Decode(reader)).GetPlayableBeatmap(editorBeatmap.BeatmapInfo.Ruleset); } From 6aab19413c793bce5650fa1aa6a95a82a3048e4c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Apr 2020 17:20:01 +0900 Subject: [PATCH 300/655] Rename differ to patcher, add xmldoc --- ...mapDifferTest.cs => LegacyEditorBeatmapPatcherTest.cs} | 8 ++++---- osu.Game/Screens/Edit/EditorChangeHandler.cs | 6 +++--- ...itorBeatmapDiffer.cs => LegacyEditorBeatmapPatcher.cs} | 7 +++++-- 3 files changed, 12 insertions(+), 9 deletions(-) rename osu.Game.Tests/Editor/{LegacyEditorBeatmapDifferTest.cs => LegacyEditorBeatmapPatcherTest.cs} (97%) rename osu.Game/Screens/Edit/{LegacyEditorBeatmapDiffer.cs => LegacyEditorBeatmapPatcher.cs} (93%) diff --git a/osu.Game.Tests/Editor/LegacyEditorBeatmapDifferTest.cs b/osu.Game.Tests/Editor/LegacyEditorBeatmapPatcherTest.cs similarity index 97% rename from osu.Game.Tests/Editor/LegacyEditorBeatmapDifferTest.cs rename to osu.Game.Tests/Editor/LegacyEditorBeatmapPatcherTest.cs index ecd3799cb1..11c6399d19 100644 --- a/osu.Game.Tests/Editor/LegacyEditorBeatmapDifferTest.cs +++ b/osu.Game.Tests/Editor/LegacyEditorBeatmapPatcherTest.cs @@ -20,15 +20,15 @@ using Decoder = osu.Game.Beatmaps.Formats.Decoder; namespace osu.Game.Tests.Editor { [TestFixture] - public class LegacyEditorBeatmapDifferTest + public class LegacyEditorBeatmapPatcherTest { - private LegacyEditorBeatmapDiffer differ; + private LegacyEditorBeatmapPatcher patcher; private EditorBeatmap current; [SetUp] public void Setup() { - differ = new LegacyEditorBeatmapDiffer(current = new EditorBeatmap(new OsuBeatmap + patcher = new LegacyEditorBeatmapPatcher(current = new EditorBeatmap(new OsuBeatmap { BeatmapInfo = { @@ -312,7 +312,7 @@ namespace osu.Game.Tests.Editor patch = decode(encode(patch)); // Apply the patch. - differ.Patch(encode(current), encode(patch)); + patcher.Patch(encode(current), encode(patch)); // Convert beatmaps to strings for assertion purposes. string currentStr = Encoding.ASCII.GetString(encode(current)); diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index 22f076d939..00a27801f4 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.Edit /// public class EditorChangeHandler : IEditorChangeHandler { - private readonly LegacyEditorBeatmapDiffer differ; + private readonly LegacyEditorBeatmapPatcher patcher; private readonly List savedStates = new List(); @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Edit editorBeatmap.HitObjectRemoved += hitObjectRemoved; editorBeatmap.HitObjectUpdated += hitObjectUpdated; - differ = new LegacyEditorBeatmapDiffer(editorBeatmap); + patcher = new LegacyEditorBeatmapPatcher(editorBeatmap); // Initial state. SaveState(); @@ -103,7 +103,7 @@ namespace osu.Game.Screens.Edit isRestoring = true; - differ.Patch(savedStates[currentState], savedStates[newState]); + patcher.Patch(savedStates[currentState], savedStates[newState]); currentState = newState; isRestoring = false; diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapDiffer.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs similarity index 93% rename from osu.Game/Screens/Edit/LegacyEditorBeatmapDiffer.cs rename to osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index c62f21bb39..17eba87076 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapDiffer.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -14,11 +14,14 @@ using Decoder = osu.Game.Beatmaps.Formats.Decoder; namespace osu.Game.Screens.Edit { - public class LegacyEditorBeatmapDiffer + /// + /// Patches an based on the difference between two legacy (.osu) states. + /// + public class LegacyEditorBeatmapPatcher { private readonly EditorBeatmap editorBeatmap; - public LegacyEditorBeatmapDiffer(EditorBeatmap editorBeatmap) + public LegacyEditorBeatmapPatcher(EditorBeatmap editorBeatmap) { this.editorBeatmap = editorBeatmap; } From dd949a3fe09ebaa45143be64075efe89aafc08f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Apr 2020 17:52:04 +0900 Subject: [PATCH 301/655] Fix test writer flush happening too late --- osu.Game.Tests/Editor/LegacyEditorBeatmapPatcherTest.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Editor/LegacyEditorBeatmapPatcherTest.cs b/osu.Game.Tests/Editor/LegacyEditorBeatmapPatcherTest.cs index 11c6399d19..c24418d688 100644 --- a/osu.Game.Tests/Editor/LegacyEditorBeatmapPatcherTest.cs +++ b/osu.Game.Tests/Editor/LegacyEditorBeatmapPatcherTest.cs @@ -324,9 +324,10 @@ namespace osu.Game.Tests.Editor private byte[] encode(IBeatmap beatmap) { using (var encoded = new MemoryStream()) - using (var sw = new StreamWriter(encoded, leaveOpen: true)) { - new LegacyBeatmapEncoder(beatmap).Encode(sw); + using (var sw = new StreamWriter(encoded)) + new LegacyBeatmapEncoder(beatmap).Encode(sw); + return encoded.ToArray(); } } @@ -334,7 +335,7 @@ namespace osu.Game.Tests.Editor private IBeatmap decode(byte[] state) { using (var stream = new MemoryStream(state)) - using (var reader = new LineBufferedReader(stream, true)) + using (var reader = new LineBufferedReader(stream)) return Decoder.GetDecoder(reader).Decode(reader); } } From 409cda3cc0d5a8edeb8470a88648499bab9d511d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2020 08:53:21 +0000 Subject: [PATCH 302/655] Bump BenchmarkDotNet from 0.12.0 to 0.12.1 Bumps [BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet) from 0.12.0 to 0.12.1. - [Release notes](https://github.com/dotnet/BenchmarkDotNet/releases) - [Commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.12.0...v0.12.1) Signed-off-by: dependabot-preview[bot] --- osu.Game.Benchmarks/osu.Game.Benchmarks.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index f2e1c0ec3b..88fe8f1150 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -7,7 +7,7 @@ - + From b741e359cd5abf64d3428c3ff0da0e2ae1f1e436 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 13 Apr 2020 12:23:28 +0300 Subject: [PATCH 303/655] Use OverlayScrollContainer for overlays --- .../Graphics/Containers/SectionsContainer.cs | 27 ++++++++++++------- osu.Game/Overlays/BeatmapListingOverlay.cs | 2 +- osu.Game/Overlays/BeatmapSetOverlay.cs | 4 +-- osu.Game/Overlays/ChangelogOverlay.cs | 2 +- osu.Game/Overlays/NewsOverlay.cs | 2 +- osu.Game/Overlays/RankingsOverlay.cs | 4 +-- .../SearchableList/SearchableListOverlay.cs | 2 +- osu.Game/Overlays/UserProfileOverlay.cs | 2 ++ 8 files changed, 27 insertions(+), 18 deletions(-) diff --git a/osu.Game/Graphics/Containers/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs index 07a50c39e1..24c61ad11c 100644 --- a/osu.Game/Graphics/Containers/SectionsContainer.cs +++ b/osu.Game/Graphics/Containers/SectionsContainer.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -19,7 +20,7 @@ namespace osu.Game.Graphics.Containers private Drawable expandableHeader, fixedHeader, footer, headerBackground; private readonly OsuScrollContainer scrollContainer; private readonly Container headerBackgroundContainer; - private readonly FlowContainer scrollContentContainer; + private FlowContainer scrollContentContainer; protected override Container Content => scrollContentContainer; @@ -125,20 +126,26 @@ namespace osu.Game.Graphics.Containers public SectionsContainer() { - AddInternal(scrollContainer = new OsuScrollContainer + AddRangeInternal(new Drawable[] { - RelativeSizeAxes = Axes.Both, - Masking = true, - ScrollbarVisible = false, - Children = new Drawable[] { scrollContentContainer = CreateScrollContentContainer() } - }); - AddInternal(headerBackgroundContainer = new Container - { - RelativeSizeAxes = Axes.X + scrollContainer = CreateScrollContainer().With(s => + { + s.RelativeSizeAxes = Axes.Both; + s.Masking = true; + s.ScrollbarVisible = false; + s.Children = new Drawable[] { scrollContentContainer = CreateScrollContentContainer() }; + }), + headerBackgroundContainer = new Container + { + RelativeSizeAxes = Axes.X + } }); originalSectionsMargin = scrollContentContainer.Margin; } + [NotNull] + protected virtual OsuScrollContainer CreateScrollContainer() => new OsuScrollContainer(); + public void ScrollTo(Drawable section) => scrollContainer.ScrollTo(scrollContainer.GetChildPosInContent(section) - (FixedHeader?.BoundingBox.Height ?? 0)); public void ScrollToTop() => scrollContainer.ScrollTo(0); diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 5bac5a5402..b450f33ee1 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -54,7 +54,7 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both, Colour = ColourProvider.Background6 }, - new BasicScrollContainer + new OverlayScrollContainer { RelativeSizeAxes = Axes.Both, ScrollbarVisible = false, diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 0d16c4842d..3e23442023 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays public BeatmapSetOverlay() : base(OverlayColourScheme.Blue) { - OsuScrollContainer scroll; + OverlayScrollContainer scroll; Info info; CommentsSection comments; @@ -49,7 +49,7 @@ namespace osu.Game.Overlays { RelativeSizeAxes = Axes.Both }, - scroll = new OsuScrollContainer + scroll = new OverlayScrollContainer { RelativeSizeAxes = Axes.Both, ScrollbarVisible = false, diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index d13ac5c2de..726be9e194 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -50,7 +50,7 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both, Colour = ColourProvider.Background4, }, - new OsuScrollContainer + new OverlayScrollContainer { RelativeSizeAxes = Axes.Both, ScrollbarVisible = false, diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index 71c205ff63..6c9477cbc4 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both, Colour = colours.PurpleDarkAlternative }, - new OsuScrollContainer + new OverlayScrollContainer { RelativeSizeAxes = Axes.Both, Child = new FillFlowContainer diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index afb23883ac..7b200d4226 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -23,7 +23,7 @@ namespace osu.Game.Overlays protected Bindable Scope => header.Current; - private readonly BasicScrollContainer scrollFlow; + private readonly OverlayScrollContainer scrollFlow; private readonly Container contentContainer; private readonly LoadingLayer loading; private readonly Box background; @@ -44,7 +44,7 @@ namespace osu.Game.Overlays { RelativeSizeAxes = Axes.Both }, - scrollFlow = new BasicScrollContainer + scrollFlow = new OverlayScrollContainer { RelativeSizeAxes = Axes.Both, ScrollbarVisible = false, diff --git a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs index d6174e0733..ebd12913f5 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs @@ -72,7 +72,7 @@ namespace osu.Game.Overlays.SearchableList { RelativeSizeAxes = Axes.Both, Masking = true, - Child = new OsuScrollContainer + Child = new OverlayScrollContainer { RelativeSizeAxes = Axes.Both, ScrollbarVisible = false, diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 6ec30f7707..b4c8a2d3ca 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -195,6 +195,8 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both; } + protected override OsuScrollContainer CreateScrollContainer() => new OverlayScrollContainer(); + protected override FlowContainer CreateScrollContentContainer() => new FillFlowContainer { Direction = FillDirection.Vertical, From 4c5d01a611ddc88cd847a2d91db27ff57bc3e037 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 13 Apr 2020 12:34:51 +0300 Subject: [PATCH 304/655] Remove unused usings --- osu.Game/Overlays/NewsOverlay.cs | 1 - osu.Game/Overlays/SearchableList/SearchableListOverlay.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index 6c9477cbc4..46d692d44d 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Overlays.News; namespace osu.Game.Overlays diff --git a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs index ebd12913f5..4ab2de06b6 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics.Backgrounds; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; namespace osu.Game.Overlays.SearchableList From cee4b005e6a5ceb3e4af6380f73321205b9d9403 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 13 Apr 2020 20:00:06 +0900 Subject: [PATCH 305/655] Fix custom sample set 0 not falling back to default samples --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 2 +- .../Objects/Legacy/ConvertHitObjectParser.cs | 2 +- osu.Game/Skinning/LegacyBeatmapSkin.cs | 13 +++++++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 561707f9ef..90a5d0dcba 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -168,7 +168,7 @@ namespace osu.Game.Beatmaps.Formats { var baseInfo = base.ApplyTo(hitSampleInfo); - if (string.IsNullOrEmpty(baseInfo.Suffix) && CustomSampleBank > 1) + if (string.IsNullOrEmpty(baseInfo.Suffix) && CustomSampleBank > 0) baseInfo.Suffix = CustomSampleBank.ToString(); return baseInfo; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 8d3ad5984f..8580cdede3 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -415,7 +415,7 @@ namespace osu.Game.Rulesets.Objects.Legacy { set { - if (value > 1) + if (value > 0) Suffix = value.ToString(); } } diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 1190a330fe..c4636f46f5 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.IO.Stores; +using osu.Game.Audio; using osu.Game.Beatmaps; namespace osu.Game.Skinning @@ -33,6 +35,17 @@ namespace osu.Game.Skinning return base.GetConfig(lookup); } + public override SampleChannel GetSample(ISampleInfo sampleInfo) + { + if (sampleInfo is HitSampleInfo hsi && string.IsNullOrEmpty(hsi.Suffix)) + { + // When no custom sample set is provided, always fall-back to the default samples. + return null; + } + + return base.GetSample(sampleInfo); + } + private static SkinInfo createSkinInfo(BeatmapInfo beatmap) => new SkinInfo { Name = beatmap.ToString(), Creator = beatmap.Metadata.Author.ToString() }; } From 58a7313091812597a622ff0f616fcd23a271febd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 13 Apr 2020 20:09:17 +0900 Subject: [PATCH 306/655] Fix fallback for file hit samples --- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 8580cdede3..1dca4a5c02 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -425,6 +425,12 @@ namespace osu.Game.Rulesets.Objects.Legacy { public string Filename; + public FileHitSampleInfo() + { + // Has no effect since LookupNames is overridden, however prompts LegacyBeatmapSkin to not fallback. + Suffix = "0"; + } + public override IEnumerable LookupNames => new[] { Filename, From 0be2dc9b2dee3eecb9fdac94a9f8e64745230d72 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Apr 2020 20:12:51 +0900 Subject: [PATCH 307/655] Tidy up SectionsContainer class layout/ordering --- .../Graphics/Containers/SectionsContainer.cs | 82 ++++++++++--------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/osu.Game/Graphics/Containers/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs index 24c61ad11c..a3125614aa 100644 --- a/osu.Game/Graphics/Containers/SectionsContainer.cs +++ b/osu.Game/Graphics/Containers/SectionsContainer.cs @@ -17,12 +17,7 @@ namespace osu.Game.Graphics.Containers public class SectionsContainer : Container where T : Drawable { - private Drawable expandableHeader, fixedHeader, footer, headerBackground; - private readonly OsuScrollContainer scrollContainer; - private readonly Container headerBackgroundContainer; - private FlowContainer scrollContentContainer; - - protected override Container Content => scrollContentContainer; + public Bindable SelectedSection { get; } = new Bindable(); public Drawable ExpandableHeader { @@ -84,6 +79,7 @@ namespace osu.Game.Graphics.Containers headerBackgroundContainer.Clear(); headerBackground = value; + if (value == null) return; headerBackgroundContainer.Add(headerBackground); @@ -92,37 +88,17 @@ namespace osu.Game.Graphics.Containers } } - public Bindable SelectedSection { get; } = new Bindable(); + protected override Container Content => scrollContentContainer; - protected virtual FlowContainer CreateScrollContentContainer() - => new FillFlowContainer - { - Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - }; - - public override void Add(T drawable) - { - base.Add(drawable); - lastKnownScroll = float.NaN; - headerHeight = float.NaN; - footerHeight = float.NaN; - } + private readonly OsuScrollContainer scrollContainer; + private readonly Container headerBackgroundContainer; + private readonly MarginPadding originalSectionsMargin; + private Drawable expandableHeader, fixedHeader, footer, headerBackground; + private FlowContainer scrollContentContainer; private float headerHeight, footerHeight; - private readonly MarginPadding originalSectionsMargin; - private void updateSectionsMargin() - { - if (!Children.Any()) return; - - var newMargin = originalSectionsMargin; - newMargin.Top += headerHeight; - newMargin.Bottom += footerHeight; - - scrollContentContainer.Margin = newMargin; - } + private float lastKnownScroll; public SectionsContainer() { @@ -133,22 +109,41 @@ namespace osu.Game.Graphics.Containers s.RelativeSizeAxes = Axes.Both; s.Masking = true; s.ScrollbarVisible = false; - s.Children = new Drawable[] { scrollContentContainer = CreateScrollContentContainer() }; + s.Child = scrollContentContainer = CreateScrollContentContainer(); }), headerBackgroundContainer = new Container { RelativeSizeAxes = Axes.X } }); + originalSectionsMargin = scrollContentContainer.Margin; } + public override void Add(T drawable) + { + base.Add(drawable); + lastKnownScroll = float.NaN; + headerHeight = float.NaN; + footerHeight = float.NaN; + } + + public void ScrollTo(Drawable section) => + scrollContainer.ScrollTo(scrollContainer.GetChildPosInContent(section) - (FixedHeader?.BoundingBox.Height ?? 0)); + + public void ScrollToTop() => scrollContainer.ScrollTo(0); + [NotNull] protected virtual OsuScrollContainer CreateScrollContainer() => new OsuScrollContainer(); - public void ScrollTo(Drawable section) => scrollContainer.ScrollTo(scrollContainer.GetChildPosInContent(section) - (FixedHeader?.BoundingBox.Height ?? 0)); - - public void ScrollToTop() => scrollContainer.ScrollTo(0); + [NotNull] + protected virtual FlowContainer CreateScrollContentContainer() => + new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + }; protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) { @@ -163,8 +158,6 @@ namespace osu.Game.Graphics.Containers return result; } - private float lastKnownScroll; - protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); @@ -215,5 +208,16 @@ namespace osu.Game.Graphics.Containers SelectedSection.Value = bestMatch; } } + + private void updateSectionsMargin() + { + if (!Children.Any()) return; + + var newMargin = originalSectionsMargin; + newMargin.Top += headerHeight; + newMargin.Bottom += footerHeight; + + scrollContentContainer.Margin = newMargin; + } } } From 2388799acfb8a781894d2c41de06c7f25b92bccb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Apr 2020 20:34:18 +0900 Subject: [PATCH 308/655] Limit upper number of editor beatmap states saved to 50 --- .../Editor/EditorChangeHandlerTest.cs | 71 +++++++++++++++++++ osu.Game/Screens/Edit/EditorChangeHandler.cs | 7 ++ 2 files changed, 78 insertions(+) create mode 100644 osu.Game.Tests/Editor/EditorChangeHandlerTest.cs diff --git a/osu.Game.Tests/Editor/EditorChangeHandlerTest.cs b/osu.Game.Tests/Editor/EditorChangeHandlerTest.cs new file mode 100644 index 0000000000..ef16976130 --- /dev/null +++ b/osu.Game.Tests/Editor/EditorChangeHandlerTest.cs @@ -0,0 +1,71 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Screens.Edit; + +namespace osu.Game.Tests.Editor +{ + [TestFixture] + public class EditorChangeHandlerTest + { + [Test] + public void TestSaveRestoreState() + { + var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap())); + + Assert.That(handler.HasUndoState, Is.False); + + handler.SaveState(); + + Assert.That(handler.HasUndoState, Is.True); + + handler.RestoreState(-1); + + Assert.That(handler.HasUndoState, Is.False); + } + + [Test] + public void TestMaxStatesSaved() + { + var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap())); + + Assert.That(handler.HasUndoState, Is.False); + + for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES; i++) + handler.SaveState(); + + Assert.That(handler.HasUndoState, Is.True); + + for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES; i++) + { + Assert.That(handler.HasUndoState, Is.True); + handler.RestoreState(-1); + } + + Assert.That(handler.HasUndoState, Is.False); + } + + [Test] + public void TestMaxStatesExceeded() + { + var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap())); + + Assert.That(handler.HasUndoState, Is.False); + + for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES * 2; i++) + handler.SaveState(); + + Assert.That(handler.HasUndoState, Is.True); + + for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES; i++) + { + Assert.That(handler.HasUndoState, Is.True); + handler.RestoreState(-1); + } + + Assert.That(handler.HasUndoState, Is.False); + } + } +} diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index 00a27801f4..a8204715cd 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -25,6 +25,8 @@ namespace osu.Game.Screens.Edit private int bulkChangesStarted; private bool isRestoring; + public const int MAX_SAVED_STATES = 50; + /// /// Creates a new . /// @@ -43,6 +45,8 @@ namespace osu.Game.Screens.Edit SaveState(); } + public bool HasUndoState => currentState > 0; + private void hitObjectAdded(HitObject obj) => SaveState(); private void hitObjectRemoved(HitObject obj) => SaveState(); @@ -74,6 +78,9 @@ namespace osu.Game.Screens.Edit if (currentState < savedStates.Count - 1) savedStates.RemoveRange(currentState + 1, savedStates.Count - currentState - 1); + if (savedStates.Count > MAX_SAVED_STATES) + savedStates.RemoveAt(0); + using (var stream = new MemoryStream()) { using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) From 1c8a71b2842dd90ff1cdfc1c4d4cd3595a618925 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Apr 2020 21:24:47 +0900 Subject: [PATCH 309/655] Exception instead of assert --- osu.Game/Online/API/APIRequest.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 6abb388c01..47600e4f68 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using Newtonsoft.Json; using osu.Framework.IO.Network; using osu.Framework.Logging; @@ -32,8 +31,8 @@ namespace osu.Game.Online.API internal void TriggerSuccess(T result) { - // disallow calling twice - Debug.Assert(Result == null); + if (Result != null) + throw new InvalidOperationException("Attempted to trigger success more than once"); Result = result; Success?.Invoke(result); From 89d806358809b62ca0c8b81ec77abbc412ae8cbe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Apr 2020 21:35:35 +0900 Subject: [PATCH 310/655] Add support for Perform/PerformAsync --- .../Online/TestDummyAPIRequestHandling.cs | 63 ++++++++++++++++--- osu.Game/Online/API/DummyAPIAccess.cs | 8 ++- 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs b/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs index b00b63f6d5..5ef01d5702 100644 --- a/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs +++ b/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs @@ -43,17 +43,9 @@ namespace osu.Game.Tests.Online } [Test] - public void TestRequestHandling() + public void TestQueueRequestHandling() { - AddStep("register request handling", () => ((DummyAPIAccess)API).HandleRequest = req => - { - switch (req) - { - case LeaveChannelRequest cRequest: - cRequest.TriggerSuccess(); - break; - } - }); + registerHandler(); LeaveChannelRequest request; bool gotResponse = false; @@ -68,5 +60,56 @@ namespace osu.Game.Tests.Online AddAssert("response event fired", () => gotResponse); } + + [Test] + public void TestPerformRequestHandling() + { + registerHandler(); + + LeaveChannelRequest request; + bool gotResponse = false; + + AddStep("fire request", () => + { + gotResponse = false; + request = new LeaveChannelRequest(new Channel(), new User()); + request.Success += () => gotResponse = true; + API.Perform(request); + }); + + AddAssert("response event fired", () => gotResponse); + } + + [Test] + public void TestPerformAsyncRequestHandling() + { + registerHandler(); + + LeaveChannelRequest request; + bool gotResponse = false; + + AddStep("fire request", () => + { + gotResponse = false; + request = new LeaveChannelRequest(new Channel(), new User()); + request.Success += () => gotResponse = true; + API.PerformAsync(request); + }); + + AddAssert("response event fired", () => gotResponse); + } + + private void registerHandler() + { + AddStep("register request handling", () => ((DummyAPIAccess)API).HandleRequest = req => + { + switch (req) + { + case LeaveChannelRequest cRequest: + cRequest.TriggerSuccess(); + break; + } + }); + } } } diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index fa5ad115d2..7800241904 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -64,9 +64,13 @@ namespace osu.Game.Online.API HandleRequest?.Invoke(request); } - public void Perform(APIRequest request) { } + public void Perform(APIRequest request) => HandleRequest?.Invoke(request); - public Task PerformAsync(APIRequest request) => Task.CompletedTask; + public Task PerformAsync(APIRequest request) + { + HandleRequest?.Invoke(request); + return Task.CompletedTask; + } public void Register(IOnlineComponent component) { From 4cfc6866835d4d92f2c6f03cd4bfeaa47c845c76 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 13 Apr 2020 21:41:18 +0900 Subject: [PATCH 311/655] Fix excption with 0 control points --- .../Sliders/Components/PathControlPointConnectionPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs index 9c620ecb2f..ba1d35c35c 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components path.ClearVertices(); int nextIndex = controlPointIndex + 1; - if (nextIndex == 0 || nextIndex == slider.Path.ControlPoints.Count) + if (nextIndex == 0 || nextIndex >= slider.Path.ControlPoints.Count) return; path.AddVertex(Vector2.Zero); From 2b2ab2bf1929d3b6d730d579b73b1bc39e2220e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Apr 2020 21:59:23 +0900 Subject: [PATCH 312/655] Show new segments as red points even when hovered --- .../Blueprints/Sliders/Components/PathControlPointPiece.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 092a13cca5..fed149b5c5 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -51,6 +52,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components this.slider = slider; ControlPoint = controlPoint; + controlPoint.Type.BindValueChanged(_ => updateMarkerDisplay()); + Origin = Anchor.Centre; AutoSizeAxes = Axes.Both; @@ -183,8 +186,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components markerRing.Alpha = IsSelected.Value ? 1 : 0; Color4 colour = ControlPoint.Type.Value != null ? colours.Red : colours.Yellow; + if (IsHovered || IsSelected.Value) - colour = Color4.White; + colour = colour.Lighten(1); + marker.Colour = colour; } } From 13812fef4c1af988e1d292e43d96e20dc17b0784 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Mon, 13 Apr 2020 17:28:02 +0300 Subject: [PATCH 313/655] Replace BindTo with setting the bindable --- osu.Game/Overlays/Direct/PanelDownloadButton.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Direct/PanelDownloadButton.cs b/osu.Game/Overlays/Direct/PanelDownloadButton.cs index 51f5b2ae4f..dfcaf5ded6 100644 --- a/osu.Game/Overlays/Direct/PanelDownloadButton.cs +++ b/osu.Game/Overlays/Direct/PanelDownloadButton.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Direct private readonly ShakeContainer shakeContainer; private readonly DownloadButton button; - private readonly BindableBool noVideoSetting = new BindableBool(); + private Bindable noVideoSetting; public PanelDownloadButton(BeatmapSetInfo beatmapSet) : base(beatmapSet) @@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Direct return; } - noVideoSetting.BindTo(osuConfig.GetBindable(OsuSetting.PreferNoVideo)); + noVideoSetting = osuConfig.GetBindable(OsuSetting.PreferNoVideo); button.Action = () => { From 3e48c26bc24eedfdc2fe0a8a6ac01f5377648ca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 14 Apr 2020 00:54:02 +0200 Subject: [PATCH 314/655] Add failing tests --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs new file mode 100644 index 0000000000..64d1024efb --- /dev/null +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -0,0 +1,57 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Rulesets.Scoring +{ + public class ScoreProcessorTest + { + private ScoreProcessor scoreProcessor; + private IBeatmap beatmap; + + [SetUp] + public void SetUp() + { + scoreProcessor = new ScoreProcessor(); + beatmap = new TestBeatmap(new RulesetInfo()) + { + HitObjects = new List + { + new HitCircle() + } + }; + } + + [TestCase(ScoringMode.Standardised, HitResult.Meh, 750_000)] + [TestCase(ScoringMode.Standardised, HitResult.Good, 800_000)] + [TestCase(ScoringMode.Standardised, HitResult.Great, 1_000_000)] + [TestCase(ScoringMode.Classic, HitResult.Meh, 50)] + [TestCase(ScoringMode.Classic, HitResult.Good, 100)] + [TestCase(ScoringMode.Classic, HitResult.Great, 300)] + public void TestSingleOsuHit(ScoringMode scoringMode, HitResult hitResult, int expectedScore) + { + scoreProcessor.Mode.Value = scoringMode; + scoreProcessor.ApplyBeatmap(beatmap); + + var judgementResult = new JudgementResult(beatmap.HitObjects.Single(), new OsuJudgement()) + { + Type = hitResult + }; + scoreProcessor.ApplyResult(judgementResult); + + Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value)); + } + } +} From 13c81db0cf90fcb1db1909a20aa753c23884faea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 14 Apr 2020 00:56:37 +0200 Subject: [PATCH 315/655] Fix incorrect classic score formula Upon closer inspection the classic score formula was subtly wrong. The version given in the wiki is: Score = Hit Value + (Hit Value * ((Combo multiplier * Difficulty multiplier * Mod multiplier) / 25)) The code previously used: bonusScore + baseScore * ((1 + Math.Max(0, HighestCombo.Value - 1) * scoreMultiplier) / 25); which is not equivalent to the version on the wiki. The error is in the 1 factor, as in the above version it is being divided by 25, while it should be outside the division to keep parity with the previous formula. The tests attached in the previous commit demonstrate that this change causes a single hit without combo to increase total score by its exact numeric value. --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 8eafaa88ec..1f40f44dce 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Scoring case ScoringMode.Classic: // should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1) - return bonusScore + baseScore * ((1 + Math.Max(0, HighestCombo.Value - 1) * scoreMultiplier) / 25); + return bonusScore + baseScore * (1 + Math.Max(0, HighestCombo.Value - 1) * scoreMultiplier / 25); } } From 5f13dc81bed4d90e9fd43c5a3e96573de00951dd Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 14 Apr 2020 04:38:18 +0300 Subject: [PATCH 316/655] Remove no longer necessary extensions --- .../Skinning/CatchSkinExtensions.cs | 20 ------------------- .../Skinning/LegacyFruitPiece.cs | 9 ++++++--- 2 files changed, 6 insertions(+), 23 deletions(-) delete mode 100644 osu.Game.Rulesets.Catch/Skinning/CatchSkinExtensions.cs diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchSkinExtensions.cs b/osu.Game.Rulesets.Catch/Skinning/CatchSkinExtensions.cs deleted file mode 100644 index 718b22a0fb..0000000000 --- a/osu.Game.Rulesets.Catch/Skinning/CatchSkinExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using JetBrains.Annotations; -using osu.Framework.Bindables; -using osu.Game.Rulesets.Catch.UI; -using osu.Game.Skinning; -using osuTK.Graphics; - -namespace osu.Game.Rulesets.Catch.Skinning -{ - internal static class CatchSkinExtensions - { - [NotNull] - public static IBindable GetHyperDashFruitColour(this ISkin skin) - => skin.GetConfig(CatchSkinColour.HyperDashFruit) ?? - skin.GetConfig(CatchSkinColour.HyperDash) ?? - new Bindable(Catcher.DEFAULT_HYPER_DASH_COLOUR); - } -} diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs index 470c12559e..5be54d3882 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Skinning; using osuTK; @@ -55,14 +56,16 @@ namespace osu.Game.Rulesets.Catch.Skinning { var hyperDash = new Sprite { - Texture = skin.GetTexture(lookupName), - Colour = skin.GetHyperDashFruitColour().Value, Anchor = Anchor.Centre, Origin = Anchor.Centre, Blending = BlendingParameters.Additive, Depth = 1, Alpha = 0.7f, - Scale = new Vector2(1.2f) + Scale = new Vector2(1.2f), + Texture = skin.GetTexture(lookupName), + Colour = skin.GetConfig(CatchSkinColour.HyperDashFruit)?.Value ?? + skin.GetConfig(CatchSkinColour.HyperDash)?.Value ?? + Catcher.DEFAULT_HYPER_DASH_COLOUR, }; AddInternal(hyperDash); From c5f8bbb25fe55fd89af091ce27b8f5ba27aaa9f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Apr 2020 11:56:37 +0900 Subject: [PATCH 317/655] Fix beatmap background not displaying when video is present --- osu.Game/Storyboards/Storyboard.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index a1ddafbacf..d13c874ee2 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -47,9 +47,6 @@ namespace osu.Game.Storyboards if (backgroundPath == null) return false; - if (GetLayer("Video").Elements.Any()) - return true; - return GetLayer("Background").Elements.Any(e => e.Path.ToLowerInvariant() == backgroundPath); } } From 3c5fb7982351a86964b3fa65c515c61f36b4d9e8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 14 Apr 2020 14:51:09 +0900 Subject: [PATCH 318/655] Mark dummy api test scene as headless --- osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs b/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs index 5ef01d5702..1e77d50115 100644 --- a/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs +++ b/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Testing; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -11,6 +12,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Online { + [HeadlessTest] public class TestDummyAPIRequestHandling : OsuTestScene { [Test] From 9619fb9f6a5fa140b3bbf0226cf12c879619c66e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 14 Apr 2020 15:00:36 +0900 Subject: [PATCH 319/655] Remove bind in Player --- osu.Game/Screens/Play/Player.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index f1df69c5db..4597ae760c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -27,7 +27,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; -using osu.Game.Screens.Play.HUD; using osu.Game.Scoring.Legacy; using osu.Game.Screens.Ranking; using osu.Game.Skinning; @@ -207,9 +206,6 @@ namespace osu.Game.Screens.Play foreach (var mod in Mods.Value.OfType()) mod.ApplyToHealthProcessor(HealthProcessor); - foreach (var overlay in DrawableRuleset.Overlays.OfType()) - overlay.BindHealthProcessor(HealthProcessor); - breakTracker.IsBreakTime.BindValueChanged(onBreakTimeChanged, true); } From 7d2d0785fd0cda708e8cfe0ecf867c7eb6214bb6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 14 Apr 2020 15:07:32 +0900 Subject: [PATCH 320/655] Fix potential unsafe ordering of binds --- osu.Game/Screens/Play/HUD/FailingLayer.cs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index a1188343ac..aa15c1fd45 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -26,7 +26,8 @@ namespace osu.Game.Screens.Play.HUD private const int fade_time = 400; - private Bindable enabled; + private readonly Bindable enabled = new Bindable(); + private Bindable configEnabled; /// /// The threshold under which the current player life should be considered low and the layer should start fading in. @@ -36,6 +37,7 @@ namespace osu.Game.Screens.Play.HUD private const float gradient_size = 0.3f; private readonly Container boxes; + private HealthProcessor healthProcessor; public FailingLayer() { @@ -73,16 +75,29 @@ namespace osu.Game.Screens.Play.HUD { boxes.Colour = color.Red; - enabled = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow); + configEnabled = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow); enabled.BindValueChanged(e => this.FadeTo(e.NewValue ? 1 : 0, fade_time, Easing.OutQuint), true); + + updateBindings(); } public override void BindHealthProcessor(HealthProcessor processor) { base.BindHealthProcessor(processor); - // don't display ever if the ruleset is not using a draining health display. - if (!(processor is DrainingHealthProcessor)) + healthProcessor = processor; + updateBindings(); + } + + private void updateBindings() + { + if (configEnabled == null || healthProcessor == null) + return; + + // Don't display ever if the ruleset is not using a draining health display. + if (healthProcessor is DrainingHealthProcessor) + enabled.BindTo(configEnabled); + else { enabled.UnbindBindings(); enabled.Value = false; From 3183827329e477c8edb138f0f3930fcc450bf8a8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 14 Apr 2020 15:09:31 +0900 Subject: [PATCH 321/655] Reorder fields --- osu.Game/Screens/Play/HUD/FailingLayer.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index aa15c1fd45..cea85af112 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -23,20 +23,18 @@ namespace osu.Game.Screens.Play.HUD public class FailingLayer : HealthDisplay { private const float max_alpha = 0.4f; - private const int fade_time = 400; - - private readonly Bindable enabled = new Bindable(); - private Bindable configEnabled; + private const float gradient_size = 0.3f; /// /// The threshold under which the current player life should be considered low and the layer should start fading in. /// public double LowHealthThreshold = 0.20f; - private const float gradient_size = 0.3f; - + private readonly Bindable enabled = new Bindable(); private readonly Container boxes; + + private Bindable configEnabled; private HealthProcessor healthProcessor; public FailingLayer() From b8b334ca27d853b81bf86eeb93d29a91d3ca4f34 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 14 Apr 2020 15:21:56 +0900 Subject: [PATCH 322/655] Always unbind bindings --- osu.Game/Screens/Play/HUD/FailingLayer.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index cea85af112..cb8b5c1a9d 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -92,14 +92,13 @@ namespace osu.Game.Screens.Play.HUD if (configEnabled == null || healthProcessor == null) return; + enabled.UnbindBindings(); + // Don't display ever if the ruleset is not using a draining health display. if (healthProcessor is DrainingHealthProcessor) enabled.BindTo(configEnabled); else - { - enabled.UnbindBindings(); enabled.Value = false; - } } protected override void Update() From 59728ffebddcdb47c121d7eaf3b1f80687f0e63a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 14 Apr 2020 15:24:34 +0900 Subject: [PATCH 323/655] Fix up/improve test scene --- .../Visual/Gameplay/TestSceneFailingLayer.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs index d831ea1835..a95e806862 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs @@ -20,7 +20,12 @@ namespace osu.Game.Tests.Visual.Gameplay [SetUpSteps] public void SetUpSteps() { - AddStep("create layer", () => Child = layer = new FailingLayer()); + AddStep("create layer", () => + { + Child = layer = new FailingLayer(); + layer.BindHealthProcessor(new DrainingHealthProcessor(1)); + }); + AddStep("enable layer", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true)); AddUntilStep("layer is visible", () => layer.IsPresent); } @@ -44,6 +49,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestLayerDisabledViaConfig() { AddStep("disable layer", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false)); + AddStep("set health to 0.10", () => layer.Current.Value = 0.1); AddUntilStep("layer is not visible", () => !layer.IsPresent); } @@ -51,6 +57,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestLayerVisibilityWithAccumulatingProcessor() { AddStep("bind accumulating processor", () => layer.BindHealthProcessor(new AccumulatingHealthProcessor(1))); + AddStep("set health to 0.10", () => layer.Current.Value = 0.1); AddUntilStep("layer is not visible", () => !layer.IsPresent); } @@ -58,6 +65,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestLayerVisibilityWithDrainingProcessor() { AddStep("bind accumulating processor", () => layer.BindHealthProcessor(new DrainingHealthProcessor(1))); + AddStep("set health to 0.10", () => layer.Current.Value = 0.1); AddWaitStep("wait for potential fade", 10); AddAssert("layer is still visible", () => layer.IsPresent); } From f3dbddd75ca735dfb75fb34efb15a2f14370a440 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 14 Apr 2020 15:52:38 +0900 Subject: [PATCH 324/655] Update bindings in LoadComplete() --- osu.Game/Screens/Play/HUD/FailingLayer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index cb8b5c1a9d..a49aa89a7c 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -75,7 +75,11 @@ namespace osu.Game.Screens.Play.HUD configEnabled = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow); enabled.BindValueChanged(e => this.FadeTo(e.NewValue ? 1 : 0, fade_time, Easing.OutQuint), true); + } + protected override void LoadComplete() + { + base.LoadComplete(); updateBindings(); } @@ -89,7 +93,7 @@ namespace osu.Game.Screens.Play.HUD private void updateBindings() { - if (configEnabled == null || healthProcessor == null) + if (LoadState < LoadState.Ready) return; enabled.UnbindBindings(); From 7f95418262d84096ea2afb38896d674170f9942e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Apr 2020 16:52:17 +0900 Subject: [PATCH 325/655] Fix osu!mania replays actuating incorrect keys when multiple stages are involved --- .../ManiaLegacyReplayTest.cs | 51 ++++++++++++ .../Replays/ManiaReplayFrame.cs | 83 ++++++++++++++++--- 2 files changed, 121 insertions(+), 13 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs new file mode 100644 index 0000000000..40bb83aece --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs @@ -0,0 +1,51 @@ +// 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.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Replays; + +namespace osu.Game.Rulesets.Mania.Tests +{ + [TestFixture] + public class ManiaLegacyReplayTest + { + [TestCase(ManiaAction.Key1)] + [TestCase(ManiaAction.Key1, ManiaAction.Key2)] + [TestCase(ManiaAction.Special1)] + [TestCase(ManiaAction.Key8)] + public void TestEncodeDecodeSingleStage(params ManiaAction[] actions) + { + var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 9 }); + + var frame = new ManiaReplayFrame(0, actions); + var legacyFrame = frame.ToLegacy(beatmap); + + var decodedFrame = new ManiaReplayFrame(); + decodedFrame.FromLegacy(legacyFrame, beatmap); + + Assert.That(decodedFrame.Actions, Is.EquivalentTo(frame.Actions)); + } + + [TestCase(ManiaAction.Key1)] + [TestCase(ManiaAction.Key1, ManiaAction.Key2)] + [TestCase(ManiaAction.Special1)] + [TestCase(ManiaAction.Special2)] + [TestCase(ManiaAction.Special1, ManiaAction.Special2)] + [TestCase(ManiaAction.Special1, ManiaAction.Key5)] + [TestCase(ManiaAction.Key8)] + public void TestEncodeDecodeDualStage(params ManiaAction[] actions) + { + var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 5 }); + beatmap.Stages.Add(new StageDefinition { Columns = 5 }); + + var frame = new ManiaReplayFrame(0, actions); + var legacyFrame = frame.ToLegacy(beatmap); + + var decodedFrame = new ManiaReplayFrame(); + decodedFrame.FromLegacy(legacyFrame, beatmap); + + Assert.That(decodedFrame.Actions, Is.EquivalentTo(frame.Actions)); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs index 8c73c36e99..0059a78a44 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs @@ -1,8 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; -using System.Linq; using osu.Game.Beatmaps; using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Mania.Beatmaps; @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Replays while (activeColumns > 0) { - var isSpecial = maniaBeatmap.Stages.First().IsSpecialColumn(counter); + bool isSpecial = isColumnAtIndexSpecial(maniaBeatmap, counter); if ((activeColumns & 1) > 0) Actions.Add(isSpecial ? specialAction : normalAction); @@ -58,33 +58,90 @@ namespace osu.Game.Rulesets.Mania.Replays int keys = 0; - var specialColumns = new List(); - - for (int i = 0; i < maniaBeatmap.TotalColumns; i++) - { - if (maniaBeatmap.Stages.First().IsSpecialColumn(i)) - specialColumns.Add(i); - } - foreach (var action in Actions) { switch (action) { case ManiaAction.Special1: - keys |= 1 << specialColumns[0]; + keys |= 1 << getSpecialColumnIndex(maniaBeatmap, 0); break; case ManiaAction.Special2: - keys |= 1 << specialColumns[1]; + keys |= 1 << getSpecialColumnIndex(maniaBeatmap, 1); break; default: - keys |= 1 << (action - ManiaAction.Key1); + // the index in lazer, which doesn't include special keys. + int nonSpecialKeyIndex = action - ManiaAction.Key1; + + int overallIndex = 0; + + // iterate to find the index including special keys. + while (true) + { + if (!isColumnAtIndexSpecial(maniaBeatmap, overallIndex)) + { + // found a non-special column we could use. + if (nonSpecialKeyIndex == 0) + break; + + // found a non-special column but not ours. + nonSpecialKeyIndex--; + } + + overallIndex++; + } + + keys |= 1 << overallIndex; break; } } return new LegacyReplayFrame(Time, keys, null, ReplayButtonState.None); } + + /// + /// Find the overall index (across all stages) for a specified special key. + /// + /// The beatmap. + /// The special key offset (0 is S1). + /// The overall index for the special column. + private int getSpecialColumnIndex(ManiaBeatmap maniaBeatmap, int specialOffset) + { + for (int i = 0; i < maniaBeatmap.TotalColumns; i++) + { + if (isColumnAtIndexSpecial(maniaBeatmap, i)) + { + if (specialOffset == 0) + return i; + + specialOffset--; + } + } + + throw new InvalidOperationException("Special key index too high"); + } + + /// + /// Check whether the column at an overall index (across all stages) is a special column. + /// + /// The beatmap. + /// The overall index to check. + /// + private bool isColumnAtIndexSpecial(ManiaBeatmap beatmap, int index) + { + foreach (var stage in beatmap.Stages) + { + for (int stageIndex = 0; stageIndex < stage.Columns; stageIndex++) + { + if (index == 0) + return stage.IsSpecialColumn(stageIndex); + + index--; + } + } + + return false; + } } } From 69352214637b9b5ef6f7e9d56da30da98455584a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 14 Apr 2020 21:05:07 +0900 Subject: [PATCH 326/655] Improve logic for CSB transfer --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 8 ++++-- .../Objects/Legacy/ConvertHitObjectParser.cs | 28 ++++++++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 90a5d0dcba..5b2b213322 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -8,6 +8,7 @@ using osu.Framework.Logging; using osu.Game.Audio; using osu.Game.Beatmaps.ControlPoints; using osu.Game.IO; +using osu.Game.Rulesets.Objects.Legacy; using osuTK.Graphics; namespace osu.Game.Beatmaps.Formats @@ -168,8 +169,11 @@ namespace osu.Game.Beatmaps.Formats { var baseInfo = base.ApplyTo(hitSampleInfo); - if (string.IsNullOrEmpty(baseInfo.Suffix) && CustomSampleBank > 0) - baseInfo.Suffix = CustomSampleBank.ToString(); + if (baseInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy + && legacy.CustomSampleBank == 0) + { + legacy.CustomSampleBank = CustomSampleBank; + } return baseInfo; } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 1dca4a5c02..95cbf3ab40 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -409,26 +409,46 @@ namespace osu.Game.Rulesets.Objects.Legacy public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone(); } - private class LegacyHitSampleInfo : HitSampleInfo + internal class LegacyHitSampleInfo : HitSampleInfo { + private int customSampleBank; + public int CustomSampleBank { + get => customSampleBank; set { + customSampleBank = value; + + // A 0 custom sample bank should cause LegacyBeatmapSkin to always fall back to the user skin. This is done by giving a null suffix. if (value > 0) Suffix = value.ToString(); } } + + public override IEnumerable LookupNames + { + get + { + // The lookup should only contain the suffix for custom sample bank 2 and beyond. + // For custom sample bank 1 and 0, the lookup should not contain the suffix as only the lookup source (beatmap or user skin) is changed. + if (CustomSampleBank >= 2) + yield return $"{Bank}-{Name}{Suffix}"; + + yield return $"{Bank}-{Name}"; + } + } } - private class FileHitSampleInfo : HitSampleInfo + private class FileHitSampleInfo : LegacyHitSampleInfo { public string Filename; public FileHitSampleInfo() { - // Has no effect since LookupNames is overridden, however prompts LegacyBeatmapSkin to not fallback. - Suffix = "0"; + // Make sure that the LegacyBeatmapSkin does not fall back to the user skin. + // Note that this does not change the lookup names, as they are overridden locally. + CustomSampleBank = 1; } public override IEnumerable LookupNames => new[] From b29957798f38c28f3940649ecc13be0ca1ee5b8e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 14 Apr 2020 21:05:42 +0900 Subject: [PATCH 327/655] Fix no audiomanager in test scene working beatmap --- osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs | 6 ++++-- osu.Game/Tests/Visual/OsuTestScene.cs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs index 6db34af20c..8f8afb87d4 100644 --- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; @@ -18,8 +19,9 @@ namespace osu.Game.Tests.Beatmaps /// /// The beatmap. /// An optional storyboard. - public TestWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) - : base(beatmap.BeatmapInfo, null) + /// The . + public TestWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null, AudioManager audioManager = null) + : base(beatmap.BeatmapInfo, audioManager) { this.beatmap = beatmap; this.storyboard = storyboard; diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index d1d8059cb1..5dc8714c07 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -179,7 +179,7 @@ namespace osu.Game.Tests.Visual /// Audio manager. Required if a reference clock isn't provided. /// The length of the returned virtual track. public ClockBackedTestWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio, double length = 60000) - : base(beatmap, storyboard) + : base(beatmap, storyboard, audio) { if (referenceClock != null) { From 00d564d29cafc4b4319e7165de2d5a5210c9d083 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 14 Apr 2020 21:05:54 +0900 Subject: [PATCH 328/655] Add tests --- .../Gameplay/TestSceneHitObjectSamples.cs | 344 ++++++++++++++++++ .../controlpoint-beatmap-custom-sample.osu | 7 + .../controlpoint-beatmap-sample.osu | 7 + .../controlpoint-skin-sample.osu | 7 + .../SampleLookups/file-beatmap-sample.osu | 4 + ...tobject-beatmap-custom-sample-override.osu | 7 + .../hitobject-beatmap-custom-sample.osu | 4 + .../hitobject-beatmap-sample.osu | 4 + .../SampleLookups/hitobject-skin-sample.osu | 4 + 9 files changed, 388 insertions(+) create mode 100644 osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs create mode 100644 osu.Game.Tests/Resources/SampleLookups/controlpoint-beatmap-custom-sample.osu create mode 100644 osu.Game.Tests/Resources/SampleLookups/controlpoint-beatmap-sample.osu create mode 100644 osu.Game.Tests/Resources/SampleLookups/controlpoint-skin-sample.osu create mode 100644 osu.Game.Tests/Resources/SampleLookups/file-beatmap-sample.osu create mode 100644 osu.Game.Tests/Resources/SampleLookups/hitobject-beatmap-custom-sample-override.osu create mode 100644 osu.Game.Tests/Resources/SampleLookups/hitobject-beatmap-custom-sample.osu create mode 100644 osu.Game.Tests/Resources/SampleLookups/hitobject-beatmap-sample.osu create mode 100644 osu.Game.Tests/Resources/SampleLookups/hitobject-skin-sample.osu diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs new file mode 100644 index 0000000000..f80ea3ae88 --- /dev/null +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs @@ -0,0 +1,344 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.IO.Stores; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Formats; +using osu.Game.IO; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Skinning; +using osu.Game.Storyboards; +using osu.Game.Tests.Resources; +using osu.Game.Tests.Visual; +using osu.Game.Users; + +namespace osu.Game.Tests.Gameplay +{ + public class TestSceneHitObjectSamples : PlayerTestScene + { + private readonly SkinInfo userSkinInfo = new SkinInfo(); + + private readonly BeatmapInfo beatmapInfo = new BeatmapInfo + { + BeatmapSet = new BeatmapSetInfo(), + Metadata = new BeatmapMetadata + { + Author = User.SYSTEM_USER + } + }; + + private readonly TestResourceStore userSkinResourceStore = new TestResourceStore(); + private readonly TestResourceStore beatmapSkinResourceStore = new TestResourceStore(); + + protected override bool HasCustomSteps => true; + + public TestSceneHitObjectSamples() + : base(new OsuRuleset()) + { + } + + private SkinSourceDependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + => new DependencyContainer(dependencies = new SkinSourceDependencyContainer(base.CreateChildDependencies(parent))); + + /// + /// Tests that a hitobject which provides no custom sample set retrieves samples from the user skin. + /// + [Test] + public void TestDefaultSampleFromUserSkin() + { + const string expected_sample = "normal-hitnormal"; + + setupSkins(expected_sample, expected_sample); + + createTestWithBeatmap("hitobject-skin-sample.osu"); + + assertUserLookup(expected_sample); + } + + /// + /// Tests that a hitobject which provides a sample set of 1 retrieves samples from the beatmap skin. + /// + [Test] + public void TestDefaultSampleFromBeatmap() + { + const string expected_sample = "normal-hitnormal"; + + setupSkins(expected_sample, expected_sample); + + createTestWithBeatmap("hitobject-beatmap-sample.osu"); + + assertBeatmapLookup(expected_sample); + } + + /// + /// Tests that a hitobject which provides a sample set of 1 retrieves samples from the user skin when the beatmap does not contain the sample. + /// + [Test] + public void TestDefaultSampleFromUserSkinFallback() + { + const string expected_sample = "normal-hitnormal"; + + setupSkins(null, expected_sample); + + createTestWithBeatmap("hitobject-beatmap-sample.osu"); + + assertUserLookup(expected_sample); + } + + /// + /// Tests that a hitobject which provides a custom sample set of 2 retrieves the following samples from the beatmap skin: + /// normal-hitnormal2 + /// normal-hitnormal + /// + [TestCase("normal-hitnormal2")] + [TestCase("normal-hitnormal")] + public void TestDefaultCustomSampleFromBeatmap(string expectedSample) + { + setupSkins(expectedSample, expectedSample); + + createTestWithBeatmap("hitobject-beatmap-custom-sample.osu"); + + assertBeatmapLookup(expectedSample); + } + + /// + /// Tests that a hitobject which provides a custom sample set of 2 retrieves the following samples from the user skin when the beatmap does not contain the sample: + /// normal-hitnormal2 + /// normal-hitnormal + /// + [TestCase("normal-hitnormal2")] + [TestCase("normal-hitnormal")] + public void TestDefaultCustomSampleFromUserSkinFallback(string expectedSample) + { + setupSkins(string.Empty, expectedSample); + + createTestWithBeatmap("hitobject-beatmap-custom-sample.osu"); + + assertUserLookup(expectedSample); + } + + /// + /// Tests that a hitobject which provides a sample file retrieves the sample file from the beatmap skin. + /// + [Test] + public void TestFileSampleFromBeatmap() + { + const string expected_sample = "hit_1.wav"; + + setupSkins(expected_sample, expected_sample); + + createTestWithBeatmap("file-beatmap-sample.osu"); + + assertBeatmapLookup(expected_sample); + } + + /// + /// Tests that a default hitobject and control point causes . + /// + [Test] + public void TestControlPointSampleFromSkin() + { + const string expected_sample = "normal-hitnormal"; + + setupSkins(expected_sample, expected_sample); + + createTestWithBeatmap("controlpoint-skin-sample.osu"); + + assertUserLookup(expected_sample); + } + + /// + /// Tests that a control point that provides a custom sample set of 1 causes . + /// + [Test] + public void TestControlPointSampleFromBeatmap() + { + const string expected_sample = "normal-hitnormal"; + + setupSkins(expected_sample, expected_sample); + + createTestWithBeatmap("controlpoint-beatmap-sample.osu"); + + assertBeatmapLookup(expected_sample); + } + + /// + /// Tests that a control point that provides a custom sample of 2 causes . + /// + [TestCase("normal-hitnormal2")] + [TestCase("normal-hitnormal")] + public void TestControlPointCustomSampleFromBeatmap(string sampleName) + { + setupSkins(sampleName, sampleName); + + createTestWithBeatmap("controlpoint-beatmap-custom-sample.osu"); + + assertBeatmapLookup(sampleName); + } + + /// + /// Tests that a hitobject's custom sample overrides the control point's. + /// + [Test] + public void TestHitObjectCustomSampleOverride() + { + const string expected_sample = "normal-hitnormal3"; + + setupSkins(expected_sample, expected_sample); + + createTestWithBeatmap("hitobject-beatmap-custom-sample-override.osu"); + + assertBeatmapLookup(expected_sample); + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentTestBeatmap; + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) + => new TestWorkingBeatmap(beatmapInfo, beatmapSkinResourceStore, beatmap, storyboard, Clock, Audio); + + private IBeatmap currentTestBeatmap; + + private void createTestWithBeatmap(string filename) + { + CreateTest(() => + { + AddStep("clear performed lookups", () => + { + userSkinResourceStore.PerformedLookups.Clear(); + beatmapSkinResourceStore.PerformedLookups.Clear(); + }); + + AddStep($"load {filename}", () => + { + using (var reader = new LineBufferedReader(TestResources.OpenResource($"SampleLookups/{filename}"))) + currentTestBeatmap = Decoder.GetDecoder(reader).Decode(reader); + }); + }); + } + + private void setupSkins(string beatmapFile, string userFile) + { + AddStep("setup skins", () => + { + userSkinInfo.Files = new List + { + new SkinFileInfo + { + Filename = userFile, + FileInfo = new IO.FileInfo { Hash = userFile } + } + }; + + beatmapInfo.BeatmapSet.Files = new List + { + new BeatmapSetFileInfo + { + Filename = beatmapFile, + FileInfo = new IO.FileInfo { Hash = beatmapFile } + } + }; + + // Need to refresh the cached skin source to refresh the skin resource store. + dependencies.SkinSource = new SkinProvidingContainer(new LegacySkin(userSkinInfo, userSkinResourceStore, dependencies.Get())); + }); + } + + private void assertBeatmapLookup(string name) => AddAssert($"\"{name}\" looked up from beatmap skin", + () => !userSkinResourceStore.PerformedLookups.Contains(name) && beatmapSkinResourceStore.PerformedLookups.Contains(name)); + + private void assertUserLookup(string name) => AddAssert($"\"{name}\" looked up from user skin", + () => !beatmapSkinResourceStore.PerformedLookups.Contains(name) && userSkinResourceStore.PerformedLookups.Contains(name)); + + private class SkinSourceDependencyContainer : IReadOnlyDependencyContainer + { + public ISkinSource SkinSource; + + private readonly IReadOnlyDependencyContainer fallback; + + public SkinSourceDependencyContainer(IReadOnlyDependencyContainer fallback) + { + this.fallback = fallback; + } + + public object Get(Type type) + { + if (type == typeof(ISkinSource)) + return SkinSource; + + return fallback.Get(type); + } + + public object Get(Type type, CacheInfo info) + { + if (type == typeof(ISkinSource)) + return SkinSource; + + return fallback.Get(type); + } + + public void Inject(T instance) where T : class + { + // Never used directly + } + } + + private class TestResourceStore : IResourceStore + { + public readonly List PerformedLookups = new List(); + + public byte[] Get(string name) + { + markLookup(name); + return Array.Empty(); + } + + public Task GetAsync(string name) + { + markLookup(name); + return Task.FromResult(Array.Empty()); + } + + public Stream GetStream(string name) + { + markLookup(name); + return new MemoryStream(); + } + + private void markLookup(string name) => PerformedLookups.Add(name.Substring(name.LastIndexOf('/') + 1)); + + public IEnumerable GetAvailableResources() => Enumerable.Empty(); + + public void Dispose() + { + } + } + + private class TestWorkingBeatmap : ClockBackedTestWorkingBeatmap + { + private readonly BeatmapInfo skinBeatmapInfo; + private readonly IResourceStore resourceStore; + + public TestWorkingBeatmap(BeatmapInfo skinBeatmapInfo, IResourceStore resourceStore, IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio, + double length = 60000) + : base(beatmap, storyboard, referenceClock, audio, length) + { + this.skinBeatmapInfo = skinBeatmapInfo; + this.resourceStore = resourceStore; + } + + protected override ISkin GetSkin() => new LegacyBeatmapSkin(skinBeatmapInfo, resourceStore, AudioManager); + } + } +} diff --git a/osu.Game.Tests/Resources/SampleLookups/controlpoint-beatmap-custom-sample.osu b/osu.Game.Tests/Resources/SampleLookups/controlpoint-beatmap-custom-sample.osu new file mode 100644 index 0000000000..91dbc6a60e --- /dev/null +++ b/osu.Game.Tests/Resources/SampleLookups/controlpoint-beatmap-custom-sample.osu @@ -0,0 +1,7 @@ +osu file format v14 + +[TimingPoints] +0,300,4,0,2,100,1,0 + +[HitObjects] +444,320,1000,5,0,0:0:0:0: \ No newline at end of file diff --git a/osu.Game.Tests/Resources/SampleLookups/controlpoint-beatmap-sample.osu b/osu.Game.Tests/Resources/SampleLookups/controlpoint-beatmap-sample.osu new file mode 100644 index 0000000000..3274820100 --- /dev/null +++ b/osu.Game.Tests/Resources/SampleLookups/controlpoint-beatmap-sample.osu @@ -0,0 +1,7 @@ +osu file format v14 + +[TimingPoints] +0,300,4,0,1,100,1,0 + +[HitObjects] +444,320,1000,5,0,0:0:0:0: \ No newline at end of file diff --git a/osu.Game.Tests/Resources/SampleLookups/controlpoint-skin-sample.osu b/osu.Game.Tests/Resources/SampleLookups/controlpoint-skin-sample.osu new file mode 100644 index 0000000000..c53ec465fb --- /dev/null +++ b/osu.Game.Tests/Resources/SampleLookups/controlpoint-skin-sample.osu @@ -0,0 +1,7 @@ +osu file format v14 + +[TimingPoints] +0,300,4,0,0,100,1,0 + +[HitObjects] +444,320,1000,5,0,0:0:0:0: \ No newline at end of file diff --git a/osu.Game.Tests/Resources/SampleLookups/file-beatmap-sample.osu b/osu.Game.Tests/Resources/SampleLookups/file-beatmap-sample.osu new file mode 100644 index 0000000000..65b5ea8707 --- /dev/null +++ b/osu.Game.Tests/Resources/SampleLookups/file-beatmap-sample.osu @@ -0,0 +1,4 @@ +osu file format v14 + +[HitObjects] +255,193,2170,1,0,0:0:0:0:hit_1.wav diff --git a/osu.Game.Tests/Resources/SampleLookups/hitobject-beatmap-custom-sample-override.osu b/osu.Game.Tests/Resources/SampleLookups/hitobject-beatmap-custom-sample-override.osu new file mode 100644 index 0000000000..13dc2faab1 --- /dev/null +++ b/osu.Game.Tests/Resources/SampleLookups/hitobject-beatmap-custom-sample-override.osu @@ -0,0 +1,7 @@ +osu file format v14 + +[TimingPoints] +0,300,4,0,2,100,1,0 + +[HitObjects] +444,320,1000,5,0,0:0:3:0: \ No newline at end of file diff --git a/osu.Game.Tests/Resources/SampleLookups/hitobject-beatmap-custom-sample.osu b/osu.Game.Tests/Resources/SampleLookups/hitobject-beatmap-custom-sample.osu new file mode 100644 index 0000000000..4ab672dbb0 --- /dev/null +++ b/osu.Game.Tests/Resources/SampleLookups/hitobject-beatmap-custom-sample.osu @@ -0,0 +1,4 @@ +osu file format v14 + +[HitObjects] +444,320,1000,5,0,0:0:2:0: \ No newline at end of file diff --git a/osu.Game.Tests/Resources/SampleLookups/hitobject-beatmap-sample.osu b/osu.Game.Tests/Resources/SampleLookups/hitobject-beatmap-sample.osu new file mode 100644 index 0000000000..33bc34949a --- /dev/null +++ b/osu.Game.Tests/Resources/SampleLookups/hitobject-beatmap-sample.osu @@ -0,0 +1,4 @@ +osu file format v14 + +[HitObjects] +444,320,1000,5,0,0:0:1:0: \ No newline at end of file diff --git a/osu.Game.Tests/Resources/SampleLookups/hitobject-skin-sample.osu b/osu.Game.Tests/Resources/SampleLookups/hitobject-skin-sample.osu new file mode 100644 index 0000000000..47f5b44c90 --- /dev/null +++ b/osu.Game.Tests/Resources/SampleLookups/hitobject-skin-sample.osu @@ -0,0 +1,4 @@ +osu file format v14 + +[HitObjects] +444,320,1000,5,0,0:0:0:0: \ No newline at end of file From 44981431c5470c457d1708ad9227e4ec1dd60566 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 14 Apr 2020 21:33:32 +0900 Subject: [PATCH 329/655] Remove suffix hackery --- .../Objects/Legacy/ConvertHitObjectParser.cs | 16 +--------------- osu.Game/Skinning/LegacyBeatmapSkin.cs | 5 +++-- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 95cbf3ab40..9a60a0a75c 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -420,24 +420,10 @@ namespace osu.Game.Rulesets.Objects.Legacy { customSampleBank = value; - // A 0 custom sample bank should cause LegacyBeatmapSkin to always fall back to the user skin. This is done by giving a null suffix. - if (value > 0) + if (value >= 2) Suffix = value.ToString(); } } - - public override IEnumerable LookupNames - { - get - { - // The lookup should only contain the suffix for custom sample bank 2 and beyond. - // For custom sample bank 1 and 0, the lookup should not contain the suffix as only the lookup source (beatmap or user skin) is changed. - if (CustomSampleBank >= 2) - yield return $"{Bank}-{Name}{Suffix}"; - - yield return $"{Bank}-{Name}"; - } - } } private class FileHitSampleInfo : LegacyHitSampleInfo diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index c4636f46f5..21533e58cd 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.IO.Stores; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects.Legacy; namespace osu.Game.Skinning { @@ -37,9 +38,9 @@ namespace osu.Game.Skinning public override SampleChannel GetSample(ISampleInfo sampleInfo) { - if (sampleInfo is HitSampleInfo hsi && string.IsNullOrEmpty(hsi.Suffix)) + if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy && legacy.CustomSampleBank == 0) { - // When no custom sample set is provided, always fall-back to the default samples. + // When no custom sample bank is provided, always fall-back to the default samples. return null; } From 64d44dedcd643cef2e971a968154e0fd17e2a6b2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 14 Apr 2020 22:39:51 +0900 Subject: [PATCH 330/655] Make testscene headless --- osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs index f80ea3ae88..a8bd902117 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs @@ -10,6 +10,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.IO.Stores; +using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; @@ -24,6 +25,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Gameplay { + [HeadlessTest] public class TestSceneHitObjectSamples : PlayerTestScene { private readonly SkinInfo userSkinInfo = new SkinInfo(); From 10486a0ad2b2aa1809d1d4f18d532cf545ebcb38 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 14 Apr 2020 23:10:14 +0900 Subject: [PATCH 331/655] Fix potential dependency-related issues --- osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs index a8bd902117..366437a771 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs @@ -253,7 +253,7 @@ namespace osu.Game.Tests.Gameplay }; // Need to refresh the cached skin source to refresh the skin resource store. - dependencies.SkinSource = new SkinProvidingContainer(new LegacySkin(userSkinInfo, userSkinResourceStore, dependencies.Get())); + dependencies.SkinSource = new SkinProvidingContainer(new LegacySkin(userSkinInfo, userSkinResourceStore, Audio)); }); } @@ -287,7 +287,7 @@ namespace osu.Game.Tests.Gameplay if (type == typeof(ISkinSource)) return SkinSource; - return fallback.Get(type); + return fallback.Get(type, info); } public void Inject(T instance) where T : class From d47e414fb142e7aa504814494d496a3d08528a46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Apr 2020 12:35:43 +0900 Subject: [PATCH 332/655] Apply review feedback (unroll inner loop / xml fixes) --- .../Replays/ManiaReplayFrame.cs | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs index 0059a78a44..da4b0c943c 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs @@ -74,22 +74,20 @@ namespace osu.Game.Rulesets.Mania.Replays // the index in lazer, which doesn't include special keys. int nonSpecialKeyIndex = action - ManiaAction.Key1; + // the index inclusive of special keys. int overallIndex = 0; // iterate to find the index including special keys. - while (true) + for (; overallIndex < maniaBeatmap.TotalColumns; overallIndex++) { - if (!isColumnAtIndexSpecial(maniaBeatmap, overallIndex)) - { - // found a non-special column we could use. - if (nonSpecialKeyIndex == 0) - break; - - // found a non-special column but not ours. - nonSpecialKeyIndex--; - } - - overallIndex++; + // skip over special columns. + if (isColumnAtIndexSpecial(maniaBeatmap, overallIndex)) + continue; + // found a non-special column to use. + if (nonSpecialKeyIndex == 0) + break; + // found a non-special column but not ours. + nonSpecialKeyIndex--; } keys |= 1 << overallIndex; @@ -127,21 +125,20 @@ namespace osu.Game.Rulesets.Mania.Replays /// /// The beatmap. /// The overall index to check. - /// private bool isColumnAtIndexSpecial(ManiaBeatmap beatmap, int index) { foreach (var stage in beatmap.Stages) { - for (int stageIndex = 0; stageIndex < stage.Columns; stageIndex++) + if (index >= stage.Columns) { - if (index == 0) - return stage.IsSpecialColumn(stageIndex); - - index--; + index -= stage.Columns; + continue; } + + return stage.IsSpecialColumn(index); } - return false; + throw new ArgumentException("Column index is too high.", nameof(index)); } } } From f4b5a17b650264a9b0fda00c1a59c94cfde58fec Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 15 Apr 2020 07:00:38 +0300 Subject: [PATCH 333/655] Fix typo in DrawableTaikoHitObject --- .../Objects/Drawables/DrawableTaikoHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 397888bb11..2f90f3b96c 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// /// Moves to a layer proxied above the playfield. - /// Does nothing is content is already proxied. + /// Does nothing if content is already proxied. /// protected void ProxyContent() { From e534d59c807ecd1350e2ced30c4595e49fc6af4a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 15 Apr 2020 13:08:15 +0900 Subject: [PATCH 334/655] Use another argument exception --- osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs index da4b0c943c..dbab54d1d0 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs @@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Mania.Replays } } - throw new InvalidOperationException("Special key index too high"); + throw new ArgumentException("Special key index is too high.", nameof(specialOffset)); } /// From 72707a9973f41f1961bd030c66d84a7627e4be81 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 15 Apr 2020 13:54:23 +0900 Subject: [PATCH 335/655] Fix OS-dependent substring --- osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs index 366437a771..f611f2717e 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs @@ -318,7 +318,7 @@ namespace osu.Game.Tests.Gameplay return new MemoryStream(); } - private void markLookup(string name) => PerformedLookups.Add(name.Substring(name.LastIndexOf('/') + 1)); + private void markLookup(string name) => PerformedLookups.Add(name.Substring(name.LastIndexOf(Path.DirectorySeparatorChar) + 1)); public IEnumerable GetAvailableResources() => Enumerable.Empty(); From 019e777d7da8022678efa7e4a60026d6d6440be7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Apr 2020 16:01:49 +0900 Subject: [PATCH 336/655] Move taiko skinning tests to own namespace --- .../{ => Skinning}/TaikoSkinnableTestScene.cs | 2 +- .../{ => Skinning}/TestSceneDrawableHit.cs | 2 +- .../{ => Skinning}/TestSceneInputDrum.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game.Rulesets.Taiko.Tests/{ => Skinning}/TaikoSkinnableTestScene.cs (92%) rename osu.Game.Rulesets.Taiko.Tests/{ => Skinning}/TestSceneDrawableHit.cs (97%) rename osu.Game.Rulesets.Taiko.Tests/{ => Skinning}/TestSceneInputDrum.cs (96%) diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TaikoSkinnableTestScene.cs similarity index 92% rename from osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs rename to osu.Game.Rulesets.Taiko.Tests/Skinning/TaikoSkinnableTestScene.cs index 6db2a6907f..161154b1a7 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TaikoSkinnableTestScene.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using osu.Game.Rulesets.Taiko.Skinning; using osu.Game.Tests.Visual; -namespace osu.Game.Rulesets.Taiko.Tests +namespace osu.Game.Rulesets.Taiko.Tests.Skinning { public abstract class TaikoSkinnableTestScene : SkinnableTestScene { diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrawableHit.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs similarity index 97% rename from osu.Game.Rulesets.Taiko.Tests/TestSceneDrawableHit.cs rename to osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs index 301295253d..a3832b010c 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrawableHit.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs @@ -13,7 +13,7 @@ using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.Taiko.Skinning; -namespace osu.Game.Rulesets.Taiko.Tests +namespace osu.Game.Rulesets.Taiko.Tests.Skinning { [TestFixture] public class TestSceneDrawableHit : TaikoSkinnableTestScene diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs similarity index 96% rename from osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs rename to osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs index 1928e9f66f..412027ca61 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs @@ -6,14 +6,14 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Taiko.Skinning; using osu.Game.Rulesets.Taiko.UI; +using osuTK; -namespace osu.Game.Rulesets.Taiko.Tests +namespace osu.Game.Rulesets.Taiko.Tests.Skinning { [TestFixture] public class TestSceneInputDrum : TaikoSkinnableTestScene From 102c1d9095d4189731f6d7ac547abd0c0e5b7527 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 15 Apr 2020 15:50:19 +0900 Subject: [PATCH 337/655] Add disabled state to menu items --- .../Visual/UserInterface/TestSceneOsuMenu.cs | 91 +++++++++++++++++++ .../UserInterface/DrawableOsuMenuItem.cs | 26 +++++- .../Graphics/UserInterface/OsuMenuItem.cs | 6 ++ 3 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneOsuMenu.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMenu.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMenu.cs new file mode 100644 index 0000000000..cdda1969ca --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMenu.cs @@ -0,0 +1,91 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Graphics.UserInterface; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneOsuMenu : OsuManualInputManagerTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OsuMenu), + typeof(DrawableOsuMenuItem) + }; + + private OsuMenu menu; + private bool actionPeformed; + + [SetUp] + public void Setup() => Schedule(() => + { + actionPeformed = false; + + Child = menu = new OsuMenu(Direction.Vertical, true) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Items = new[] + { + new OsuMenuItem("standard", MenuItemType.Standard, performAction), + new OsuMenuItem("highlighted", MenuItemType.Highlighted, performAction), + new OsuMenuItem("destructive", MenuItemType.Destructive, performAction), + } + }; + }); + + [Test] + public void TestClickEnabledMenuItem() + { + AddStep("move to first menu item", () => InputManager.MoveMouseTo(menu.ChildrenOfType().First())); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + + AddAssert("action performed", () => actionPeformed); + } + + [Test] + public void TestDisableMenuItemsAndClick() + { + AddStep("disable menu items", () => + { + foreach (var item in menu.Items) + ((OsuMenuItem)item).Enabled.Value = false; + }); + + AddStep("move to first menu item", () => InputManager.MoveMouseTo(menu.ChildrenOfType().First())); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + + AddAssert("action not performed", () => !actionPeformed); + } + + [Test] + public void TestEnableMenuItemsAndClick() + { + AddStep("disable menu items", () => + { + foreach (var item in menu.Items) + ((OsuMenuItem)item).Enabled.Value = false; + }); + + AddStep("enable menu items", () => + { + foreach (var item in menu.Items) + ((OsuMenuItem)item).Enabled.Value = true; + }); + + AddStep("move to first menu item", () => InputManager.MoveMouseTo(menu.ChildrenOfType().First())); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + + AddAssert("action performed", () => actionPeformed); + } + + private void performAction() => actionPeformed = true; + } +} diff --git a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs index a3ca851341..abaae7b43c 100644 --- a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs @@ -42,6 +42,8 @@ namespace osu.Game.Graphics.UserInterface BackgroundColourHover = Color4Extensions.FromHex(@"172023"); updateTextColour(); + + Item.Action.BindDisabledChanged(_ => updateState(), true); } private void updateTextColour() @@ -65,19 +67,33 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnHover(HoverEvent e) { - sampleHover.Play(); - text.BoldText.FadeIn(transition_length, Easing.OutQuint); - text.NormalText.FadeOut(transition_length, Easing.OutQuint); + updateState(); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - text.BoldText.FadeOut(transition_length, Easing.OutQuint); - text.NormalText.FadeIn(transition_length, Easing.OutQuint); + updateState(); base.OnHoverLost(e); } + private void updateState() + { + Alpha = Item.Action.Disabled ? 0.2f : 1; + + if (IsHovered && !Item.Action.Disabled) + { + sampleHover.Play(); + text.BoldText.FadeIn(transition_length, Easing.OutQuint); + text.NormalText.FadeOut(transition_length, Easing.OutQuint); + } + else + { + text.BoldText.FadeOut(transition_length, Easing.OutQuint); + text.NormalText.FadeIn(transition_length, Easing.OutQuint); + } + } + protected override bool OnClick(ClickEvent e) { sampleClick.Play(); diff --git a/osu.Game/Graphics/UserInterface/OsuMenuItem.cs b/osu.Game/Graphics/UserInterface/OsuMenuItem.cs index 0fe41937ce..36122ca0b2 100644 --- a/osu.Game/Graphics/UserInterface/OsuMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/OsuMenuItem.cs @@ -2,12 +2,15 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; using osu.Framework.Graphics.UserInterface; namespace osu.Game.Graphics.UserInterface { public class OsuMenuItem : MenuItem { + public readonly Bindable Enabled = new Bindable(true); + public readonly MenuItemType Type; public OsuMenuItem(string text, MenuItemType type = MenuItemType.Standard) @@ -19,6 +22,9 @@ namespace osu.Game.Graphics.UserInterface : base(text, action) { Type = type; + + Enabled.BindValueChanged(enabled => Action.Disabled = !enabled.NewValue); + Action.BindDisabledChanged(disabled => Enabled.Value = !disabled); } } } From e8c955ed9b0a253ba2bfa6bd1e1af737a4e34440 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 15 Apr 2020 15:50:43 +0900 Subject: [PATCH 338/655] Add CanUndo/CanRedo bindables --- .../Editor/EditorChangeHandlerTest.cs | 25 +++++++++++-------- osu.Game/Screens/Edit/EditorChangeHandler.cs | 17 ++++++++++--- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Editor/EditorChangeHandlerTest.cs b/osu.Game.Tests/Editor/EditorChangeHandlerTest.cs index ef16976130..9613f250c4 100644 --- a/osu.Game.Tests/Editor/EditorChangeHandlerTest.cs +++ b/osu.Game.Tests/Editor/EditorChangeHandlerTest.cs @@ -15,15 +15,18 @@ namespace osu.Game.Tests.Editor { var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap())); - Assert.That(handler.HasUndoState, Is.False); + Assert.That(handler.CanUndo.Value, Is.False); + Assert.That(handler.CanRedo.Value, Is.False); handler.SaveState(); - Assert.That(handler.HasUndoState, Is.True); + Assert.That(handler.CanUndo.Value, Is.True); + Assert.That(handler.CanRedo.Value, Is.False); handler.RestoreState(-1); - Assert.That(handler.HasUndoState, Is.False); + Assert.That(handler.CanUndo.Value, Is.False); + Assert.That(handler.CanRedo.Value, Is.True); } [Test] @@ -31,20 +34,20 @@ namespace osu.Game.Tests.Editor { var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap())); - Assert.That(handler.HasUndoState, Is.False); + Assert.That(handler.CanUndo.Value, Is.False); for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES; i++) handler.SaveState(); - Assert.That(handler.HasUndoState, Is.True); + Assert.That(handler.CanUndo.Value, Is.True); for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES; i++) { - Assert.That(handler.HasUndoState, Is.True); + Assert.That(handler.CanUndo.Value, Is.True); handler.RestoreState(-1); } - Assert.That(handler.HasUndoState, Is.False); + Assert.That(handler.CanUndo.Value, Is.False); } [Test] @@ -52,20 +55,20 @@ namespace osu.Game.Tests.Editor { var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap())); - Assert.That(handler.HasUndoState, Is.False); + Assert.That(handler.CanUndo.Value, Is.False); for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES * 2; i++) handler.SaveState(); - Assert.That(handler.HasUndoState, Is.True); + Assert.That(handler.CanUndo.Value, Is.True); for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES; i++) { - Assert.That(handler.HasUndoState, Is.True); + Assert.That(handler.CanUndo.Value, Is.True); handler.RestoreState(-1); } - Assert.That(handler.HasUndoState, Is.False); + Assert.That(handler.CanUndo.Value, Is.False); } } } diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index a8204715cd..1553c2d2ef 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Text; +using osu.Framework.Bindables; using osu.Game.Beatmaps.Formats; using osu.Game.Rulesets.Objects; @@ -15,8 +16,10 @@ namespace osu.Game.Screens.Edit /// public class EditorChangeHandler : IEditorChangeHandler { - private readonly LegacyEditorBeatmapPatcher patcher; + public readonly Bindable CanUndo = new Bindable(); + public readonly Bindable CanRedo = new Bindable(); + private readonly LegacyEditorBeatmapPatcher patcher; private readonly List savedStates = new List(); private int currentState = -1; @@ -45,8 +48,6 @@ namespace osu.Game.Screens.Edit SaveState(); } - public bool HasUndoState => currentState > 0; - private void hitObjectAdded(HitObject obj) => SaveState(); private void hitObjectRemoved(HitObject obj) => SaveState(); @@ -90,6 +91,8 @@ namespace osu.Game.Screens.Edit } currentState = savedStates.Count - 1; + + updateBindables(); } /// @@ -114,6 +117,14 @@ namespace osu.Game.Screens.Edit currentState = newState; isRestoring = false; + + updateBindables(); + } + + private void updateBindables() + { + CanUndo.Value = savedStates.Count > 0 && currentState > 0; + CanRedo.Value = currentState < savedStates.Count - 1; } } } From ce21cfbb035b16bb42b473614edbe372b5ace04b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 15 Apr 2020 16:17:34 +0900 Subject: [PATCH 339/655] Use bindables in menu items --- osu.Game/Screens/Edit/Editor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 14a227eb07..ad17498d93 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -155,8 +155,8 @@ namespace osu.Game.Screens.Edit { Items = new[] { - new EditorMenuItem("Undo", MenuItemType.Standard, undo), - new EditorMenuItem("Redo", MenuItemType.Standard, redo) + new EditorMenuItem("Undo", MenuItemType.Standard, undo) { Enabled = { BindTarget = changeHandler.CanUndo } }, + new EditorMenuItem("Redo", MenuItemType.Standard, redo) { Enabled = { BindTarget = changeHandler.CanRedo } } } } } From 18c28390ef6f441acd11acd318cffa057331fa4e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Apr 2020 16:29:39 +0900 Subject: [PATCH 340/655] Setup drumroll testing --- .../Skinning/TestSceneDrawableDrumRoll.cs | 84 +++++++++++++++++++ .../Tests/Visual/ScrollingTestContainer.cs | 4 +- 2 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRoll.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRoll.cs new file mode 100644 index 0000000000..388be5bbc4 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRoll.cs @@ -0,0 +1,84 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests.Skinning +{ + [TestFixture] + public class TestSceneDrawableDrumRoll : TaikoSkinnableTestScene + { + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] + { + typeof(DrawableDrumRoll), + typeof(DrawableDrumRollTick), + }).ToList(); + + [Cached(typeof(IScrollingInfo))] + private ScrollingTestContainer.TestScrollingInfo info = new ScrollingTestContainer.TestScrollingInfo + { + Direction = { Value = ScrollingDirection.Left }, + TimeRange = { Value = 5000 }, + }; + + [BackgroundDependencyLoader] + private void load() + { + AddStep("Drum roll", () => SetContents(() => + { + var hoc = new ScrollingHitObjectContainer(); + + hoc.Add(new DrawableDrumRoll(createDrumRollAtCurrentTime()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 500, + }); + + return hoc; + })); + + AddStep("Drum roll (strong)", () => SetContents(() => + { + var hoc = new ScrollingHitObjectContainer(); + + hoc.Add(new DrawableDrumRoll(createDrumRollAtCurrentTime(true)) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 500, + }); + + return hoc; + })); + } + + private DrumRoll createDrumRollAtCurrentTime(bool strong = false) + { + var drumroll = new DrumRoll + { + IsStrong = strong, + StartTime = Time.Current + 1000, + Duration = 4000, + }; + + var cpi = new ControlPointInfo(); + cpi.Add(0, new TimingControlPoint { BeatLength = 500 }); + + drumroll.ApplyDefaults(cpi, new BeatmapDifficulty()); + + return drumroll; + } + } +} diff --git a/osu.Game/Tests/Visual/ScrollingTestContainer.cs b/osu.Game/Tests/Visual/ScrollingTestContainer.cs index 18326a78ad..3b741fcf1d 100644 --- a/osu.Game/Tests/Visual/ScrollingTestContainer.cs +++ b/osu.Game/Tests/Visual/ScrollingTestContainer.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual public void Flip() => scrollingInfo.Direction.Value = scrollingInfo.Direction.Value == ScrollingDirection.Up ? ScrollingDirection.Down : ScrollingDirection.Up; - private class TestScrollingInfo : IScrollingInfo + public class TestScrollingInfo : IScrollingInfo { public readonly Bindable Direction = new Bindable(); IBindable IScrollingInfo.Direction => Direction; @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual IScrollAlgorithm IScrollingInfo.Algorithm => Algorithm; } - private class TestScrollAlgorithm : IScrollAlgorithm + public class TestScrollAlgorithm : IScrollAlgorithm { public readonly SortedList ControlPoints = new SortedList(); From eb165840cb4e202846dfbc11b2da997af6a814fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Apr 2020 16:54:50 +0900 Subject: [PATCH 341/655] Add remaining taiko hitobject skinnables and expose as SkinnableDrawable for safety --- .../Objects/Drawables/DrawableCentreHit.cs | 3 +-- .../Objects/Drawables/DrawableDrumRoll.cs | 27 +++++++++++++------ .../Objects/Drawables/DrawableDrumRollTick.cs | 8 +++--- .../Objects/Drawables/DrawableHit.cs | 2 +- .../Objects/Drawables/DrawableRimHit.cs | 3 +-- .../Objects/Drawables/DrawableSwell.cs | 16 ++++++----- .../Objects/Drawables/DrawableSwellTick.cs | 5 ++-- .../Drawables/DrawableTaikoHitObject.cs | 5 ++-- .../Objects/Drawables/Pieces/CirclePiece.cs | 3 ++- .../TaikoSkinComponents.cs | 5 +++- 10 files changed, 46 insertions(+), 31 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs index f3f4c59a62..a87da44415 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osu.Game.Skinning; @@ -16,7 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { } - protected override CompositeDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.CentreHit), + protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.CentreHit), _ => new CentreHitCirclePiece(), confineMode: ConfineMode.ScaleToFit); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 0627eb95fd..5c3433cbf4 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -29,25 +30,29 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// private int rollingHits; - private readonly Container tickContainer; + private Container tickContainer; private Color4 colourIdle; private Color4 colourEngaged; - private ElongatedCirclePiece elongatedPiece; - public DrawableDrumRoll(DrumRoll drumRoll) : base(drumRoll) { RelativeSizeAxes = Axes.Y; - elongatedPiece.Add(tickContainer = new Container { RelativeSizeAxes = Axes.Both }); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - elongatedPiece.AccentColour = colourIdle = colours.YellowDark; + colourIdle = colours.YellowDark; colourEngaged = colours.YellowDarker; + + updateColour(); + + ((Container)MainPiece.Drawable).Add(tickContainer = new Container { RelativeSizeAxes = Axes.Both }); + + if (MainPiece.Drawable is IHasAccentColour accentMain) + accentMain.AccentColour = colourIdle; } protected override void LoadComplete() @@ -86,7 +91,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables return base.CreateNestedHitObject(hitObject); } - protected override CompositeDrawable CreateMainPiece() => elongatedPiece = new ElongatedCirclePiece(); + protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.DrumRollBody), + _ => new ElongatedCirclePiece()); public override bool OnPressed(TaikoAction action) => false; @@ -102,8 +108,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables rollingHits = Math.Clamp(rollingHits, 0, rolling_hits_for_engaged_colour); - Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1); - (MainPiece as IHasAccentColour)?.FadeAccent(newColour, 100); + updateColour(); } protected override void CheckForResult(bool userTriggered, double timeOffset) @@ -151,5 +156,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public override bool OnPressed(TaikoAction action) => false; } + + private void updateColour() + { + Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1); + (MainPiece.Drawable as IHasAccentColour)?.FadeAccent(newColour, 100); + } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index fea3eea6a9..e11e019826 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -3,10 +3,10 @@ using System; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -20,10 +20,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public override bool DisplayResult => false; - protected override CompositeDrawable CreateMainPiece() => new TickPiece - { - Filled = HitObject.FirstTick - }; + protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.DrumRollTick), + _ => new TickPiece()); protected override void CheckForResult(bool userTriggered, double timeOffset) { diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 85dfc8d5e0..9333e5f144 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables // If we're far enough away from the left stage, we should bring outselves in front of it ProxyContent(); - var flash = (MainPiece as CirclePiece)?.FlashBox; + var flash = (MainPiece.Drawable as CirclePiece)?.FlashBox; flash?.FadeTo(0.9f).FadeOut(300); const float gravity_time = 300; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs index 463a8b746c..f767403c65 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osu.Game.Skinning; @@ -16,7 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { } - protected override CompositeDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.RimHit), + protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.RimHit), _ => new RimHitCirclePiece(), confineMode: ConfineMode.ScaleToFit); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 3a2e44038f..32f7acadc8 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -114,12 +115,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables targetRing.BorderColour = colours.YellowDark.Opacity(0.25f); } - protected override CompositeDrawable CreateMainPiece() => new SwellCirclePiece - { - // to allow for rotation transform - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; + protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Swell), + _ => new SwellCirclePiece + { + // to allow for rotation transform + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); protected override void LoadComplete() { @@ -184,7 +186,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables .Then() .FadeTo(completion / 8, 2000, Easing.OutQuint); - MainPiece.RotateTo((float)(completion * HitObject.Duration / 8), 4000, Easing.OutQuint); + MainPiece.Drawable.RotateTo((float)(completion * HitObject.Duration / 8), 4000, Easing.OutQuint); expandingRing.ScaleTo(1f + Math.Min(target_ring_scale - 1f, (target_ring_scale - 1f) * completion * 1.3f), 260, Easing.OutQuint); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs index 5a954addfb..1685576f0d 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs @@ -2,9 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public override bool OnPressed(TaikoAction action) => false; - protected override CompositeDrawable CreateMainPiece() => new TickPiece(); + protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.DrumRollTick), + _ => new TickPiece()); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 2f90f3b96c..1be04f1760 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Objects; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -115,7 +116,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public new TObject HitObject; protected readonly Vector2 BaseSize; - protected readonly CompositeDrawable MainPiece; + protected readonly SkinnableDrawable MainPiece; private readonly Container strongHitContainer; @@ -167,7 +168,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables // Normal and clap samples are handled by the drum protected override IEnumerable GetSamples() => HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP); - protected abstract CompositeDrawable CreateMainPiece(); + protected abstract SkinnableDrawable CreateMainPiece(); /// /// Creates the handler for this 's . diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs index 6ca77e666d..b5471e6976 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs @@ -10,6 +10,7 @@ using osuTK.Graphics; using osu.Game.Beatmaps.ControlPoints; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Effects; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces @@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces /// for a usage example. /// /// - public abstract class CirclePiece : BeatSyncedContainer + public abstract class CirclePiece : BeatSyncedContainer, IHasAccentColour { public const float SYMBOL_SIZE = 0.45f; public const float SYMBOL_BORDER = 8; diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs index babf21b6a9..156ea71c16 100644 --- a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs +++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs @@ -7,6 +7,9 @@ namespace osu.Game.Rulesets.Taiko { InputDrum, CentreHit, - RimHit + RimHit, + DrumRollBody, + DrumRollTick, + Swell } } From 45d88b70f8de3cf146a1e28e99d07dabf6d511ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Apr 2020 17:50:37 +0900 Subject: [PATCH 342/655] Split out base logic from LegacyHit into LegacyCirclePiece --- .../Skinning/TestSceneDrawableHit.cs | 1 + .../Skinning/LegacyCirclePiece.cs | 96 +++++++++++++++++++ osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs | 69 +------------ 3 files changed, 99 insertions(+), 67 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs index a3832b010c..6d6da1fb5b 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs @@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning typeof(DrawableCentreHit), typeof(DrawableRimHit), typeof(LegacyHit), + typeof(LegacyCirclePiece), }).ToList(); [BackgroundDependencyLoader] diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs new file mode 100644 index 0000000000..bfcf268c3d --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs @@ -0,0 +1,96 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Animations; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Objects.Drawables; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Taiko.Skinning +{ + public class LegacyCirclePiece : CompositeDrawable, IHasAccentColour + { + private Drawable backgroundLayer; + + public LegacyCirclePiece() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin, DrawableHitObject drawableHitObject) + { + Drawable getDrawableFor(string lookup) + { + const string normal_hit = "taikohit"; + const string big_hit = "taikobig"; + + string prefix = ((drawableHitObject as DrawableTaikoHitObject)?.HitObject.IsStrong ?? false) ? big_hit : normal_hit; + + return skin.GetAnimation($"{prefix}{lookup}", true, false) ?? + // fallback to regular size if "big" version doesn't exist. + skin.GetAnimation($"{normal_hit}{lookup}", true, false); + } + + // backgroundLayer is guaranteed to exist due to the pre-check in TaikoLegacySkinTransformer. + AddInternal(backgroundLayer = getDrawableFor("circle")); + + var foregroundLayer = getDrawableFor("circleoverlay"); + if (foregroundLayer != null) + AddInternal(foregroundLayer); + + // Animations in taiko skins are used in a custom way (>150 combo and animating in time with beat). + // For now just stop at first frame for sanity. + foreach (var c in InternalChildren) + { + (c as IFramedAnimation)?.Stop(); + + c.Anchor = Anchor.Centre; + c.Origin = Anchor.Centre; + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + updateAccentColour(); + } + + protected override void Update() + { + base.Update(); + + // Not all skins (including the default osu-stable) have similar sizes for "hitcircle" and "hitcircleoverlay". + // This ensures they are scaled relative to each other but also match the expected DrawableHit size. + foreach (var c in InternalChildren) + c.Scale = new Vector2(DrawHeight / 128); + } + + private Color4 accentColour; + + public Color4 AccentColour + { + get => accentColour; + set + { + if (value == accentColour) + return; + + accentColour = value; + if (IsLoaded) + updateAccentColour(); + } + } + + private void updateAccentColour() + { + backgroundLayer.Colour = accentColour; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs index 80bf97936d..656728f6e4 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs @@ -2,90 +2,25 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Animations; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Taiko.Objects.Drawables; -using osu.Game.Skinning; -using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Taiko.Skinning { - public class LegacyHit : CompositeDrawable, IHasAccentColour + public class LegacyHit : LegacyCirclePiece { private readonly TaikoSkinComponents component; - private Drawable backgroundLayer; - public LegacyHit(TaikoSkinComponents component) { this.component = component; - - RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] - private void load(ISkinSource skin, DrawableHitObject drawableHitObject) + private void load() { - Drawable getDrawableFor(string lookup) - { - const string normal_hit = "taikohit"; - const string big_hit = "taikobig"; - - string prefix = ((drawableHitObject as DrawableTaikoHitObject)?.HitObject.IsStrong ?? false) ? big_hit : normal_hit; - - return skin.GetAnimation($"{prefix}{lookup}", true, false) ?? - // fallback to regular size if "big" version doesn't exist. - skin.GetAnimation($"{normal_hit}{lookup}", true, false); - } - - // backgroundLayer is guaranteed to exist due to the pre-check in TaikoLegacySkinTransformer. - AddInternal(backgroundLayer = getDrawableFor("circle")); - - var foregroundLayer = getDrawableFor("circleoverlay"); - if (foregroundLayer != null) - AddInternal(foregroundLayer); - - // Animations in taiko skins are used in a custom way (>150 combo and animating in time with beat). - // For now just stop at first frame for sanity. - foreach (var c in InternalChildren) - { - (c as IFramedAnimation)?.Stop(); - - c.Anchor = Anchor.Centre; - c.Origin = Anchor.Centre; - } - AccentColour = component == TaikoSkinComponents.CentreHit ? new Color4(235, 69, 44, 255) : new Color4(67, 142, 172, 255); } - - protected override void Update() - { - base.Update(); - - // Not all skins (including the default osu-stable) have similar sizes for "hitcircle" and "hitcircleoverlay". - // This ensures they are scaled relative to each other but also match the expected DrawableHit size. - foreach (var c in InternalChildren) - c.Scale = new Vector2(DrawWidth / 128); - } - - private Color4 accentColour; - - public Color4 AccentColour - { - get => accentColour; - set - { - if (value == accentColour) - return; - - backgroundLayer.Colour = accentColour = value; - } - } } } From 313741799468b34fb5abb903b9513b9bbafbe4a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Apr 2020 17:50:57 +0900 Subject: [PATCH 343/655] Add drumroll skinning --- .../Skinning/TestSceneDrawableDrumRoll.cs | 2 + .../Skinning/LegacyDrumRoll.cs | 110 ++++++++++++++++++ .../Skinning/TaikoLegacySkinTransformer.cs | 6 + 3 files changed, 118 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRoll.cs index 388be5bbc4..554894bf68 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRoll.cs @@ -11,6 +11,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Skinning; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; @@ -23,6 +24,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning { typeof(DrawableDrumRoll), typeof(DrawableDrumRollTick), + typeof(LegacyDrumRoll), }).ToList(); [Cached(typeof(IScrollingInfo))] diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs new file mode 100644 index 0000000000..d3579fbbbd --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs @@ -0,0 +1,110 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Skinning; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Taiko.Skinning +{ + public class LegacyDrumRoll : Container, IHasAccentColour + { + protected override Container Content => content; + + private Container content; + + private LegacyCirclePiece headCircle; + + private Sprite body; + + private Sprite end; + + public LegacyDrumRoll() + { + RelativeSizeAxes = Axes.Y; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + InternalChildren = new Drawable[] + { + content = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + headCircle = new LegacyCirclePiece + { + Depth = float.MinValue, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + }, + body = new Sprite + { + RelativeSizeAxes = Axes.Both, + Texture = skin.GetTexture("taiko-roll-middle"), + }, + end = new Sprite + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Texture = skin.GetTexture("taiko-roll-end"), + FillMode = FillMode.Fit, + }, + }, + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + updateAccentColour(); + } + + protected override void Update() + { + base.Update(); + + var padding = Content.DrawHeight * Content.Width / 2; + + Content.Padding = new MarginPadding + { + Left = padding, + Right = padding, + }; + + Width = Parent.DrawSize.X + DrawHeight; + } + + private Color4 accentColour; + + public Color4 AccentColour + { + get => accentColour; + set + { + if (value == accentColour) + return; + + accentColour = value; + if (IsLoaded) + updateAccentColour(); + } + } + + private void updateAccentColour() + { + headCircle.AccentColour = accentColour; + body.Colour = accentColour; + end.Colour = accentColour; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs index 9cd625c35f..86e3945021 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs @@ -27,6 +27,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning switch (taikoComponent.Component) { + case TaikoSkinComponents.DrumRollBody: + if (GetTexture("taiko-roll-middle") != null) + return new LegacyDrumRoll(); + + return null; + case TaikoSkinComponents.InputDrum: if (GetTexture("taiko-bar-left") != null) return new LegacyInputDrum(); From 07632cd1e53a9e297861b5f152f5e74e7d2552bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Apr 2020 18:44:12 +0900 Subject: [PATCH 344/655] Remove unnecessary container logic --- .../Objects/Drawables/DrawableDrumRoll.cs | 16 ++--- .../Drawables/Pieces/ElongatedCirclePiece.cs | 17 +++-- .../Skinning/LegacyDrumRoll.cs | 65 ++++++------------- 3 files changed, 35 insertions(+), 63 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 5c3433cbf4..0a6f462607 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// private int rollingHits; - private Container tickContainer; + private Container tickContainer; private Color4 colourIdle; private Color4 colourEngaged; @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables updateColour(); - ((Container)MainPiece.Drawable).Add(tickContainer = new Container { RelativeSizeAxes = Axes.Both }); + Content.Add(tickContainer = new Container { RelativeSizeAxes = Axes.Both }); if (MainPiece.Drawable is IHasAccentColour accentMain) accentMain.AccentColour = colourIdle; @@ -139,6 +139,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override DrawableStrongNestedHit CreateStrongHit(StrongHitObject hitObject) => new StrongNestedHit(hitObject, this); + private void updateColour() + { + Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1); + (MainPiece.Drawable as IHasAccentColour)?.FadeAccent(newColour, 100); + } + private class StrongNestedHit : DrawableStrongNestedHit { public StrongNestedHit(StrongHitObject strong, DrawableDrumRoll drumRoll) @@ -156,11 +162,5 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public override bool OnPressed(TaikoAction action) => false; } - - private void updateColour() - { - Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1); - (MainPiece.Drawable as IHasAccentColour)?.FadeAccent(newColour, 100); - } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs index 7e3272e42b..034ab6dd21 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs @@ -1,7 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Graphics; namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces { @@ -12,18 +14,15 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces RelativeSizeAxes = Axes.Y; } + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.YellowDark; + } + protected override void Update() { base.Update(); - - var padding = Content.DrawHeight * Content.Width / 2; - - Content.Padding = new MarginPadding - { - Left = padding, - Right = padding, - }; - Width = Parent.DrawSize.X + DrawHeight; } } diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs index d3579fbbbd..8531f3cefd 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs @@ -11,12 +11,8 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Taiko.Skinning { - public class LegacyDrumRoll : Container, IHasAccentColour + public class LegacyDrumRoll : CompositeDrawable, IHasAccentColour { - protected override Container Content => content; - - private Container content; - private LegacyCirclePiece headCircle; private Sprite body; @@ -25,42 +21,34 @@ namespace osu.Game.Rulesets.Taiko.Skinning public LegacyDrumRoll() { - RelativeSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] - private void load(ISkinSource skin) + private void load(ISkinSource skin, OsuColour colours) { InternalChildren = new Drawable[] { - content = new Container + end = new Sprite + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Texture = skin.GetTexture("taiko-roll-end"), + FillMode = FillMode.Fit, + }, + body = new Sprite { RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - headCircle = new LegacyCirclePiece - { - Depth = float.MinValue, - RelativeSizeAxes = Axes.Y, - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - }, - body = new Sprite - { - RelativeSizeAxes = Axes.Both, - Texture = skin.GetTexture("taiko-roll-middle"), - }, - end = new Sprite - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - Texture = skin.GetTexture("taiko-roll-end"), - FillMode = FillMode.Fit, - }, - }, + Texture = skin.GetTexture("taiko-roll-middle"), + }, + headCircle = new LegacyCirclePiece + { + RelativeSizeAxes = Axes.Y, }, }; + + AccentColour = colours.YellowDark; } protected override void LoadComplete() @@ -69,21 +57,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning updateAccentColour(); } - protected override void Update() - { - base.Update(); - - var padding = Content.DrawHeight * Content.Width / 2; - - Content.Padding = new MarginPadding - { - Left = padding, - Right = padding, - }; - - Width = Parent.DrawSize.X + DrawHeight; - } - private Color4 accentColour; public Color4 AccentColour From bfc0d41c0ca81f5a0cc4c3fb00e7d320fac55330 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Apr 2020 19:24:50 +0900 Subject: [PATCH 345/655] Add tick skinning support --- osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs index 86e3945021..3af7df07c4 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs @@ -46,6 +46,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning return new LegacyHit(taikoComponent.Component); return null; + + case TaikoSkinComponents.DrumRollTick: + return this.GetAnimation("sliderscorepoint", false, false); } return source.GetDrawableComponent(component); From f36477e39dd1bbd055d345997f91c99701ffa208 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Apr 2020 10:04:09 +0900 Subject: [PATCH 346/655] Add back "filled" property setting --- .../Objects/Drawables/DrawableDrumRollTick.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index e11e019826..689a7bfa64 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -21,7 +21,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public override bool DisplayResult => false; protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.DrumRollTick), - _ => new TickPiece()); + _ => new TickPiece + { + Filled = HitObject.FirstTick + }); protected override void CheckForResult(bool userTriggered, double timeOffset) { From e2b28bfe88cc94c685a8ddbe3f496190c39103c1 Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 15 Apr 2020 18:17:12 -0700 Subject: [PATCH 347/655] Hide edit context menu item in multiplayer song select --- .../Select/Carousel/DrawableCarouselBeatmap.cs | 12 +++++++++--- osu.Game/Screens/Select/SongSelect.cs | 10 ---------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 2520c70989..a371c56101 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -41,6 +41,9 @@ namespace osu.Game.Screens.Select.Carousel [Resolved(CanBeNull = true)] private BeatmapSetOverlay beatmapOverlay { get; set; } + [Resolved(CanBeNull = true)] + private SongSelect songSelect { get; set; } + public DrawableCarouselBeatmap(CarouselBeatmap panel) : base(panel) { @@ -49,7 +52,7 @@ namespace osu.Game.Screens.Select.Carousel } [BackgroundDependencyLoader(true)] - private void load(SongSelect songSelect, BeatmapManager manager) + private void load(BeatmapManager manager) { if (songSelect != null) { @@ -190,10 +193,13 @@ namespace osu.Game.Screens.Select.Carousel List items = new List { new OsuMenuItem("Play", MenuItemType.Highlighted, () => startRequested?.Invoke(beatmap)), - new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested?.Invoke(beatmap)), - new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested?.Invoke(beatmap)), }; + if (songSelect.AllowEditing) + items.Add(new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested?.Invoke(beatmap))); + + items.Add(new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested?.Invoke(beatmap))); + if (beatmap.OnlineBeatmapID.HasValue) items.Add(new OsuMenuItem("Details", MenuItemType.Standard, () => beatmapOverlay?.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index f164056ede..8967628954 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -34,7 +34,6 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; -using osu.Game.Overlays.Notifications; using osu.Game.Scoring; namespace osu.Game.Screens.Select @@ -71,9 +70,6 @@ namespace osu.Game.Screens.Select /// public virtual bool AllowEditing => true; - [Resolved(canBeNull: true)] - private NotificationOverlay notificationOverlay { get; set; } - [Resolved] private Bindable> selectedMods { get; set; } @@ -328,12 +324,6 @@ namespace osu.Game.Screens.Select public void Edit(BeatmapInfo beatmap = null) { - if (!AllowEditing) - { - notificationOverlay?.Post(new SimpleNotification { Text = "Editing is not available from the current mode." }); - return; - } - Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap ?? beatmapNoDebounce); this.Push(new Editor()); } From 06e25091f666c8b8f2ac4e4e42f1d24d83026c37 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Apr 2020 10:44:08 +0900 Subject: [PATCH 348/655] Fix typo --- .../Visual/UserInterface/TestSceneOsuMenu.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMenu.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMenu.cs index cdda1969ca..c171e567ad 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMenu.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMenu.cs @@ -21,12 +21,12 @@ namespace osu.Game.Tests.Visual.UserInterface }; private OsuMenu menu; - private bool actionPeformed; + private bool actionPerformed; [SetUp] public void Setup() => Schedule(() => { - actionPeformed = false; + actionPerformed = false; Child = menu = new OsuMenu(Direction.Vertical, true) { @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("move to first menu item", () => InputManager.MoveMouseTo(menu.ChildrenOfType().First())); AddStep("click", () => InputManager.Click(MouseButton.Left)); - AddAssert("action performed", () => actionPeformed); + AddAssert("action performed", () => actionPerformed); } [Test] @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("move to first menu item", () => InputManager.MoveMouseTo(menu.ChildrenOfType().First())); AddStep("click", () => InputManager.Click(MouseButton.Left)); - AddAssert("action not performed", () => !actionPeformed); + AddAssert("action not performed", () => !actionPerformed); } [Test] @@ -83,9 +83,9 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("move to first menu item", () => InputManager.MoveMouseTo(menu.ChildrenOfType().First())); AddStep("click", () => InputManager.Click(MouseButton.Left)); - AddAssert("action performed", () => actionPeformed); + AddAssert("action performed", () => actionPerformed); } - private void performAction() => actionPeformed = true; + private void performAction() => actionPerformed = true; } } From c4caf38febbe7862589c2f33d761c005a7e6f0fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Apr 2020 12:10:20 +0900 Subject: [PATCH 349/655] Simplify menu item checks (and add for other items) --- .../Carousel/DrawableCarouselBeatmap.cs | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index a371c56101..3e4798a812 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -41,9 +41,6 @@ namespace osu.Game.Screens.Select.Carousel [Resolved(CanBeNull = true)] private BeatmapSetOverlay beatmapOverlay { get; set; } - [Resolved(CanBeNull = true)] - private SongSelect songSelect { get; set; } - public DrawableCarouselBeatmap(CarouselBeatmap panel) : base(panel) { @@ -52,12 +49,13 @@ namespace osu.Game.Screens.Select.Carousel } [BackgroundDependencyLoader(true)] - private void load(BeatmapManager manager) + private void load(BeatmapManager manager, SongSelect songSelect) { if (songSelect != null) { startRequested = b => songSelect.FinaliseSelection(b); - editRequested = songSelect.Edit; + if (songSelect.AllowEditing) + editRequested = songSelect.Edit; } if (manager != null) @@ -190,18 +188,19 @@ namespace osu.Game.Screens.Select.Carousel { get { - List items = new List - { - new OsuMenuItem("Play", MenuItemType.Highlighted, () => startRequested?.Invoke(beatmap)), - }; + List items = new List(); - if (songSelect.AllowEditing) - items.Add(new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested?.Invoke(beatmap))); + if (startRequested != null) + items.Add(new OsuMenuItem("Play", MenuItemType.Highlighted, () => startRequested(beatmap))); - items.Add(new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested?.Invoke(beatmap))); + if (editRequested != null) + items.Add(new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested(beatmap))); - if (beatmap.OnlineBeatmapID.HasValue) - items.Add(new OsuMenuItem("Details", MenuItemType.Standard, () => beatmapOverlay?.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); + if (hideRequested != null) + items.Add(new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested(beatmap))); + + if (beatmap.OnlineBeatmapID.HasValue && beatmapOverlay != null) + items.Add(new OsuMenuItem("Details", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); return items.ToArray(); } From 91b13f91eaaaad87dd221bdb8daf3ed34d7166b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Apr 2020 12:11:12 +0900 Subject: [PATCH 350/655] Add exception disallowing potential edit when disabled at a property level --- osu.Game/Screens/Select/SongSelect.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 8967628954..5bc2e1aa56 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -324,6 +324,9 @@ namespace osu.Game.Screens.Select public void Edit(BeatmapInfo beatmap = null) { + if (!AllowEditing) + throw new InvalidOperationException($"Attempted to edit when {nameof(AllowEditing)} is disabled"); + Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap ?? beatmapNoDebounce); this.Push(new Editor()); } From 03a74a4320db317e063130161933694d4563ca85 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Apr 2020 12:13:26 +0900 Subject: [PATCH 351/655] Apply same conditional check changes to DrawableCarouselBeatmapSet --- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index a53b74c1b8..5acb6d1946 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -46,6 +46,7 @@ namespace osu.Game.Screens.Select.Carousel private void load(BeatmapManager manager, BeatmapSetOverlay beatmapOverlay) { restoreHiddenRequested = s => s.Beatmaps.ForEach(manager.Restore); + if (beatmapOverlay != null) viewDetails = beatmapOverlay.FetchAndShowBeatmapSet; @@ -131,13 +132,14 @@ namespace osu.Game.Screens.Select.Carousel if (Item.State.Value == CarouselItemState.NotSelected) items.Add(new OsuMenuItem("Expand", MenuItemType.Highlighted, () => Item.State.Value = CarouselItemState.Selected)); - if (beatmapSet.OnlineBeatmapSetID != null) - items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails?.Invoke(beatmapSet.OnlineBeatmapSetID.Value))); + if (beatmapSet.OnlineBeatmapSetID != null && viewDetails != null) + items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails(beatmapSet.OnlineBeatmapSetID.Value))); if (beatmapSet.Beatmaps.Any(b => b.Hidden)) - items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested?.Invoke(beatmapSet))); + items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested(beatmapSet))); - items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay?.Push(new BeatmapDeleteDialog(beatmapSet)))); + if (dialogOverlay != null) + items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet)))); return items.ToArray(); } From 9e2be6f2f438dcc288bbe711c486a8cc112e310d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 16 Apr 2020 13:25:08 +0900 Subject: [PATCH 352/655] Remove bindable to promote one-way access --- osu.Game.Tests/Visual/UserInterface/TestSceneOsuMenu.cs | 6 +++--- osu.Game/Graphics/UserInterface/OsuMenuItem.cs | 6 ------ osu.Game/Screens/Edit/Editor.cs | 9 +++++++-- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMenu.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMenu.cs index c171e567ad..9ea76c2c7b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMenu.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMenu.cs @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("disable menu items", () => { foreach (var item in menu.Items) - ((OsuMenuItem)item).Enabled.Value = false; + ((OsuMenuItem)item).Action.Disabled = true; }); AddStep("move to first menu item", () => InputManager.MoveMouseTo(menu.ChildrenOfType().First())); @@ -71,13 +71,13 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("disable menu items", () => { foreach (var item in menu.Items) - ((OsuMenuItem)item).Enabled.Value = false; + ((OsuMenuItem)item).Action.Disabled = true; }); AddStep("enable menu items", () => { foreach (var item in menu.Items) - ((OsuMenuItem)item).Enabled.Value = true; + ((OsuMenuItem)item).Action.Disabled = false; }); AddStep("move to first menu item", () => InputManager.MoveMouseTo(menu.ChildrenOfType().First())); diff --git a/osu.Game/Graphics/UserInterface/OsuMenuItem.cs b/osu.Game/Graphics/UserInterface/OsuMenuItem.cs index 36122ca0b2..0fe41937ce 100644 --- a/osu.Game/Graphics/UserInterface/OsuMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/OsuMenuItem.cs @@ -2,15 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Bindables; using osu.Framework.Graphics.UserInterface; namespace osu.Game.Graphics.UserInterface { public class OsuMenuItem : MenuItem { - public readonly Bindable Enabled = new Bindable(true); - public readonly MenuItemType Type; public OsuMenuItem(string text, MenuItemType type = MenuItemType.Standard) @@ -22,9 +19,6 @@ namespace osu.Game.Graphics.UserInterface : base(text, action) { Type = type; - - Enabled.BindValueChanged(enabled => Action.Disabled = !enabled.NewValue); - Action.BindDisabledChanged(disabled => Enabled.Value = !disabled); } } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index ad17498d93..9a1f450dc6 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -107,6 +107,8 @@ namespace osu.Game.Screens.Edit dependencies.CacheAs(changeHandler); EditorMenuBar menuBar; + OsuMenuItem undoMenuItem; + OsuMenuItem redoMenuItem; var fileMenuItems = new List { @@ -155,8 +157,8 @@ namespace osu.Game.Screens.Edit { Items = new[] { - new EditorMenuItem("Undo", MenuItemType.Standard, undo) { Enabled = { BindTarget = changeHandler.CanUndo } }, - new EditorMenuItem("Redo", MenuItemType.Standard, redo) { Enabled = { BindTarget = changeHandler.CanRedo } } + undoMenuItem = new EditorMenuItem("Undo", MenuItemType.Standard, undo), + redoMenuItem = new EditorMenuItem("Redo", MenuItemType.Standard, redo) } } } @@ -214,6 +216,9 @@ namespace osu.Game.Screens.Edit } }); + changeHandler.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true); + changeHandler.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true); + menuBar.Mode.ValueChanged += onModeChanged; bottomBackground.Colour = colours.Gray2; From 9dda7da489918120d251c6c266272f41a2fa8671 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Apr 2020 14:11:38 +0900 Subject: [PATCH 353/655] Fix spinners being considered the "first object" for increased visibility in hidden --- .../Mods/TestSceneOsuModHidden.cs | 106 ++++++++++++++++++ osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 2 + osu.Game/Rulesets/Mods/ModHidden.cs | 14 ++- 3 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs new file mode 100644 index 0000000000..8bd3d3c7cc --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs @@ -0,0 +1,106 @@ +// 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.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public class TestSceneOsuModHidden : ModTestScene + { + public TestSceneOsuModHidden() + : base(new OsuRuleset()) + { + } + + [Test] + public void TestDefaultBeatmapTest() => CreateModTest(new ModTestData + { + Mod = new OsuModHidden(), + Autoplay = true, + PassCondition = checkSomeHit + }); + + [Test] + public void FirstCircleAfterTwoSpinners() => CreateModTest(new ModTestData + { + Mod = new OsuModHidden(), + Autoplay = true, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Spinner + { + Position = new Vector2(256, 192), + EndTime = 1000, + }, + new Spinner + { + Position = new Vector2(256, 192), + StartTime = 1200, + EndTime = 2200, + }, + new HitCircle + { + Position = new Vector2(300, 192), + StartTime = 3200, + }, + new HitCircle + { + Position = new Vector2(384, 192), + StartTime = 4200, + } + } + }, + PassCondition = checkSomeHit + }); + + [Test] + public void FirstSliderAfterTwoSpinners() => CreateModTest(new ModTestData + { + Mod = new OsuModHidden(), + Autoplay = true, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Spinner + { + Position = new Vector2(256, 192), + EndTime = 1000, + }, + new Spinner + { + Position = new Vector2(256, 192), + StartTime = 1200, + EndTime = 2200, + }, + new Slider + { + StartTime = 3200, + Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), }) + }, + new Slider + { + StartTime = 5200, + Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), }) + } + } + }, + PassCondition = checkSomeHit + }); + + private bool checkSomeHit() + { + return Player.ScoreProcessor.JudgedHits >= 4; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 91a4e049e3..fdba03f260 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -23,6 +23,8 @@ namespace osu.Game.Rulesets.Osu.Mods private const double fade_in_duration_multiplier = 0.4; private const double fade_out_duration_multiplier = 0.3; + protected override bool IsFirstHideableObject(DrawableHitObject hitObject) => !(hitObject is DrawableSpinner); + public override void ApplyToDrawableHitObjects(IEnumerable drawables) { static void adjustFadeIn(OsuHitObject h) => h.TimeFadeIn = h.TimePreempt * fade_in_duration_multiplier; diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index 4e4a75db82..a1915b974c 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -23,6 +23,13 @@ namespace osu.Game.Rulesets.Mods protected Bindable IncreaseFirstObjectVisibility = new Bindable(); + /// + /// Check whether the provided hitobject should be considered the "first" hideable object. + /// Can be used to skip spinners, for instance. + /// + /// The hitobject to check. + protected virtual bool IsFirstHideableObject(DrawableHitObject hitObject) => true; + public void ReadFromConfig(OsuConfigManager config) { IncreaseFirstObjectVisibility = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility); @@ -30,8 +37,11 @@ namespace osu.Game.Rulesets.Mods public virtual void ApplyToDrawableHitObjects(IEnumerable drawables) { - foreach (var d in drawables.Skip(IncreaseFirstObjectVisibility.Value ? 1 : 0)) - d.ApplyCustomUpdateState += ApplyHiddenState; + if (IncreaseFirstObjectVisibility.Value) + drawables = drawables.SkipWhile(h => !IsFirstHideableObject(h)).Skip(1); + + foreach (var dho in drawables) + dho.ApplyCustomUpdateState += ApplyHiddenState; } public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) From ef0da9e3e831096674d37ba799246de1d569a786 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 16 Apr 2020 11:01:36 +0300 Subject: [PATCH 354/655] Basic overlay layout implementation --- .../Online/TestSceneDashboardOverlay.cs | 43 +++++++++++++ .../Dashboard/DashboardOverlayHeader.cs | 24 +++++++ osu.Game/Overlays/DashboardOverlay.cs | 62 +++++++++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs create mode 100644 osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs create mode 100644 osu.Game/Overlays/DashboardOverlay.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs new file mode 100644 index 0000000000..df95f24686 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs @@ -0,0 +1,43 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Overlays; +using osu.Game.Overlays.Dashboard; +using osu.Game.Overlays.Dashboard.Friends; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneDashboardOverlay : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(DashboardOverlay), + typeof(DashboardOverlayHeader), + typeof(FriendDisplay) + }; + + protected override bool UseOnlineAPI => true; + + private readonly DashboardOverlay overlay; + + public TestSceneDashboardOverlay() + { + Add(overlay = new DashboardOverlay()); + } + + [Test] + public void TestShow() + { + AddStep("Show", overlay.Show); + } + + [Test] + public void TestHide() + { + AddStep("Hide", overlay.Hide); + } + } +} diff --git a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs new file mode 100644 index 0000000000..1c52b033a5 --- /dev/null +++ b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Overlays.Dashboard +{ + public class DashboardOverlayHeader : TabControlOverlayHeader + { + protected override OverlayTitle CreateTitle() => new DashboardTitle(); + + private class DashboardTitle : OverlayTitle + { + public DashboardTitle() + { + Title = "dashboard"; + IconTexture = "Icons/changelog"; + } + } + } + + public enum HomeOverlayTabs + { + Friends + } +} diff --git a/osu.Game/Overlays/DashboardOverlay.cs b/osu.Game/Overlays/DashboardOverlay.cs new file mode 100644 index 0000000000..a1a7c9889a --- /dev/null +++ b/osu.Game/Overlays/DashboardOverlay.cs @@ -0,0 +1,62 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Dashboard; + +namespace osu.Game.Overlays +{ + public class DashboardOverlay : FullscreenOverlay + { + private readonly Box background; + private readonly Container content; + + public DashboardOverlay() + : base(OverlayColourScheme.Purple) + { + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new OverlayScrollContainer + { + RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new DashboardOverlayHeader + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Depth = -float.MaxValue + }, + content = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + } + } + } + }, + new LoadingLayer(content), + }; + } + + [BackgroundDependencyLoader] + private void load() + { + background.Colour = ColourProvider.Background5; + } + } +} From 2ab4a7293ec507b691fbf5fcd1208634fbe74aa2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Apr 2020 17:26:09 +0900 Subject: [PATCH 355/655] Clean up enum sorting attribute code --- .../API/Requests/SearchBeatmapSetsRequest.cs | 18 +------ .../BeatmapListing/BeatmapSearchFilterRow.cs | 28 ++-------- osu.Game/Utils/OrderAttribute.cs | 52 +++++++++++++++++++ 3 files changed, 56 insertions(+), 42 deletions(-) create mode 100644 osu.Game/Utils/OrderAttribute.cs diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index aef0788b49..1206563b18 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -1,12 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.ComponentModel; using osu.Framework.IO.Network; using osu.Game.Overlays; using osu.Game.Overlays.Direct; using osu.Game.Rulesets; +using osu.Game.Utils; namespace osu.Game.Online.API.Requests { @@ -139,20 +139,4 @@ namespace osu.Game.Online.API.Requests [Order(5)] Italian } - - [AttributeUsage(AttributeTargets.Field)] - public class OrderAttribute : Attribute - { - public readonly int Order; - - public OrderAttribute(int order) - { - Order = order; - } - } - - [AttributeUsage(AttributeTargets.Enum)] - public class HasOrderedElementsAttribute : Attribute - { - } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs index bc0a011e31..64b3afcae1 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -14,10 +13,10 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API.Requests; using osuTK; using osuTK.Graphics; using Humanizer; +using osu.Game.Utils; namespace osu.Game.Overlays.BeatmapListing { @@ -82,30 +81,9 @@ namespace osu.Game.Overlays.BeatmapListing TabContainer.Spacing = new Vector2(10, 0); - var type = typeof(T); - - if (type.IsEnum) + if (typeof(T).IsEnum) { - if (Attribute.GetCustomAttribute(type, typeof(HasOrderedElementsAttribute)) != null) - { - var enumValues = Enum.GetValues(type).Cast().ToArray(); - var enumNames = Enum.GetNames(type); - - int[] enumPositions = Array.ConvertAll(enumNames, n => - { - var orderAttr = (OrderAttribute)type.GetField(n).GetCustomAttributes(typeof(OrderAttribute), false)[0]; - return orderAttr.Order; - }); - - Array.Sort(enumPositions, enumValues); - - foreach (var val in enumValues) - AddItem(val); - - return; - } - - foreach (var val in (T[])Enum.GetValues(type)) + foreach (var val in OrderAttributeUtils.GetValuesInOrder()) AddItem(val); } } diff --git a/osu.Game/Utils/OrderAttribute.cs b/osu.Game/Utils/OrderAttribute.cs new file mode 100644 index 0000000000..4959caa726 --- /dev/null +++ b/osu.Game/Utils/OrderAttribute.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; +using System.Collections.Generic; +using System.Linq; + +namespace osu.Game.Utils +{ + public static class OrderAttributeUtils + { + /// + /// Get values of an enum in order. Supports custom ordering via . + /// + public static IEnumerable GetValuesInOrder() + { + var type = typeof(T); + + if (!type.IsEnum) + throw new InvalidOperationException("T must be an enum"); + + IEnumerable items = (T[])Enum.GetValues(type); + + if (Attribute.GetCustomAttribute(type, typeof(HasOrderedElementsAttribute)) == null) + return items; + + return items.OrderBy(i => + { + if (type.GetField(i.ToString()).GetCustomAttributes(typeof(OrderAttribute), false).FirstOrDefault() is OrderAttribute attr) + return attr.Order; + + return 0; + }); + } + } + + [AttributeUsage(AttributeTargets.Field)] + public class OrderAttribute : Attribute + { + public readonly int Order; + + public OrderAttribute(int order) + { + Order = order; + } + } + + [AttributeUsage(AttributeTargets.Enum)] + public class HasOrderedElementsAttribute : Attribute + { + } +} From c6aa6acc1b2f46c6c39a0d241325b1d2d8f154a5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 16 Apr 2020 17:28:06 +0900 Subject: [PATCH 356/655] Apply performance calculator changes --- .../Difficulty/CatchPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index 21d4642c22..bc52f0b812 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty if (approachRate > 9.0f) approachRateFactor += 0.1f * (approachRate - 9.0f); // 10% for each AR above 9 if (approachRate > 10.0f) - approachRateFactor += 0.2f * (float)Math.Pow(approachRate - 10.0f, 1.5f); // Additional 20% at AR 11, 40% total + approachRateFactor += 0.1f * (approachRate - 10.0f); // Additional 10% at AR 11, 30% total else if (approachRate < 8.0f) approachRateFactor += 0.025f * (8.0f - approachRate); // 2.5% for each AR below 8 From 29bea4e11c03292545a9937a149f28c3686c14c4 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 16 Apr 2020 11:42:21 +0300 Subject: [PATCH 357/655] Implement OverlayView component --- .../Visual/Online/TestSceneFriendDisplay.cs | 17 ++- .../Dashboard/Friends/FriendDisplay.cs | 143 ++++++++---------- osu.Game/Overlays/OverlayView.cs | 71 +++++++++ 3 files changed, 149 insertions(+), 82 deletions(-) create mode 100644 osu.Game/Overlays/OverlayView.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs index cf365a7614..0b5ff1c960 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs @@ -10,6 +10,7 @@ using osu.Game.Users; using osu.Game.Overlays; using osu.Framework.Allocation; using NUnit.Framework; +using osu.Game.Online.API; namespace osu.Game.Tests.Visual.Online { @@ -27,7 +28,7 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - private FriendDisplay display; + private TestFriendDisplay display; [SetUp] public void Setup() => Schedule(() => @@ -35,7 +36,7 @@ namespace osu.Game.Tests.Visual.Online Child = new BasicScrollContainer { RelativeSizeAxes = Axes.Both, - Child = display = new FriendDisplay() + Child = display = new TestFriendDisplay() }; }); @@ -83,5 +84,17 @@ namespace osu.Game.Tests.Visual.Online LastVisit = DateTimeOffset.Now } }; + + private class TestFriendDisplay : FriendDisplay + { + public void Fetch() + { + base.APIStateChanged(API, APIState.Online); + } + + public override void APIStateChanged(IAPIProvider api, APIState state) + { + } + } } } diff --git a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs index 3c9b31daae..9764f82199 100644 --- a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs +++ b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs @@ -16,7 +16,7 @@ using osuTK; namespace osu.Game.Overlays.Dashboard.Friends { - public class FriendDisplay : CompositeDrawable + public class FriendDisplay : OverlayView> { private List users = new List(); @@ -26,15 +26,10 @@ namespace osu.Game.Overlays.Dashboard.Friends set { users = value; - onlineStreamControl.Populate(value); } } - [Resolved] - private IAPIProvider api { get; set; } - - private GetFriendsRequest request; private CancellationTokenSource cancellationToken; private Drawable currentContent; @@ -48,92 +43,85 @@ namespace osu.Game.Overlays.Dashboard.Friends public FriendDisplay() { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - InternalChild = new FillFlowContainer + AddRange(new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] + new Container { - new Container + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] + controlBackground = new Box { - controlBackground = new Box + RelativeSizeAxes = Axes.Both + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { - RelativeSizeAxes = Axes.Both + Top = 20, + Horizontal = 45 }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding - { - Top = 20, - Horizontal = 45 - }, - Child = onlineStreamControl = new FriendOnlineStreamControl(), - } + Child = onlineStreamControl = new FriendOnlineStreamControl(), } - }, - new Container + } + }, + new Container + { + Name = "User List", + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] { - Name = "User List", - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] + background = new Box { - background = new Box + RelativeSizeAxes = Axes.Both + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Margin = new MarginPadding { Bottom = 20 }, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Margin = new MarginPadding { Bottom = 20 }, - Children = new Drawable[] + new Container { - new Container + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding - { - Horizontal = 40, - Vertical = 20 - }, - Child = userListToolbar = new UserListToolbar - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - } + Horizontal = 40, + Vertical = 20 }, - new Container + Child = userListToolbar = new UserListToolbar { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + } + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + itemsPlaceholder = new Container { - itemsPlaceholder = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = 50 } - }, - loading = new LoadingLayer(itemsPlaceholder) - } + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = 50 } + }, + loading = new LoadingLayer(itemsPlaceholder) } } } } } } - }; + }); } [BackgroundDependencyLoader] @@ -152,14 +140,11 @@ namespace osu.Game.Overlays.Dashboard.Friends userListToolbar.SortCriteria.BindValueChanged(_ => recreatePanels()); } - public void Fetch() - { - if (!api.IsLoggedIn) - return; + protected override APIRequest> CreateRequest() => new GetFriendsRequest(); - request = new GetFriendsRequest(); - request.Success += response => Schedule(() => Users = response); - api.Queue(request); + protected override void OnSuccess(List response) + { + Users = response; } private void recreatePanels() @@ -258,9 +243,7 @@ namespace osu.Game.Overlays.Dashboard.Friends protected override void Dispose(bool isDisposing) { - request?.Cancel(); cancellationToken?.Cancel(); - base.Dispose(isDisposing); } } diff --git a/osu.Game/Overlays/OverlayView.cs b/osu.Game/Overlays/OverlayView.cs new file mode 100644 index 0000000000..f39c6bd1b9 --- /dev/null +++ b/osu.Game/Overlays/OverlayView.cs @@ -0,0 +1,71 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.API; + +namespace osu.Game.Overlays +{ + /// + /// Drawable which used to represent online content in . + /// + /// Response type + public abstract class OverlayView : Container, IOnlineComponent + where T : class + { + [Resolved] + protected IAPIProvider API { get; private set; } + + protected override Container Content => content; + + private readonly FillFlowContainer content; + + protected OverlayView() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + AddInternal(content = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + API.Register(this); + } + + private APIRequest request; + + protected abstract APIRequest CreateRequest(); + + protected abstract void OnSuccess(T response); + + public virtual void APIStateChanged(IAPIProvider api, APIState state) + { + switch (state) + { + case APIState.Online: + request = CreateRequest(); + request.Success += response => Schedule(() => OnSuccess(response)); + api.Queue(request); + break; + + default: + break; + } + } + + protected override void Dispose(bool isDisposing) + { + request?.Cancel(); + API?.Unregister(this); + base.Dispose(isDisposing); + } + } +} From 894598eb220e7cc05f3fab5df81a786973f804d5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 16 Apr 2020 12:05:51 +0300 Subject: [PATCH 358/655] Replace SocialOverlay with DashboardOverlay --- osu.Game.Tests/Visual/TestSceneOsuGame.cs | 2 +- osu.Game/OsuGame.cs | 8 +- .../Dashboard/DashboardOverlayHeader.cs | 4 +- osu.Game/Overlays/DashboardOverlay.cs | 94 ++++++++++++++++++- .../Overlays/Toolbar/ToolbarSocialButton.cs | 4 +- 5 files changed, 100 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/TestSceneOsuGame.cs index 492494ada3..8793d880e3 100644 --- a/osu.Game.Tests/Visual/TestSceneOsuGame.cs +++ b/osu.Game.Tests/Visual/TestSceneOsuGame.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual typeof(OnScreenDisplay), typeof(NotificationOverlay), typeof(DirectOverlay), - typeof(SocialOverlay), + typeof(DashboardOverlay), typeof(ChannelManager), typeof(ChatOverlay), typeof(SettingsOverlay), diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5e93d760e3..c861b84835 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -67,7 +67,7 @@ namespace osu.Game private DirectOverlay direct; - private SocialOverlay social; + private DashboardOverlay dashboard; private UserProfileOverlay userProfile; @@ -611,7 +611,7 @@ namespace osu.Game //overlay elements loadComponentSingleFile(direct = new DirectOverlay(), overlayContent.Add, true); - loadComponentSingleFile(social = new SocialOverlay(), overlayContent.Add, true); + loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true); var rankingsOverlay = loadComponentSingleFile(new RankingsOverlay(), overlayContent.Add, true); loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true); loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true); @@ -670,7 +670,7 @@ namespace osu.Game } // ensure only one of these overlays are open at once. - var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, social, direct, changelogOverlay, rankingsOverlay }; + var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, dashboard, direct, changelogOverlay, rankingsOverlay }; foreach (var overlay in singleDisplayOverlays) { @@ -842,7 +842,7 @@ namespace osu.Game return true; case GlobalAction.ToggleSocial: - social.ToggleVisibility(); + dashboard.ToggleVisibility(); return true; case GlobalAction.ResetInputSettings: diff --git a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs index 1c52b033a5..9ee679a866 100644 --- a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs +++ b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs @@ -3,7 +3,7 @@ namespace osu.Game.Overlays.Dashboard { - public class DashboardOverlayHeader : TabControlOverlayHeader + public class DashboardOverlayHeader : TabControlOverlayHeader { protected override OverlayTitle CreateTitle() => new DashboardTitle(); @@ -17,7 +17,7 @@ namespace osu.Game.Overlays.Dashboard } } - public enum HomeOverlayTabs + public enum DashboardOverlayTabs { Friends } diff --git a/osu.Game/Overlays/DashboardOverlay.cs b/osu.Game/Overlays/DashboardOverlay.cs index a1a7c9889a..1e0fbc90b4 100644 --- a/osu.Game/Overlays/DashboardOverlay.cs +++ b/osu.Game/Overlays/DashboardOverlay.cs @@ -1,19 +1,29 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Threading; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; using osu.Game.Overlays.Dashboard; +using osu.Game.Overlays.Dashboard.Friends; namespace osu.Game.Overlays { public class DashboardOverlay : FullscreenOverlay { + private CancellationTokenSource cancellationToken; + private readonly Box background; private readonly Container content; + private readonly DashboardOverlayHeader header; + private readonly LoadingLayer loading; + private readonly OverlayScrollContainer scrollFlow; public DashboardOverlay() : base(OverlayColourScheme.Purple) @@ -24,7 +34,7 @@ namespace osu.Game.Overlays { RelativeSizeAxes = Axes.Both }, - new OverlayScrollContainer + scrollFlow = new OverlayScrollContainer { RelativeSizeAxes = Axes.Both, ScrollbarVisible = false, @@ -35,7 +45,7 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - new DashboardOverlayHeader + header = new DashboardOverlayHeader { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -49,7 +59,7 @@ namespace osu.Game.Overlays } } }, - new LoadingLayer(content), + loading = new LoadingLayer(content), }; } @@ -58,5 +68,83 @@ namespace osu.Game.Overlays { background.Colour = ColourProvider.Background5; } + + protected override void LoadComplete() + { + base.LoadComplete(); + header.Current.BindValueChanged(onTabChanged); + } + + private bool displayUpdateRequired = true; + + protected override void PopIn() + { + base.PopIn(); + + // We don't want to create new display on every call, only when exiting from fully closed state. + if (displayUpdateRequired) + { + header.Current.TriggerChange(); + displayUpdateRequired = false; + } + } + + protected override void PopOutComplete() + { + base.PopOutComplete(); + loadDisplay(Empty()); + displayUpdateRequired = true; + } + + private void loadDisplay(Drawable display) + { + scrollFlow.ScrollToStart(); + + LoadComponentAsync(display, loaded => + { + loading.Hide(); + content.Child = loaded; + }, (cancellationToken = new CancellationTokenSource()).Token); + } + + private void onTabChanged(ValueChangedEvent tab) + { + cancellationToken?.Cancel(); + + loading.Show(); + + switch (tab.NewValue) + { + case DashboardOverlayTabs.Friends: + loadDisplay(new FriendDisplay()); + break; + + default: + throw new NotImplementedException($"Display for {tab.NewValue} tab is not implemented"); + } + } + + public override void APIStateChanged(IAPIProvider api, APIState state) + { + switch (state) + { + case APIState.Online: + // Will force to create a display based on visibility state + displayUpdateRequired = true; + State.TriggerChange(); + return; + + default: + content.Clear(); + loading.Show(); + return; + } + } + + protected override void Dispose(bool isDisposing) + { + cancellationToken?.Cancel(); + base.Dispose(isDisposing); + } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs index 5e353d3319..f6646eb81d 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs @@ -14,9 +14,9 @@ namespace osu.Game.Overlays.Toolbar } [BackgroundDependencyLoader(true)] - private void load(SocialOverlay chat) + private void load(DashboardOverlay dashboard) { - StateContainer = chat; + StateContainer = dashboard; } } } From eb86be0a6da6d76dfef8526eff26ddb584d8bd7b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 16 Apr 2020 12:07:38 +0300 Subject: [PATCH 359/655] Adjust header content margin --- osu.Game/Overlays/OverlayHeader.cs | 4 +++- osu.Game/Overlays/TabControlOverlayHeader.cs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/OverlayHeader.cs b/osu.Game/Overlays/OverlayHeader.cs index 4ac0f697c3..dbc934bde9 100644 --- a/osu.Game/Overlays/OverlayHeader.cs +++ b/osu.Game/Overlays/OverlayHeader.cs @@ -12,6 +12,8 @@ namespace osu.Game.Overlays { public abstract class OverlayHeader : Container { + public const int CONTENT_X_MARGIN = 50; + private readonly Box titleBackground; protected readonly FillFlowContainer HeaderInfo; @@ -54,7 +56,7 @@ namespace osu.Game.Overlays AutoSizeAxes = Axes.Y, Padding = new MarginPadding { - Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, + Horizontal = CONTENT_X_MARGIN, }, Children = new[] { diff --git a/osu.Game/Overlays/TabControlOverlayHeader.cs b/osu.Game/Overlays/TabControlOverlayHeader.cs index ab1a6aff78..e8e000f441 100644 --- a/osu.Game/Overlays/TabControlOverlayHeader.cs +++ b/osu.Game/Overlays/TabControlOverlayHeader.cs @@ -44,7 +44,7 @@ namespace osu.Game.Overlays }, TabControl = CreateTabControl().With(control => { - control.Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }; + control.Margin = new MarginPadding { Left = CONTENT_X_MARGIN }; control.Current = Current; }) } From 87f52b82331dc1f6ba4b198d96cb8b768a152c19 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 16 Apr 2020 12:09:44 +0300 Subject: [PATCH 360/655] Remove redundant switch section --- osu.Game/Overlays/OverlayView.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Overlays/OverlayView.cs b/osu.Game/Overlays/OverlayView.cs index f39c6bd1b9..e3a07fc2de 100644 --- a/osu.Game/Overlays/OverlayView.cs +++ b/osu.Game/Overlays/OverlayView.cs @@ -55,9 +55,6 @@ namespace osu.Game.Overlays request.Success += response => Schedule(() => OnSuccess(response)); api.Queue(request); break; - - default: - break; } } From d62094cd4ba1e9d20d60edc8e326198c615f8732 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Apr 2020 18:10:35 +0900 Subject: [PATCH 361/655] Fix carousel not correctly updating when selection changes to a new beatmap from a child screen --- 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 a8225ba1ec..d8178bbbbb 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -217,6 +217,9 @@ namespace osu.Game.Screens.Select /// True if a selection was made, False if it wasn't. public bool SelectBeatmap(BeatmapInfo beatmap, bool bypassFilters = true) { + // ensure that any pending events from BeatmapManager have been run before attempting a selection. + Scheduler.Update(); + if (beatmap?.Hidden != false) return false; From d7ea5432a8b1eda9d85e29de2261b0d4cfa3ccbc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 16 Apr 2020 18:15:52 +0900 Subject: [PATCH 362/655] Fix incorrect combo calculation --- .../Difficulty/CatchPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index bc52f0b812..e7ce680365 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty // Combo scaling if (Attributes.MaxCombo > 0) - value *= Math.Min(Math.Pow(Attributes.MaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0); + value *= Math.Min(Math.Pow(Score.MaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0); float approachRate = (float)Attributes.ApproachRate; float approachRateFactor = 1.0f; From ae210d567d794f5b16587b8684bb9bdb9edfeeb2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 16 Apr 2020 18:16:08 +0900 Subject: [PATCH 363/655] Add temporary solution for tick hit/miss count --- osu.Game/Rulesets/Scoring/HitResult.cs | 5 +++++ .../Scoring/Legacy/ScoreInfoExtensions.cs | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 7ba88d3df8..0c895bd086 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -43,5 +43,10 @@ namespace osu.Game.Rulesets.Scoring /// [Description(@"Perfect")] Perfect, + + SmallTickHit, + SmallTickMiss, + LargeTickHit, + LargeTickMiss, } } diff --git a/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs b/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs index 66b1acf591..9745d1abef 100644 --- a/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs +++ b/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs @@ -66,6 +66,9 @@ namespace osu.Game.Scoring.Legacy { case 3: return scoreInfo.Statistics[HitResult.Good]; + + case 2: + return scoreInfo.Statistics[HitResult.SmallTickMiss]; } return null; @@ -78,6 +81,10 @@ namespace osu.Game.Scoring.Legacy case 3: scoreInfo.Statistics[HitResult.Good] = value; break; + + case 2: + scoreInfo.Statistics[HitResult.SmallTickMiss] = value; + break; } } @@ -91,6 +98,9 @@ namespace osu.Game.Scoring.Legacy case 3: return scoreInfo.Statistics[HitResult.Ok]; + + case 2: + return scoreInfo.Statistics[HitResult.LargeTickHit]; } return null; @@ -108,6 +118,10 @@ namespace osu.Game.Scoring.Legacy case 3: scoreInfo.Statistics[HitResult.Ok] = value; break; + + case 2: + scoreInfo.Statistics[HitResult.LargeTickHit] = value; + break; } } @@ -118,6 +132,9 @@ namespace osu.Game.Scoring.Legacy case 0: case 3: return scoreInfo.Statistics[HitResult.Meh]; + + case 2: + return scoreInfo.Statistics[HitResult.SmallTickHit]; } return null; @@ -131,6 +148,10 @@ namespace osu.Game.Scoring.Legacy case 3: scoreInfo.Statistics[HitResult.Meh] = value; break; + + case 2: + scoreInfo.Statistics[HitResult.SmallTickHit] = value; + break; } } From c5a343d3a07daf31ad95a036850a05e7007f2a41 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 16 Apr 2020 14:10:39 +0300 Subject: [PATCH 364/655] Fix overlay accepting state changes while hidden --- osu.Game/Overlays/DashboardOverlay.cs | 28 +++++++++++++-------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/DashboardOverlay.cs b/osu.Game/Overlays/DashboardOverlay.cs index 1e0fbc90b4..86c0f3bd83 100644 --- a/osu.Game/Overlays/DashboardOverlay.cs +++ b/osu.Game/Overlays/DashboardOverlay.cs @@ -81,7 +81,7 @@ namespace osu.Game.Overlays { base.PopIn(); - // We don't want to create new display on every call, only when exiting from fully closed state. + // We don't want to create a new display on every call, only when exiting from fully closed state. if (displayUpdateRequired) { header.Current.TriggerChange(); @@ -102,7 +102,9 @@ namespace osu.Game.Overlays LoadComponentAsync(display, loaded => { - loading.Hide(); + if (API.IsLoggedIn) + loading.Hide(); + content.Child = loaded; }, (cancellationToken = new CancellationTokenSource()).Token); } @@ -110,9 +112,14 @@ namespace osu.Game.Overlays private void onTabChanged(ValueChangedEvent tab) { cancellationToken?.Cancel(); - loading.Show(); + if (!API.IsLoggedIn) + { + loadDisplay(Empty()); + return; + } + switch (tab.NewValue) { case DashboardOverlayTabs.Friends: @@ -126,19 +133,10 @@ namespace osu.Game.Overlays public override void APIStateChanged(IAPIProvider api, APIState state) { - switch (state) - { - case APIState.Online: - // Will force to create a display based on visibility state - displayUpdateRequired = true; - State.TriggerChange(); - return; + if (State.Value == Visibility.Hidden) + return; - default: - content.Clear(); - loading.Show(); - return; - } + header.Current.TriggerChange(); } protected override void Dispose(bool isDisposing) From 3daacbc2d202b3d42ac84e7242e571a045d8fa09 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 17 Apr 2020 13:34:20 +0900 Subject: [PATCH 365/655] Initial inefficient refactor of hitobject enumeration --- osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs | 76 ++++++++------------ osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- 2 files changed, 29 insertions(+), 49 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs index dfca2aff7b..171ce6fe61 100644 --- a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs @@ -1,9 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.UI; @@ -37,12 +37,9 @@ namespace osu.Game.Rulesets.Osu.UI DrawableHitObject blockingObject = null; // Find the last hitobject which blocks future hits. - foreach (var obj in hitObjectContainer.AliveObjects) + foreach (var obj in enumerateHitObjectsUpTo(hitObject)) { - if (obj == hitObject) - break; - - if (drawableCanBlockFutureHits(obj)) + if (hitObjectCanBlockFutureHits(obj)) blockingObject = obj; } @@ -64,64 +61,47 @@ namespace osu.Game.Rulesets.Osu.UI /// Handles a being hit to potentially miss all earlier s. /// /// The that was hit. - public void HandleHit(HitObject hitObject) + public void HandleHit(DrawableHitObject hitObject) { // Hitobjects which themselves don't block future hitobjects don't cause misses (e.g. slider ticks, spinners). if (!hitObjectCanBlockFutureHits(hitObject)) return; - double maximumTime = hitObject.StartTime; - - // Iterate through and apply miss results to all top-level and nested hitobjects which block future hits. - foreach (var obj in hitObjectContainer.AliveObjects) + foreach (var obj in enumerateHitObjectsUpTo(hitObject)) { - if (obj.Judged || obj.HitObject.StartTime >= maximumTime) + if (obj.Judged) continue; - if (hitObjectCanBlockFutureHits(obj.HitObject)) - applyMiss(obj); - - foreach (var nested in obj.NestedHitObjects) - { - if (nested.Judged || nested.HitObject.StartTime >= maximumTime) - continue; - - if (hitObjectCanBlockFutureHits(nested.HitObject)) - applyMiss(nested); - } + if (hitObjectCanBlockFutureHits(obj)) + ((DrawableOsuHitObject)obj).MissForcefully(); } - - static void applyMiss(DrawableHitObject obj) => ((DrawableOsuHitObject)obj).MissForcefully(); - } - - /// - /// Whether a blocks hits on future s until its start time is reached. - /// - /// - /// This will ONLY match on top-most s. - /// - /// The to test. - private static bool drawableCanBlockFutureHits(DrawableHitObject hitObject) - { - // Special considerations for slider tails aren't required since only top-most drawable hitobjects are being iterated over. - return hitObject is DrawableHitCircle || hitObject is DrawableSlider; } /// /// Whether a blocks hits on future s until its start time is reached. /// - /// - /// This is more rigorous and may not match on top-most s as does. - /// /// The to test. - private static bool hitObjectCanBlockFutureHits(HitObject hitObject) - { - // Unlike the above we will receive slider tails, but they do not block future hits. - if (hitObject is SliderTailCircle) - return false; + private static bool hitObjectCanBlockFutureHits(DrawableHitObject hitObject) + => hitObject is DrawableHitCircle; - // All other hitcircles continue to block future hits. - return hitObject is HitCircle; + // Todo: Inefficient + private IEnumerable enumerateHitObjectsUpTo(DrawableHitObject hitObject) + { + return enumerate(hitObjectContainer.AliveObjects); + + IEnumerable enumerate(IEnumerable list) + { + foreach (var obj in list) + { + if (obj.HitObject.StartTime >= hitObject.HitObject.StartTime) + yield break; + + yield return obj; + + foreach (var nested in enumerate(obj.NestedHitObjects)) + yield return nested; + } + } } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 2f222f59b4..4b1a2ce43c 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.UI private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) { // Hitobjects that block future hits should miss previous hitobjects if they're hit out-of-order. - hitPolicy.HandleHit(result.HitObject); + hitPolicy.HandleHit(judgedObject); if (!judgedObject.DisplayResult || !DisplayJudgements.Value) return; From 62f77a05befb156ac6cda6411f2dda85ffcc8b44 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 17 Apr 2020 14:00:00 +0900 Subject: [PATCH 366/655] Optimise by removing state machine --- osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs | 115 +++++++++++++++---- 1 file changed, 95 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs index 171ce6fe61..b55e04ec4c 100644 --- a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs @@ -1,7 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -36,11 +38,14 @@ namespace osu.Game.Rulesets.Osu.UI { DrawableHitObject blockingObject = null; - // Find the last hitobject which blocks future hits. - foreach (var obj in enumerateHitObjectsUpTo(hitObject)) + var enumerator = new HitObjectEnumerator(hitObjectContainer, hitObject.HitObject.StartTime); + + while (enumerator.MoveNext()) { - if (hitObjectCanBlockFutureHits(obj)) - blockingObject = obj; + Debug.Assert(enumerator.Current != null); + + if (hitObjectCanBlockFutureHits(enumerator.Current)) + blockingObject = enumerator.Current; } // If there is no previous hitobject, allow the hit. @@ -67,13 +72,17 @@ namespace osu.Game.Rulesets.Osu.UI if (!hitObjectCanBlockFutureHits(hitObject)) return; - foreach (var obj in enumerateHitObjectsUpTo(hitObject)) + var enumerator = new HitObjectEnumerator(hitObjectContainer, hitObject.HitObject.StartTime); + + while (enumerator.MoveNext()) { - if (obj.Judged) + Debug.Assert(enumerator.Current != null); + + if (enumerator.Current.Judged) continue; - if (hitObjectCanBlockFutureHits(obj)) - ((DrawableOsuHitObject)obj).MissForcefully(); + if (hitObjectCanBlockFutureHits(enumerator.Current)) + ((DrawableOsuHitObject)enumerator.Current).MissForcefully(); } } @@ -84,23 +93,89 @@ namespace osu.Game.Rulesets.Osu.UI private static bool hitObjectCanBlockFutureHits(DrawableHitObject hitObject) => hitObject is DrawableHitCircle; - // Todo: Inefficient - private IEnumerable enumerateHitObjectsUpTo(DrawableHitObject hitObject) + private struct HitObjectEnumerator : IEnumerator { - return enumerate(hitObjectContainer.AliveObjects); + private readonly IEnumerator hitObjectEnumerator; + private readonly double targetTime; - IEnumerable enumerate(IEnumerable list) + private DrawableHitObject currentTopLevel; + private int currentNestedIndex; + + public HitObjectEnumerator(HitObjectContainer hitObjectContainer, double targetTime) { - foreach (var obj in list) - { - if (obj.HitObject.StartTime >= hitObject.HitObject.StartTime) - yield break; + hitObjectEnumerator = hitObjectContainer.AliveObjects.GetEnumerator(); + this.targetTime = targetTime; - yield return obj; + currentTopLevel = null; + currentNestedIndex = -1; + Current = null; + } - foreach (var nested in enumerate(obj.NestedHitObjects)) - yield return nested; - } + /// + /// Attempts to move to the next top-level or nested hitobject. + /// Stops when no such hitobject is found or until the hitobject start time reaches . + /// + /// Whether a new hitobject was moved to. + public bool MoveNext() + { + // If we don't already have a top-level hitobject, try to get one. + if (currentTopLevel == null) + return moveNextTopLevel(); + + // If we have a top-level hitobject, try to move to the next nested hitobject or otherwise move to the next top-level hitobject. + if (!moveNextNested()) + return moveNextTopLevel(); + + // Guaranteed by moveNextNested() to have a hitobject. + return true; + } + + /// + /// Attempts to move to the next top-level hitobject. + /// + /// Whether a new top-level hitobject was found. + private bool moveNextTopLevel() + { + currentNestedIndex = -1; + + hitObjectEnumerator.MoveNext(); + currentTopLevel = hitObjectEnumerator.Current; + + Current = currentTopLevel; + + return Current?.HitObject.StartTime < targetTime; + } + + /// + /// Attempts to move to the next nested hitobject in the current top-level hitobject. + /// + /// Whether a new nested hitobject was moved to. + private bool moveNextNested() + { + currentNestedIndex++; + if (currentNestedIndex >= currentTopLevel.NestedHitObjects.Count) + return false; + + Current = currentTopLevel.NestedHitObjects[currentNestedIndex]; + Debug.Assert(Current != null); + + return Current?.HitObject.StartTime < targetTime; + } + + public void Reset() + { + hitObjectEnumerator.Reset(); + currentTopLevel = null; + currentNestedIndex = -1; + Current = null; + } + + public DrawableHitObject Current { get; set; } + + object IEnumerator.Current => Current; + + public void Dispose() + { } } } From ee5301b887a78a3bd0cabab17c306857133da794 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 17 Apr 2020 14:12:38 +0900 Subject: [PATCH 367/655] Fix head/tail circles not getting correct hit windows --- osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs index d6858f831e..3df51be600 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs @@ -371,6 +371,9 @@ namespace osu.Game.Rulesets.Osu.Tests { HeadCircle.HitWindows = new TestHitWindows(); TailCircle.HitWindows = new TestHitWindows(); + + HeadCircle.HitWindows.SetDifficulty(0); + TailCircle.HitWindows.SetDifficulty(0); }; } } From 08df9d49e52a968691b77a76fe360c3111eb3436 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 17 Apr 2020 14:12:43 +0900 Subject: [PATCH 368/655] Add failing test --- .../TestSceneOutOfOrderHits.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs index 3df51be600..40ee53e8f2 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs @@ -296,6 +296,44 @@ namespace osu.Game.Rulesets.Osu.Tests addJudgementAssert(hitObjects[1], HitResult.Great); } + [Test] + public void TestHitSliderHeadBeforeHitCircle() + { + const double time_circle = 1000; + const double time_slider = 1200; + Vector2 positionCircle = Vector2.Zero; + Vector2 positionSlider = new Vector2(80); + + var hitObjects = new List + { + new TestHitCircle + { + StartTime = time_circle, + Position = positionCircle + }, + new TestSlider + { + StartTime = time_slider, + Position = positionSlider, + Path = new SliderPath(PathType.Linear, new[] + { + Vector2.Zero, + new Vector2(25, 0), + }) + } + }; + + performTest(hitObjects, new List + { + new OsuReplayFrame { Time = time_circle - 100, Position = positionSlider, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_circle, Position = positionCircle, Actions = { OsuAction.RightButton } }, + new OsuReplayFrame { Time = time_slider, Position = positionSlider, Actions = { OsuAction.LeftButton } }, + }); + + addJudgementAssert(hitObjects[0], HitResult.Great); + addJudgementAssert(hitObjects[1], HitResult.Great); + } + private void addJudgementAssert(OsuHitObject hitObject, HitResult result) { AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}", From a4a782381797f927bc80f108cbbf94f410faef99 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 17 Apr 2020 14:22:03 +0900 Subject: [PATCH 369/655] Add fail-safe to ensure hittability after a hit --- osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs index b55e04ec4c..31edefea83 100644 --- a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; @@ -72,6 +73,9 @@ namespace osu.Game.Rulesets.Osu.UI if (!hitObjectCanBlockFutureHits(hitObject)) return; + if (!IsHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset)) + throw new InvalidOperationException($"A {hitObject} was hit before it become hittable!"); + var enumerator = new HitObjectEnumerator(hitObjectContainer, hitObject.HitObject.StartTime); while (enumerator.MoveNext()) From 4e4fe5cc904107ac647ffaa57d5ac17361ee073e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 17 Apr 2020 14:33:29 +0900 Subject: [PATCH 370/655] Fix slider heads not being blocked when hit out of order --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 522217a916..72502c02cd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -125,7 +125,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return new DrawableSliderTail(slider, tail); case SliderHeadCircle head: - return new DrawableSliderHead(slider, head) { OnShake = Shake }; + return new DrawableSliderHead(slider, head) + { + OnShake = Shake, + CheckHittable = (d, t) => CheckHittable?.Invoke(d, t) ?? true + }; case SliderTick tick: return new DrawableSliderTick(tick) { Position = tick.Position - slider.Position }; From 2dee5e03e30f158880a09aaa04ded47879d6f74d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 17 Apr 2020 14:40:29 +0900 Subject: [PATCH 371/655] Dispose enumerators for safety --- osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs | 31 +++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs index 31edefea83..4bc7da4794 100644 --- a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs @@ -39,14 +39,15 @@ namespace osu.Game.Rulesets.Osu.UI { DrawableHitObject blockingObject = null; - var enumerator = new HitObjectEnumerator(hitObjectContainer, hitObject.HitObject.StartTime); - - while (enumerator.MoveNext()) + using (var enumerator = new HitObjectEnumerator(hitObjectContainer, hitObject.HitObject.StartTime)) { - Debug.Assert(enumerator.Current != null); + while (enumerator.MoveNext()) + { + Debug.Assert(enumerator.Current != null); - if (hitObjectCanBlockFutureHits(enumerator.Current)) - blockingObject = enumerator.Current; + if (hitObjectCanBlockFutureHits(enumerator.Current)) + blockingObject = enumerator.Current; + } } // If there is no previous hitobject, allow the hit. @@ -76,17 +77,18 @@ namespace osu.Game.Rulesets.Osu.UI if (!IsHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset)) throw new InvalidOperationException($"A {hitObject} was hit before it become hittable!"); - var enumerator = new HitObjectEnumerator(hitObjectContainer, hitObject.HitObject.StartTime); - - while (enumerator.MoveNext()) + using (var enumerator = new HitObjectEnumerator(hitObjectContainer, hitObject.HitObject.StartTime)) { - Debug.Assert(enumerator.Current != null); + while (enumerator.MoveNext()) + { + Debug.Assert(enumerator.Current != null); - if (enumerator.Current.Judged) - continue; + if (enumerator.Current.Judged) + continue; - if (hitObjectCanBlockFutureHits(enumerator.Current)) - ((DrawableOsuHitObject)enumerator.Current).MissForcefully(); + if (hitObjectCanBlockFutureHits(enumerator.Current)) + ((DrawableOsuHitObject)enumerator.Current).MissForcefully(); + } } } @@ -180,6 +182,7 @@ namespace osu.Game.Rulesets.Osu.UI public void Dispose() { + hitObjectEnumerator?.Dispose(); } } } From bbcbd7e3fbc790e91415bc96ffcfb93c63bcffc6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 17 Apr 2020 14:48:12 +0900 Subject: [PATCH 372/655] Simplify by removing custom enumerator --- osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs | 118 +++---------------- 1 file changed, 19 insertions(+), 99 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs index 4bc7da4794..cd9838e7bf 100644 --- a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs @@ -2,9 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections; using System.Collections.Generic; -using System.Diagnostics; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -39,15 +37,10 @@ namespace osu.Game.Rulesets.Osu.UI { DrawableHitObject blockingObject = null; - using (var enumerator = new HitObjectEnumerator(hitObjectContainer, hitObject.HitObject.StartTime)) + foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime)) { - while (enumerator.MoveNext()) - { - Debug.Assert(enumerator.Current != null); - - if (hitObjectCanBlockFutureHits(enumerator.Current)) - blockingObject = enumerator.Current; - } + if (hitObjectCanBlockFutureHits(obj)) + blockingObject = obj; } // If there is no previous hitobject, allow the hit. @@ -77,18 +70,13 @@ namespace osu.Game.Rulesets.Osu.UI if (!IsHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset)) throw new InvalidOperationException($"A {hitObject} was hit before it become hittable!"); - using (var enumerator = new HitObjectEnumerator(hitObjectContainer, hitObject.HitObject.StartTime)) + foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime)) { - while (enumerator.MoveNext()) - { - Debug.Assert(enumerator.Current != null); + if (obj.Judged) + continue; - if (enumerator.Current.Judged) - continue; - - if (hitObjectCanBlockFutureHits(enumerator.Current)) - ((DrawableOsuHitObject)enumerator.Current).MissForcefully(); - } + if (hitObjectCanBlockFutureHits(obj)) + ((DrawableOsuHitObject)obj).MissForcefully(); } } @@ -99,90 +87,22 @@ namespace osu.Game.Rulesets.Osu.UI private static bool hitObjectCanBlockFutureHits(DrawableHitObject hitObject) => hitObject is DrawableHitCircle; - private struct HitObjectEnumerator : IEnumerator + private IEnumerable enumerateHitObjectsUpTo(double targetTime) { - private readonly IEnumerator hitObjectEnumerator; - private readonly double targetTime; - - private DrawableHitObject currentTopLevel; - private int currentNestedIndex; - - public HitObjectEnumerator(HitObjectContainer hitObjectContainer, double targetTime) + foreach (var obj in hitObjectContainer.AliveObjects) { - hitObjectEnumerator = hitObjectContainer.AliveObjects.GetEnumerator(); - this.targetTime = targetTime; + if (obj.HitObject.StartTime >= targetTime) + yield break; - currentTopLevel = null; - currentNestedIndex = -1; - Current = null; - } + yield return obj; - /// - /// Attempts to move to the next top-level or nested hitobject. - /// Stops when no such hitobject is found or until the hitobject start time reaches . - /// - /// Whether a new hitobject was moved to. - public bool MoveNext() - { - // If we don't already have a top-level hitobject, try to get one. - if (currentTopLevel == null) - return moveNextTopLevel(); + for (int i = 0; i < obj.NestedHitObjects.Count; i++) + { + if (obj.NestedHitObjects[i].HitObject.StartTime >= targetTime) + break; - // If we have a top-level hitobject, try to move to the next nested hitobject or otherwise move to the next top-level hitobject. - if (!moveNextNested()) - return moveNextTopLevel(); - - // Guaranteed by moveNextNested() to have a hitobject. - return true; - } - - /// - /// Attempts to move to the next top-level hitobject. - /// - /// Whether a new top-level hitobject was found. - private bool moveNextTopLevel() - { - currentNestedIndex = -1; - - hitObjectEnumerator.MoveNext(); - currentTopLevel = hitObjectEnumerator.Current; - - Current = currentTopLevel; - - return Current?.HitObject.StartTime < targetTime; - } - - /// - /// Attempts to move to the next nested hitobject in the current top-level hitobject. - /// - /// Whether a new nested hitobject was moved to. - private bool moveNextNested() - { - currentNestedIndex++; - if (currentNestedIndex >= currentTopLevel.NestedHitObjects.Count) - return false; - - Current = currentTopLevel.NestedHitObjects[currentNestedIndex]; - Debug.Assert(Current != null); - - return Current?.HitObject.StartTime < targetTime; - } - - public void Reset() - { - hitObjectEnumerator.Reset(); - currentTopLevel = null; - currentNestedIndex = -1; - Current = null; - } - - public DrawableHitObject Current { get; set; } - - object IEnumerator.Current => Current; - - public void Dispose() - { - hitObjectEnumerator?.Dispose(); + yield return obj.NestedHitObjects[i]; + } } } } From 69fb984e71fae2c371b19de763cbf8a80ff861d2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 17 Apr 2020 17:04:09 +0900 Subject: [PATCH 373/655] Remove EquivalentTo() and Equals() --- osu.Game/Beatmaps/ControlPoints/ControlPoint.cs | 11 +---------- .../Beatmaps/ControlPoints/DifficultyControlPoint.cs | 7 +++---- osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs | 10 +++++----- osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs | 9 ++++----- osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 9 ++------- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 7 ++++--- osu.Game/Graphics/Containers/BeatSyncedContainer.cs | 2 +- 7 files changed, 20 insertions(+), 35 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index 9599ad184b..f9bb3877d3 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -5,7 +5,7 @@ using System; namespace osu.Game.Beatmaps.ControlPoints { - public abstract class ControlPoint : IComparable, IEquatable + public abstract class ControlPoint : IComparable { /// /// The time at which the control point takes effect. @@ -18,13 +18,6 @@ namespace osu.Game.Beatmaps.ControlPoints public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time); - /// - /// Whether this control point is equivalent to another, ignoring time. - /// - /// Another control point to compare with. - /// Whether equivalent. - public abstract bool EquivalentTo(ControlPoint other); - /// /// Whether this control point results in a meaningful change when placed after another. /// @@ -32,7 +25,5 @@ namespace osu.Game.Beatmaps.ControlPoints /// The time this control point will be placed at if it is added. /// Whether redundant. public abstract bool IsRedundant(ControlPoint existing, double time); - - public bool Equals(ControlPoint other) => Time == other?.Time && EquivalentTo(other); } } diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index dc856b0a0a..42140462cb 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -27,9 +27,8 @@ namespace osu.Game.Beatmaps.ControlPoints set => SpeedMultiplierBindable.Value = value; } - public override bool EquivalentTo(ControlPoint other) => - other is DifficultyControlPoint otherTyped && otherTyped.SpeedMultiplier.Equals(SpeedMultiplier); - - public override bool IsRedundant(ControlPoint existing, double time) => EquivalentTo(existing); + public override bool IsRedundant(ControlPoint existing, double time) + => existing is DifficultyControlPoint existingDifficulty + && SpeedMultiplier == existingDifficulty.SpeedMultiplier; } } diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index d050f44ba4..f7a232c394 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -35,10 +35,10 @@ namespace osu.Game.Beatmaps.ControlPoints set => KiaiModeBindable.Value = value; } - public override bool EquivalentTo(ControlPoint other) => - other is EffectControlPoint otherTyped && - KiaiMode == otherTyped.KiaiMode && OmitFirstBarLine == otherTyped.OmitFirstBarLine; - - public override bool IsRedundant(ControlPoint existing, double time) => !OmitFirstBarLine && EquivalentTo(existing); + public override bool IsRedundant(ControlPoint existing, double time) + => !OmitFirstBarLine + && existing is EffectControlPoint existingEffect + && KiaiMode == existingEffect.KiaiMode + && OmitFirstBarLine == existingEffect.OmitFirstBarLine; } } diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index 38edbe70da..0fced16b4d 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -68,10 +68,9 @@ namespace osu.Game.Beatmaps.ControlPoints return newSampleInfo; } - public override bool EquivalentTo(ControlPoint other) => - other is SampleControlPoint otherTyped && - SampleBank == otherTyped.SampleBank && SampleVolume == otherTyped.SampleVolume; - - public override bool IsRedundant(ControlPoint existing, double time) => EquivalentTo(existing); + public override bool IsRedundant(ControlPoint existing, double time) + => existing is SampleControlPoint existingSample + && SampleBank == existingSample.SampleBank + && SampleVolume == existingSample.SampleVolume; } } diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 316c603ece..27f4662d49 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -48,12 +48,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// public double BPM => 60000 / BeatLength; - public override bool EquivalentTo(ControlPoint other) => - other is TimingControlPoint otherTyped - && TimeSignature == otherTyped.TimeSignature && BeatLength.Equals(otherTyped.BeatLength); - - public override bool IsRedundant(ControlPoint existing, double time) => - EquivalentTo(existing) - && existing.Time == time; + // Timing points are never redundant as they can change the time signature. + public override bool IsRedundant(ControlPoint existing, double time) => false; } } diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 561707f9ef..5fa1da111d 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -174,9 +174,10 @@ namespace osu.Game.Beatmaps.Formats return baseInfo; } - public override bool EquivalentTo(ControlPoint other) => - base.EquivalentTo(other) && other is LegacySampleControlPoint otherTyped && - CustomSampleBank == otherTyped.CustomSampleBank; + public override bool IsRedundant(ControlPoint existing, double time) + => base.IsRedundant(existing, time) + && existing is LegacySampleControlPoint existingSample + && CustomSampleBank == existingSample.CustomSampleBank; } } } diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index f36079682e..5a613d1a54 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -103,7 +103,7 @@ namespace osu.Game.Graphics.Containers TimeSinceLastBeat = beatLength - TimeUntilNextBeat; - if (timingPoint.Equals(lastTimingPoint) && beatIndex == lastBeat) + if (timingPoint == lastTimingPoint && beatIndex == lastBeat) return; using (BeginDelayedSequence(-TimeSinceLastBeat, true)) From 9aac98664ce9379938cea94e62dc3bb31df13a26 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 17 Apr 2020 17:06:12 +0900 Subject: [PATCH 374/655] Remove unnecessary time property --- osu.Game/Beatmaps/ControlPoints/ControlPoint.cs | 7 +++---- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 2 +- osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs | 2 +- osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs | 2 +- osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs | 2 +- osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 4 ++-- 7 files changed, 10 insertions(+), 11 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index f9bb3877d3..a1822a1163 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -19,11 +19,10 @@ namespace osu.Game.Beatmaps.ControlPoints public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time); /// - /// Whether this control point results in a meaningful change when placed after another. + /// Determines whether this results in a meaningful change when placed alongside another. /// /// An existing control point to compare with. - /// The time this control point will be placed at if it is added. - /// Whether redundant. - public abstract bool IsRedundant(ControlPoint existing, double time); + /// Whether this is redundant when placed alongside . + public abstract bool IsRedundant(ControlPoint existing); } } diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 37a3dbf592..8e4079f776 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -247,7 +247,7 @@ namespace osu.Game.Beatmaps.ControlPoints break; } - return newPoint.IsRedundant(existing, time); + return newPoint.IsRedundant(existing); } private void groupItemAdded(ControlPoint controlPoint) diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 42140462cb..2448b2b25c 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -27,7 +27,7 @@ namespace osu.Game.Beatmaps.ControlPoints set => SpeedMultiplierBindable.Value = value; } - public override bool IsRedundant(ControlPoint existing, double time) + public override bool IsRedundant(ControlPoint existing) => existing is DifficultyControlPoint existingDifficulty && SpeedMultiplier == existingDifficulty.SpeedMultiplier; } diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index f7a232c394..9b69147468 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -35,7 +35,7 @@ namespace osu.Game.Beatmaps.ControlPoints set => KiaiModeBindable.Value = value; } - public override bool IsRedundant(ControlPoint existing, double time) + public override bool IsRedundant(ControlPoint existing) => !OmitFirstBarLine && existing is EffectControlPoint existingEffect && KiaiMode == existingEffect.KiaiMode diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index 0fced16b4d..61851a00d7 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -68,7 +68,7 @@ namespace osu.Game.Beatmaps.ControlPoints return newSampleInfo; } - public override bool IsRedundant(ControlPoint existing, double time) + public override bool IsRedundant(ControlPoint existing) => existing is SampleControlPoint existingSample && SampleBank == existingSample.SampleBank && SampleVolume == existingSample.SampleVolume; diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 27f4662d49..1927dd6575 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -49,6 +49,6 @@ namespace osu.Game.Beatmaps.ControlPoints public double BPM => 60000 / BeatLength; // Timing points are never redundant as they can change the time signature. - public override bool IsRedundant(ControlPoint existing, double time) => false; + public override bool IsRedundant(ControlPoint existing) => false; } } diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 5fa1da111d..556527bfd5 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -174,8 +174,8 @@ namespace osu.Game.Beatmaps.Formats return baseInfo; } - public override bool IsRedundant(ControlPoint existing, double time) - => base.IsRedundant(existing, time) + public override bool IsRedundant(ControlPoint existing) + => base.IsRedundant(existing) && existing is LegacySampleControlPoint existingSample && CustomSampleBank == existingSample.CustomSampleBank; } From 0fba93bf658d917286defb5b029fcf5bc0f1b566 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 17 Apr 2020 17:10:13 +0900 Subject: [PATCH 375/655] Add back null check --- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 8e4079f776..d33a922a32 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -247,7 +247,7 @@ namespace osu.Game.Beatmaps.ControlPoints break; } - return newPoint.IsRedundant(existing); + return newPoint?.IsRedundant(existing) == true; } private void groupItemAdded(ControlPoint controlPoint) From 5833a7ac913af77a8c8d801ffd5d117b9861930c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Apr 2020 18:50:58 +0900 Subject: [PATCH 376/655] Fix presenting new ruleset and beatmap at once causing wedge display desync --- .../SongSelect/TestScenePlaySongSelect.cs | 62 +++++++++++++++++++ osu.Game/Screens/Select/SongSelect.cs | 6 +- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 4405c75744..39e04ed39a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -359,6 +359,68 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmap == null); } + [Test] + public void TestPresentNewRulesetNewBeatmap() + { + createSongSelect(); + changeRuleset(2); + + addRulesetImportStep(2); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 2); + + addRulesetImportStep(0); + addRulesetImportStep(0); + addRulesetImportStep(0); + + BeatmapInfo target = null; + + AddStep("select beatmap/ruleset externally", () => + { + target = manager.GetAllUsableBeatmapSets() + .Last(b => b.Beatmaps.Any(bi => bi.RulesetID == 0)).Beatmaps.Last(); + + Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == 0); + Beatmap.Value = manager.GetWorkingBeatmap(target); + }); + + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.Equals(target)); + + // this is an important check, to make sure updateComponentFromBeatmap() was actually run + AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo == target); + } + + [Test] + public void TestPresentNewBeatmapNewRuleset() + { + createSongSelect(); + changeRuleset(2); + + addRulesetImportStep(2); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 2); + + addRulesetImportStep(0); + addRulesetImportStep(0); + addRulesetImportStep(0); + + BeatmapInfo target = null; + + AddStep("select beatmap/ruleset externally", () => + { + target = manager.GetAllUsableBeatmapSets() + .Last(b => b.Beatmaps.Any(bi => bi.RulesetID == 0)).Beatmaps.Last(); + + Beatmap.Value = manager.GetWorkingBeatmap(target); + Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == 0); + }); + + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.Equals(target)); + + AddUntilStep("has correct ruleset", () => Ruleset.Value.ID == 0); + + // this is an important check, to make sure updateComponentFromBeatmap() was actually run + AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo == target); + } + [Test] public void TestRulesetChangeResetsMods() { diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 5bc2e1aa56..9d3dc58a26 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -448,8 +448,10 @@ namespace osu.Game.Screens.Select { Mods.Value = Array.Empty(); - // required to return once in order to have the carousel in a good state. - // if the ruleset changed, the rest of the selection update will happen via updateSelectedRuleset. + // the ruleset transfer may cause a deselection of the current beatmap (due to incompatibility). + // this can happen via Carousel.FlushPendingFilterOperations(). + // to ensure a good state, re-transfer no-debounce values. + performUpdateSelected(); return; } From 58a1c6e17186084345feedbd1c5b0d44c2ce8695 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Apr 2020 19:52:58 +0900 Subject: [PATCH 377/655] Reapply taiko visibility hack at a higher level --- .../Objects/Drawables/DrawableDrumRoll.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 0a6f462607..99f48afff0 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -137,6 +138,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } } + protected override void Update() + { + base.Update(); + + OriginPosition = new Vector2(DrawHeight); + Content.X = DrawHeight / 2; + } + protected override DrawableStrongNestedHit CreateStrongHit(StrongHitObject hitObject) => new StrongNestedHit(hitObject, this); private void updateColour() From 5f3ed3e93aab472b9d748d857e4394ec13843742 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Apr 2020 22:25:24 +0900 Subject: [PATCH 378/655] 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 723844155f..d2bdbc8b61 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 76f7a030f9..5facb04117 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 7a487a6430..dda1ee5c42 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 61e3491e603daf0a497ec988318043001a4e068a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 18 Apr 2020 12:57:09 +0900 Subject: [PATCH 379/655] Fix hard crash in editor on legacy modes without encoder implementation --- osu.Game/Screens/Edit/EditorChangeHandler.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index 1553c2d2ef..ac889500b4 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -82,12 +82,19 @@ namespace osu.Game.Screens.Edit if (savedStates.Count > MAX_SAVED_STATES) savedStates.RemoveAt(0); - using (var stream = new MemoryStream()) + try { - using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - new LegacyBeatmapEncoder(editorBeatmap).Encode(sw); + using (var stream = new MemoryStream()) + { + using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) + new LegacyBeatmapEncoder(editorBeatmap).Encode(sw); - savedStates.Add(stream.ToArray()); + savedStates.Add(stream.ToArray()); + } + } + catch (NotImplementedException) + { + // some rulesets don't have encoder implementations yet. } currentState = savedStates.Count - 1; From c00a386ff681a3ba4a037de6654bd1fd199d6770 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 18 Apr 2020 21:46:04 +0900 Subject: [PATCH 380/655] Remove exceptions instead --- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 21 +------------------ osu.Game/Screens/Edit/EditorChangeHandler.cs | 15 ++++--------- 2 files changed, 5 insertions(+), 31 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 12f2c58e35..8d9dfc318a 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -194,20 +194,7 @@ namespace osu.Game.Beatmaps.Formats handleOsuHitObject(writer, h); break; - case 1: - foreach (var h in beatmap.HitObjects) - handleTaikoHitObject(writer, h); - break; - - case 2: - foreach (var h in beatmap.HitObjects) - handleCatchHitObject(writer, h); - break; - - case 3: - foreach (var h in beatmap.HitObjects) - handleManiaHitObject(writer, h); - break; + // TODO: implement other legacy rulesets } } @@ -328,12 +315,6 @@ namespace osu.Game.Beatmaps.Formats } } - private void handleTaikoHitObject(TextWriter writer, HitObject hitObject) => throw new NotImplementedException(); - - private void handleCatchHitObject(TextWriter writer, HitObject hitObject) => throw new NotImplementedException(); - - private void handleManiaHitObject(TextWriter writer, HitObject hitObject) => throw new NotImplementedException(); - private string getSampleBank(IList samples, bool banksOnly = false, bool zeroBanks = false) { LegacySampleBank normalBank = toLegacySampleBank(samples.SingleOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank); diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index ac889500b4..1553c2d2ef 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -82,19 +82,12 @@ namespace osu.Game.Screens.Edit if (savedStates.Count > MAX_SAVED_STATES) savedStates.RemoveAt(0); - try + using (var stream = new MemoryStream()) { - using (var stream = new MemoryStream()) - { - using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - new LegacyBeatmapEncoder(editorBeatmap).Encode(sw); + using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) + new LegacyBeatmapEncoder(editorBeatmap).Encode(sw); - savedStates.Add(stream.ToArray()); - } - } - catch (NotImplementedException) - { - // some rulesets don't have encoder implementations yet. + savedStates.Add(stream.ToArray()); } currentState = savedStates.Count - 1; From 6b16908c05bafd95e069b986d5efd6d73631875f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 18 Apr 2020 21:51:37 +0900 Subject: [PATCH 381/655] Move todo to appease dotnet-format --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 8d9dfc318a..fe63eec3f9 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -187,14 +187,13 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine("[HitObjects]"); + // TODO: implement other legacy rulesets switch (beatmap.BeatmapInfo.RulesetID) { case 0: foreach (var h in beatmap.HitObjects) handleOsuHitObject(writer, h); break; - - // TODO: implement other legacy rulesets } } From fc6c245de5dc362489706fbae2b1c299260f9fb8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 19 Apr 2020 05:36:04 +0300 Subject: [PATCH 382/655] Replace all judged event logic with HasCompleted bindable --- .../TestSceneHoldNoteInput.cs | 8 +------- .../TestSceneOutOfOrderHits.cs | 8 +------- .../TestSceneSliderInput.cs | 8 +------- .../TestSceneSwellJudgements.cs | 2 +- osu.Game/Rulesets/Scoring/JudgementProcessor.cs | 17 +++++++++-------- osu.Game/Screens/Play/BreakTracker.cs | 2 +- osu.Game/Tests/Visual/ModPerfectTestScene.cs | 2 +- 7 files changed, 15 insertions(+), 32 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index 7b0cf40d45..0d13b85901 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -27,7 +27,6 @@ namespace osu.Game.Rulesets.Mania.Tests private const double time_after_tail = 5250; private List judgementResults; - private bool allJudgedFired; /// /// -----[ ]----- @@ -283,20 +282,15 @@ namespace osu.Game.Rulesets.Mania.Tests { if (currentPlayer == p) judgementResults.Add(result); }; - p.ScoreProcessor.AllJudged += () => - { - if (currentPlayer == p) allJudgedFired = true; - }; }; LoadScreen(currentPlayer = p); - allJudgedFired = false; judgementResults = new List(); }); AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); - AddUntilStep("Wait for all judged", () => allJudgedFired); + AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); } private class ScoreAccessibleReplayPlayer : ReplayPlayer diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs index d6858f831e..91d5e04f6f 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs @@ -316,7 +316,6 @@ namespace osu.Game.Rulesets.Osu.Tests private ScoreAccessibleReplayPlayer currentPlayer; private List judgementResults; - private bool allJudgedFired; private void performTest(List hitObjects, List frames) { @@ -342,20 +341,15 @@ namespace osu.Game.Rulesets.Osu.Tests { if (currentPlayer == p) judgementResults.Add(result); }; - p.ScoreProcessor.AllJudged += () => - { - if (currentPlayer == p) allJudgedFired = true; - }; }; LoadScreen(currentPlayer = p); - allJudgedFired = false; judgementResults = new List(); }); AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); - AddUntilStep("Wait for all judged", () => allJudgedFired); + AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); } private class TestHitCircle : HitCircle diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index 67e1b77770..b0c2e56c3e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -47,7 +47,6 @@ namespace osu.Game.Rulesets.Osu.Tests private const double time_slider_end = 4000; private List judgementResults; - private bool allJudgedFired; /// /// Scenario: @@ -375,20 +374,15 @@ namespace osu.Game.Rulesets.Osu.Tests { if (currentPlayer == p) judgementResults.Add(result); }; - p.ScoreProcessor.AllJudged += () => - { - if (currentPlayer == p) allJudgedFired = true; - }; }; LoadScreen(currentPlayer = p); - allJudgedFired = false; judgementResults = new List(); }); AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); - AddUntilStep("Wait for all judged", () => allJudgedFired); + AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); } private class ScoreAccessibleReplayPlayer : ReplayPlayer diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs index 303f0163b1..923e28a45e 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Tests [Test] public void TestZeroTickTimeOffsets() { - AddUntilStep("gameplay finished", () => Player.ScoreProcessor.HasCompleted); + AddUntilStep("gameplay finished", () => Player.ScoreProcessor.HasCompleted.Value); AddAssert("all tick offsets are 0", () => Player.Results.Where(r => r.HitObject is SwellTick).All(r => r.TimeOffset == 0)); } diff --git a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs index 334b95f808..d878ef0a5c 100644 --- a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs +++ b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Game.Beatmaps; @@ -12,11 +13,6 @@ namespace osu.Game.Rulesets.Scoring { public abstract class JudgementProcessor : Component { - /// - /// Invoked when all s have been judged by this . - /// - public event Action AllJudged; - /// /// Invoked when a new judgement has occurred. This occurs after the judgement has been processed by this . /// @@ -32,10 +28,12 @@ namespace osu.Game.Rulesets.Scoring /// public int JudgedHits { get; private set; } + private readonly BindableBool hasCompleted = new BindableBool(); + /// /// Whether all s have been processed. /// - public bool HasCompleted => JudgedHits == MaxHits; + public IBindable HasCompleted => hasCompleted; /// /// Applies a to this . @@ -60,8 +58,8 @@ namespace osu.Game.Rulesets.Scoring NewJudgement?.Invoke(result); - if (HasCompleted) - AllJudged?.Invoke(); + if (JudgedHits == MaxHits) + hasCompleted.Value = true; } /// @@ -72,6 +70,9 @@ namespace osu.Game.Rulesets.Scoring { JudgedHits--; + if (JudgedHits < MaxHits) + hasCompleted.Value = false; + RevertResultInternal(result); } diff --git a/osu.Game/Screens/Play/BreakTracker.cs b/osu.Game/Screens/Play/BreakTracker.cs index 64262d52b5..fcd7ed6b73 100644 --- a/osu.Game/Screens/Play/BreakTracker.cs +++ b/osu.Game/Screens/Play/BreakTracker.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Play isBreakTime.Value = getCurrentBreak()?.HasEffect == true || Clock.CurrentTime < gameplayStartTime - || scoreProcessor?.HasCompleted == true; + || scoreProcessor?.HasCompleted.Value == true; } private BreakPeriod getCurrentBreak() diff --git a/osu.Game/Tests/Visual/ModPerfectTestScene.cs b/osu.Game/Tests/Visual/ModPerfectTestScene.cs index 798947eb40..5948283428 100644 --- a/osu.Game/Tests/Visual/ModPerfectTestScene.cs +++ b/osu.Game/Tests/Visual/ModPerfectTestScene.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual public bool CheckFailed(bool failed) { if (!failed) - return ScoreProcessor.HasCompleted && !HealthProcessor.HasFailed; + return ScoreProcessor.HasCompleted.Value && !HealthProcessor.HasFailed; return HealthProcessor.HasFailed; } From 7e64bec94f286b4d1bbc550d4e95f9ca601ca997 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 19 Apr 2020 05:58:22 +0300 Subject: [PATCH 383/655] Use HasCompleted in Player --- 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 4597ae760c..542e226809 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -197,7 +197,7 @@ namespace osu.Game.Screens.Play }; // Bind the judgement processors to ourselves - ScoreProcessor.AllJudged += onCompletion; + ScoreProcessor.HasCompleted.ValueChanged += updateCompletionState; HealthProcessor.Failed += onFail; foreach (var mod in Mods.Value.OfType()) @@ -412,7 +412,7 @@ namespace osu.Game.Screens.Play private ScheduledDelegate completionProgressDelegate; - private void onCompletion() + private void updateCompletionState(ValueChangedEvent completionState) { // screen may be in the exiting transition phase. if (!this.IsCurrentScreen()) From 6d276890a7dca11c37855655f8f760c4b3176314 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 19 Apr 2020 05:59:56 +0300 Subject: [PATCH 384/655] Fix results screen pushed after rewinding in-between push delay --- osu.Game/Screens/Play/Player.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 542e226809..c6c83e5379 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -418,6 +418,16 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; + // cancel push delegate in case judges reverted + // after delegate may have already been scheduled. + if (!completionState.NewValue) + { + completionProgressDelegate?.Cancel(); + completionProgressDelegate = null; + ValidForResume = true; + return; + } + // Only show the completion screen if the player hasn't failed if (HealthProcessor.HasFailed || completionProgressDelegate != null) return; From 65a8860a65812dc4de6aae708d5352508898cdb7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 19 Apr 2020 06:01:09 +0300 Subject: [PATCH 385/655] Add test cases to ensure no regression in "cancelling completion" --- .../TestSceneCompletionCancellation.cs | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs new file mode 100644 index 0000000000..54e1ff5345 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs @@ -0,0 +1,129 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Track; +using osu.Framework.Screens; +using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Storyboards; +using osuTK; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneCompletionCancellation : PlayerTestScene + { + private Track track; + + [Resolved] + private AudioManager audio { get; set; } + + protected override bool AllowFail => false; + + public TestSceneCompletionCancellation() + : base(new OsuRuleset()) + { + } + + [SetUpSteps] + public override void SetUpSteps() + { + base.SetUpSteps(); + + // Ensure track has actually running before attempting to seek + AddUntilStep("wait for track to start running", () => track.IsRunning); + } + + [Test] + public void TestCancelCompletionOnRewind() + { + cancelCompletionSteps(); + + AddAssert("no attempt to push ranking", () => !((FakeRankingPushPlayer)Player).GotoRankingInvoked); + } + + [Test] + public void TestReCompleteAfterCancellation() + { + cancelCompletionSteps(); + + // Attempt completing again. + AddStep("seek to completion again", () => track.Seek(5000)); + AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); + + AddWaitStep("wait", 5); + + AddAssert("attempted to push ranking", () => ((FakeRankingPushPlayer)Player).GotoRankingInvoked); + } + + /// + /// Tests whether can still pause after cancelling completion + /// by reverting back to true. + /// + [Test] + public void TestCanPauseAfterCancellation() + { + cancelCompletionSteps(); + + AddStep("pause", () => Player.Pause()); + AddAssert("paused successfully", () => Player.GameplayClockContainer.IsPaused.Value); + } + + private void cancelCompletionSteps() + { + AddStep("seek to completion", () => track.Seek(5000)); + AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); + + AddStep("rewind to cancel", () => track.Seek(4000)); + AddUntilStep("completion cleared by processor", () => !Player.ScoreProcessor.HasCompleted.Value); + + AddWaitStep("wait", 5); + } + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) + { + var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audio); + track = working.Track; + return working; + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + var beatmap = new Beatmap(); + + for (int i = 1; i <= 19; i++) + { + beatmap.HitObjects.Add(new HitCircle + { + Position = new Vector2(256, 192), + StartTime = i * 250, + }); + } + + return beatmap; + } + + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new FakeRankingPushPlayer(); + + public class FakeRankingPushPlayer : TestPlayer + { + public bool GotoRankingInvoked; + + public FakeRankingPushPlayer() + : base(true, true) + { + } + + protected override void GotoRanking() + { + GotoRankingInvoked = true; + } + } + } +} From 1dd471dfcc1ae9465d17c3d21ea2577d7a2b46c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 19 Apr 2020 15:12:36 +0900 Subject: [PATCH 386/655] Add /np (now playing) command support in chat --- .../Visual/Online/TestNowPlayingCommand.cs | 85 +++++++++++++++++++ osu.Game/Online/Chat/ChannelManager.cs | 6 +- osu.Game/Online/Chat/IChannelPostTarget.cs | 19 +++++ osu.Game/Online/Chat/NowPlayingCommand.cs | 55 ++++++++++++ osu.Game/Online/PollingComponent.cs | 4 +- 5 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Tests/Visual/Online/TestNowPlayingCommand.cs create mode 100644 osu.Game/Online/Chat/IChannelPostTarget.cs create mode 100644 osu.Game/Online/Chat/NowPlayingCommand.cs diff --git a/osu.Game.Tests/Visual/Online/TestNowPlayingCommand.cs b/osu.Game.Tests/Visual/Online/TestNowPlayingCommand.cs new file mode 100644 index 0000000000..60032ab118 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestNowPlayingCommand.cs @@ -0,0 +1,85 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Online.Chat; +using osu.Game.Rulesets; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Online +{ + [HeadlessTest] + public class TestNowPlayingCommand : OsuTestScene + { + [Cached(typeof(IChannelPostTarget))] + private PostTarget postTarget { get; set; } + + public TestNowPlayingCommand() + { + Add(postTarget = new PostTarget()); + } + + [Test] + public void TestGenericActivity() + { + AddStep("Set activity", () => API.Activity.Value = new UserActivity.InLobby()); + + AddStep("Run command", () => Add(new NowPlayingCommand())); + + AddAssert("Check correct response", () => postTarget.LastMessage.Contains("is listening")); + } + + [Test] + public void TestEditActivity() + { + AddStep("Set activity", () => API.Activity.Value = new UserActivity.Editing(new BeatmapInfo())); + + AddStep("Run command", () => Add(new NowPlayingCommand())); + + AddAssert("Check correct response", () => postTarget.LastMessage.Contains("is editing")); + } + + [Test] + public void TestPlayActivity() + { + AddStep("Set activity", () => API.Activity.Value = new UserActivity.SoloGame(new BeatmapInfo(), new RulesetInfo())); + + AddStep("Run command", () => Add(new NowPlayingCommand())); + + AddAssert("Check correct response", () => postTarget.LastMessage.Contains("is playing")); + } + + [TestCase(true)] + [TestCase(false)] + public void TestLinkPresence(bool hasOnlineId) + { + AddStep("Set activity", () => API.Activity.Value = new UserActivity.InLobby()); + + AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(null, null) + { + BeatmapInfo = { OnlineBeatmapID = hasOnlineId ? 1234 : (int?)null } + }); + + AddStep("Run command", () => Add(new NowPlayingCommand())); + + if (hasOnlineId) + AddAssert("Check link presence", () => postTarget.LastMessage.Contains("https://osu.ppy.sh/b/1234")); + else + AddAssert("Check link not present", () => !postTarget.LastMessage.Contains("https://")); + } + + public class PostTarget : Component, IChannelPostTarget + { + public void PostMessage(string text, bool isAction = false, Channel target = null) + { + LastMessage = text; + } + + public string LastMessage { get; private set; } + } + } +} diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 2c37216fd6..f53beefeb5 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -18,7 +18,7 @@ namespace osu.Game.Online.Chat /// /// Manages everything channel related /// - public class ChannelManager : PollingComponent + public class ChannelManager : PollingComponent, IChannelPostTarget { /// /// The channels the player joins on startup @@ -204,6 +204,10 @@ namespace osu.Game.Online.Chat switch (command) { + case "np": + AddInternal(new NowPlayingCommand()); + break; + case "me": if (string.IsNullOrWhiteSpace(content)) { diff --git a/osu.Game/Online/Chat/IChannelPostTarget.cs b/osu.Game/Online/Chat/IChannelPostTarget.cs new file mode 100644 index 0000000000..5697e918f0 --- /dev/null +++ b/osu.Game/Online/Chat/IChannelPostTarget.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; + +namespace osu.Game.Online.Chat +{ + [Cached(typeof(IChannelPostTarget))] + public interface IChannelPostTarget + { + /// + /// Posts a message to the currently opened channel. + /// + /// The message text that is going to be posted + /// Is true if the message is an action, e.g.: user is currently eating + /// An optional target channel. If null, will be used. + void PostMessage(string text, bool isAction = false, Channel target = null); + } +} diff --git a/osu.Game/Online/Chat/NowPlayingCommand.cs b/osu.Game/Online/Chat/NowPlayingCommand.cs new file mode 100644 index 0000000000..c0b54812b6 --- /dev/null +++ b/osu.Game/Online/Chat/NowPlayingCommand.cs @@ -0,0 +1,55 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Online.API; +using osu.Game.Users; + +namespace osu.Game.Online.Chat +{ + public class NowPlayingCommand : Component + { + [Resolved] + private IChannelPostTarget channelManager { get; set; } + + [Resolved] + private IAPIProvider api { get; set; } + + [Resolved] + private Bindable currentBeatmap { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + string verb; + BeatmapInfo beatmap; + + switch (api.Activity.Value) + { + case UserActivity.SoloGame solo: + verb = "playing"; + beatmap = solo.Beatmap; + break; + + case UserActivity.Editing edit: + verb = "editing"; + beatmap = edit.Beatmap; + break; + + default: + verb = "listening to"; + beatmap = currentBeatmap.Value.BeatmapInfo; + break; + } + + var beatmapString = beatmap.OnlineBeatmapID.HasValue ? $"[https://osu.ppy.sh/b/{beatmap.OnlineBeatmapID} {beatmap}]" : beatmap.ToString(); + + channelManager.PostMessage($"is {verb} {beatmapString}", true); + Expire(); + } + } +} diff --git a/osu.Game/Online/PollingComponent.cs b/osu.Game/Online/PollingComponent.cs index acbb2c39f4..228f147835 100644 --- a/osu.Game/Online/PollingComponent.cs +++ b/osu.Game/Online/PollingComponent.cs @@ -3,7 +3,7 @@ using System; using System.Threading.Tasks; -using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Threading; namespace osu.Game.Online @@ -11,7 +11,7 @@ namespace osu.Game.Online /// /// A component which requires a constant polling process. /// - public abstract class PollingComponent : Component + public abstract class PollingComponent : CompositeDrawable // switch away from Component because InternalChildren are used in usages. { private double? lastTimePolled; From e4d4040afb90a990eb369ced822d65f07acd1e25 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 19 Apr 2020 16:57:47 +0900 Subject: [PATCH 387/655] Rename test to match other classes --- ...TestNowPlayingCommand.cs => TestSceneNowPlayingCommand.cs} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename osu.Game.Tests/Visual/Online/{TestNowPlayingCommand.cs => TestSceneNowPlayingCommand.cs} (96%) diff --git a/osu.Game.Tests/Visual/Online/TestNowPlayingCommand.cs b/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs similarity index 96% rename from osu.Game.Tests/Visual/Online/TestNowPlayingCommand.cs rename to osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs index 60032ab118..103308d34d 100644 --- a/osu.Game.Tests/Visual/Online/TestNowPlayingCommand.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs @@ -13,12 +13,12 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Online { [HeadlessTest] - public class TestNowPlayingCommand : OsuTestScene + public class TestSceneNowPlayingCommand : OsuTestScene { [Cached(typeof(IChannelPostTarget))] private PostTarget postTarget { get; set; } - public TestNowPlayingCommand() + public TestSceneNowPlayingCommand() { Add(postTarget = new PostTarget()); } From e3e0cd149f94d033a36d7622c0a8bf91e029fde0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 19 Apr 2020 12:41:00 +0200 Subject: [PATCH 388/655] Refactor test code to eliminate boolean flags --- .../TestSceneHyperDashColouring.cs | 127 +++++++++--------- 1 file changed, 65 insertions(+), 62 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index 846b17f324..a48ecb9b79 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -27,74 +27,71 @@ namespace osu.Game.Rulesets.Catch.Tests private SkinManager skins { get; set; } [Test] - public void TestHyperDashFruitColour() + public void TestDefaultFruitColour() { - DrawableFruit drawableFruit = null; + var skin = new TestSkin(); - AddStep("setup hyper-dash fruit", () => - { - var fruit = new Fruit { HyperDashTarget = new Banana() }; - fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - - Child = setupSkinHierarchy(drawableFruit = new DrawableFruit(fruit) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(4f), - }, false, false, false); - }); - - AddAssert("hyper-dash fruit has default colour", () => checkLegacyFruitHyperDashColour(drawableFruit, Catcher.DEFAULT_HYPER_DASH_COLOUR)); - } - - [TestCase(true)] - [TestCase(false)] - public void TestCustomHyperDashFruitColour(bool customCatcherHyperDashColour) - { - DrawableFruit drawableFruit = null; - - AddStep("setup hyper-dash fruit", () => - { - var fruit = new Fruit { HyperDashTarget = new Banana() }; - fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - - Child = setupSkinHierarchy(drawableFruit = new DrawableFruit(fruit) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(4f), - }, customCatcherHyperDashColour, false, true); - }); - - AddAssert("hyper-dash fruit use fruit colour from skin", () => checkLegacyFruitHyperDashColour(drawableFruit, TestSkin.CUSTOM_HYPER_DASH_FRUIT_COLOUR)); + checkHyperDashFruitColour(skin, Catcher.DEFAULT_HYPER_DASH_COLOUR); } [Test] - public void TestCustomHyperDashFruitColourFallback() + public void TestCustomFruitColour() + { + var skin = new TestSkin + { + HyperDashFruitColour = Color4.Cyan + }; + + checkHyperDashFruitColour(skin, skin.HyperDashFruitColour); + } + + [Test] + public void TestCustomFruitColourPriority() + { + var skin = new TestSkin + { + HyperDashColour = Color4.Goldenrod, + HyperDashFruitColour = Color4.Cyan + }; + + checkHyperDashFruitColour(skin, skin.HyperDashFruitColour); + } + + [Test] + public void TestFruitColourFallback() + { + var skin = new TestSkin + { + HyperDashColour = Color4.Goldenrod + }; + + checkHyperDashFruitColour(skin, skin.HyperDashColour); + } + + private void checkHyperDashFruitColour(ISkin skin, Color4 expectedColour) { DrawableFruit drawableFruit = null; - AddStep("setup hyper-dash fruit", () => + AddStep("create hyper-dash fruit", () => { var fruit = new Fruit { HyperDashTarget = new Banana() }; fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - Child = setupSkinHierarchy( - drawableFruit = new DrawableFruit(fruit) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(4f), - }, true, false, false); + Child = setupSkinHierarchy(drawableFruit = new DrawableFruit(fruit) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(4f), + }, skin); }); - AddAssert("hyper-dash fruit colour falls back to catcher colour from skin", () => checkLegacyFruitHyperDashColour(drawableFruit, TestSkin.CUSTOM_HYPER_DASH_COLOUR)); + AddAssert("hyper-dash colour is correct", () => checkLegacyFruitHyperDashColour(drawableFruit, expectedColour)); } - private Drawable setupSkinHierarchy(Drawable child, bool customCatcherColour, bool customAfterColour, bool customFruitColour) + private Drawable setupSkinHierarchy(Drawable child, ISkin skin) { var legacySkinProvider = new SkinProvidingContainer(skins.GetSkin(DefaultLegacySkin.Info)); - var testSkinProvider = new SkinProvidingContainer(new TestSkin(customCatcherColour, customAfterColour, customFruitColour)); + var testSkinProvider = new SkinProvidingContainer(skin); var legacySkinTransformer = new SkinProvidingContainer(new CatchLegacySkinTransformer(testSkinProvider)); return legacySkinProvider @@ -108,21 +105,27 @@ namespace osu.Game.Rulesets.Catch.Tests private class TestSkin : LegacySkin { - public static readonly Color4 CUSTOM_HYPER_DASH_COLOUR = Color4.Goldenrod; - public static readonly Color4 CUSTOM_HYPER_DASH_AFTER_COLOUR = Color4.Lime; - public static readonly Color4 CUSTOM_HYPER_DASH_FRUIT_COLOUR = Color4.Cyan; + public Color4 HyperDashColour + { + get => Configuration.CustomColours[CatchSkinColour.HyperDash.ToString()]; + set => Configuration.CustomColours[CatchSkinColour.HyperDash.ToString()] = value; + } - public TestSkin(bool customCatcherColour, bool customAfterColour, bool customFruitColour) + public Color4 HyperDashAfterImageColour + { + get => Configuration.CustomColours[CatchSkinColour.HyperDashAfterImage.ToString()]; + set => Configuration.CustomColours[CatchSkinColour.HyperDashAfterImage.ToString()] = value; + } + + public Color4 HyperDashFruitColour + { + get => Configuration.CustomColours[CatchSkinColour.HyperDashFruit.ToString()]; + set => Configuration.CustomColours[CatchSkinColour.HyperDashFruit.ToString()] = value; + } + + public TestSkin() : base(new SkinInfo(), null, null, string.Empty) { - if (customCatcherColour) - Configuration.CustomColours[CatchSkinColour.HyperDash.ToString()] = CUSTOM_HYPER_DASH_COLOUR; - - if (customAfterColour) - Configuration.CustomColours[CatchSkinColour.HyperDashAfterImage.ToString()] = CUSTOM_HYPER_DASH_AFTER_COLOUR; - - if (customFruitColour) - Configuration.CustomColours[CatchSkinColour.HyperDashFruit.ToString()] = CUSTOM_HYPER_DASH_FRUIT_COLOUR; } } } From a7179d1c8751b179ae8d676ee76cd73b8930336f Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 19 Apr 2020 15:15:04 +0200 Subject: [PATCH 389/655] Fix comment wording MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Bartłomiej Dach --- osu.Game/Rulesets/RulesetStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 34da2dc2db..d4b41dcb38 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets // the requesting assembly may be located out of the executable's base directory, thus requiring manual resolving of its dependencies. // this assumes the only explicit dependency of the ruleset is the game core assembly. - // the ruleset dependency on the game core assembly requires manual resolving, transient dependencies should be resolved automatically + // the ruleset dependency on the game core assembly requires manual resolving, transitive dependencies should be resolved automatically if (asm.Name.Equals(typeof(OsuGame).Assembly.GetName().Name, StringComparison.Ordinal)) return Assembly.GetExecutingAssembly(); From 07b8ef83c95c209a9953013b3cadc01bc529b7aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 19 Apr 2020 22:15:07 +0900 Subject: [PATCH 390/655] Add /np to help line --- osu.Game/Online/Chat/ChannelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index f53beefeb5..822f628dd2 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -238,7 +238,7 @@ namespace osu.Game.Online.Chat break; case "help": - target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel]")); + target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel], /np")); break; default: From ba1c465edf296fe55bf4d623e5543918ab85f7af Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 19 Apr 2020 15:25:21 +0200 Subject: [PATCH 391/655] Add comment in regards to the attaching of event handler to the assembly lookup event before call to loadUserRulesets(). --- 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 d4b41dcb38..ab169c741d 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -29,6 +29,10 @@ namespace osu.Game.Rulesets // We cannot read assemblies from cwd, so should check loaded assemblies instead. loadFromAppDomain(); loadFromDisk(); + + // the event handler contains code for resolving dependency on the game assembly for rulesets located outside the base game directory. + // It needs to be attached to the assembly lookup event before the actual call to loadUserRulesets() else rulesets located out of the base game directory will fail + // to load as unable to locate the game core assembly. AppDomain.CurrentDomain.AssemblyResolve += resolveRulesetDependencyAssembly; loadUserRulesets(); addMissingRulesets(); From 1dcb0f53a233452392efadd949c12342842eb7c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 19 Apr 2020 16:29:32 +0200 Subject: [PATCH 392/655] Fix whitespace formatting --- osu.Game/Rulesets/RulesetStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index ab169c741d..7e165311a3 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets // We cannot read assemblies from cwd, so should check loaded assemblies instead. loadFromAppDomain(); loadFromDisk(); - + // the event handler contains code for resolving dependency on the game assembly for rulesets located outside the base game directory. // It needs to be attached to the assembly lookup event before the actual call to loadUserRulesets() else rulesets located out of the base game directory will fail // to load as unable to locate the game core assembly. From b57d709d151d4296e35a03fb79130cf55e4a34d3 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 19 Apr 2020 18:29:06 +0300 Subject: [PATCH 393/655] Don't use Parent --- osu.Game/Screens/Select/BeatmapCarousel.cs | 18 +++++++++--------- osu.Game/Screens/Select/SongSelect.cs | 2 ++ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index b5dfcadeaa..cf3a5a7199 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -28,8 +28,8 @@ namespace osu.Game.Screens.Select { public class BeatmapCarousel : CompositeDrawable, IKeyBindingHandler { - private const float bleed_top = FilterControl.HEIGHT; - private const float bleed_bottom = Footer.HEIGHT; + public float BleedTop; + public float BleedBottom; /// /// Triggered when the loaded change and are completely loaded. @@ -373,17 +373,17 @@ namespace osu.Game.Screens.Select /// the beatmap carousel bleeds into the and the /// /// - private float visibleHalfHeight => (DrawHeight + bleed_bottom + bleed_top) / 2; + private float visibleHalfHeight => (DrawHeight + BleedBottom + BleedTop) / 2; /// /// The position of the lower visible bound with respect to the current scroll position. /// - private float visibleBottomBound => scroll.Current + DrawHeight + bleed_bottom; + private float visibleBottomBound => scroll.Current + DrawHeight + BleedBottom; /// /// The position of the upper visible bound with respect to the current scroll position. /// - private float visibleUpperBound => scroll.Current - bleed_top; + private float visibleUpperBound => scroll.Current - BleedTop; public void FlushPendingFilterOperations() { @@ -641,11 +641,11 @@ namespace osu.Game.Screens.Select case DrawableCarouselBeatmap beatmap: { if (beatmap.Item.State.Value == CarouselItemState.Selected) - // scroll position at currentY makes the set panel appear at the very top of the carousel in screen space - // move down by half of parent height (which is the height of the carousel's visible extent, including semi-transparent areas) - // then reapply parent's padding from the top by adding it + // scroll position at currentY makes the set panel appear at the very top of the carousel's screen space + // move down by half of visible height (height of the carousel's visible extent, including semi-transparent areas) + // then reapply the top semi-transparent area (because carousel's screen space starts below it) // and finally add half of the panel's own height to achieve vertical centering of the panel itself - scrollTarget = currentY - Parent.DrawHeight / 2 + Parent.Padding.Top + beatmap.DrawHeight / 2; + scrollTarget = currentY - visibleHalfHeight + BleedTop + beatmap.DrawHeight / 2; void performMove(float y, float? startY = null) { diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 5bc2e1aa56..bd6204d8cd 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -153,6 +153,8 @@ namespace osu.Game.Screens.Select Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Both, + BleedTop = FilterControl.HEIGHT, + BleedBottom = Footer.HEIGHT, SelectionChanged = updateSelectedBeatmap, BeatmapSetsChanged = carouselBeatmapsLoaded, GetRecommendedBeatmap = recommender.GetRecommendedBeatmap, From f3fee734417154e36ef9594a67e3060983455d91 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Apr 2020 09:35:00 +0900 Subject: [PATCH 394/655] Fix DatabasedKeyBindingContainer not using defaults for non-databased ruleset --- .../Visual/Gameplay/TestSceneKeyBindings.cs | 99 +++++++++++++++++++ .../Bindings/DatabasedKeyBindingContainer.cs | 10 +- 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs new file mode 100644 index 0000000000..45d9819c0e --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs @@ -0,0 +1,99 @@ +// 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.Framework.Graphics.Containers; +using osu.Framework.Input.Bindings; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Input.Bindings; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Gameplay +{ + [HeadlessTest] + public class TestSceneKeyBindings : OsuManualInputManagerTestScene + { + private readonly ActionReceiver receiver; + + public TestSceneKeyBindings() + { + Add(new TestKeyBindingContainer + { + Child = receiver = new ActionReceiver() + }); + } + + [Test] + public void TestDefaultsWhenNotDatabased() + { + AddStep("fire key", () => + { + InputManager.PressKey(Key.A); + InputManager.ReleaseKey(Key.A); + }); + + AddAssert("received key", () => receiver.ReceivedAction); + } + + private class TestRuleset : Ruleset + { + public override IEnumerable GetModsFor(ModType type) => + throw new System.NotImplementedException(); + + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => + throw new System.NotImplementedException(); + + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => + throw new System.NotImplementedException(); + + public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => + throw new System.NotImplementedException(); + + public override IEnumerable GetDefaultKeyBindings(int variant = 0) + { + return new[] + { + new KeyBinding(InputKey.A, TestAction.Down), + }; + } + + public override string Description => "test"; + public override string ShortName => "test"; + } + + private enum TestAction + { + Down, + } + + private class TestKeyBindingContainer : DatabasedKeyBindingContainer + { + public TestKeyBindingContainer() + : base(new TestRuleset().RulesetInfo, 0) + { + } + } + + private class ActionReceiver : CompositeDrawable, IKeyBindingHandler + { + public bool ReceivedAction; + + public bool OnPressed(TestAction action) + { + ReceivedAction = action == TestAction.Down; + return true; + } + + public void OnReleased(TestAction action) + { + throw new System.NotImplementedException(); + } + } + } +} diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index e83d899469..94edc33099 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -62,6 +62,14 @@ namespace osu.Game.Input.Bindings store.KeyBindingChanged -= ReloadMappings; } - protected override void ReloadMappings() => KeyBindings = store.Query(ruleset?.ID, variant).ToList(); + protected override void ReloadMappings() + { + if (ruleset != null && !ruleset.ID.HasValue) + // if the provided ruleset is not stored to the database, we have no way to retrieve custom bindings. + // fallback to defaults instead. + KeyBindings = DefaultKeyBindings; + else + KeyBindings = store.Query(ruleset?.ID, variant).ToList(); + } } } From 2444dd42d0a931a9b6288813956f435b1bc5d4f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Apr 2020 09:57:46 +0900 Subject: [PATCH 395/655] Remove not-implemented-exception --- osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs index 45d9819c0e..db65e91d17 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs @@ -92,7 +92,6 @@ namespace osu.Game.Tests.Visual.Gameplay public void OnReleased(TestAction action) { - throw new System.NotImplementedException(); } } } From 28318a0140a4e3854d81896e7a762a451f8d7637 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Apr 2020 10:59:08 +0900 Subject: [PATCH 396/655] Add mention of notelock in xmldoc (potentially easier to find class) --- osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs index cd9838e7bf..176402c831 100644 --- a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.UI { /// - /// Ensures that s are hit in-order. + /// Ensures that s are hit in-order. Affectionately known as "note lock". /// If a is hit out of order: /// /// The hit is blocked if it occurred earlier than the previous 's start time. From e1acfd26a6849be6cee96edc66ae58c17e9a1fac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Apr 2020 10:59:44 +0900 Subject: [PATCH 397/655] Simplify return logic --- osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs index 176402c831..1f027e9726 100644 --- a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs @@ -51,10 +51,7 @@ namespace osu.Game.Rulesets.Osu.UI // 1. The last blocking hitobject has been judged. // 2. The current time is after the last hitobject's start time. // Hits at exactly the same time as the blocking hitobject are allowed for maps that contain simultaneous hitobjects (e.g. /b/372245). - if (blockingObject.Judged || time >= blockingObject.HitObject.StartTime) - return true; - - return false; + return blockingObject.Judged || time >= blockingObject.HitObject.StartTime; } /// From 8c85602ad013001281d4148b6109d606d6885c8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Apr 2020 11:00:42 +0900 Subject: [PATCH 398/655] Use foreach for conformity --- osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs index 1f027e9726..53dd1127d6 100644 --- a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs @@ -93,12 +93,12 @@ namespace osu.Game.Rulesets.Osu.UI yield return obj; - for (int i = 0; i < obj.NestedHitObjects.Count; i++) + foreach (var nestedObj in obj.NestedHitObjects) { - if (obj.NestedHitObjects[i].HitObject.StartTime >= targetTime) + if (nestedObj.HitObject.StartTime >= targetTime) break; - yield return obj.NestedHitObjects[i]; + yield return nestedObj; } } } From 6f233917b18dc05bb5f7d620cfc8fc512a6df9f7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 20 Apr 2020 06:40:51 +0300 Subject: [PATCH 399/655] Centralize updating HasCompleted bindable logic --- osu.Game/Rulesets/Scoring/JudgementProcessor.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs index d878ef0a5c..8aef615b5f 100644 --- a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs +++ b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs @@ -58,8 +58,7 @@ namespace osu.Game.Rulesets.Scoring NewJudgement?.Invoke(result); - if (JudgedHits == MaxHits) - hasCompleted.Value = true; + updateHasCompleted(); } /// @@ -70,8 +69,7 @@ namespace osu.Game.Rulesets.Scoring { JudgedHits--; - if (JudgedHits < MaxHits) - hasCompleted.Value = false; + updateHasCompleted(); RevertResultInternal(result); } @@ -135,5 +133,7 @@ namespace osu.Game.Rulesets.Scoring ApplyResult(result); } } + + private void updateHasCompleted() => hasCompleted.Value = JudgedHits == MaxHits; } } From e12e3391fb803f9bdf75ebb51abaf275427c1d07 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 20 Apr 2020 06:42:33 +0300 Subject: [PATCH 400/655] Base wait steps duration on the delay used for results display With `* 2` for safety of not potentially going to the next step and the delegate not executed yet. --- .../Visual/Gameplay/TestSceneCompletionCancellation.cs | 6 +++++- osu.Game/Screens/Play/Player.cs | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs index 54e1ff5345..aef173c36a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs @@ -24,6 +24,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Resolved] private AudioManager audio { get; set; } + private int resultsDisplayWaitCount => + (int)((Screens.Play.Player.RESULTS_DISPLAY_DELAY / TimePerAction) * 2); + protected override bool AllowFail => false; public TestSceneCompletionCancellation() @@ -83,7 +86,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("rewind to cancel", () => track.Seek(4000)); AddUntilStep("completion cleared by processor", () => !Player.ScoreProcessor.HasCompleted.Value); - AddWaitStep("wait", 5); + // wait to ensure there was no attempt of pushing the results screen. + AddWaitStep("wait", resultsDisplayWaitCount); } protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index c6c83e5379..2f3807753a 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -37,6 +37,11 @@ namespace osu.Game.Screens.Play [Cached] public class Player : ScreenWithBeatmapBackground { + /// + /// The delay upon completion of the beatmap before displaying the results screen. + /// + public const double RESULTS_DISPLAY_DELAY = 1000.0; + public override bool AllowBackButton => false; // handled by HoldForMenuButton protected override UserActivity InitialActivity => new UserActivity.SoloGame(Beatmap.Value.BeatmapInfo, Ruleset.Value); @@ -436,7 +441,7 @@ namespace osu.Game.Screens.Play if (!showResults) return; - using (BeginDelayedSequence(1000)) + using (BeginDelayedSequence(RESULTS_DISPLAY_DELAY)) scheduleGotoRanking(); } From 2c012b9af1106bbc154b25453a8cd71c3eb5001b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 20 Apr 2020 06:43:18 +0300 Subject: [PATCH 401/655] Use AddUntilStep whenever possible Avoid redundant usage --- .../Visual/Gameplay/TestSceneCompletionCancellation.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs index aef173c36a..a3fb17942f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs @@ -60,9 +60,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("seek to completion again", () => track.Seek(5000)); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); - AddWaitStep("wait", 5); - - AddAssert("attempted to push ranking", () => ((FakeRankingPushPlayer)Player).GotoRankingInvoked); + AddUntilStep("attempted to push ranking", () => ((FakeRankingPushPlayer)Player).GotoRankingInvoked); } /// From 355e682e24557b3b87d791943c0e2523e3331de6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 20 Apr 2020 13:23:27 +0900 Subject: [PATCH 402/655] Fix typo in exception --- osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs index 53dd1127d6..8e4f81347d 100644 --- a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.UI return; if (!IsHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset)) - throw new InvalidOperationException($"A {hitObject} was hit before it become hittable!"); + throw new InvalidOperationException($"A {hitObject} was hit before it became hittable!"); foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime)) { From ee1ccb8bcb14c637e6912c9f278b10b7573348af Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Apr 2020 14:03:55 +0900 Subject: [PATCH 403/655] Fix in a slightly different and hopefully more understandable way --- osu.Game/Screens/Select/SongSelect.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 9d3dc58a26..478c46fb36 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -426,7 +426,7 @@ namespace osu.Game.Screens.Select } /// - /// selection has been changed as the result of a user interaction. + /// Selection has been changed as the result of a user interaction. /// private void performUpdateSelected() { @@ -435,7 +435,7 @@ namespace osu.Game.Screens.Select selectionChangedDebounce?.Cancel(); - if (beatmap == null) + if (beatmapNoDebounce == null) run(); else selectionChangedDebounce = Scheduler.AddDelayed(run, 200); @@ -448,11 +448,11 @@ namespace osu.Game.Screens.Select { Mods.Value = Array.Empty(); - // the ruleset transfer may cause a deselection of the current beatmap (due to incompatibility). - // this can happen via Carousel.FlushPendingFilterOperations(). - // to ensure a good state, re-transfer no-debounce values. - performUpdateSelected(); - return; + // transferRulesetValue() may trigger a refilter. If the current selection does not match the new ruleset, we want to switch away from it. + // The default logic on WorkingBeatmap change is to switch to a matching ruleset (see workingBeatmapChanged()), but we don't want that here. + // We perform an early selection attempt and clear out the beatmap selection to avoid a second ruleset change (revert). + if (beatmap != null && !Carousel.SelectBeatmap(beatmap, false)) + beatmap = null; } // We may be arriving here due to another component changing the bindable Beatmap. @@ -716,7 +716,7 @@ namespace osu.Game.Screens.Select if (decoupledRuleset.Value?.Equals(Ruleset.Value) == true) return false; - Logger.Log($"decoupled ruleset transferred (\"{decoupledRuleset.Value}\" -> \"{Ruleset.Value}\""); + Logger.Log($"decoupled ruleset transferred (\"{decoupledRuleset.Value}\" -> \"{Ruleset.Value}\")"); rulesetNoDebounce = decoupledRuleset.Value = Ruleset.Value; // if we have a pending filter operation, we want to run it now. From b881293b98410e47dfde82f3f330f8aeda7d2c96 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 20 Apr 2020 14:08:23 +0900 Subject: [PATCH 404/655] Allow 10k to be played on a single stage --- .../Beatmaps/ManiaBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 67 +++++++++++++------ 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index d904474815..189dd17934 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps { TargetColumns = (int)Math.Max(1, roundedCircleSize); - if (TargetColumns >= 10) + if (TargetColumns > 10) { TargetColumns /= 2; Dual = true; diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 2bd88fee90..e8698ef01c 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -250,7 +250,7 @@ namespace osu.Game.Rulesets.Mania { get { - for (int i = 1; i <= 9; i++) + for (int i = 1; i <= 10; i++) yield return (int)PlayfieldType.Single + i; for (int i = 2; i <= 18; i += 2) yield return (int)PlayfieldType.Dual + i; @@ -262,26 +262,53 @@ namespace osu.Game.Rulesets.Mania switch (getPlayfieldType(variant)) { case PlayfieldType.Single: - return new VariantMappingGenerator + switch (variant) { - LeftKeys = new[] - { - InputKey.A, - InputKey.S, - InputKey.D, - InputKey.F - }, - RightKeys = new[] - { - InputKey.J, - InputKey.K, - InputKey.L, - InputKey.Semicolon - }, - SpecialKey = InputKey.Space, - SpecialAction = ManiaAction.Special1, - NormalActionStart = ManiaAction.Key1, - }.GenerateKeyBindingsFor(variant, out _); + case 10: + // 10K is special because it extents one key towards the centre of the keyboard (V/N), rather than towards the edges of the keyboard. + return new VariantMappingGenerator + { + LeftKeys = new[] + { + InputKey.A, + InputKey.S, + InputKey.D, + InputKey.F, + InputKey.V + }, + RightKeys = new[] + { + InputKey.N, + InputKey.J, + InputKey.K, + InputKey.L, + InputKey.Semicolon, + }, + NormalActionStart = ManiaAction.Key1, + }.GenerateKeyBindingsFor(variant, out _); + + default: + return new VariantMappingGenerator + { + LeftKeys = new[] + { + InputKey.A, + InputKey.S, + InputKey.D, + InputKey.F + }, + RightKeys = new[] + { + InputKey.J, + InputKey.K, + InputKey.L, + InputKey.Semicolon + }, + SpecialKey = InputKey.Space, + SpecialAction = ManiaAction.Special1, + NormalActionStart = ManiaAction.Key1, + }.GenerateKeyBindingsFor(variant, out _); + } case PlayfieldType.Dual: int keys = getDualStageKeyCount(variant); From 5b4f69bb8cabf89c41aff3b16c3c081975474236 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Mon, 20 Apr 2020 13:32:51 +0800 Subject: [PATCH 405/655] Moved flying hit objects to separate files --- .../Objects/Drawables/DrawableCentreHit.cs | 17 -------------- .../Drawables/DrawableFlyingCentreHit.cs | 23 +++++++++++++++++++ .../Objects/Drawables/DrawableFlyingRimHit.cs | 23 +++++++++++++++++++ .../Objects/Drawables/DrawableRimHit.cs | 14 ----------- 4 files changed, 46 insertions(+), 31 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingCentreHit.cs create mode 100644 osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingRimHit.cs diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs index b6f6f04821..4979135f50 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs @@ -2,10 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; namespace osu.Game.Rulesets.Taiko.Objects.Drawables @@ -26,18 +23,4 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables MainPiece.AccentColour = colours.PinkDarker; } } - - public class DrawableFlyingCentreHit : DrawableCentreHit - { - protected override void CheckForResult(bool userTriggered, double timeOffset) - { - ApplyResult(r => r.Type = HitResult.Good); - } - - public DrawableFlyingCentreHit(double time, bool isStrong = false) - : base(new IgnoreHit { StartTime = time, IsStrong = isStrong }) - { - HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - } - } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingCentreHit.cs new file mode 100644 index 0000000000..826a4467f8 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingCentreHit.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Taiko.Objects.Drawables +{ + public class DrawableFlyingCentreHit : DrawableCentreHit + { + protected override void CheckForResult(bool userTriggered, double timeOffset) + { + ApplyResult(r => r.Type = HitResult.Good); + } + + public DrawableFlyingCentreHit(double time, bool isStrong = false) + : base(new IgnoreHit { StartTime = time, IsStrong = isStrong }) + { + HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingRimHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingRimHit.cs new file mode 100644 index 0000000000..4a6fed8302 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingRimHit.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Taiko.Objects.Drawables +{ + public class DrawableFlyingRimHit : DrawableRimHit + { + protected override void CheckForResult(bool userTriggered, double timeOffset) + { + ApplyResult(r => r.Type = HitResult.Good); + } + + public DrawableFlyingRimHit(double time, bool isStrong = false) + : base(new IgnoreHit { StartTime = time, IsStrong = isStrong }) + { + HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs index e5cfc0562b..c10c195019 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs @@ -26,18 +26,4 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables MainPiece.AccentColour = colours.BlueDarker; } } - - public class DrawableFlyingRimHit : DrawableRimHit - { - protected override void CheckForResult(bool userTriggered, double timeOffset) - { - ApplyResult(r => r.Type = HitResult.Good); - } - - public DrawableFlyingRimHit(double time, bool isStrong = false) - : base(new IgnoreHit { StartTime = time, IsStrong = isStrong }) - { - HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - } - } } From 5d96d672268cbdc0b7cb20d4a9adc47a4acd451c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 20 Apr 2020 14:40:37 +0900 Subject: [PATCH 406/655] Add special key definition just for sanity --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index e8698ef01c..2147776d03 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -284,6 +284,8 @@ namespace osu.Game.Rulesets.Mania InputKey.L, InputKey.Semicolon, }, + SpecialKey = InputKey.Space, + SpecialAction = ManiaAction.Special1, NormalActionStart = ManiaAction.Key1, }.GenerateKeyBindingsFor(variant, out _); From 5464746d3d899a136bfa392789d351559d6f5752 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Apr 2020 15:25:58 +0900 Subject: [PATCH 407/655] Switch to using CompositeDrawable --- .../Dashboard/Friends/FriendDisplay.cs | 23 ++++++++----------- osu.Game/Overlays/OverlayView.cs | 14 ++--------- 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs index 9764f82199..7c4a0a4164 100644 --- a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs +++ b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs @@ -34,16 +34,17 @@ namespace osu.Game.Overlays.Dashboard.Friends private Drawable currentContent; - private readonly FriendOnlineStreamControl onlineStreamControl; - private readonly Box background; - private readonly Box controlBackground; - private readonly UserListToolbar userListToolbar; - private readonly Container itemsPlaceholder; - private readonly LoadingLayer loading; + private FriendOnlineStreamControl onlineStreamControl; + private Box background; + private Box controlBackground; + private UserListToolbar userListToolbar; + private Container itemsPlaceholder; + private LoadingLayer loading; - public FriendDisplay() + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) { - AddRange(new Drawable[] + InternalChildren = new Drawable[] { new Container { @@ -121,12 +122,8 @@ namespace osu.Game.Overlays.Dashboard.Friends } } } - }); - } + }; - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { background.Colour = colourProvider.Background4; controlBackground.Colour = colourProvider.Background5; } diff --git a/osu.Game/Overlays/OverlayView.cs b/osu.Game/Overlays/OverlayView.cs index e3a07fc2de..724658f22f 100644 --- a/osu.Game/Overlays/OverlayView.cs +++ b/osu.Game/Overlays/OverlayView.cs @@ -9,29 +9,19 @@ using osu.Game.Online.API; namespace osu.Game.Overlays { /// - /// Drawable which used to represent online content in . + /// A subview containing online content, to be displayed inside a . /// /// Response type - public abstract class OverlayView : Container, IOnlineComponent + public abstract class OverlayView : CompositeDrawable, IOnlineComponent where T : class { [Resolved] protected IAPIProvider API { get; private set; } - protected override Container Content => content; - - private readonly FillFlowContainer content; - protected OverlayView() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - - AddInternal(content = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }); } protected override void LoadComplete() From 99e13b8ed9c124484b4410eb9b03da4f2be03997 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Apr 2020 15:32:50 +0900 Subject: [PATCH 408/655] Add better xml documentation and extract fetch method --- osu.Game/Overlays/OverlayView.cs | 33 ++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/OverlayView.cs b/osu.Game/Overlays/OverlayView.cs index 724658f22f..3e2c54c726 100644 --- a/osu.Game/Overlays/OverlayView.cs +++ b/osu.Game/Overlays/OverlayView.cs @@ -11,13 +11,18 @@ namespace osu.Game.Overlays /// /// A subview containing online content, to be displayed inside a . /// - /// Response type + /// + /// Automatically performs a data fetch on load. + /// + /// The type of the API response. public abstract class OverlayView : CompositeDrawable, IOnlineComponent where T : class { [Resolved] protected IAPIProvider API { get; private set; } + private APIRequest request; + protected OverlayView() { RelativeSizeAxes = Axes.X; @@ -30,20 +35,36 @@ namespace osu.Game.Overlays API.Register(this); } - private APIRequest request; - + /// + /// Create the API request for fetching data. + /// protected abstract APIRequest CreateRequest(); + /// + /// Fired when results arrive from the main API request. + /// + /// protected abstract void OnSuccess(T response); + /// + /// Force a re-request for data from the API. + /// + protected void PerformFetch() + { + request?.Cancel(); + + request = CreateRequest(); + request.Success += response => Schedule(() => OnSuccess(response)); + + API.Queue(request); + } + public virtual void APIStateChanged(IAPIProvider api, APIState state) { switch (state) { case APIState.Online: - request = CreateRequest(); - request.Success += response => Schedule(() => OnSuccess(response)); - api.Queue(request); + PerformFetch(); break; } } From 6b89c638c9aa2db2744a4d65bad92774e1e49a3a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Apr 2020 15:34:48 +0900 Subject: [PATCH 409/655] Move load to bdl --- osu.Game/Overlays/DashboardOverlay.cs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/DashboardOverlay.cs b/osu.Game/Overlays/DashboardOverlay.cs index 86c0f3bd83..a72c3f4fa5 100644 --- a/osu.Game/Overlays/DashboardOverlay.cs +++ b/osu.Game/Overlays/DashboardOverlay.cs @@ -19,14 +19,19 @@ namespace osu.Game.Overlays { private CancellationTokenSource cancellationToken; - private readonly Box background; - private readonly Container content; - private readonly DashboardOverlayHeader header; - private readonly LoadingLayer loading; - private readonly OverlayScrollContainer scrollFlow; + private Box background; + private Container content; + private DashboardOverlayHeader header; + private LoadingLayer loading; + private OverlayScrollContainer scrollFlow; public DashboardOverlay() : base(OverlayColourScheme.Purple) + { + } + + [BackgroundDependencyLoader] + private void load() { Children = new Drawable[] { @@ -61,17 +66,14 @@ namespace osu.Game.Overlays }, loading = new LoadingLayer(content), }; - } - [BackgroundDependencyLoader] - private void load() - { background.Colour = ColourProvider.Background5; } protected override void LoadComplete() { base.LoadComplete(); + header.Current.BindValueChanged(onTabChanged); } From e61a90d4695ca1ca8ee6aa479b541d29e6cf08f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Apr 2020 15:50:48 +0900 Subject: [PATCH 410/655] Throw instead of returning zero --- osu.Game/Utils/OrderAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Utils/OrderAttribute.cs b/osu.Game/Utils/OrderAttribute.cs index 4959caa726..aded7f9814 100644 --- a/osu.Game/Utils/OrderAttribute.cs +++ b/osu.Game/Utils/OrderAttribute.cs @@ -29,7 +29,7 @@ namespace osu.Game.Utils if (type.GetField(i.ToString()).GetCustomAttributes(typeof(OrderAttribute), false).FirstOrDefault() is OrderAttribute attr) return attr.Order; - return 0; + throw new ArgumentException($"Not all values of {nameof(T)} have {nameof(OrderAttribute)} specified."); }); } } From 801f02a3d7bb28b842a7dfd7e1d39b404b7d9690 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Apr 2020 17:48:02 +0900 Subject: [PATCH 411/655] Fix inline executions of APIRequest.Perform not getting result populated early enough --- osu.Game/Online/API/APIRequest.cs | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 47600e4f68..0bba04cac3 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -18,24 +18,32 @@ namespace osu.Game.Online.API public T Result { get; private set; } - protected APIRequest() - { - base.Success += () => TriggerSuccess(((OsuJsonWebRequest)WebRequest)?.ResponseObject); - } - /// /// Invoked on successful completion of an API request. /// This will be scheduled to the API's internal scheduler (run on update thread automatically). /// public new event APISuccessHandler Success; + protected override void PostProcess() + { + base.PostProcess(); + Result = ((OsuJsonWebRequest)WebRequest)?.ResponseObject; + } + internal void TriggerSuccess(T result) { if (Result != null) throw new InvalidOperationException("Attempted to trigger success more than once"); Result = result; - Success?.Invoke(result); + + TriggerSuccess(); + } + + internal override void TriggerSuccess() + { + base.TriggerSuccess(); + Success?.Invoke(Result); } } @@ -99,6 +107,8 @@ namespace osu.Game.Online.API if (checkAndScheduleFailure()) return; + PostProcess(); + API.Schedule(delegate { if (cancelled) return; @@ -107,7 +117,14 @@ namespace osu.Game.Online.API }); } - internal void TriggerSuccess() + /// + /// Perform any post-processing actions after a successful request. + /// + protected virtual void PostProcess() + { + } + + internal virtual void TriggerSuccess() { Success?.Invoke(); } From 3f3ff5fdb1b145f7ac885a691f7f0fbe93d1b99c Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2020 09:24:40 +0000 Subject: [PATCH 412/655] Bump Humanizer from 2.7.9 to 2.8.2 Bumps [Humanizer](https://github.com/Humanizr/Humanizer) from 2.7.9 to 2.8.2. - [Release notes](https://github.com/Humanizr/Humanizer/releases) - [Changelog](https://github.com/Humanizr/Humanizer/blob/master/release_notes.md) - [Commits](https://github.com/Humanizr/Humanizer/compare/v2.7.9...v2.8.2) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 5facb04117..35ee0864e1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -19,7 +19,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index dda1ee5c42..0200fca9a3 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -76,7 +76,7 @@ - + From b3d4b4a3f42696861d8c17cd395423e4a2656f74 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Apr 2020 18:25:39 +0900 Subject: [PATCH 413/655] Add back missing fill flow --- .../Dashboard/Friends/FriendDisplay.cs | 124 +++++++++--------- 1 file changed, 65 insertions(+), 59 deletions(-) diff --git a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs index 7c4a0a4164..79fda99c73 100644 --- a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs +++ b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs @@ -44,78 +44,84 @@ namespace osu.Game.Overlays.Dashboard.Friends [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - InternalChildren = new Drawable[] + InternalChild = new FillFlowContainer { - new Container + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] + new Container { - controlBackground = new Box + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding + controlBackground = new Box { - Top = 20, - Horizontal = 45 + RelativeSizeAxes = Axes.Both }, - Child = onlineStreamControl = new FriendOnlineStreamControl(), - } - } - }, - new Container - { - Name = "User List", - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Margin = new MarginPadding { Bottom = 20 }, - Children = new Drawable[] + new Container { - new Container + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding - { - Horizontal = 40, - Vertical = 20 - }, - Child = userListToolbar = new UserListToolbar - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - } + Top = 20, + Horizontal = 45 }, - new Container + Child = onlineStreamControl = new FriendOnlineStreamControl(), + } + } + }, + new Container + { + Name = "User List", + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Margin = new MarginPadding { Bottom = 20 }, + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] + new Container { - itemsPlaceholder = new Container + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = 50 } + Horizontal = 40, + Vertical = 20 }, - loading = new LoadingLayer(itemsPlaceholder) + Child = userListToolbar = new UserListToolbar + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + } + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + itemsPlaceholder = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = 50 } + }, + loading = new LoadingLayer(itemsPlaceholder) + } } } } From 8ebc2ae03dc6080028f5406d361f42f8d68855cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Apr 2020 20:48:35 +0900 Subject: [PATCH 414/655] Never run subtree masking --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index b14927bcd5..e847dcec40 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -398,7 +398,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } } - public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => AllJudged && base.UpdateSubTreeMasking(source, maskingBounds); + public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => false; protected override void UpdateAfterChildren() { From a541f9268205f76c1eb4321faefeb1721d4f0f8f Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 20 Apr 2020 13:56:15 +0200 Subject: [PATCH 415/655] Resolve ruleset dependencies on game core / framework assemblies by checking already loaded assemblies in AppDomain. --- osu.Game/Rulesets/RulesetStore.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 7e165311a3..543134cfb4 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -62,10 +62,13 @@ namespace osu.Game.Rulesets var asm = new AssemblyName(args.Name); // the requesting assembly may be located out of the executable's base directory, thus requiring manual resolving of its dependencies. - // this assumes the only explicit dependency of the ruleset is the game core assembly. - // the ruleset dependency on the game core assembly requires manual resolving, transitive dependencies should be resolved automatically - if (asm.Name.Equals(typeof(OsuGame).Assembly.GetName().Name, StringComparison.Ordinal)) - return Assembly.GetExecutingAssembly(); + // this attempts resolving the ruleset dependencies on game core and framework assemblies by returning assemblies with the same assembly name + // already loaded in the AppDomain. + foreach (var curAsm in AppDomain.CurrentDomain.GetAssemblies()) + { + if (asm.Name.Equals(curAsm.GetName().Name, StringComparison.Ordinal)) + return curAsm; + } return loadedAssemblies.Keys.FirstOrDefault(a => a.FullName == asm.FullName); } From 4e271ff46fcae5132bcc7ef235117518cd8f1268 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 20 Apr 2020 21:28:36 +0900 Subject: [PATCH 416/655] Add support for 10K mod + 20K dual stages --- .../DualStageVariantGenerator.cs | 64 ++++++++ osu.Game.Rulesets.Mania/ManiaInputManager.cs | 6 + osu.Game.Rulesets.Mania/ManiaRuleset.cs | 152 +----------------- osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs | 13 ++ .../SingleStageVariantGenerator.cs | 41 +++++ .../VariantMappingGenerator.cs | 61 +++++++ 6 files changed, 189 insertions(+), 148 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/DualStageVariantGenerator.cs create mode 100644 osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs create mode 100644 osu.Game.Rulesets.Mania/SingleStageVariantGenerator.cs create mode 100644 osu.Game.Rulesets.Mania/VariantMappingGenerator.cs diff --git a/osu.Game.Rulesets.Mania/DualStageVariantGenerator.cs b/osu.Game.Rulesets.Mania/DualStageVariantGenerator.cs new file mode 100644 index 0000000000..8d39e08b26 --- /dev/null +++ b/osu.Game.Rulesets.Mania/DualStageVariantGenerator.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Input.Bindings; + +namespace osu.Game.Rulesets.Mania +{ + public class DualStageVariantGenerator + { + private readonly int singleStageVariant; + private readonly InputKey[] stage1LeftKeys; + private readonly InputKey[] stage1RightKeys; + private readonly InputKey[] stage2LeftKeys; + private readonly InputKey[] stage2RightKeys; + + public DualStageVariantGenerator(int singleStageVariant) + { + this.singleStageVariant = singleStageVariant; + + // 10K is special because it expands towards the centre of the keyboard (VM/BN), rather than towards the edges of the keyboard. + if (singleStageVariant == 10) + { + stage1LeftKeys = new[] { InputKey.Q, InputKey.W, InputKey.E, InputKey.R, InputKey.V }; + stage1RightKeys = new[] { InputKey.M, InputKey.I, InputKey.O, InputKey.P, InputKey.BracketLeft }; + + stage2LeftKeys = new[] { InputKey.S, InputKey.D, InputKey.F, InputKey.G, InputKey.B }; + stage2RightKeys = new[] { InputKey.N, InputKey.J, InputKey.K, InputKey.L, InputKey.Semicolon }; + } + else + { + stage1LeftKeys = new[] { InputKey.Q, InputKey.W, InputKey.E, InputKey.R }; + stage1RightKeys = new[] { InputKey.I, InputKey.O, InputKey.P, InputKey.BracketLeft }; + + stage2LeftKeys = new[] { InputKey.S, InputKey.D, InputKey.F, InputKey.G }; + stage2RightKeys = new[] { InputKey.J, InputKey.K, InputKey.L, InputKey.Semicolon }; + } + } + + public IEnumerable GenerateMappings() + { + var stage1Bindings = new VariantMappingGenerator + { + LeftKeys = stage1LeftKeys, + RightKeys = stage1RightKeys, + SpecialKey = InputKey.V, + SpecialAction = ManiaAction.Special1, + NormalActionStart = ManiaAction.Key1 + }.GenerateKeyBindingsFor(singleStageVariant, out var nextNormal); + + var stage2Bindings = new VariantMappingGenerator + { + LeftKeys = stage2LeftKeys, + RightKeys = stage2RightKeys, + SpecialKey = InputKey.B, + SpecialAction = ManiaAction.Special2, + NormalActionStart = nextNormal + }.GenerateKeyBindingsFor(singleStageVariant, out _); + + return stage1Bindings.Concat(stage2Bindings); + } + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaInputManager.cs b/osu.Game.Rulesets.Mania/ManiaInputManager.cs index 292990fd7e..186fc4b15d 100644 --- a/osu.Game.Rulesets.Mania/ManiaInputManager.cs +++ b/osu.Game.Rulesets.Mania/ManiaInputManager.cs @@ -78,5 +78,11 @@ namespace osu.Game.Rulesets.Mania [Description("Key 18")] Key18, + + [Description("Key 19")] + Key19, + + [Description("Key 20")] + Key20, } } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 2147776d03..21315e4bfb 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -202,6 +202,7 @@ namespace osu.Game.Rulesets.Mania new ManiaModKey7(), new ManiaModKey8(), new ManiaModKey9(), + new ManiaModKey10(), new ManiaModKey1(), new ManiaModKey2(), new ManiaModKey3()), @@ -252,7 +253,7 @@ namespace osu.Game.Rulesets.Mania { for (int i = 1; i <= 10; i++) yield return (int)PlayfieldType.Single + i; - for (int i = 2; i <= 18; i += 2) + for (int i = 2; i <= 20; i += 2) yield return (int)PlayfieldType.Dual + i; } } @@ -262,102 +263,10 @@ namespace osu.Game.Rulesets.Mania switch (getPlayfieldType(variant)) { case PlayfieldType.Single: - switch (variant) - { - case 10: - // 10K is special because it extents one key towards the centre of the keyboard (V/N), rather than towards the edges of the keyboard. - return new VariantMappingGenerator - { - LeftKeys = new[] - { - InputKey.A, - InputKey.S, - InputKey.D, - InputKey.F, - InputKey.V - }, - RightKeys = new[] - { - InputKey.N, - InputKey.J, - InputKey.K, - InputKey.L, - InputKey.Semicolon, - }, - SpecialKey = InputKey.Space, - SpecialAction = ManiaAction.Special1, - NormalActionStart = ManiaAction.Key1, - }.GenerateKeyBindingsFor(variant, out _); - - default: - return new VariantMappingGenerator - { - LeftKeys = new[] - { - InputKey.A, - InputKey.S, - InputKey.D, - InputKey.F - }, - RightKeys = new[] - { - InputKey.J, - InputKey.K, - InputKey.L, - InputKey.Semicolon - }, - SpecialKey = InputKey.Space, - SpecialAction = ManiaAction.Special1, - NormalActionStart = ManiaAction.Key1, - }.GenerateKeyBindingsFor(variant, out _); - } + return new SingleStageVariantGenerator(variant).GenerateMappings(); case PlayfieldType.Dual: - int keys = getDualStageKeyCount(variant); - - var stage1Bindings = new VariantMappingGenerator - { - LeftKeys = new[] - { - InputKey.Q, - InputKey.W, - InputKey.E, - InputKey.R, - }, - RightKeys = new[] - { - InputKey.X, - InputKey.C, - InputKey.V, - InputKey.B - }, - SpecialKey = InputKey.S, - SpecialAction = ManiaAction.Special1, - NormalActionStart = ManiaAction.Key1 - }.GenerateKeyBindingsFor(keys, out var nextNormal); - - var stage2Bindings = new VariantMappingGenerator - { - LeftKeys = new[] - { - InputKey.Number7, - InputKey.Number8, - InputKey.Number9, - InputKey.Number0 - }, - RightKeys = new[] - { - InputKey.K, - InputKey.L, - InputKey.Semicolon, - InputKey.Quote - }, - SpecialKey = InputKey.I, - SpecialAction = ManiaAction.Special2, - NormalActionStart = nextNormal - }.GenerateKeyBindingsFor(keys, out _); - - return stage1Bindings.Concat(stage2Bindings); + return new DualStageVariantGenerator(getDualStageKeyCount(variant)).GenerateMappings(); } return Array.Empty(); @@ -393,59 +302,6 @@ namespace osu.Game.Rulesets.Mania { return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast().OrderByDescending(i => i).First(v => variant >= v); } - - private class VariantMappingGenerator - { - /// - /// All the s available to the left hand. - /// - public InputKey[] LeftKeys; - - /// - /// All the s available to the right hand. - /// - public InputKey[] RightKeys; - - /// - /// The for the special key. - /// - public InputKey SpecialKey; - - /// - /// The at which the normal columns should begin. - /// - public ManiaAction NormalActionStart; - - /// - /// The for the special column. - /// - public ManiaAction SpecialAction; - - /// - /// Generates a list of s for a specific number of columns. - /// - /// The number of columns that need to be bound. - /// The next to use for normal columns. - /// The keybindings. - public IEnumerable GenerateKeyBindingsFor(int columns, out ManiaAction nextNormalAction) - { - ManiaAction currentNormalAction = NormalActionStart; - - var bindings = new List(); - - for (int i = LeftKeys.Length - columns / 2; i < LeftKeys.Length; i++) - bindings.Add(new KeyBinding(LeftKeys[i], currentNormalAction++)); - - if (columns % 2 == 1) - bindings.Add(new KeyBinding(SpecialKey, SpecialAction)); - - for (int i = 0; i < columns / 2; i++) - bindings.Add(new KeyBinding(RightKeys[i], currentNormalAction++)); - - nextNormalAction = currentNormalAction; - return bindings; - } - } } public enum PlayfieldType diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs new file mode 100644 index 0000000000..684370fc3d --- /dev/null +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs @@ -0,0 +1,13 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModKey10 : ManiaKeyMod + { + public override int KeyCount => 10; + public override string Name => "Ten Keys"; + public override string Acronym => "10K"; + public override string Description => @"Play with ten keys."; + } +} diff --git a/osu.Game.Rulesets.Mania/SingleStageVariantGenerator.cs b/osu.Game.Rulesets.Mania/SingleStageVariantGenerator.cs new file mode 100644 index 0000000000..2069329d9a --- /dev/null +++ b/osu.Game.Rulesets.Mania/SingleStageVariantGenerator.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Input.Bindings; + +namespace osu.Game.Rulesets.Mania +{ + public class SingleStageVariantGenerator + { + private readonly int variant; + private readonly InputKey[] leftKeys; + private readonly InputKey[] rightKeys; + + public SingleStageVariantGenerator(int variant) + { + this.variant = variant; + + // 10K is special because it expands towards the centre of the keyboard (V/N), rather than towards the edges of the keyboard. + if (variant == 10) + { + leftKeys = new[] { InputKey.A, InputKey.S, InputKey.D, InputKey.F, InputKey.V }; + rightKeys = new[] { InputKey.N, InputKey.J, InputKey.K, InputKey.L, InputKey.Semicolon }; + } + else + { + leftKeys = new[] { InputKey.A, InputKey.S, InputKey.D, InputKey.F }; + rightKeys = new[] { InputKey.J, InputKey.K, InputKey.L, InputKey.Semicolon }; + } + } + + public IEnumerable GenerateMappings() => new VariantMappingGenerator + { + LeftKeys = leftKeys, + RightKeys = rightKeys, + SpecialKey = InputKey.Space, + SpecialAction = ManiaAction.Special1, + NormalActionStart = ManiaAction.Key1, + }.GenerateKeyBindingsFor(variant, out _); + } +} diff --git a/osu.Game.Rulesets.Mania/VariantMappingGenerator.cs b/osu.Game.Rulesets.Mania/VariantMappingGenerator.cs new file mode 100644 index 0000000000..878d1088a6 --- /dev/null +++ b/osu.Game.Rulesets.Mania/VariantMappingGenerator.cs @@ -0,0 +1,61 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Input.Bindings; + +namespace osu.Game.Rulesets.Mania +{ + public class VariantMappingGenerator + { + /// + /// All the s available to the left hand. + /// + public InputKey[] LeftKeys; + + /// + /// All the s available to the right hand. + /// + public InputKey[] RightKeys; + + /// + /// The for the special key. + /// + public InputKey SpecialKey; + + /// + /// The at which the normal columns should begin. + /// + public ManiaAction NormalActionStart; + + /// + /// The for the special column. + /// + public ManiaAction SpecialAction; + + /// + /// Generates a list of s for a specific number of columns. + /// + /// The number of columns that need to be bound. + /// The next to use for normal columns. + /// The keybindings. + public IEnumerable GenerateKeyBindingsFor(int columns, out ManiaAction nextNormalAction) + { + ManiaAction currentNormalAction = NormalActionStart; + + var bindings = new List(); + + for (int i = LeftKeys.Length - columns / 2; i < LeftKeys.Length; i++) + bindings.Add(new KeyBinding(LeftKeys[i], currentNormalAction++)); + + if (columns % 2 == 1) + bindings.Add(new KeyBinding(SpecialKey, SpecialAction)); + + for (int i = 0; i < columns / 2; i++) + bindings.Add(new KeyBinding(RightKeys[i], currentNormalAction++)); + + nextNormalAction = currentNormalAction; + return bindings; + } + } +} From 9b6e26583bdb69c01219ab2b9ceb8d554883d1f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Apr 2020 21:42:43 +0200 Subject: [PATCH 417/655] Add xmldocs --- osu.Game/Screens/Select/BeatmapCarousel.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index cf3a5a7199..e21faf321e 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -28,7 +28,14 @@ namespace osu.Game.Screens.Select { public class BeatmapCarousel : CompositeDrawable, IKeyBindingHandler { + /// + /// Height of the area above the carousel that should be treated as visible due to transparency of elements in front of it. + /// public float BleedTop; + + /// + /// Height of the area below the carousel that should be treated as visible due to transparency of elements in front of it. + /// public float BleedBottom; /// From e3cd3cf1da7e8e9458697caccf6bb30c008d5791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Apr 2020 21:43:07 +0200 Subject: [PATCH 418/655] Convert to auto-properties --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index e21faf321e..1bbd7c1270 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -31,12 +31,12 @@ namespace osu.Game.Screens.Select /// /// Height of the area above the carousel that should be treated as visible due to transparency of elements in front of it. /// - public float BleedTop; + public float BleedTop { get; set; } /// /// Height of the area below the carousel that should be treated as visible due to transparency of elements in front of it. /// - public float BleedBottom; + public float BleedBottom { get; set; } /// /// Triggered when the loaded change and are completely loaded. From 4c689c6ad2585087d8495514f755e4c25579b9d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Apr 2020 10:56:04 +0900 Subject: [PATCH 419/655] Add constant for max stage keys --- .../Beatmaps/ManiaBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 189dd17934..4187e39b43 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps { TargetColumns = (int)Math.Max(1, roundedCircleSize); - if (TargetColumns > 10) + if (TargetColumns > ManiaRuleset.MAX_STAGE_KEYS) { TargetColumns /= 2; Dual = true; diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 21315e4bfb..a37aaa8cc4 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -35,6 +35,11 @@ namespace osu.Game.Rulesets.Mania { public class ManiaRuleset : Ruleset, ILegacyRuleset { + /// + /// The maximum number of supported keys in a single stage. + /// + public const int MAX_STAGE_KEYS = 10; + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableManiaRuleset(this, beatmap, mods); public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(); @@ -251,9 +256,9 @@ namespace osu.Game.Rulesets.Mania { get { - for (int i = 1; i <= 10; i++) + for (int i = 1; i <= MAX_STAGE_KEYS; i++) yield return (int)PlayfieldType.Single + i; - for (int i = 2; i <= 20; i += 2) + for (int i = 2; i <= MAX_STAGE_KEYS * 2; i += 2) yield return (int)PlayfieldType.Dual + i; } } From a91c63819b0f781f1fdba608aa398a6612bb2e61 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Apr 2020 11:51:20 +0900 Subject: [PATCH 420/655] Refactor updateCompletionState implementation for legibility and code share --- osu.Game/Screens/Play/Player.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 2f3807753a..ece4c6307e 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -423,8 +423,6 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; - // cancel push delegate in case judges reverted - // after delegate may have already been scheduled. if (!completionState.NewValue) { completionProgressDelegate?.Cancel(); @@ -433,8 +431,11 @@ namespace osu.Game.Screens.Play return; } + if (completionProgressDelegate != null) + throw new InvalidOperationException($"{nameof(updateCompletionState)} was fired more than once"); + // Only show the completion screen if the player hasn't failed - if (HealthProcessor.HasFailed || completionProgressDelegate != null) + if (HealthProcessor.HasFailed) return; ValidForResume = false; @@ -442,7 +443,7 @@ namespace osu.Game.Screens.Play if (!showResults) return; using (BeginDelayedSequence(RESULTS_DISPLAY_DELAY)) - scheduleGotoRanking(); + completionProgressDelegate = Schedule(GotoRanking); } protected virtual ScoreInfo CreateScore() @@ -694,12 +695,6 @@ namespace osu.Game.Screens.Play storyboardReplacesBackground.Value = false; } - private void scheduleGotoRanking() - { - completionProgressDelegate?.Cancel(); - completionProgressDelegate = Schedule(GotoRanking); - } - #endregion } } From 3b0099c687edce710dfcac9ac2d8d0e1a67153f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Apr 2020 12:26:43 +0900 Subject: [PATCH 421/655] Refactor tests --- .../TestSceneCompletionCancellation.cs | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs index a3fb17942f..512584bd42 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs @@ -46,46 +46,54 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestCancelCompletionOnRewind() { - cancelCompletionSteps(); + complete(); + cancel(); - AddAssert("no attempt to push ranking", () => !((FakeRankingPushPlayer)Player).GotoRankingInvoked); + checkNoRanking(); } [Test] public void TestReCompleteAfterCancellation() { - cancelCompletionSteps(); - - // Attempt completing again. - AddStep("seek to completion again", () => track.Seek(5000)); - AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); + complete(); + cancel(); + complete(); AddUntilStep("attempted to push ranking", () => ((FakeRankingPushPlayer)Player).GotoRankingInvoked); } /// - /// Tests whether can still pause after cancelling completion - /// by reverting back to true. + /// Tests whether can still pause after cancelling completion by reverting back to true. /// [Test] public void TestCanPauseAfterCancellation() { - cancelCompletionSteps(); + complete(); + cancel(); AddStep("pause", () => Player.Pause()); AddAssert("paused successfully", () => Player.GameplayClockContainer.IsPaused.Value); + + checkNoRanking(); } - private void cancelCompletionSteps() + private void complete() { AddStep("seek to completion", () => track.Seek(5000)); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); + } + private void cancel() + { AddStep("rewind to cancel", () => track.Seek(4000)); AddUntilStep("completion cleared by processor", () => !Player.ScoreProcessor.HasCompleted.Value); + } + private void checkNoRanking() + { // wait to ensure there was no attempt of pushing the results screen. AddWaitStep("wait", resultsDisplayWaitCount); + AddAssert("no attempt to push ranking", () => !((FakeRankingPushPlayer)Player).GotoRankingInvoked); } protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) From 9252b7876b6221c5a9cc3128de91796278a4d2dd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Apr 2020 13:58:23 +0900 Subject: [PATCH 422/655] Don't serialise AllControlPoints --- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index d33a922a32..af6ca24165 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -56,6 +56,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// All control points, of all types. /// + [JsonIgnore] public IEnumerable AllControlPoints => Groups.SelectMany(g => g.ControlPoints).ToArray(); /// From 72fb34f82cf1ae65ad7a96ed98a40a12aae7b26b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Apr 2020 14:19:05 +0900 Subject: [PATCH 423/655] Fix overriding control points incorrectly --- .../Formats/LegacyBeatmapDecoderTest.cs | 5 ++++ .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 30 +++++++++---------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 33f484a9aa..acb30a6277 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -241,6 +241,11 @@ namespace osu.Game.Tests.Beatmaps.Formats { var controlPoints = decoder.Decode(stream).ControlPointInfo; + Assert.That(controlPoints.TimingPoints.Count, Is.EqualTo(4)); + Assert.That(controlPoints.DifficultyPoints.Count, Is.EqualTo(3)); + Assert.That(controlPoints.EffectPoints.Count, Is.EqualTo(3)); + Assert.That(controlPoints.SamplePoints.Count, Is.EqualTo(3)); + Assert.That(controlPoints.DifficultyPointAt(500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1)); Assert.That(controlPoints.DifficultyPointAt(1500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1)); Assert.That(controlPoints.DifficultyPointAt(2500).SpeedMultiplier, Is.EqualTo(0.75).Within(0.1)); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 33bb9774df..388abf4648 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -386,17 +386,10 @@ namespace osu.Game.Beatmaps.Formats SampleVolume = sampleVolume, CustomSampleBank = customSampleBank, }, timingChange); - - // To handle the scenario where a non-timing line shares the same time value as a subsequent timing line but - // appears earlier in the file, we buffer non-timing control points and rewrite them *after* control points from the timing line - // with the same time value (allowing them to overwrite as necessary). - // - // The expected outcome is that we prefer the non-timing line's adjustments over the timing line's adjustments when time is equal. - if (timingChange) - flushPendingPoints(); } private readonly List pendingControlPoints = new List(); + private readonly HashSet pendingControlPointTypes = new HashSet(); private double pendingControlPointsTime; private void addControlPoint(double time, ControlPoint point, bool timingChange) @@ -405,21 +398,28 @@ namespace osu.Game.Beatmaps.Formats flushPendingPoints(); if (timingChange) - { - beatmap.ControlPointInfo.Add(time, point); - return; - } + pendingControlPoints.Insert(0, point); + else + pendingControlPoints.Add(point); - pendingControlPoints.Add(point); pendingControlPointsTime = time; } private void flushPendingPoints() { - foreach (var p in pendingControlPoints) - beatmap.ControlPointInfo.Add(pendingControlPointsTime, p); + // Changes from non-timing-points are added to the end of the list (see addControlPoint()) and should override any changes from timing-points (added to the start of the list). + for (int i = pendingControlPoints.Count - 1; i >= 0; i--) + { + var type = pendingControlPoints[i].GetType(); + if (pendingControlPointTypes.Contains(type)) + continue; + + pendingControlPointTypes.Add(type); + beatmap.ControlPointInfo.Add(pendingControlPointsTime, pendingControlPoints[i]); + } pendingControlPoints.Clear(); + pendingControlPointTypes.Clear(); } private void handleHitObject(string line) From 89320b510c10a2aad660ab9c3938565828520fde Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Apr 2020 15:13:19 +0900 Subject: [PATCH 424/655] Apply class renaming --- .../Online/TestSceneBeatmapListingOverlay.cs | 2 +- ...> TestSceneBeatmapListingSearchControl.cs} | 26 ++++++++--------- ... TestSceneBeatmapListingSortTabControl.cs} | 5 ++-- ...dler.cs => BeatmapListingFilterControl.cs} | 28 +++++++++---------- ...tion.cs => BeatmapListingSearchControl.cs} | 4 +-- osu.Game/Overlays/BeatmapListingOverlay.cs | 2 +- 6 files changed, 33 insertions(+), 34 deletions(-) rename osu.Game.Tests/Visual/UserInterface/{TestSceneBeatmapListingSearchSection.cs => TestSceneBeatmapListingSearchControl.cs} (76%) rename osu.Game.Tests/Visual/UserInterface/{TestSceneBeatmapListingSort.cs => TestSceneBeatmapListingSortTabControl.cs} (91%) rename osu.Game/Overlays/BeatmapListing/{BeatmapListingSearchHandler.cs => BeatmapListingFilterControl.cs} (85%) rename osu.Game/Overlays/BeatmapListing/{BeatmapListingSearchSection.cs => BeatmapListingSearchControl.cs} (97%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index f80687e142..64d1a9ddcd 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Online public override IReadOnlyList RequiredTypes => new[] { typeof(BeatmapListingOverlay), - typeof(BeatmapListingSearchHandler) + typeof(BeatmapListingFilterControl) }; protected override bool UseOnlineAPI => true; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchSection.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs similarity index 76% rename from osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchSection.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index 69e3fbd75f..d6ede950df 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchSection.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -15,19 +15,19 @@ using osuTK; namespace osu.Game.Tests.Visual.UserInterface { - public class TestSceneBeatmapListingSearchSection : OsuTestScene + public class TestSceneBeatmapListingSearchControl : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { - typeof(BeatmapListingSearchSection), + typeof(BeatmapListingSearchControl), }; [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); - private readonly BeatmapListingSearchSection section; + private readonly BeatmapListingSearchControl control; - public TestSceneBeatmapListingSearchSection() + public TestSceneBeatmapListingSearchControl() { OsuSpriteText query; OsuSpriteText ruleset; @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.UserInterface OsuSpriteText genre; OsuSpriteText language; - Add(section = new BeatmapListingSearchSection + Add(control = new BeatmapListingSearchControl { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -56,19 +56,19 @@ namespace osu.Game.Tests.Visual.UserInterface } }); - section.Query.BindValueChanged(q => query.Text = $"Query: {q.NewValue}", true); - section.Ruleset.BindValueChanged(r => ruleset.Text = $"Ruleset: {r.NewValue}", true); - section.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true); - section.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); - section.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true); + control.Query.BindValueChanged(q => query.Text = $"Query: {q.NewValue}", true); + control.Ruleset.BindValueChanged(r => ruleset.Text = $"Ruleset: {r.NewValue}", true); + control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true); + control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); + control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true); } [Test] public void TestCovers() { - AddStep("Set beatmap", () => section.BeatmapSet = beatmap_set); - AddStep("Set beatmap (no cover)", () => section.BeatmapSet = no_cover_beatmap_set); - AddStep("Set null beatmap", () => section.BeatmapSet = null); + AddStep("Set beatmap", () => control.BeatmapSet = beatmap_set); + AddStep("Set beatmap (no cover)", () => control.BeatmapSet = no_cover_beatmap_set); + AddStep("Set null beatmap", () => control.BeatmapSet = null); } private static readonly BeatmapSetInfo beatmap_set = new BeatmapSetInfo diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSort.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSortTabControl.cs similarity index 91% rename from osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSort.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSortTabControl.cs index a5fa085abf..f643d4e3fe 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSort.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSortTabControl.cs @@ -13,18 +13,17 @@ using osuTK; namespace osu.Game.Tests.Visual.UserInterface { - public class TestSceneBeatmapListingSort : OsuTestScene + public class TestSceneBeatmapListingSortTabControl : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { - typeof(BeatmapListingSortTabControl), typeof(OverlaySortTabControl<>), }; [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); - public TestSceneBeatmapListingSort() + public TestSceneBeatmapListingSortTabControl() { BeatmapListingSortTabControl control; OsuSpriteText current; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchHandler.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs similarity index 85% rename from osu.Game/Overlays/BeatmapListing/BeatmapListingSearchHandler.cs rename to osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index ce3d37fb98..8817031bce 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchHandler.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -21,7 +21,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapListing { - public class BeatmapListingSearchHandler : CompositeDrawable + public class BeatmapListingFilterControl : CompositeDrawable { public Action> SearchFinished; public Action SearchStarted; @@ -32,13 +32,13 @@ namespace osu.Game.Overlays.BeatmapListing [Resolved] private RulesetStore rulesets { get; set; } - private readonly BeatmapListingSearchSection searchSection; + private readonly BeatmapListingSearchControl searchControl; private readonly BeatmapListingSortTabControl sortControl; private readonly Box sortControlBackground; private SearchBeatmapSetsRequest getSetsRequest; - public BeatmapListingSearchHandler() + public BeatmapListingFilterControl() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -62,7 +62,7 @@ namespace osu.Game.Overlays.BeatmapListing Radius = 3, Offset = new Vector2(0f, 1f), }, - Child = searchSection = new BeatmapListingSearchSection(), + Child = searchControl = new BeatmapListingSearchControl(), }, new Container { @@ -99,17 +99,17 @@ namespace osu.Game.Overlays.BeatmapListing var sortCriteria = sortControl.Current; var sortDirection = sortControl.SortDirection; - searchSection.Query.BindValueChanged(query => + searchControl.Query.BindValueChanged(query => { sortCriteria.Value = string.IsNullOrEmpty(query.NewValue) ? DirectSortCriteria.Ranked : DirectSortCriteria.Relevance; sortDirection.Value = SortDirection.Descending; queueUpdateSearch(true); }); - searchSection.Ruleset.BindValueChanged(_ => queueUpdateSearch()); - searchSection.Category.BindValueChanged(_ => queueUpdateSearch()); - searchSection.Genre.BindValueChanged(_ => queueUpdateSearch()); - searchSection.Language.BindValueChanged(_ => queueUpdateSearch()); + searchControl.Ruleset.BindValueChanged(_ => queueUpdateSearch()); + searchControl.Category.BindValueChanged(_ => queueUpdateSearch()); + searchControl.Genre.BindValueChanged(_ => queueUpdateSearch()); + searchControl.Language.BindValueChanged(_ => queueUpdateSearch()); sortCriteria.BindValueChanged(_ => queueUpdateSearch()); sortDirection.BindValueChanged(_ => queueUpdateSearch()); @@ -129,13 +129,13 @@ namespace osu.Game.Overlays.BeatmapListing private void updateSearch() { - getSetsRequest = new SearchBeatmapSetsRequest(searchSection.Query.Value, searchSection.Ruleset.Value) + getSetsRequest = new SearchBeatmapSetsRequest(searchControl.Query.Value, searchControl.Ruleset.Value) { - SearchCategory = searchSection.Category.Value, + SearchCategory = searchControl.Category.Value, SortCriteria = sortControl.Current.Value, SortDirection = sortControl.SortDirection.Value, - Genre = searchSection.Genre.Value, - Language = searchSection.Language.Value + Genre = searchControl.Genre.Value, + Language = searchControl.Language.Value }; getSetsRequest.Success += response => Schedule(() => onSearchFinished(response)); @@ -147,7 +147,7 @@ namespace osu.Game.Overlays.BeatmapListing { var beatmaps = response.BeatmapSets.Select(r => r.ToBeatmapSet(rulesets)).ToList(); - searchSection.BeatmapSet = response.Total == 0 ? null : beatmaps.First(); + searchControl.BeatmapSet = response.Total == 0 ? null : beatmaps.First(); SearchFinished?.Invoke(beatmaps); } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs similarity index 97% rename from osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs rename to osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 3f9cc211df..9ae2696a22 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -17,7 +17,7 @@ using osu.Game.Rulesets; namespace osu.Game.Overlays.BeatmapListing { - public class BeatmapListingSearchSection : CompositeDrawable + public class BeatmapListingSearchControl : CompositeDrawable { public Bindable Query => textBox.Current; @@ -53,7 +53,7 @@ namespace osu.Game.Overlays.BeatmapListing private readonly Box background; private readonly UpdateableBeatmapSetCover beatmapCover; - public BeatmapListingSearchSection() + public BeatmapListingSearchControl() { AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 31dd692528..e16924464d 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -57,7 +57,7 @@ namespace osu.Game.Overlays Children = new Drawable[] { new BeatmapListingHeader(), - new BeatmapListingSearchHandler + new BeatmapListingFilterControl { SearchStarted = onSearchStarted, SearchFinished = onSearchFinished, From 5e3fad86cffb380f93eb78d1e541bb050b94094c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Apr 2020 15:28:25 +0900 Subject: [PATCH 425/655] Fix relax replays playing back incorrectly --- osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs | 13 +++++++++++-- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 18 ++++++++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs index 1ef235f764..16414261a5 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs @@ -9,17 +9,26 @@ using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; using osuTK; namespace osu.Game.Rulesets.Catch.Mods { - public class CatchModRelax : ModRelax, IApplicableToDrawableRuleset + public class CatchModRelax : ModRelax, IApplicableToDrawableRuleset, IApplicableToPlayer { public override string Description => @"Use the mouse to control the catcher."; + private DrawableRuleset drawableRuleset; + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - drawableRuleset.Cursor.Add(new MouseInputHelper((CatchPlayfield)drawableRuleset.Playfield)); + this.drawableRuleset = drawableRuleset; + } + + public void ApplyToPlayer(Player player) + { + if (!drawableRuleset.HasReplayLoaded.Value) + drawableRuleset.Cursor.Add(new MouseInputHelper((CatchPlayfield)drawableRuleset.Playfield)); } private class MouseInputHelper : Drawable, IKeyBindingHandler, IRequireHighFrequencyMousePosition diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 9b0759d9d2..9a7b967117 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -11,11 +11,12 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; using static osu.Game.Input.Handlers.ReplayInputHandler; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset + public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset, IApplicableToPlayer { public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things."; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray(); @@ -33,15 +34,28 @@ namespace osu.Game.Rulesets.Osu.Mods private ReplayState state; private double lastStateChangeTime; + private bool hasReplay; + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { // grab the input manager for future use. osuInputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; - osuInputManager.AllowUserPresses = false; + } + + public void ApplyToPlayer(Player player) + { + if (osuInputManager.ReplayInputHandler != null) + { + hasReplay = true; + return; + } } public void Update(Playfield playfield) { + if (hasReplay) + return; + bool requiresHold = false; bool requiresHit = false; From c2ed6491a9953dce878acf3d36c2c4b9d35b0716 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Apr 2020 15:37:50 +0900 Subject: [PATCH 426/655] Move and shorten enum names --- .../TestSceneBeatmapSearchFilter.cs | 5 +- .../API/Requests/SearchBeatmapSetsRequest.cs | 96 ++----------------- .../BeatmapListingSearchControl.cs | 21 ++-- .../Overlays/BeatmapListing/SearchCategory.cs | 26 +++++ .../Overlays/BeatmapListing/SearchGenre.cs | 25 +++++ .../Overlays/BeatmapListing/SearchLanguage.cs | 47 +++++++++ 6 files changed, 119 insertions(+), 101 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapListing/SearchCategory.cs create mode 100644 osu.Game/Overlays/BeatmapListing/SearchGenre.cs create mode 100644 osu.Game/Overlays/BeatmapListing/SearchLanguage.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapSearchFilter.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapSearchFilter.cs index fac58a6754..283fe03af3 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapSearchFilter.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapSearchFilter.cs @@ -8,7 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; -using osu.Game.Online.API.Requests; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapListing; using osuTK; @@ -41,8 +40,8 @@ namespace osu.Game.Tests.Visual.UserInterface Children = new Drawable[] { new BeatmapSearchRulesetFilterRow(), - new BeatmapSearchFilterRow("Categories"), - new BeatmapSearchFilterRow("Header Name") + new BeatmapSearchFilterRow("Categories"), + new BeatmapSearchFilterRow("Header Name") } }); } diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 1206563b18..8345be5f82 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -1,26 +1,25 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.ComponentModel; using osu.Framework.IO.Network; using osu.Game.Overlays; +using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.Direct; using osu.Game.Rulesets; -using osu.Game.Utils; namespace osu.Game.Online.API.Requests { public class SearchBeatmapSetsRequest : APIRequest { - public BeatmapSearchCategory SearchCategory { get; set; } + public SearchCategory SearchCategory { get; set; } public DirectSortCriteria SortCriteria { get; set; } public SortDirection SortDirection { get; set; } - public BeatmapSearchGenre Genre { get; set; } + public SearchGenre Genre { get; set; } - public BeatmapSearchLanguage Language { get; set; } + public SearchLanguage Language { get; set; } private readonly string query; private readonly RulesetInfo ruleset; @@ -32,11 +31,11 @@ namespace osu.Game.Online.API.Requests this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); this.ruleset = ruleset; - SearchCategory = BeatmapSearchCategory.Any; + SearchCategory = SearchCategory.Any; SortCriteria = DirectSortCriteria.Ranked; SortDirection = SortDirection.Descending; - Genre = BeatmapSearchGenre.Any; - Language = BeatmapSearchLanguage.Any; + Genre = SearchGenre.Any; + Language = SearchLanguage.Any; } protected override WebRequest CreateWebRequest() @@ -49,10 +48,10 @@ namespace osu.Game.Online.API.Requests req.AddParameter("s", SearchCategory.ToString().ToLowerInvariant()); - if (Genre != BeatmapSearchGenre.Any) + if (Genre != SearchGenre.Any) req.AddParameter("g", ((int)Genre).ToString()); - if (Language != BeatmapSearchLanguage.Any) + if (Language != SearchLanguage.Any) req.AddParameter("l", ((int)Language).ToString()); req.AddParameter("sort", $"{SortCriteria.ToString().ToLowerInvariant()}_{directionString}"); @@ -62,81 +61,4 @@ namespace osu.Game.Online.API.Requests protected override string Target => @"beatmapsets/search"; } - - public enum BeatmapSearchCategory - { - Any, - - [Description("Has Leaderboard")] - Leaderboard, - Ranked, - Qualified, - Loved, - Favourites, - - [Description("Pending & WIP")] - Pending, - Graveyard, - - [Description("My Maps")] - Mine, - } - - public enum BeatmapSearchGenre - { - Any = 0, - Unspecified = 1, - - [Description("Video Game")] - VideoGame = 2, - Anime = 3, - Rock = 4, - Pop = 5, - Other = 6, - Novelty = 7, - - [Description("Hip Hop")] - HipHop = 9, - Electronic = 10 - } - - [HasOrderedElements] - public enum BeatmapSearchLanguage - { - [Order(0)] - Any, - - [Order(11)] - Other, - - [Order(1)] - English, - - [Order(6)] - Japanese, - - [Order(2)] - Chinese, - - [Order(10)] - Instrumental, - - [Order(7)] - Korean, - - [Order(3)] - French, - - [Order(4)] - German, - - [Order(9)] - Swedish, - - [Order(8)] - Spanish, - - [Order(5)] - Italian - } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 9ae2696a22..2ecdb18667 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Online.API.Requests; using osuTK; using osu.Framework.Bindables; using osu.Game.Beatmaps.Drawables; @@ -23,11 +22,11 @@ namespace osu.Game.Overlays.BeatmapListing public Bindable Ruleset => modeFilter.Current; - public Bindable Category => categoryFilter.Current; + public Bindable Category => categoryFilter.Current; - public Bindable Genre => genreFilter.Current; + public Bindable Genre => genreFilter.Current; - public Bindable Language => languageFilter.Current; + public Bindable Language => languageFilter.Current; public BeatmapSetInfo BeatmapSet { @@ -46,9 +45,9 @@ namespace osu.Game.Overlays.BeatmapListing private readonly BeatmapSearchTextBox textBox; private readonly BeatmapSearchRulesetFilterRow modeFilter; - private readonly BeatmapSearchFilterRow categoryFilter; - private readonly BeatmapSearchFilterRow genreFilter; - private readonly BeatmapSearchFilterRow languageFilter; + private readonly BeatmapSearchFilterRow categoryFilter; + private readonly BeatmapSearchFilterRow genreFilter; + private readonly BeatmapSearchFilterRow languageFilter; private readonly Box background; private readonly UpdateableBeatmapSetCover beatmapCover; @@ -103,9 +102,9 @@ namespace osu.Game.Overlays.BeatmapListing Children = new Drawable[] { modeFilter = new BeatmapSearchRulesetFilterRow(), - categoryFilter = new BeatmapSearchFilterRow(@"Categories"), - genreFilter = new BeatmapSearchFilterRow(@"Genre"), - languageFilter = new BeatmapSearchFilterRow(@"Language"), + categoryFilter = new BeatmapSearchFilterRow(@"Categories"), + genreFilter = new BeatmapSearchFilterRow(@"Genre"), + languageFilter = new BeatmapSearchFilterRow(@"Language"), } } } @@ -113,7 +112,7 @@ namespace osu.Game.Overlays.BeatmapListing } }); - categoryFilter.Current.Value = BeatmapSearchCategory.Leaderboard; + categoryFilter.Current.Value = SearchCategory.Leaderboard; } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/BeatmapListing/SearchCategory.cs b/osu.Game/Overlays/BeatmapListing/SearchCategory.cs new file mode 100644 index 0000000000..84859bf5b5 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/SearchCategory.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.ComponentModel; + +namespace osu.Game.Overlays.BeatmapListing +{ + public enum SearchCategory + { + Any, + + [Description("Has Leaderboard")] + Leaderboard, + Ranked, + Qualified, + Loved, + Favourites, + + [Description("Pending & WIP")] + Pending, + Graveyard, + + [Description("My Maps")] + Mine, + } +} diff --git a/osu.Game/Overlays/BeatmapListing/SearchGenre.cs b/osu.Game/Overlays/BeatmapListing/SearchGenre.cs new file mode 100644 index 0000000000..b12bba6249 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/SearchGenre.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.ComponentModel; + +namespace osu.Game.Overlays.BeatmapListing +{ + public enum SearchGenre + { + Any = 0, + Unspecified = 1, + + [Description("Video Game")] + VideoGame = 2, + Anime = 3, + Rock = 4, + Pop = 5, + Other = 6, + Novelty = 7, + + [Description("Hip Hop")] + HipHop = 9, + Electronic = 10 + } +} diff --git a/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs b/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs new file mode 100644 index 0000000000..dac7e4f1a2 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Utils; + +namespace osu.Game.Overlays.BeatmapListing +{ + [HasOrderedElements] + public enum SearchLanguage + { + [Order(0)] + Any, + + [Order(11)] + Other, + + [Order(1)] + English, + + [Order(6)] + Japanese, + + [Order(2)] + Chinese, + + [Order(10)] + Instrumental, + + [Order(7)] + Korean, + + [Order(3)] + French, + + [Order(4)] + German, + + [Order(9)] + Swedish, + + [Order(8)] + Spanish, + + [Order(5)] + Italian + } +} From eeb76120106b08a108f8036c95391a77fc5f968f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Apr 2020 15:40:08 +0900 Subject: [PATCH 427/655] Update DirectOverlay implementation --- osu.Game/Overlays/Direct/FilterControl.cs | 6 +++--- osu.Game/Overlays/DirectOverlay.cs | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs index e5b2b5cc34..4ab5544550 100644 --- a/osu.Game/Overlays/Direct/FilterControl.cs +++ b/osu.Game/Overlays/Direct/FilterControl.cs @@ -6,20 +6,20 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Game.Graphics; -using osu.Game.Online.API.Requests; +using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.SearchableList; using osu.Game.Rulesets; using osuTK.Graphics; namespace osu.Game.Overlays.Direct { - public class FilterControl : SearchableListFilterControl + public class FilterControl : SearchableListFilterControl { private DirectRulesetSelector rulesetSelector; protected override Color4 BackgroundColour => Color4Extensions.FromHex(@"384552"); protected override DirectSortCriteria DefaultTab => DirectSortCriteria.Ranked; - protected override BeatmapSearchCategory DefaultCategory => BeatmapSearchCategory.Leaderboard; + protected override SearchCategory DefaultCategory => SearchCategory.Leaderboard; protected override Drawable CreateSupplementaryControls() => rulesetSelector = new DirectRulesetSelector(); diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index 3eb88be690..5ed39af0dc 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -16,6 +16,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests; +using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.Direct; using osu.Game.Overlays.SearchableList; using osu.Game.Rulesets; @@ -24,7 +25,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays { - public class DirectOverlay : SearchableListOverlay + public class DirectOverlay : SearchableListOverlay { private const float panel_padding = 10f; @@ -40,7 +41,7 @@ namespace osu.Game.Overlays protected override Color4 TrianglesColourDark => Color4Extensions.FromHex(@"3f5265"); protected override SearchableListHeader CreateHeader() => new Header(); - protected override SearchableListFilterControl CreateFilterControl() => new FilterControl(); + protected override SearchableListFilterControl CreateFilterControl() => new FilterControl(); private IEnumerable beatmapSets; From 1f0b7465e2410c8c69d16f31becd52e076602901 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Apr 2020 16:06:40 +0900 Subject: [PATCH 428/655] Add back missing line --- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 9a7b967117..7b1941b7f9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -49,6 +49,8 @@ namespace osu.Game.Rulesets.Osu.Mods hasReplay = true; return; } + + osuInputManager.AllowUserPresses = false; } public void Update(Playfield playfield) From 594cef14738f99d3f0fa3466756bcf151c3df4ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Apr 2020 15:47:43 +0900 Subject: [PATCH 429/655] Fix BeatmapListingOverlay not taking focus --- .../BeatmapListing/BeatmapListingFilterControl.cs | 2 ++ .../BeatmapListing/BeatmapListingSearchControl.cs | 2 ++ osu.Game/Overlays/BeatmapListingOverlay.cs | 12 +++++++++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 8817031bce..8c50409783 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -159,5 +159,7 @@ namespace osu.Game.Overlays.BeatmapListing base.Dispose(isDisposing); } + + public void TakeFocus() => searchControl.TakeFocus(); } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 2ecdb18667..29c4fe0d2e 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -121,6 +121,8 @@ namespace osu.Game.Overlays.BeatmapListing background.Colour = colourProvider.Dark6; } + public void TakeFocus() => textBox.TakeFocus(); + private class BeatmapSearchTextBox : SearchTextBox { protected override Color4 SelectionColour => Color4.Gray; diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index e16924464d..000ca6b91c 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Input.Events; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; @@ -35,6 +36,8 @@ namespace osu.Game.Overlays { } + private BeatmapListingFilterControl filterControl; + [BackgroundDependencyLoader] private void load() { @@ -57,7 +60,7 @@ namespace osu.Game.Overlays Children = new Drawable[] { new BeatmapListingHeader(), - new BeatmapListingFilterControl + filterControl = new BeatmapListingFilterControl { SearchStarted = onSearchStarted, SearchFinished = onSearchFinished, @@ -88,6 +91,13 @@ namespace osu.Game.Overlays }; } + protected override void OnFocus(FocusEvent e) + { + base.OnFocus(e); + + filterControl.TakeFocus(); + } + private CancellationTokenSource cancellationToken; private void onSearchStarted() From 1cec0575b78203dbc257b4d72851012d37eeac91 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Apr 2020 16:00:00 +0900 Subject: [PATCH 430/655] Remove unused classes and replace overlay in game --- .../Visual/Online/TestSceneDirectOverlay.cs | 215 ------------- osu.Game.Tests/Visual/TestSceneOsuGame.cs | 7 + .../API/Requests/SearchBeatmapSetsRequest.cs | 5 +- osu.Game/OsuGame.cs | 8 +- .../BeatmapListingFilterControl.cs | 3 +- .../BeatmapListingSortTabControl.cs | 13 +- .../Overlays/BeatmapListing/SortCriteria.cs | 17 + .../Overlays/Direct/DirectRulesetSelector.cs | 93 ------ osu.Game/Overlays/Direct/FilterControl.cs | 47 --- osu.Game/Overlays/Direct/Header.cs | 43 --- osu.Game/Overlays/DirectOverlay.cs | 299 ------------------ osu.Game/Overlays/SocialOverlay.cs | 6 - osu.Game/Overlays/SortDirection.cs | 11 + osu.Game/Overlays/Toolbar/Toolbar.cs | 2 +- ...tton.cs => ToolbarBeatmapListingButton.cs} | 8 +- osu.Game/Screens/Menu/ButtonSystem.cs | 4 +- osu.Game/Screens/Menu/MainMenu.cs | 4 +- 17 files changed, 57 insertions(+), 728 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs create mode 100644 osu.Game/Overlays/BeatmapListing/SortCriteria.cs delete mode 100644 osu.Game/Overlays/Direct/DirectRulesetSelector.cs delete mode 100644 osu.Game/Overlays/Direct/FilterControl.cs delete mode 100644 osu.Game/Overlays/Direct/Header.cs delete mode 100644 osu.Game/Overlays/DirectOverlay.cs create mode 100644 osu.Game/Overlays/SortDirection.cs rename osu.Game/Overlays/Toolbar/{ToolbarDirectButton.cs => ToolbarBeatmapListingButton.cs} (63%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs deleted file mode 100644 index d9873ea243..0000000000 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs +++ /dev/null @@ -1,215 +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.Collections.Generic; -using NUnit.Framework; -using osu.Game.Beatmaps; -using osu.Game.Overlays; - -namespace osu.Game.Tests.Visual.Online -{ - [TestFixture] - public class TestSceneDirectOverlay : OsuTestScene - { - private DirectOverlay direct; - - protected override bool UseOnlineAPI => true; - - protected override void LoadComplete() - { - base.LoadComplete(); - - Add(direct = new DirectOverlay()); - newBeatmaps(); - - AddStep(@"toggle", direct.ToggleVisibility); - AddStep(@"result counts", () => direct.ResultAmounts = new DirectOverlay.ResultCounts(1, 4, 13)); - AddStep(@"trigger disabled", () => Ruleset.Disabled = !Ruleset.Disabled); - } - - private void newBeatmaps() - { - direct.BeatmapSets = new[] - { - new BeatmapSetInfo - { - OnlineBeatmapSetID = 578332, - Metadata = new BeatmapMetadata - { - Title = @"OrVid", - Artist = @"An", - AuthorString = @"RLC", - Source = @"", - Tags = @"acuticnotes an-fillnote revid tear tearvid encrpted encryption axi axivid quad her hervid recoll", - }, - OnlineInfo = new BeatmapSetOnlineInfo - { - Covers = new BeatmapSetOnlineCovers - { - Card = @"https://assets.ppy.sh/beatmaps/578332/covers/card.jpg?1494591390", - Cover = @"https://assets.ppy.sh/beatmaps/578332/covers/cover.jpg?1494591390", - }, - Preview = @"https://b.ppy.sh/preview/578332.mp3", - PlayCount = 97, - FavouriteCount = 72, - }, - Beatmaps = new List - { - new BeatmapInfo - { - Ruleset = Ruleset.Value, - StarDifficulty = 5.35f, - Metadata = new BeatmapMetadata(), - }, - }, - }, - new BeatmapSetInfo - { - OnlineBeatmapSetID = 599627, - Metadata = new BeatmapMetadata - { - Title = @"tiny lamp", - Artist = @"fhana", - AuthorString = @"Sotarks", - Source = @"ぎんぎつね", - Tags = @"lantis junichi sato yuxuki waga kevin mitsunaga towana gingitsune opening op full ver version kalibe collab collaboration", - }, - OnlineInfo = new BeatmapSetOnlineInfo - { - Covers = new BeatmapSetOnlineCovers - { - Card = @"https://assets.ppy.sh/beatmaps/599627/covers/card.jpg?1494539318", - Cover = @"https://assets.ppy.sh/beatmaps/599627/covers/cover.jpg?1494539318", - }, - Preview = @"https//b.ppy.sh/preview/599627.mp3", - PlayCount = 3082, - FavouriteCount = 14, - }, - Beatmaps = new List - { - new BeatmapInfo - { - Ruleset = Ruleset.Value, - StarDifficulty = 5.81f, - Metadata = new BeatmapMetadata(), - }, - }, - }, - new BeatmapSetInfo - { - OnlineBeatmapSetID = 513268, - Metadata = new BeatmapMetadata - { - Title = @"At Gwanghwamun", - Artist = @"KYUHYUN", - AuthorString = @"Cerulean Veyron", - Source = @"", - Tags = @"soul ballad kh super junior sj suju 슈퍼주니어 kt뮤직 sm엔터테인먼트 s.m.entertainment kt music 1st mini album ep", - }, - OnlineInfo = new BeatmapSetOnlineInfo - { - Covers = new BeatmapSetOnlineCovers - { - Card = @"https://assets.ppy.sh/beatmaps/513268/covers/card.jpg?1494502863", - Cover = @"https://assets.ppy.sh/beatmaps/513268/covers/cover.jpg?1494502863", - }, - Preview = @"https//b.ppy.sh/preview/513268.mp3", - PlayCount = 2762, - FavouriteCount = 15, - }, - Beatmaps = new List - { - new BeatmapInfo - { - Ruleset = Ruleset.Value, - StarDifficulty = 0.9f, - Metadata = new BeatmapMetadata(), - }, - new BeatmapInfo - { - Ruleset = Ruleset.Value, - StarDifficulty = 1.1f, - }, - new BeatmapInfo - { - Ruleset = Ruleset.Value, - StarDifficulty = 2.02f, - }, - new BeatmapInfo - { - Ruleset = Ruleset.Value, - StarDifficulty = 3.49f, - }, - }, - }, - new BeatmapSetInfo - { - OnlineBeatmapSetID = 586841, - Metadata = new BeatmapMetadata - { - Title = @"RHAPSODY OF BLUE SKY", - Artist = @"fhana", - AuthorString = @"[Kamiya]", - Source = @"小林さんちのメイドラゴン", - Tags = @"kobayashi san chi no maidragon aozora no opening anime maid dragon oblivion karen dynamix imoutosan pata-mon gxytcgxytc", - }, - OnlineInfo = new BeatmapSetOnlineInfo - { - Covers = new BeatmapSetOnlineCovers - { - Card = @"https://assets.ppy.sh/beatmaps/586841/covers/card.jpg?1494052741", - Cover = @"https://assets.ppy.sh/beatmaps/586841/covers/cover.jpg?1494052741", - }, - Preview = @"https//b.ppy.sh/preview/586841.mp3", - PlayCount = 62317, - FavouriteCount = 161, - }, - Beatmaps = new List - { - new BeatmapInfo - { - Ruleset = Ruleset.Value, - StarDifficulty = 1.26f, - Metadata = new BeatmapMetadata(), - }, - new BeatmapInfo - { - Ruleset = Ruleset.Value, - StarDifficulty = 2.01f, - }, - new BeatmapInfo - { - Ruleset = Ruleset.Value, - StarDifficulty = 2.87f, - }, - new BeatmapInfo - { - Ruleset = Ruleset.Value, - StarDifficulty = 3.76f, - }, - new BeatmapInfo - { - Ruleset = Ruleset.Value, - StarDifficulty = 3.93f, - }, - new BeatmapInfo - { - Ruleset = Ruleset.Value, - StarDifficulty = 4.37f, - }, - new BeatmapInfo - { - Ruleset = Ruleset.Value, - StarDifficulty = 5.13f, - }, - new BeatmapInfo - { - Ruleset = Ruleset.Value, - StarDifficulty = 5.42f, - }, - }, - }, - }; - } - } -} diff --git a/osu.Game.Tests/Visual/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/TestSceneOsuGame.cs index 8793d880e3..d68217dcfd 100644 --- a/osu.Game.Tests/Visual/TestSceneOsuGame.cs +++ b/osu.Game.Tests/Visual/TestSceneOsuGame.cs @@ -47,8 +47,15 @@ namespace osu.Game.Tests.Visual typeof(IdleTracker), typeof(OnScreenDisplay), typeof(NotificationOverlay), +<<<<<<< HEAD typeof(DirectOverlay), typeof(DashboardOverlay), +||||||| parent of 96a3a08a9... Remove unused classes and replace overlay in game + typeof(DirectOverlay), + typeof(SocialOverlay), +======= + typeof(SocialOverlay), +>>>>>>> 96a3a08a9... Remove unused classes and replace overlay in game typeof(ChannelManager), typeof(ChatOverlay), typeof(SettingsOverlay), diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 8345be5f82..047496b473 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -4,7 +4,6 @@ using osu.Framework.IO.Network; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapListing; -using osu.Game.Overlays.Direct; using osu.Game.Rulesets; namespace osu.Game.Online.API.Requests @@ -13,7 +12,7 @@ namespace osu.Game.Online.API.Requests { public SearchCategory SearchCategory { get; set; } - public DirectSortCriteria SortCriteria { get; set; } + public SortCriteria SortCriteria { get; set; } public SortDirection SortDirection { get; set; } @@ -32,7 +31,7 @@ namespace osu.Game.Online.API.Requests this.ruleset = ruleset; SearchCategory = SearchCategory.Any; - SortCriteria = DirectSortCriteria.Ranked; + SortCriteria = SortCriteria.Ranked; SortDirection = SortDirection.Descending; Genre = SearchGenre.Any; Language = SearchLanguage.Any; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c861b84835..f5f7d0cef4 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -65,7 +65,7 @@ namespace osu.Game private NowPlayingOverlay nowPlaying; - private DirectOverlay direct; + private BeatmapListingOverlay beatmapListing; private DashboardOverlay dashboard; @@ -610,7 +610,7 @@ namespace osu.Game loadComponentSingleFile(screenshotManager, Add); //overlay elements - loadComponentSingleFile(direct = new DirectOverlay(), overlayContent.Add, true); + loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true); loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true); var rankingsOverlay = loadComponentSingleFile(new RankingsOverlay(), overlayContent.Add, true); loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true); @@ -670,7 +670,7 @@ namespace osu.Game } // ensure only one of these overlays are open at once. - var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, dashboard, direct, changelogOverlay, rankingsOverlay }; + var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, dashboard, beatmapListing, changelogOverlay, rankingsOverlay }; foreach (var overlay in singleDisplayOverlays) { @@ -865,7 +865,7 @@ namespace osu.Game return true; case GlobalAction.ToggleDirect: - direct.ToggleVisibility(); + beatmapListing.ToggleVisibility(); return true; case GlobalAction.ToggleGameplayMouseButtons: diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 8c50409783..4dd60c7113 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -14,7 +14,6 @@ using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; -using osu.Game.Overlays.Direct; using osu.Game.Rulesets; using osuTK; using osuTK.Graphics; @@ -101,7 +100,7 @@ namespace osu.Game.Overlays.BeatmapListing searchControl.Query.BindValueChanged(query => { - sortCriteria.Value = string.IsNullOrEmpty(query.NewValue) ? DirectSortCriteria.Ranked : DirectSortCriteria.Relevance; + sortCriteria.Value = string.IsNullOrEmpty(query.NewValue) ? SortCriteria.Ranked : SortCriteria.Relevance; sortDirection.Value = SortDirection.Descending; queueUpdateSearch(true); }); diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs index 27c43b092a..4c77a736ac 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs @@ -8,17 +8,16 @@ using osu.Framework.Graphics; using osuTK.Graphics; using osuTK; using osu.Framework.Input.Events; -using osu.Game.Overlays.Direct; namespace osu.Game.Overlays.BeatmapListing { - public class BeatmapListingSortTabControl : OverlaySortTabControl + public class BeatmapListingSortTabControl : OverlaySortTabControl { public readonly Bindable SortDirection = new Bindable(Overlays.SortDirection.Descending); public BeatmapListingSortTabControl() { - Current.Value = DirectSortCriteria.Ranked; + Current.Value = SortCriteria.Ranked; } protected override SortTabControl CreateControl() => new BeatmapSortTabControl @@ -30,7 +29,7 @@ namespace osu.Game.Overlays.BeatmapListing { public readonly Bindable SortDirection = new Bindable(); - protected override TabItem CreateTabItem(DirectSortCriteria value) => new BeatmapSortTabItem(value) + protected override TabItem CreateTabItem(SortCriteria value) => new BeatmapSortTabItem(value) { SortDirection = { BindTarget = SortDirection } }; @@ -40,12 +39,12 @@ namespace osu.Game.Overlays.BeatmapListing { public readonly Bindable SortDirection = new Bindable(); - public BeatmapSortTabItem(DirectSortCriteria value) + public BeatmapSortTabItem(SortCriteria value) : base(value) { } - protected override TabButton CreateTabButton(DirectSortCriteria value) => new BeatmapTabButton(value) + protected override TabButton CreateTabButton(SortCriteria value) => new BeatmapTabButton(value) { Active = { BindTarget = Active }, SortDirection = { BindTarget = SortDirection } @@ -67,7 +66,7 @@ namespace osu.Game.Overlays.BeatmapListing private readonly SpriteIcon icon; - public BeatmapTabButton(DirectSortCriteria value) + public BeatmapTabButton(SortCriteria value) : base(value) { Add(icon = new SpriteIcon diff --git a/osu.Game/Overlays/BeatmapListing/SortCriteria.cs b/osu.Game/Overlays/BeatmapListing/SortCriteria.cs new file mode 100644 index 0000000000..e409cbdda7 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/SortCriteria.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Overlays.BeatmapListing +{ + public enum SortCriteria + { + Title, + Artist, + Difficulty, + Ranked, + Rating, + Plays, + Favourites, + Relevance + } +} diff --git a/osu.Game/Overlays/Direct/DirectRulesetSelector.cs b/osu.Game/Overlays/Direct/DirectRulesetSelector.cs deleted file mode 100644 index 106aaa616b..0000000000 --- a/osu.Game/Overlays/Direct/DirectRulesetSelector.cs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Overlays.Direct -{ - public class DirectRulesetSelector : RulesetSelector - { - public override bool HandleNonPositionalInput => !Current.Disabled && base.HandleNonPositionalInput; - - public override bool HandlePositionalInput => !Current.Disabled && base.HandlePositionalInput; - - public override bool PropagatePositionalInputSubTree => !Current.Disabled && base.PropagatePositionalInputSubTree; - - public DirectRulesetSelector() - { - TabContainer.Masking = false; - TabContainer.Spacing = new Vector2(10, 0); - AutoSizeAxes = Axes.Both; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - Current.BindDisabledChanged(value => SelectedTab.FadeColour(value ? Color4.DarkGray : Color4.White, 200, Easing.OutQuint), true); - } - - protected override TabItem CreateTabItem(RulesetInfo value) => new DirectRulesetTabItem(value); - - protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - }; - - private class DirectRulesetTabItem : TabItem - { - private readonly ConstrainedIconContainer iconContainer; - - public DirectRulesetTabItem(RulesetInfo value) - : base(value) - { - AutoSizeAxes = Axes.Both; - - Children = new Drawable[] - { - iconContainer = new ConstrainedIconContainer - { - Icon = value.CreateInstance().CreateIcon(), - Size = new Vector2(32), - }, - new HoverClickSounds() - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - updateState(); - } - - protected override bool OnHover(HoverEvent e) - { - base.OnHover(e); - updateState(); - return true; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - base.OnHoverLost(e); - updateState(); - } - - protected override void OnActivated() => updateState(); - - protected override void OnDeactivated() => updateState(); - - private void updateState() => iconContainer.FadeColour(IsHovered || Active.Value ? Color4.White : Color4.Gray, 120, Easing.InQuad); - } - } -} diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs deleted file mode 100644 index 4ab5544550..0000000000 --- a/osu.Game/Overlays/Direct/FilterControl.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Game.Graphics; -using osu.Game.Overlays.BeatmapListing; -using osu.Game.Overlays.SearchableList; -using osu.Game.Rulesets; -using osuTK.Graphics; - -namespace osu.Game.Overlays.Direct -{ - public class FilterControl : SearchableListFilterControl - { - private DirectRulesetSelector rulesetSelector; - - protected override Color4 BackgroundColour => Color4Extensions.FromHex(@"384552"); - protected override DirectSortCriteria DefaultTab => DirectSortCriteria.Ranked; - protected override SearchCategory DefaultCategory => SearchCategory.Leaderboard; - - protected override Drawable CreateSupplementaryControls() => rulesetSelector = new DirectRulesetSelector(); - - public Bindable Ruleset => rulesetSelector.Current; - - [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, Bindable ruleset) - { - DisplayStyleControl.Dropdown.AccentColour = colours.BlueDark; - rulesetSelector.Current.BindTo(ruleset); - } - } - - public enum DirectSortCriteria - { - Title, - Artist, - Difficulty, - Ranked, - Rating, - Plays, - Favourites, - Relevance, - } -} diff --git a/osu.Game/Overlays/Direct/Header.cs b/osu.Game/Overlays/Direct/Header.cs deleted file mode 100644 index 5b3e394a18..0000000000 --- a/osu.Game/Overlays/Direct/Header.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.ComponentModel; -using osu.Framework.Extensions.Color4Extensions; -using osuTK.Graphics; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Overlays.SearchableList; - -namespace osu.Game.Overlays.Direct -{ - public class Header : SearchableListHeader - { - protected override Color4 BackgroundColour => Color4Extensions.FromHex(@"252f3a"); - - protected override DirectTab DefaultTab => DirectTab.Search; - protected override Drawable CreateHeaderText() => new OsuSpriteText { Text = @"osu!direct", Font = OsuFont.GetFont(size: 25) }; - protected override IconUsage Icon => OsuIcon.ChevronDownCircle; - - public Header() - { - Tabs.Current.Value = DirectTab.NewestMaps; - Tabs.Current.TriggerChange(); - } - } - - public enum DirectTab - { - Search, - - [Description("Newest Maps")] - NewestMaps = DirectSortCriteria.Ranked, - - [Description("Top Rated")] - TopRated = DirectSortCriteria.Rating, - - [Description("Most Played")] - MostPlayed = DirectSortCriteria.Plays, - } -} diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs deleted file mode 100644 index 5ed39af0dc..0000000000 --- a/osu.Game/Overlays/DirectOverlay.cs +++ /dev/null @@ -1,299 +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.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Humanizer; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Threading; -using osu.Game.Audio; -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Online.API.Requests; -using osu.Game.Overlays.BeatmapListing; -using osu.Game.Overlays.Direct; -using osu.Game.Overlays.SearchableList; -using osu.Game.Rulesets; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Overlays -{ - public class DirectOverlay : SearchableListOverlay - { - private const float panel_padding = 10f; - - [Resolved] - private RulesetStore rulesets { get; set; } - - private readonly FillFlowContainer resultCountsContainer; - private readonly OsuSpriteText resultCountsText; - private FillFlowContainer panels; - - protected override Color4 BackgroundColour => Color4Extensions.FromHex(@"485e74"); - protected override Color4 TrianglesColourLight => Color4Extensions.FromHex(@"465b71"); - protected override Color4 TrianglesColourDark => Color4Extensions.FromHex(@"3f5265"); - - protected override SearchableListHeader CreateHeader() => new Header(); - protected override SearchableListFilterControl CreateFilterControl() => new FilterControl(); - - private IEnumerable beatmapSets; - - public IEnumerable BeatmapSets - { - get => beatmapSets; - set - { - if (ReferenceEquals(beatmapSets, value)) return; - - beatmapSets = value?.ToList(); - - if (beatmapSets == null) return; - - var artists = new List(); - var songs = new List(); - var tags = new List(); - - foreach (var s in beatmapSets) - { - artists.Add(s.Metadata.Artist); - songs.Add(s.Metadata.Title); - tags.AddRange(s.Metadata.Tags.Split(' ')); - } - - ResultAmounts = new ResultCounts(distinctCount(artists), distinctCount(songs), distinctCount(tags)); - } - } - - private ResultCounts resultAmounts; - - public ResultCounts ResultAmounts - { - get => resultAmounts; - set - { - if (value == ResultAmounts) return; - - resultAmounts = value; - - updateResultCounts(); - } - } - - public DirectOverlay() - : base(OverlayColourScheme.Blue) - { - ScrollFlow.Children = new Drawable[] - { - resultCountsContainer = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Margin = new MarginPadding { Top = 5 }, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = "Found ", - Font = OsuFont.GetFont(size: 15) - }, - resultCountsText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold) - }, - } - }, - }; - - Filter.Search.Current.ValueChanged += text => - { - if (!string.IsNullOrEmpty(text.NewValue)) - { - Header.Tabs.Current.Value = DirectTab.Search; - - if (Filter.Tabs.Current.Value == DirectSortCriteria.Ranked) - Filter.Tabs.Current.Value = DirectSortCriteria.Relevance; - } - else - { - Header.Tabs.Current.Value = DirectTab.NewestMaps; - - if (Filter.Tabs.Current.Value == DirectSortCriteria.Relevance) - Filter.Tabs.Current.Value = DirectSortCriteria.Ranked; - } - }; - ((FilterControl)Filter).Ruleset.ValueChanged += _ => queueUpdateSearch(); - Filter.DisplayStyleControl.DisplayStyle.ValueChanged += style => recreatePanels(style.NewValue); - Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => queueUpdateSearch(); - - Header.Tabs.Current.ValueChanged += tab => - { - if (tab.NewValue != DirectTab.Search) - { - currentQuery.Value = string.Empty; - Filter.Tabs.Current.Value = (DirectSortCriteria)Header.Tabs.Current.Value; - queueUpdateSearch(); - } - }; - - currentQuery.ValueChanged += text => queueUpdateSearch(!string.IsNullOrEmpty(text.NewValue)); - - currentQuery.BindTo(Filter.Search.Current); - - Filter.Tabs.Current.ValueChanged += tab => - { - if (Header.Tabs.Current.Value != DirectTab.Search && tab.NewValue != (DirectSortCriteria)Header.Tabs.Current.Value) - Header.Tabs.Current.Value = DirectTab.Search; - - queueUpdateSearch(); - }; - - updateResultCounts(); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - resultCountsContainer.Colour = colours.Yellow; - } - - private void updateResultCounts() - { - resultCountsContainer.FadeTo(ResultAmounts == null ? 0f : 1f, 200, Easing.OutQuint); - if (ResultAmounts == null) return; - - resultCountsText.Text = "Artist".ToQuantity(ResultAmounts.Artists) + ", " + - "Song".ToQuantity(ResultAmounts.Songs) + ", " + - "Tag".ToQuantity(ResultAmounts.Tags); - } - - private void recreatePanels(PanelDisplayStyle displayStyle) - { - if (panels != null) - { - panels.FadeOut(200); - panels.Expire(); - panels = null; - } - - if (BeatmapSets == null) return; - - var newPanels = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(panel_padding), - Margin = new MarginPadding { Top = 10 }, - ChildrenEnumerable = BeatmapSets.Select(b => - { - switch (displayStyle) - { - case PanelDisplayStyle.Grid: - return new DirectGridPanel(b) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }; - - default: - return new DirectListPanel(b); - } - }) - }; - - LoadComponentAsync(newPanels, p => - { - if (panels != null) ScrollFlow.Remove(panels); - ScrollFlow.Add(panels = newPanels); - }); - } - - protected override void PopIn() - { - base.PopIn(); - - // Queries are allowed to be run only on the first pop-in - if (getSetsRequest == null) - queueUpdateSearch(); - } - - private SearchBeatmapSetsRequest getSetsRequest; - - private readonly Bindable currentQuery = new Bindable(string.Empty); - - private ScheduledDelegate queryChangedDebounce; - - [Resolved] - private PreviewTrackManager previewTrackManager { get; set; } - - private void queueUpdateSearch(bool queryTextChanged = false) - { - BeatmapSets = null; - ResultAmounts = null; - - getSetsRequest?.Cancel(); - - queryChangedDebounce?.Cancel(); - queryChangedDebounce = Scheduler.AddDelayed(updateSearch, queryTextChanged ? 500 : 100); - } - - private void updateSearch() - { - if (!IsLoaded) - return; - - if (State.Value == Visibility.Hidden) - return; - - if (API == null) - return; - - previewTrackManager.StopAnyPlaying(this); - - getSetsRequest = new SearchBeatmapSetsRequest(currentQuery.Value, ((FilterControl)Filter).Ruleset.Value) - { - SearchCategory = Filter.DisplayStyleControl.Dropdown.Current.Value, - SortCriteria = Filter.Tabs.Current.Value - }; - - getSetsRequest.Success += response => - { - Task.Run(() => - { - var sets = response.BeatmapSets.Select(r => r.ToBeatmapSet(rulesets)).ToList(); - - // may not need scheduling; loads async internally. - Schedule(() => - { - BeatmapSets = sets; - recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value); - }); - }); - }; - - API.Queue(getSetsRequest); - } - - private int distinctCount(List list) => list.Distinct().ToArray().Length; - - public class ResultCounts - { - public readonly int Artists; - public readonly int Songs; - public readonly int Tags; - - public ResultCounts(int artists, int songs, int tags) - { - Artists = artists; - Songs = songs; - Tags = tags; - } - } - } -} diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index 02f7c9b0d3..9548573b4f 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -239,10 +239,4 @@ namespace osu.Game.Overlays } } } - - public enum SortDirection - { - Ascending, - Descending - } } diff --git a/osu.Game/Overlays/SortDirection.cs b/osu.Game/Overlays/SortDirection.cs new file mode 100644 index 0000000000..3af9614972 --- /dev/null +++ b/osu.Game/Overlays/SortDirection.cs @@ -0,0 +1,11 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Overlays +{ + public enum SortDirection + { + Ascending, + Descending + } +} diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 897587d198..227347112c 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -71,7 +71,7 @@ namespace osu.Game.Overlays.Toolbar { new ToolbarChangelogButton(), new ToolbarRankingsButton(), - new ToolbarDirectButton(), + new ToolbarBeatmapListingButton(), new ToolbarChatButton(), new ToolbarSocialButton(), new ToolbarMusicButton(), diff --git a/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs b/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs similarity index 63% rename from osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs rename to osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs index 1d07a3ae70..eecb368ee9 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs @@ -6,17 +6,17 @@ using osu.Game.Graphics; namespace osu.Game.Overlays.Toolbar { - public class ToolbarDirectButton : ToolbarOverlayToggleButton + public class ToolbarBeatmapListingButton : ToolbarOverlayToggleButton { - public ToolbarDirectButton() + public ToolbarBeatmapListingButton() { SetIcon(OsuIcon.ChevronDownCircle); } [BackgroundDependencyLoader(true)] - private void load(DirectOverlay direct) + private void load(BeatmapListingOverlay beatmapListing) { - StateContainer = direct; + StateContainer = beatmapListing; } } } diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index fe538728e3..30e5e9702e 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Menu public Action OnEdit; public Action OnExit; - public Action OnDirect; + public Action OnBeatmapListing; public Action OnSolo; public Action OnSettings; public Action OnMulti; @@ -130,7 +130,7 @@ namespace osu.Game.Screens.Menu buttonsTopLevel.Add(new Button(@"play", @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P)); buttonsTopLevel.Add(new Button(@"osu!editor", @"button-generic-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E)); - buttonsTopLevel.Add(new Button(@"osu!direct", @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnDirect?.Invoke(), 0, Key.D)); + buttonsTopLevel.Add(new Button(@"osu!direct", @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.D)); if (host.CanExit) buttonsTopLevel.Add(new Button(@"exit", string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q)); diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 174eadfe26..0589e4d12b 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -72,7 +72,7 @@ namespace osu.Game.Screens.Menu private SongTicker songTicker; [BackgroundDependencyLoader(true)] - private void load(DirectOverlay direct, SettingsOverlay settings, RankingsOverlay rankings, OsuConfigManager config, SessionStatics statics) + private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings, RankingsOverlay rankings, OsuConfigManager config, SessionStatics statics) { holdDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); loginDisplayed = statics.GetBindable(Static.LoginOverlayDisplayed); @@ -133,7 +133,7 @@ namespace osu.Game.Screens.Menu }; buttons.OnSettings = () => settings?.ToggleVisibility(); - buttons.OnDirect = () => direct?.ToggleVisibility(); + buttons.OnBeatmapListing = () => beatmapListing?.ToggleVisibility(); buttons.OnChart = () => rankings?.ShowSpotlights(); LoadComponentAsync(background = new BackgroundScreenDefault()); From 9b9b710ded76bfc99d326f94c9fd6a40c69f9412 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Apr 2020 16:03:18 +0900 Subject: [PATCH 431/655] Move and rename remaining direct classes --- .../Online/TestSceneDirectDownloadButton.cs | 6 ++--- .../Visual/Online/TestSceneDirectPanel.cs | 18 +++++++-------- osu.Game.Tests/Visual/TestSceneOsuGame.cs | 9 +------- .../BeatmapDownloadTrackingComposite.cs | 2 +- .../Panels/BeatmapPanel.cs} | 8 +++---- .../Panels/BeatmapPanelDownloadButton.cs} | 6 ++--- .../Panels/BeatmapPanelGrid.cs} | 16 +++++++------- .../Panels/BeatmapPanelList.cs} | 22 +++++++++---------- .../Panels}/DownloadProgressBar.cs | 2 +- .../Panels}/IconPill.cs | 2 +- .../Panels}/PlayButton.cs | 2 +- osu.Game/Overlays/BeatmapListingOverlay.cs | 6 ++--- .../Buttons/HeaderDownloadButton.cs | 2 +- .../BeatmapSet/Buttons/PreviewButton.cs | 2 +- osu.Game/Overlays/BeatmapSet/Header.cs | 4 ++-- .../Beatmaps/PaginatedBeatmapContainer.cs | 4 ++-- .../Overlays/Rankings/SpotlightsLayout.cs | 4 ++-- .../Screens/Multi/DrawableRoomPlaylistItem.cs | 4 ++-- 18 files changed, 56 insertions(+), 63 deletions(-) rename osu.Game/Overlays/{Direct => }/BeatmapDownloadTrackingComposite.cs (94%) rename osu.Game/Overlays/{Direct/DirectPanel.cs => BeatmapListing/Panels/BeatmapPanel.cs} (96%) rename osu.Game/Overlays/{Direct/PanelDownloadButton.cs => BeatmapListing/Panels/BeatmapPanelDownloadButton.cs} (93%) rename osu.Game/Overlays/{Direct/DirectGridPanel.cs => BeatmapListing/Panels/BeatmapPanelGrid.cs} (97%) rename osu.Game/Overlays/{Direct/DirectListPanel.cs => BeatmapListing/Panels/BeatmapPanelList.cs} (97%) rename osu.Game/Overlays/{Direct => BeatmapListing/Panels}/DownloadProgressBar.cs (97%) rename osu.Game/Overlays/{Direct => BeatmapListing/Panels}/IconPill.cs (96%) rename osu.Game/Overlays/{Direct => BeatmapListing/Panels}/PlayButton.cs (98%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs index f612992bf6..9fe873cb6a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs @@ -9,7 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Online; -using osu.Game.Overlays.Direct; +using osu.Game.Overlays.BeatmapListing.Panels; using osu.Game.Rulesets.Osu; using osu.Game.Tests.Resources; using osuTK; @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Online { public override IReadOnlyList RequiredTypes => new[] { - typeof(PanelDownloadButton) + typeof(BeatmapPanelDownloadButton) }; private TestDownloadButton downloadButton; @@ -143,7 +143,7 @@ namespace osu.Game.Tests.Visual.Online return beatmap; } - private class TestDownloadButton : PanelDownloadButton + private class TestDownloadButton : BeatmapPanelDownloadButton { public new bool DownloadEnabled => base.DownloadEnabled; diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs index cb08cded37..5809f93d90 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Audio; using osu.Game.Beatmaps; -using osu.Game.Overlays.Direct; +using osu.Game.Overlays.BeatmapListing.Panels; using osu.Game.Rulesets; using osu.Game.Users; using osuTK; @@ -20,8 +20,8 @@ namespace osu.Game.Tests.Visual.Online { public override IReadOnlyList RequiredTypes => new[] { - typeof(DirectGridPanel), - typeof(DirectListPanel), + typeof(BeatmapPanelGrid), + typeof(BeatmapPanelList), typeof(IconPill) }; @@ -126,12 +126,12 @@ namespace osu.Game.Tests.Visual.Online Spacing = new Vector2(5, 20), Children = new Drawable[] { - new DirectGridPanel(normal), - new DirectGridPanel(undownloadable), - new DirectGridPanel(manyDifficulties), - new DirectListPanel(normal), - new DirectListPanel(undownloadable), - new DirectListPanel(manyDifficulties), + new BeatmapPanelGrid(normal), + new BeatmapPanelGrid(undownloadable), + new BeatmapPanelGrid(manyDifficulties), + new BeatmapPanelList(normal), + new BeatmapPanelList(undownloadable), + new BeatmapPanelList(manyDifficulties), }, }, }; diff --git a/osu.Game.Tests/Visual/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/TestSceneOsuGame.cs index d68217dcfd..2eaac2a45f 100644 --- a/osu.Game.Tests/Visual/TestSceneOsuGame.cs +++ b/osu.Game.Tests/Visual/TestSceneOsuGame.cs @@ -47,15 +47,8 @@ namespace osu.Game.Tests.Visual typeof(IdleTracker), typeof(OnScreenDisplay), typeof(NotificationOverlay), -<<<<<<< HEAD - typeof(DirectOverlay), + typeof(BeatmapListingOverlay), typeof(DashboardOverlay), -||||||| parent of 96a3a08a9... Remove unused classes and replace overlay in game - typeof(DirectOverlay), - typeof(SocialOverlay), -======= - typeof(SocialOverlay), ->>>>>>> 96a3a08a9... Remove unused classes and replace overlay in game typeof(ChannelManager), typeof(ChatOverlay), typeof(SettingsOverlay), diff --git a/osu.Game/Overlays/Direct/BeatmapDownloadTrackingComposite.cs b/osu.Game/Overlays/BeatmapDownloadTrackingComposite.cs similarity index 94% rename from osu.Game/Overlays/Direct/BeatmapDownloadTrackingComposite.cs rename to osu.Game/Overlays/BeatmapDownloadTrackingComposite.cs index fd04a1541e..f6b5b181c3 100644 --- a/osu.Game/Overlays/Direct/BeatmapDownloadTrackingComposite.cs +++ b/osu.Game/Overlays/BeatmapDownloadTrackingComposite.cs @@ -5,7 +5,7 @@ using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Online; -namespace osu.Game.Overlays.Direct +namespace osu.Game.Overlays { public abstract class BeatmapDownloadTrackingComposite : DownloadTrackingComposite { diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanel.cs similarity index 96% rename from osu.Game/Overlays/Direct/DirectPanel.cs rename to osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanel.cs index 4ad8e95512..f260bf1573 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanel.cs @@ -26,9 +26,9 @@ using osu.Game.Graphics.UserInterface; using osuTK; using osuTK.Graphics; -namespace osu.Game.Overlays.Direct +namespace osu.Game.Overlays.BeatmapListing.Panels { - public abstract class DirectPanel : OsuClickableContainer, IHasContextMenu + public abstract class BeatmapPanel : OsuClickableContainer, IHasContextMenu { public readonly BeatmapSetInfo SetInfo; @@ -49,7 +49,7 @@ namespace osu.Game.Overlays.Direct protected Action ViewBeatmap; - protected DirectPanel(BeatmapSetInfo setInfo) + protected BeatmapPanel(BeatmapSetInfo setInfo) { Debug.Assert(setInfo.OnlineBeatmapSetID != null); @@ -148,7 +148,7 @@ namespace osu.Game.Overlays.Direct if (SetInfo.Beatmaps.Count > maximum_difficulty_icons) { foreach (var ruleset in SetInfo.Beatmaps.Select(b => b.Ruleset).Distinct()) - icons.Add(new GroupedDifficultyIcon(SetInfo.Beatmaps.FindAll(b => b.Ruleset.Equals(ruleset)), ruleset, this is DirectListPanel ? Color4.White : colours.Gray5)); + icons.Add(new GroupedDifficultyIcon(SetInfo.Beatmaps.FindAll(b => b.Ruleset.Equals(ruleset)), ruleset, this is BeatmapPanelList ? Color4.White : colours.Gray5)); } else { diff --git a/osu.Game/Overlays/Direct/PanelDownloadButton.cs b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs similarity index 93% rename from osu.Game/Overlays/Direct/PanelDownloadButton.cs rename to osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs index 387ced6acb..589f2d5072 100644 --- a/osu.Game/Overlays/Direct/PanelDownloadButton.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs @@ -11,9 +11,9 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online; -namespace osu.Game.Overlays.Direct +namespace osu.Game.Overlays.BeatmapListing.Panels { - public class PanelDownloadButton : BeatmapDownloadTrackingComposite + public class BeatmapPanelDownloadButton : BeatmapDownloadTrackingComposite { protected bool DownloadEnabled => button.Enabled.Value; @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.Direct private readonly DownloadButton button; private Bindable noVideoSetting; - public PanelDownloadButton(BeatmapSetInfo beatmapSet) + public BeatmapPanelDownloadButton(BeatmapSetInfo beatmapSet) : base(beatmapSet) { InternalChild = shakeContainer = new ShakeContainer diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelGrid.cs similarity index 97% rename from osu.Game/Overlays/Direct/DirectGridPanel.cs rename to osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelGrid.cs index 2528ccec41..caa7eb6441 100644 --- a/osu.Game/Overlays/Direct/DirectGridPanel.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelGrid.cs @@ -1,25 +1,25 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK; -using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; -namespace osu.Game.Overlays.Direct +namespace osu.Game.Overlays.BeatmapListing.Panels { - public class DirectGridPanel : DirectPanel + public class BeatmapPanelGrid : BeatmapPanel { private const float horizontal_padding = 10; private const float vertical_padding = 5; @@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Direct protected override PlayButton PlayButton => playButton; protected override Box PreviewBar => progressBar; - public DirectGridPanel(BeatmapSetInfo beatmap) + public BeatmapPanelGrid(BeatmapSetInfo beatmap) : base(beatmap) { Width = 380; @@ -156,7 +156,7 @@ namespace osu.Game.Overlays.Direct }, }, }, - new PanelDownloadButton(SetInfo) + new BeatmapPanelDownloadButton(SetInfo) { Size = new Vector2(50, 30), Margin = new MarginPadding(horizontal_padding), diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelList.cs similarity index 97% rename from osu.Game/Overlays/Direct/DirectListPanel.cs rename to osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelList.cs index b64142dfe7..3245ddea99 100644 --- a/osu.Game/Overlays/Direct/DirectListPanel.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelList.cs @@ -1,25 +1,25 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK; -using osuTK.Graphics; +using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Colour; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; -namespace osu.Game.Overlays.Direct +namespace osu.Game.Overlays.BeatmapListing.Panels { - public class DirectListPanel : DirectPanel + public class BeatmapPanelList : BeatmapPanel { private const float transition_duration = 120; private const float horizontal_padding = 10; @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Direct private const float height = 70; private FillFlowContainer statusContainer; - protected PanelDownloadButton DownloadButton; + protected BeatmapPanelDownloadButton DownloadButton; private PlayButton playButton; private Box progressBar; @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Direct protected override PlayButton PlayButton => playButton; protected override Box PreviewBar => progressBar; - public DirectListPanel(BeatmapSetInfo beatmap) + public BeatmapPanelList(BeatmapSetInfo beatmap) : base(beatmap) { RelativeSizeAxes = Axes.X; @@ -151,7 +151,7 @@ namespace osu.Game.Overlays.Direct Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, AutoSizeAxes = Axes.Both, - Child = DownloadButton = new PanelDownloadButton(SetInfo) + Child = DownloadButton = new BeatmapPanelDownloadButton(SetInfo) { Size = new Vector2(height - vertical_padding * 3), Margin = new MarginPadding { Left = vertical_padding * 2, Right = vertical_padding }, diff --git a/osu.Game/Overlays/Direct/DownloadProgressBar.cs b/osu.Game/Overlays/BeatmapListing/Panels/DownloadProgressBar.cs similarity index 97% rename from osu.Game/Overlays/Direct/DownloadProgressBar.cs rename to osu.Game/Overlays/BeatmapListing/Panels/DownloadProgressBar.cs index 9a8644efd2..93cf8799b5 100644 --- a/osu.Game/Overlays/Direct/DownloadProgressBar.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/DownloadProgressBar.cs @@ -10,7 +10,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osuTK.Graphics; -namespace osu.Game.Overlays.Direct +namespace osu.Game.Overlays.BeatmapListing.Panels { public class DownloadProgressBar : BeatmapDownloadTrackingComposite { diff --git a/osu.Game/Overlays/Direct/IconPill.cs b/osu.Game/Overlays/BeatmapListing/Panels/IconPill.cs similarity index 96% rename from osu.Game/Overlays/Direct/IconPill.cs rename to osu.Game/Overlays/BeatmapListing/Panels/IconPill.cs index d63bb2a292..1cb6c84f13 100644 --- a/osu.Game/Overlays/Direct/IconPill.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/IconPill.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Sprites; using osuTK; using osuTK.Graphics; -namespace osu.Game.Overlays.Direct +namespace osu.Game.Overlays.BeatmapListing.Panels { public class IconPill : CircularContainer { diff --git a/osu.Game/Overlays/Direct/PlayButton.cs b/osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs similarity index 98% rename from osu.Game/Overlays/Direct/PlayButton.cs rename to osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs index d9f335b6a7..e95fdeecf4 100644 --- a/osu.Game/Overlays/Direct/PlayButton.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs @@ -14,7 +14,7 @@ using osu.Game.Graphics.UserInterface; using osuTK; using osuTK.Graphics; -namespace osu.Game.Overlays.Direct +namespace osu.Game.Overlays.BeatmapListing.Panels { public class PlayButton : Container { diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 000ca6b91c..a024e2c74e 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -17,7 +17,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.BeatmapListing; -using osu.Game.Overlays.Direct; +using osu.Game.Overlays.BeatmapListing.Panels; using osuTK; namespace osu.Game.Overlays @@ -118,14 +118,14 @@ namespace osu.Game.Overlays return; } - var newPanels = new FillFlowContainer + var newPanels = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Spacing = new Vector2(10), Alpha = 0, Margin = new MarginPadding { Vertical = 15 }, - ChildrenEnumerable = beatmaps.Select(b => new DirectGridPanel(b) + ChildrenEnumerable = beatmaps.Select(b => new BeatmapPanelGrid(b) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs index e64256b850..56c0052bfe 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs @@ -13,7 +13,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online; using osu.Game.Online.API; -using osu.Game.Overlays.Direct; +using osu.Game.Overlays.BeatmapListing.Panels; using osu.Game.Users; using osuTK; using osuTK.Graphics; diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs index 7eae05e4a9..6accce7d77 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs @@ -11,7 +11,7 @@ using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Overlays.Direct; +using osu.Game.Overlays.BeatmapListing.Panels; using osuTK; namespace osu.Game.Overlays.BeatmapSet.Buttons diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index 11dc424183..17fa689cd2 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -15,8 +15,8 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online; +using osu.Game.Overlays.BeatmapListing.Panels; using osu.Game.Overlays.BeatmapSet.Buttons; -using osu.Game.Overlays.Direct; using osu.Game.Rulesets; using osuTK; using osuTK.Graphics; @@ -274,7 +274,7 @@ namespace osu.Game.Overlays.BeatmapSet { case DownloadState.LocallyAvailable: // temporary for UX until new design is implemented. - downloadButtonsContainer.Child = new PanelDownloadButton(BeatmapSet.Value) + downloadButtonsContainer.Child = new BeatmapPanelDownloadButton(BeatmapSet.Value) { Width = 50, RelativeSizeAxes = Axes.Y, diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index fcd12e2b54..5f70dc4d75 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Overlays.Direct; +using osu.Game.Overlays.BeatmapListing.Panels; using osu.Game.Users; using osuTK; @@ -33,7 +33,7 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps protected override Drawable CreateDrawableItem(APIBeatmapSet model) => !model.OnlineBeatmapSetID.HasValue ? null - : new DirectGridPanel(model.ToBeatmapSet(Rulesets)) + : new BeatmapPanelGrid(model.ToBeatmapSet(Rulesets)) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs index 6f06eecd6e..895fa94af5 100644 --- a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs +++ b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs @@ -12,10 +12,10 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Rankings.Tables; using System.Linq; -using osu.Game.Overlays.Direct; using System.Threading; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.BeatmapListing.Panels; namespace osu.Game.Overlays.Rankings { @@ -140,7 +140,7 @@ namespace osu.Game.Overlays.Rankings AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Spacing = new Vector2(10), - Children = response.BeatmapSets.Select(b => new DirectGridPanel(b.ToBeatmapSet(rulesets)) + Children = response.BeatmapSets.Select(b => new BeatmapPanelGrid(b.ToBeatmapSet(rulesets)) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs index d7dcca9809..c024304856 100644 --- a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs @@ -21,7 +21,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.Chat; using osu.Game.Online.Multiplayer; -using osu.Game.Overlays.Direct; +using osu.Game.Overlays.BeatmapListing.Panels; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play.HUD; @@ -210,7 +210,7 @@ namespace osu.Game.Screens.Multi return true; } - private class PlaylistDownloadButton : PanelDownloadButton + private class PlaylistDownloadButton : BeatmapPanelDownloadButton { public PlaylistDownloadButton(BeatmapSetInfo beatmapSet) : base(beatmapSet) From b8a1831d98feb8fc6752a2755b66df55de66c832 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Apr 2020 17:14:04 +0900 Subject: [PATCH 432/655] Read line widths from skin --- osu.Game/Skinning/LegacySkin.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 91f970d19f..003fa24d5b 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -249,6 +249,14 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.RightStageImage: return SkinUtils.As(getManiaImage(existing, "StageRight")); + + case LegacyManiaSkinConfigurationLookups.LeftLineWidth: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(new Bindable(existing.ColumnLineWidth[maniaLookup.TargetColumn.Value])); + + case LegacyManiaSkinConfigurationLookups.RightLineWidth: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(new Bindable(existing.ColumnLineWidth[maniaLookup.TargetColumn.Value + 1])); } return null; From 0a2b585c65ca963a1fac0eb917328f329b722d54 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Apr 2020 17:14:49 +0900 Subject: [PATCH 433/655] Apply missing scale --- osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs index 6504321bb2..1a097405ac 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs @@ -67,6 +67,7 @@ namespace osu.Game.Rulesets.Mania.Skinning { RelativeSizeAxes = Axes.Y, Width = leftLineWidth, + Scale = new Vector2(0.740f, 1), Colour = lineColour, Alpha = hasLeftLine ? 1 : 0 }, @@ -76,6 +77,7 @@ namespace osu.Game.Rulesets.Mania.Skinning Origin = Anchor.TopRight, RelativeSizeAxes = Axes.Y, Width = rightLineWidth, + Scale = new Vector2(0.740f, 1), Colour = lineColour, Alpha = hasRightLine ? 1 : 0 }, From a41ac50e2f052854032131e62d12407e6fd3242d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Apr 2020 17:15:06 +0900 Subject: [PATCH 434/655] Line widths should not receive scale factor --- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index 2db902c182..a988bd589f 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -74,7 +74,7 @@ namespace osu.Game.Skinning switch (pair.Key) { case "ColumnLineWidth": - parseArrayValue(pair.Value, currentConfig.ColumnLineWidth); + parseArrayValue(pair.Value, currentConfig.ColumnLineWidth, false); break; case "ColumnSpacing": @@ -124,7 +124,7 @@ namespace osu.Game.Skinning pendingLines.Clear(); } - private void parseArrayValue(string value, float[] output) + private void parseArrayValue(string value, float[] output, bool applyScaleFactor = true) { string[] values = value.Split(','); @@ -133,7 +133,7 @@ namespace osu.Game.Skinning if (i >= output.Length) break; - output[i] = float.Parse(values[i], CultureInfo.InvariantCulture) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; + output[i] = float.Parse(values[i], CultureInfo.InvariantCulture) * (applyScaleFactor ? LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR : 1); } } } From 4642a6093c2b6e5fa559059567e77863125e2c2a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Apr 2020 17:15:13 +0900 Subject: [PATCH 435/655] Add test --- .../Resources/special-skin/mania-key1@2x.png | Bin 0 -> 12914 bytes .../Resources/special-skin/skin.ini | 6 ++++++ .../Skinning/TestSceneColumnBackground.cs | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-key1@2x.png create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-key1@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-key1@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..aa681f6f223ea44319b3a383935237d1a2e4b345 GIT binary patch literal 12914 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini new file mode 100644 index 0000000000..56564776b3 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini @@ -0,0 +1,6 @@ +[General] +Version: 2.4 + +[Mania] +Keys: 4 +ColumnLineWidth: 3,1,3,1,1 \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs index d6bacbe59e..bde323f187 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { RelativeSizeAxes = Axes.Both, Width = 0.5f, - Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, 0), _ => new DefaultColumnBackground()) + Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, 1), _ => new DefaultColumnBackground()) { RelativeSizeAxes = Axes.Both } From d1c701a9972a42af10021bccb5d528ddb51f71c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Apr 2020 18:34:56 +0900 Subject: [PATCH 436/655] Rename existing test to something more relevant --- .../{TestSceneTaikoPlayfield.cs => TestSceneHits.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Rulesets.Taiko.Tests/{TestSceneTaikoPlayfield.cs => TestSceneHits.cs} (99%) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs similarity index 99% rename from osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs rename to osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs index 0d9e813c60..c2ca578dfa 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs @@ -24,7 +24,7 @@ using osuTK; namespace osu.Game.Rulesets.Taiko.Tests { [TestFixture] - public class TestSceneTaikoPlayfield : OsuTestScene + public class TestSceneHits : OsuTestScene { private const double default_duration = 1000; private const float scroll_time = 1000; From e74f9024836b56b4485d724d867fa1f71ea0a5b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Apr 2020 18:40:18 +0900 Subject: [PATCH 437/655] Add playfield test scene --- .../Skinning/TestSceneTaikoPlayfield.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs new file mode 100644 index 0000000000..e255baf459 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Taiko.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests.Skinning +{ + public class TestSceneTaikoPlayfield : TaikoSkinnableTestScene + { + [Cached(typeof(IScrollingInfo))] + private ScrollingTestContainer.TestScrollingInfo info = new ScrollingTestContainer.TestScrollingInfo + { + Direction = { Value = ScrollingDirection.Left }, + TimeRange = { Value = 5000 }, + }; + + public TestSceneTaikoPlayfield() + { + AddStep("Load playfield", () => SetContents(() => new TaikoPlayfield(new ControlPointInfo()) + { + Height = 0.4f, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + })); + } + } +} From bfc17bf4c09ac2493748c3751f3921b374746086 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Apr 2020 19:00:34 +0900 Subject: [PATCH 438/655] Add taiko hit target skinning --- .../metrics-skin/approachcircle@2x.png | Bin 0 -> 13816 bytes .../metrics-skin/taikobigcircle@2x.png | Bin 0 -> 12145 bytes .../Resources/old-skin/approachcircle.png | Bin 0 -> 10333 bytes .../Resources/special-skin/approachcircle.png | Bin 0 -> 4504 bytes .../special-skin/taikobigcircle@2x.png | Bin 0 -> 12374 bytes .../Skinning/TestSceneTaikoPlayfield.cs | 10 +++++ .../Skinning/LegacyHitTarget.cs | 41 ++++++++++++++++++ .../Skinning/TaikoLegacySkinTransformer.cs | 6 +++ .../TaikoSkinComponents.cs | 3 +- osu.Game.Rulesets.Taiko/UI/HitTarget.cs | 2 + osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 7 +-- 11 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/approachcircle@2x.png create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taikobigcircle@2x.png create mode 100755 osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/approachcircle.png create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/approachcircle.png create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikobigcircle@2x.png create mode 100644 osu.Game.Rulesets.Taiko/Skinning/LegacyHitTarget.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/approachcircle@2x.png b/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/approachcircle@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..72ef665478b758c364197aa79f238230e229b3c4 GIT binary patch literal 13816 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 X;|*6ltxe$d|7PiG>1$T1+eQB$HM;D+ literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taikobigcircle@2x.png b/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taikobigcircle@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..440e5b55e52c8629f69272993a75b1a56002fb60 GIT binary patch literal 12145 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/approachcircle.png b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/approachcircle.png new file mode 100755 index 0000000000000000000000000000000000000000..5aba6887564782a216c030b6a0143830612193ee GIT binary patch literal 10333 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 H<7ECnYE>!V literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/approachcircle.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/approachcircle.png new file mode 100644 index 0000000000000000000000000000000000000000..56d6d34c1abf6ab153c9cef62eecff3016d21ed9 GIT binary patch literal 4504 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 Qd;A||`ImC#G8P~IA7%MrKL7v# literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs index e255baf459..730eed0e0f 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs @@ -1,9 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Taiko.Skinning; using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; @@ -12,6 +16,12 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning { public class TestSceneTaikoPlayfield : TaikoSkinnableTestScene { + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] + { + typeof(HitTarget), + typeof(LegacyHitTarget), + }).ToList(); + [Cached(typeof(IScrollingInfo))] private ScrollingTestContainer.TestScrollingInfo info = new ScrollingTestContainer.TestScrollingInfo { diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitTarget.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitTarget.cs new file mode 100644 index 0000000000..51aea9b9ab --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitTarget.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Taiko.Skinning +{ + public class LegacyHitTarget : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new Sprite + { + Texture = skin.GetTexture("approachcircle"), + Scale = new Vector2(0.73f), + Alpha = 0.7f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new Sprite + { + Texture = skin.GetTexture("taikobigcircle"), + Scale = new Vector2(0.7f), + Alpha = 0.5f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + }; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs index 3af7df07c4..6b59718173 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs @@ -49,6 +49,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning case TaikoSkinComponents.DrumRollTick: return this.GetAnimation("sliderscorepoint", false, false); + + case TaikoSkinComponents.HitTarget: + if (GetTexture("taikobigcircle") != null) + return new LegacyHitTarget(); + + return null; } return source.GetDrawableComponent(component); diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs index 156ea71c16..775eeb4e38 100644 --- a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs +++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs @@ -10,6 +10,7 @@ namespace osu.Game.Rulesets.Taiko RimHit, DrumRollBody, DrumRollTick, - Swell + Swell, + HitTarget } } diff --git a/osu.Game.Rulesets.Taiko/UI/HitTarget.cs b/osu.Game.Rulesets.Taiko/UI/HitTarget.cs index 2bb208bd1d..88886508af 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitTarget.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitTarget.cs @@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Taiko.UI public HitTarget() { + RelativeSizeAxes = Axes.Both; + Children = new Drawable[] { new Box diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index bde9085c23..375d9995c0 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -17,6 +17,7 @@ using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -42,7 +43,7 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Container hitExplosionContainer; private readonly Container kiaiExplosionContainer; private readonly JudgementContainer judgementContainer; - internal readonly HitTarget HitTarget; + internal readonly Drawable HitTarget; private readonly ProxyContainer topLevelHitContainer; private readonly ProxyContainer barlineContainer; @@ -90,7 +91,7 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, Masking = true, - Children = new Drawable[] + Children = new[] { hitExplosionContainer = new Container { @@ -98,7 +99,7 @@ namespace osu.Game.Rulesets.Taiko.UI FillMode = FillMode.Fit, Blending = BlendingParameters.Additive, }, - HitTarget = new HitTarget + HitTarget = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.HitTarget), _ => new HitTarget()) { Anchor = Anchor.CentreLeft, Origin = Anchor.Centre, From ed9663985b439dba6b5c51281ba40cd6cb2e1c07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Apr 2020 20:55:33 +0900 Subject: [PATCH 439/655] Rename panels --- .../Visual/Online/TestSceneDirectPanel.cs | 16 ++++++++-------- .../BeatmapListing/Panels/BeatmapPanel.cs | 2 +- .../{BeatmapPanelGrid.cs => GridBeatmapPanel.cs} | 4 ++-- .../{BeatmapPanelList.cs => ListBeatmapPanel.cs} | 4 ++-- osu.Game/Overlays/BeatmapListingOverlay.cs | 2 +- .../Beatmaps/PaginatedBeatmapContainer.cs | 2 +- osu.Game/Overlays/Rankings/SpotlightsLayout.cs | 2 +- 7 files changed, 16 insertions(+), 16 deletions(-) rename osu.Game/Overlays/BeatmapListing/Panels/{BeatmapPanelGrid.cs => GridBeatmapPanel.cs} (98%) rename osu.Game/Overlays/BeatmapListing/Panels/{BeatmapPanelList.cs => ListBeatmapPanel.cs} (99%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs index 5809f93d90..d6ed654bac 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs @@ -20,8 +20,8 @@ namespace osu.Game.Tests.Visual.Online { public override IReadOnlyList RequiredTypes => new[] { - typeof(BeatmapPanelGrid), - typeof(BeatmapPanelList), + typeof(GridBeatmapPanel), + typeof(ListBeatmapPanel), typeof(IconPill) }; @@ -126,12 +126,12 @@ namespace osu.Game.Tests.Visual.Online Spacing = new Vector2(5, 20), Children = new Drawable[] { - new BeatmapPanelGrid(normal), - new BeatmapPanelGrid(undownloadable), - new BeatmapPanelGrid(manyDifficulties), - new BeatmapPanelList(normal), - new BeatmapPanelList(undownloadable), - new BeatmapPanelList(manyDifficulties), + new GridBeatmapPanel(normal), + new GridBeatmapPanel(undownloadable), + new GridBeatmapPanel(manyDifficulties), + new ListBeatmapPanel(normal), + new ListBeatmapPanel(undownloadable), + new ListBeatmapPanel(manyDifficulties), }, }, }; diff --git a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanel.cs index f260bf1573..88c15776cd 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanel.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanel.cs @@ -148,7 +148,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels if (SetInfo.Beatmaps.Count > maximum_difficulty_icons) { foreach (var ruleset in SetInfo.Beatmaps.Select(b => b.Ruleset).Distinct()) - icons.Add(new GroupedDifficultyIcon(SetInfo.Beatmaps.FindAll(b => b.Ruleset.Equals(ruleset)), ruleset, this is BeatmapPanelList ? Color4.White : colours.Gray5)); + icons.Add(new GroupedDifficultyIcon(SetInfo.Beatmaps.FindAll(b => b.Ruleset.Equals(ruleset)), ruleset, this is ListBeatmapPanel ? Color4.White : colours.Gray5)); } else { diff --git a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelGrid.cs b/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs similarity index 98% rename from osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelGrid.cs rename to osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs index caa7eb6441..84d35da096 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelGrid.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs @@ -19,7 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapListing.Panels { - public class BeatmapPanelGrid : BeatmapPanel + public class GridBeatmapPanel : BeatmapPanel { private const float horizontal_padding = 10; private const float vertical_padding = 5; @@ -31,7 +31,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels protected override PlayButton PlayButton => playButton; protected override Box PreviewBar => progressBar; - public BeatmapPanelGrid(BeatmapSetInfo beatmap) + public GridBeatmapPanel(BeatmapSetInfo beatmap) : base(beatmap) { Width = 380; diff --git a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelList.cs b/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs similarity index 99% rename from osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelList.cs rename to osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs index 3245ddea99..433ea37f06 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelList.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs @@ -19,7 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapListing.Panels { - public class BeatmapPanelList : BeatmapPanel + public class ListBeatmapPanel : BeatmapPanel { private const float transition_duration = 120; private const float horizontal_padding = 10; @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels protected override PlayButton PlayButton => playButton; protected override Box PreviewBar => progressBar; - public BeatmapPanelList(BeatmapSetInfo beatmap) + public ListBeatmapPanel(BeatmapSetInfo beatmap) : base(beatmap) { RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index a024e2c74e..f680f7c67b 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -125,7 +125,7 @@ namespace osu.Game.Overlays Spacing = new Vector2(10), Alpha = 0, Margin = new MarginPadding { Vertical = 15 }, - ChildrenEnumerable = beatmaps.Select(b => new BeatmapPanelGrid(b) + ChildrenEnumerable = beatmaps.Select(b => new GridBeatmapPanel(b) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 5f70dc4d75..191f3c908a 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -33,7 +33,7 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps protected override Drawable CreateDrawableItem(APIBeatmapSet model) => !model.OnlineBeatmapSetID.HasValue ? null - : new BeatmapPanelGrid(model.ToBeatmapSet(Rulesets)) + : new GridBeatmapPanel(model.ToBeatmapSet(Rulesets)) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs index 895fa94af5..917509e842 100644 --- a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs +++ b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs @@ -140,7 +140,7 @@ namespace osu.Game.Overlays.Rankings AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Spacing = new Vector2(10), - Children = response.BeatmapSets.Select(b => new BeatmapPanelGrid(b.ToBeatmapSet(rulesets)) + Children = response.BeatmapSets.Select(b => new GridBeatmapPanel(b.ToBeatmapSet(rulesets)) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, From 6193b80589790cc4f90a6141cd34fa6900b2723f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Apr 2020 21:20:57 +0900 Subject: [PATCH 440/655] 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 d2bdbc8b61..25942863c5 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 35ee0864e1..9c17c453a6 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 0200fca9a3..07ea4b9c2a 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From e9a2e92adf3573c27096b796d99db094f2fd6f5d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Apr 2020 14:43:22 +0900 Subject: [PATCH 441/655] Fix incorrect beatmap comments --- osu.Game.Tests/Resources/hitobject-combo-offset.osu | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Resources/hitobject-combo-offset.osu b/osu.Game.Tests/Resources/hitobject-combo-offset.osu index c1f0dab8e9..d39a3e8548 100644 --- a/osu.Game.Tests/Resources/hitobject-combo-offset.osu +++ b/osu.Game.Tests/Resources/hitobject-combo-offset.osu @@ -5,27 +5,27 @@ osu file format v14 255,193,1000,49,0,0:0:0:0: // Combo index = 4 -// Slider with new combo followed by circle with no new combo +// Spinner with new combo followed by circle with no new combo 256,192,2000,12,0,2000,0:0:0:0: 255,193,3000,1,0,0:0:0:0: // Combo index = 5 -// Slider without new combo followed by circle with no new combo +// Spinner without new combo followed by circle with no new combo 256,192,4000,8,0,5000,0:0:0:0: 255,193,6000,1,0,0:0:0:0: // Combo index = 5 -// Slider without new combo followed by circle with new combo +// Spinner without new combo followed by circle with new combo 256,192,7000,8,0,8000,0:0:0:0: 255,193,9000,5,0,0:0:0:0: // Combo index = 6 -// Slider with new combo and offset (1) followed by circle with new combo and offset (3) +// Spinner with new combo and offset (1) followed by circle with new combo and offset (3) 256,192,10000,28,0,11000,0:0:0:0: 255,193,12000,53,0,0:0:0:0: // Combo index = 11 -// Slider with new combo and offset (2) followed by slider with no new combo followed by circle with no new combo +// Spinner with new combo and offset (2) followed by slider with no new combo followed by circle with no new combo 256,192,13000,44,0,14000,0:0:0:0: 256,192,15000,8,0,16000,0:0:0:0: 255,193,17000,1,0,0:0:0:0: From 9713d903884469c247397ae3f7bd223fd0df21e9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Apr 2020 14:47:12 +0900 Subject: [PATCH 442/655] Always apply beatmap converter/processor --- .../Formats/LegacyBeatmapEncoderTest.cs | 61 +++++++++++++++++-- 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index f2b3a16f68..62cf2cec43 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -1,14 +1,21 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.IO; using System.Linq; using NUnit.Framework; +using osu.Framework.Audio.Track; +using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.IO.Serialization; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Taiko; using osu.Game.Tests.Resources; namespace osu.Game.Tests.Beatmaps.Formats @@ -29,26 +36,68 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(encoded.Serialize(), Is.EqualTo(decoded.Serialize())); } - private Beatmap decode(string filename, out Beatmap encoded) + private IBeatmap decode(string filename, out IBeatmap encoded) { - using (var stream = TestResources.OpenResource(filename)) + using (var stream = TestResources.GetStore().GetStream(filename)) using (var sr = new LineBufferedReader(stream)) { - var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr); + var legacyDecoded = convert(new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr)); using (var ms = new MemoryStream()) using (var sw = new StreamWriter(ms)) - using (var sr2 = new LineBufferedReader(ms)) + using (var sr2 = new LineBufferedReader(ms, true)) { new LegacyBeatmapEncoder(legacyDecoded).Encode(sw); - sw.Flush(); + sw.Flush(); ms.Position = 0; - encoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr2); + encoded = convert(new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr2)); + return legacyDecoded; } } } + + private IBeatmap convert(IBeatmap beatmap) + { + switch (beatmap.BeatmapInfo.RulesetID) + { + case 0: + beatmap.BeatmapInfo.Ruleset = new OsuRuleset().RulesetInfo; + break; + + case 1: + beatmap.BeatmapInfo.Ruleset = new TaikoRuleset().RulesetInfo; + break; + + case 2: + beatmap.BeatmapInfo.Ruleset = new CatchRuleset().RulesetInfo; + break; + + case 3: + beatmap.BeatmapInfo.Ruleset = new ManiaRuleset().RulesetInfo; + break; + } + + return new TestWorkingBeatmap(beatmap).GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset); + } + + private class TestWorkingBeatmap : WorkingBeatmap + { + private readonly IBeatmap beatmap; + + public TestWorkingBeatmap(IBeatmap beatmap) + : base(beatmap.BeatmapInfo, null) + { + this.beatmap = beatmap; + } + + protected override IBeatmap GetBeatmap() => beatmap; + + protected override Texture GetBackground() => throw new NotImplementedException(); + + protected override Track GetTrack() => throw new NotImplementedException(); + } } } From 8ea76244a26699bc98a27926315292ee44a83897 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Apr 2020 14:48:32 +0900 Subject: [PATCH 443/655] Fix only single beatmap being tested --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 62cf2cec43..01edafcf31 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -23,14 +23,12 @@ namespace osu.Game.Tests.Beatmaps.Formats [TestFixture] public class LegacyBeatmapEncoderTest { - private const string normal = "Soleily - Renatus (Gamu) [Insane].osu"; - private static IEnumerable allBeatmaps => TestResources.GetStore().GetAvailableResources().Where(res => res.EndsWith(".osu")); [TestCaseSource(nameof(allBeatmaps))] - public void TestDecodeEncodedBeatmap(string name) + public void TestBeatmap(string name) { - var decoded = decode(normal, out var encoded); + var decoded = decode(name, out var encoded); Assert.That(decoded.HitObjects.Count, Is.EqualTo(encoded.HitObjects.Count)); Assert.That(encoded.Serialize(), Is.EqualTo(decoded.Serialize())); From 1e7e7417ed77dcec18944565b826c28919490a94 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Apr 2020 14:49:31 +0900 Subject: [PATCH 444/655] Fix testing relying on control point order --- .../Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 01edafcf31..bcc873b0b7 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; @@ -9,6 +10,7 @@ using NUnit.Framework; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.IO.Serialization; @@ -30,10 +32,22 @@ namespace osu.Game.Tests.Beatmaps.Formats { var decoded = decode(name, out var encoded); - Assert.That(decoded.HitObjects.Count, Is.EqualTo(encoded.HitObjects.Count)); + sort(decoded); + sort(encoded); + Assert.That(encoded.Serialize(), Is.EqualTo(decoded.Serialize())); } + private void sort(IBeatmap beatmap) + { + // Sort control points to ensure a sane ordering, as they may be parsed in different orders. This works because each group contains only uniquely-typed control points. + foreach (var g in beatmap.ControlPointInfo.Groups) + { + ArrayList.Adapter((IList)g.ControlPoints).Sort( + Comparer.Create((c1, c2) => string.Compare(c1.GetType().ToString(), c2.GetType().ToString(), StringComparison.Ordinal))); + } + } + private IBeatmap decode(string filename, out IBeatmap encoded) { using (var stream = TestResources.GetStore().GetStream(filename)) From 21949ac499ab3ece38ddfb799a4f704a0a589c4b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Apr 2020 14:49:43 +0900 Subject: [PATCH 445/655] Add osu! test beatmap --- .../Resources/sample-beatmap-osu.osu | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 osu.Game.Tests/Resources/sample-beatmap-osu.osu diff --git a/osu.Game.Tests/Resources/sample-beatmap-osu.osu b/osu.Game.Tests/Resources/sample-beatmap-osu.osu new file mode 100644 index 0000000000..27c96077e6 --- /dev/null +++ b/osu.Game.Tests/Resources/sample-beatmap-osu.osu @@ -0,0 +1,32 @@ +osu file format v14 + +[General] +SampleSet: Normal +StackLeniency: 0.7 +Mode: 0 + +[Difficulty] +HPDrainRate:3 +CircleSize:5 +OverallDifficulty:8 +ApproachRate:8 +SliderMultiplier:3.59999990463257 +SliderTickRate:2 + +[TimingPoints] +24,352.941176470588,4,1,1,100,1,0 +6376,-50,4,1,1,100,0,0 + +[HitObjects] +98,69,24,1,0,0:0:0:0: +419,72,200,1,2,0:0:0:0: +81,314,376,1,6,0:0:0:0: +423,321,553,1,12,0:0:0:0: +86,192,729,2,0,P|459:193|460:193,1,359.999990463257 +86,192,1259,2,0,P|246:82|453:203,1,449.999988079071 +86,192,1876,2,0,B|256:30|257:313|464:177,1,359.999990463257 +86,55,2406,2,12,B|447:51|447:51|452:348|452:348|78:344,1,989.999973773957,14|2,0:0|0:0,0:0:0:0: +256,192,3553,12,0,4259,0:0:0:0: +67,57,4435,5,0,0:0:0:0: +440,52,4612,5,0,0:0:0:0: +86,181,4788,6,0,L|492:183,1,359.999990463257 \ No newline at end of file From a702a521f8c4af9fe4d645fcbd472466b6ba6ff5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Apr 2020 14:52:58 +0900 Subject: [PATCH 446/655] Fix not being able to serialise converted beatmaps --- .../Converters/TypedListConverter.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs index 6d244bff60..837650eb0a 100644 --- a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs +++ b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections; using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -43,13 +44,13 @@ namespace osu.Game.IO.Serialization.Converters var list = new List(); var obj = JObject.Load(reader); - var lookupTable = serializer.Deserialize>(obj["lookup_table"].CreateReader()); + var lookupTable = serializer.Deserialize>(obj["$lookup_table"].CreateReader()); - foreach (var tok in obj["items"]) + foreach (var tok in obj["$items"]) { var itemReader = tok.CreateReader(); - var typeName = lookupTable[(int)tok["type"]]; + var typeName = lookupTable[(int)tok["$type"]]; var instance = (T)Activator.CreateInstance(Type.GetType(typeName)); serializer.Populate(itemReader, instance); @@ -61,7 +62,7 @@ namespace osu.Game.IO.Serialization.Converters public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { - var list = (List)value; + var list = (IList)value; var lookupTable = new List(); var objects = new List(); @@ -84,16 +85,16 @@ namespace osu.Game.IO.Serialization.Converters } var itemObject = JObject.FromObject(item, serializer); - itemObject.AddFirst(new JProperty("type", typeId)); + itemObject.AddFirst(new JProperty("$type", typeId)); objects.Add(itemObject); } writer.WriteStartObject(); - writer.WritePropertyName("lookup_table"); + writer.WritePropertyName("$lookup_table"); serializer.Serialize(writer, lookupTable); - writer.WritePropertyName("items"); + writer.WritePropertyName("$items"); serializer.Serialize(writer, objects); writer.WriteEndObject(); From 3093c3e1857c8d5bc7fce5f5b8fa21b3bfeead05 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Apr 2020 14:55:17 +0900 Subject: [PATCH 447/655] Fix custom sample set not being written correctly --- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index fe63eec3f9..7721d50227 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Text; @@ -10,6 +11,7 @@ using osu.Game.Audio; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Beatmaps.Formats @@ -159,7 +161,7 @@ namespace osu.Game.Beatmaps.Formats beatLength = -100 / difficultyPoint.SpeedMultiplier; // Apply the control point to a hit sample to uncover legacy properties (e.g. suffix) - HitSampleInfo tempHitSample = samplePoint.ApplyTo(new HitSampleInfo()); + HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo()); // Convert effect flags to the legacy format LegacyEffectFlags effectFlags = LegacyEffectFlags.None; @@ -172,7 +174,7 @@ namespace osu.Game.Beatmaps.Formats writer.Write(FormattableString.Invariant($"{beatLength},")); writer.Write(FormattableString.Invariant($"{(int)beatmap.ControlPointInfo.TimingPointAt(group.Time).TimeSignature},")); writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},")); - writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample.Suffix)},")); + writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample)},")); writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},")); writer.Write(FormattableString.Invariant($"{(timingPoint != null ? '1' : '0')},")); writer.Write(FormattableString.Invariant($"{(int)effectFlags}")); @@ -326,7 +328,7 @@ namespace osu.Game.Beatmaps.Formats if (!banksOnly) { - string customSampleBank = toLegacyCustomSampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name))?.Suffix); + string customSampleBank = toLegacyCustomSampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name))); string sampleFilename = samples.FirstOrDefault(s => string.IsNullOrEmpty(s.Name))?.LookupNames.First() ?? string.Empty; int volume = samples.FirstOrDefault()?.Volume ?? 100; @@ -382,6 +384,15 @@ namespace osu.Game.Beatmaps.Formats } } - private string toLegacyCustomSampleBank(string sampleSuffix) => string.IsNullOrEmpty(sampleSuffix) ? "0" : sampleSuffix; + private string toLegacyCustomSampleBank(HitSampleInfo hitSampleInfo) + { + if (hitSampleInfo == null) + return "0"; + + if (hitSampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy) + return legacy.CustomSampleBank.ToString(CultureInfo.InvariantCulture); + + return "0"; + } } } From d8d85e5b08980c80a9f5f0a917dafe16a1ce71a1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Apr 2020 15:04:04 +0900 Subject: [PATCH 448/655] Don't output certain properties if they don't exist --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 7721d50227..3ba68c6086 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -60,7 +60,7 @@ namespace osu.Game.Beatmaps.Formats { writer.WriteLine("[General]"); - writer.WriteLine(FormattableString.Invariant($"AudioFilename: {Path.GetFileName(beatmap.Metadata.AudioFile)}")); + if (beatmap.Metadata.AudioFile != null) writer.WriteLine(FormattableString.Invariant($"AudioFilename: {Path.GetFileName(beatmap.Metadata.AudioFile)}")); writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.BeatmapInfo.AudioLeadIn}")); writer.WriteLine(FormattableString.Invariant($"PreviewTime: {beatmap.Metadata.PreviewTime}")); // Todo: Not all countdown types are supported by lazer yet @@ -105,15 +105,15 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine("[Metadata]"); writer.WriteLine(FormattableString.Invariant($"Title: {beatmap.Metadata.Title}")); - writer.WriteLine(FormattableString.Invariant($"TitleUnicode: {beatmap.Metadata.TitleUnicode}")); + if (beatmap.Metadata.TitleUnicode != null) writer.WriteLine(FormattableString.Invariant($"TitleUnicode: {beatmap.Metadata.TitleUnicode}")); writer.WriteLine(FormattableString.Invariant($"Artist: {beatmap.Metadata.Artist}")); - writer.WriteLine(FormattableString.Invariant($"ArtistUnicode: {beatmap.Metadata.ArtistUnicode}")); + if (beatmap.Metadata.ArtistUnicode != null) writer.WriteLine(FormattableString.Invariant($"ArtistUnicode: {beatmap.Metadata.ArtistUnicode}")); writer.WriteLine(FormattableString.Invariant($"Creator: {beatmap.Metadata.AuthorString}")); writer.WriteLine(FormattableString.Invariant($"Version: {beatmap.BeatmapInfo.Version}")); - writer.WriteLine(FormattableString.Invariant($"Source: {beatmap.Metadata.Source}")); - writer.WriteLine(FormattableString.Invariant($"Tags: {beatmap.Metadata.Tags}")); - writer.WriteLine(FormattableString.Invariant($"BeatmapID: {beatmap.BeatmapInfo.OnlineBeatmapID ?? 0}")); - writer.WriteLine(FormattableString.Invariant($"BeatmapSetID: {beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID ?? -1}")); + if (beatmap.Metadata.Source != null) writer.WriteLine(FormattableString.Invariant($"Source: {beatmap.Metadata.Source}")); + if (beatmap.Metadata.Tags != null) writer.WriteLine(FormattableString.Invariant($"Tags: {beatmap.Metadata.Tags}")); + if (beatmap.BeatmapInfo.OnlineBeatmapID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapID: {beatmap.BeatmapInfo.OnlineBeatmapID}")); + if (beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapSetID: {beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID}")); } private void handleDifficulty(TextWriter writer) From 1421e876b11479cdb4d7d313dc84124c9f3c254d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Apr 2020 15:04:43 +0900 Subject: [PATCH 449/655] Remove implicit new combo from spinners --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 3ba68c6086..f1f0a0a5de 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -242,7 +242,7 @@ namespace osu.Game.Beatmaps.Formats break; case IHasEndTime _: - type |= LegacyHitObjectType.Spinner | LegacyHitObjectType.NewCombo; + type |= LegacyHitObjectType.Spinner; break; default: From 516e6a4bb10586bfa3505bf776f7ab263d6a8ddd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Apr 2020 15:05:24 +0900 Subject: [PATCH 450/655] Fix overlapping control points not written correctly --- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index f1f0a0a5de..44ccbb350d 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -50,7 +50,7 @@ namespace osu.Game.Beatmaps.Formats handleEvents(writer); writer.WriteLine(); - handleTimingPoints(writer); + handleControlPoints(writer); writer.WriteLine(); handleHitObjects(writer); @@ -139,7 +139,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Break},{b.StartTime},{b.EndTime}")); } - private void handleTimingPoints(TextWriter writer) + private void handleControlPoints(TextWriter writer) { if (beatmap.ControlPointInfo.Groups.Count == 0) return; @@ -148,17 +148,27 @@ namespace osu.Game.Beatmaps.Formats foreach (var group in beatmap.ControlPointInfo.Groups) { - var timingPoint = group.ControlPoints.OfType().FirstOrDefault(); - var difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(group.Time); - var samplePoint = beatmap.ControlPointInfo.SamplePointAt(group.Time); - var effectPoint = beatmap.ControlPointInfo.EffectPointAt(group.Time); + var groupTimingPoint = group.ControlPoints.OfType().FirstOrDefault(); - // Convert beat length the legacy format - double beatLength; - if (timingPoint != null) - beatLength = timingPoint.BeatLength; - else - beatLength = -100 / difficultyPoint.SpeedMultiplier; + // If the group contains a timing control point, it needs to be output separately. + if (groupTimingPoint != null) + { + writer.Write(FormattableString.Invariant($"{groupTimingPoint.Time},")); + writer.Write(FormattableString.Invariant($"{groupTimingPoint.BeatLength},")); + outputControlPointEffectsAt(groupTimingPoint.Time, true); + } + + // Output any remaining effects as secondary non-timing control point. + var difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(group.Time); + writer.Write(FormattableString.Invariant($"{group.Time},")); + writer.Write(FormattableString.Invariant($"{-100 / difficultyPoint.SpeedMultiplier},")); + outputControlPointEffectsAt(group.Time, false); + } + + void outputControlPointEffectsAt(double time, bool isTimingPoint) + { + var samplePoint = beatmap.ControlPointInfo.SamplePointAt(time); + var effectPoint = beatmap.ControlPointInfo.EffectPointAt(time); // Apply the control point to a hit sample to uncover legacy properties (e.g. suffix) HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo()); @@ -170,13 +180,11 @@ namespace osu.Game.Beatmaps.Formats if (effectPoint.OmitFirstBarLine) effectFlags |= LegacyEffectFlags.OmitFirstBarLine; - writer.Write(FormattableString.Invariant($"{group.Time},")); - writer.Write(FormattableString.Invariant($"{beatLength},")); - writer.Write(FormattableString.Invariant($"{(int)beatmap.ControlPointInfo.TimingPointAt(group.Time).TimeSignature},")); + writer.Write(FormattableString.Invariant($"{(int)beatmap.ControlPointInfo.TimingPointAt(time).TimeSignature},")); writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},")); writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample)},")); writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},")); - writer.Write(FormattableString.Invariant($"{(timingPoint != null ? '1' : '0')},")); + writer.Write(FormattableString.Invariant($"{(isTimingPoint ? '1' : '0')},")); writer.Write(FormattableString.Invariant($"{(int)effectFlags}")); writer.WriteLine(); } From d27ca725f946d7a85da2c37bbb9ee53bd5efb95e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Apr 2020 08:51:21 +0900 Subject: [PATCH 451/655] Use IEnumerable instead --- osu.Game/IO/Serialization/Converters/TypedListConverter.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs index 837650eb0a..64f1ebeb1a 100644 --- a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs +++ b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections; using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -62,7 +61,7 @@ namespace osu.Game.IO.Serialization.Converters public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { - var list = (IList)value; + var list = (IEnumerable)value; var lookupTable = new List(); var objects = new List(); From ee278a2e1bd3e460a7092e2348c84f0d305f8cb3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Apr 2020 15:55:36 +0900 Subject: [PATCH 452/655] Add taiko/catch/mania sample beatmaps --- .../Resources/sample-beatmap-catch.osu | 30 +++++++++++++ .../Resources/sample-beatmap-mania.osu | 39 +++++++++++++++++ .../Resources/sample-beatmap-taiko.osu | 42 +++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 osu.Game.Tests/Resources/sample-beatmap-catch.osu create mode 100644 osu.Game.Tests/Resources/sample-beatmap-mania.osu create mode 100644 osu.Game.Tests/Resources/sample-beatmap-taiko.osu diff --git a/osu.Game.Tests/Resources/sample-beatmap-catch.osu b/osu.Game.Tests/Resources/sample-beatmap-catch.osu new file mode 100644 index 0000000000..09ef762e3e --- /dev/null +++ b/osu.Game.Tests/Resources/sample-beatmap-catch.osu @@ -0,0 +1,30 @@ +osu file format v14 + +[General] +SampleSet: Normal +StackLeniency: 0.7 +Mode: 2 + +[Difficulty] +HPDrainRate:3 +CircleSize:5 +OverallDifficulty:8 +ApproachRate:8 +SliderMultiplier:3.59999990463257 +SliderTickRate:2 + +[TimingPoints] +24,352.941176470588,4,1,1,100,1,0 +6376,-50,4,1,1,100,0,0 + +[HitObjects] +32,183,24,5,0,0:0:0:0: +106,123,200,1,10,0:0:0:0: +199,108,376,1,2,0:0:0:0: +305,105,553,5,4,0:0:0:0: +386,112,729,1,14,0:0:0:0: +486,197,906,5,12,0:0:0:0: +14,199,1082,2,0,L|473:198,1,449.999988079071 +14,199,1700,6,6,P|248:33|490:222,1,629.9999833107,0|8,0:0|0:0,0:0:0:0: +10,190,2494,2,8,B|252:29|254:335|468:167,1,449.999988079071,10|12,0:0|0:0,0:0:0:0: +256,192,3112,12,0,3906,0:0:0:0: \ No newline at end of file diff --git a/osu.Game.Tests/Resources/sample-beatmap-mania.osu b/osu.Game.Tests/Resources/sample-beatmap-mania.osu new file mode 100644 index 0000000000..04d6a31ab6 --- /dev/null +++ b/osu.Game.Tests/Resources/sample-beatmap-mania.osu @@ -0,0 +1,39 @@ +osu file format v14 + +[General] +SampleSet: Normal +StackLeniency: 0.7 +Mode: 3 + +[Difficulty] +HPDrainRate:3 +CircleSize:5 +OverallDifficulty:8 +ApproachRate:8 +SliderMultiplier:3.59999990463257 +SliderTickRate:2 + +[TimingPoints] +24,352.941176470588,4,1,1,100,1,0 +6376,-50,4,1,1,100,0,0 + +[HitObjects] +51,192,24,1,0,0:0:0:0: +153,192,200,1,0,0:0:0:0: +358,192,376,1,0,0:0:0:0: +460,192,553,1,0,0:0:0:0: +460,192,729,128,0,1435:0:0:0:0: +358,192,906,128,0,1612:0:0:0:0: +256,192,1082,128,0,1788:0:0:0:0: +153,192,1259,128,0,1965:0:0:0:0: +51,192,1435,128,0,2141:0:0:0:0: +51,192,2318,1,12,0:0:0:0: +153,192,2318,1,4,0:0:0:0: +256,192,2318,1,6,0:0:0:0: +358,192,2318,1,14,0:0:0:0: +460,192,2318,1,0,0:0:0:0: +51,192,2494,128,0,2582:0:0:0:0: +153,192,2494,128,14,2582:0:0:0:0: +256,192,2494,128,6,2582:0:0:0:0: +358,192,2494,128,4,2582:0:0:0:0: +460,192,2494,128,12,2582:0:0:0:0: \ No newline at end of file diff --git a/osu.Game.Tests/Resources/sample-beatmap-taiko.osu b/osu.Game.Tests/Resources/sample-beatmap-taiko.osu new file mode 100644 index 0000000000..94b4288336 --- /dev/null +++ b/osu.Game.Tests/Resources/sample-beatmap-taiko.osu @@ -0,0 +1,42 @@ +osu file format v14 + +[General] +SampleSet: Normal +StackLeniency: 0.7 +Mode: 1 + +[Difficulty] +HPDrainRate:3 +CircleSize:5 +OverallDifficulty:8 +ApproachRate:8 +SliderMultiplier:3.59999990463257 +SliderTickRate:2 + +[TimingPoints] +24,352.941176470588,4,1,1,100,1,0 +6376,-50,4,1,1,100,0,0 + +[HitObjects] +231,129,24,1,0,0:0:0:0: +231,129,200,1,0,0:0:0:0: +231,129,376,1,0,0:0:0:0: +231,129,553,1,0,0:0:0:0: +231,129,729,1,0,0:0:0:0: +373,132,906,1,4,0:0:0:0: +373,132,1082,1,4,0:0:0:0: +373,132,1259,1,4,0:0:0:0: +373,132,1435,1,4,0:0:0:0: +231,129,1788,1,8,0:0:0:0: +231,129,1964,1,8,0:0:0:0: +231,129,2140,1,8,0:0:0:0: +231,129,2317,1,8,0:0:0:0: +231,129,2493,1,8,0:0:0:0: +373,132,2670,1,12,0:0:0:0: +373,132,2846,1,12,0:0:0:0: +373,132,3023,1,12,0:0:0:0: +373,132,3199,1,12,0:0:0:0: +51,189,3553,2,0,L|150:188,1,89.9999976158143 +52,191,3906,2,0,L|512:189,1,449.999988079071 +26,196,4612,2,4,L|501:195,1,449.999988079071 +17,242,5318,2,10,P|250:69|495:243,1,629.9999833107,0|8,0:0|0:0,0:0:0:0: \ No newline at end of file From ea0ebc8527035e4884acebd77e5cad73de3200e3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Apr 2020 16:04:58 +0900 Subject: [PATCH 453/655] Implement beatmap encoding for all legacy rulesets --- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 53 ++++++++++++------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 44ccbb350d..af0adb65a5 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -13,6 +13,7 @@ using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Types; +using osuTK; namespace osu.Game.Beatmaps.Formats { @@ -197,32 +198,40 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine("[HitObjects]"); - // TODO: implement other legacy rulesets + foreach (var h in beatmap.HitObjects) + handleHitObject(writer, h); + } + + private void handleHitObject(TextWriter writer, HitObject hitObject) + { + Vector2 position = new Vector2(256, 192); + switch (beatmap.BeatmapInfo.RulesetID) { case 0: - foreach (var h in beatmap.HitObjects) - handleOsuHitObject(writer, h); + position = ((IHasPosition)hitObject).Position; + break; + + case 2: + position.X = ((IHasXPosition)hitObject).X * 512; + break; + + case 3: + int totalColumns = (int)Math.Max(1, beatmap.BeatmapInfo.BaseDifficulty.CircleSize); + position.X = (int)Math.Ceiling(((IHasXPosition)hitObject).X * (512f / totalColumns)); break; } - } - private void handleOsuHitObject(TextWriter writer, HitObject hitObject) - { - var positionData = (IHasPosition)hitObject; - - writer.Write(FormattableString.Invariant($"{positionData.X},")); - writer.Write(FormattableString.Invariant($"{positionData.Y},")); + writer.Write(FormattableString.Invariant($"{position.X},")); + writer.Write(FormattableString.Invariant($"{position.Y},")); writer.Write(FormattableString.Invariant($"{hitObject.StartTime},")); writer.Write(FormattableString.Invariant($"{(int)getObjectType(hitObject)},")); - writer.Write(hitObject is IHasCurve - ? FormattableString.Invariant($"0,") - : FormattableString.Invariant($"{(int)toLegacyHitSoundType(hitObject.Samples)},")); + writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(hitObject.Samples)},")); if (hitObject is IHasCurve curveData) { - addCurveData(writer, curveData, positionData); + addCurveData(writer, curveData, position); writer.Write(getSampleBank(hitObject.Samples, zeroBanks: true)); } else @@ -237,11 +246,15 @@ namespace osu.Game.Beatmaps.Formats private static LegacyHitObjectType getObjectType(HitObject hitObject) { - var comboData = (IHasCombo)hitObject; + LegacyHitObjectType type = 0; - var type = (LegacyHitObjectType)(comboData.ComboOffset << 4); + if (hitObject is IHasCombo combo) + { + type = (LegacyHitObjectType)(combo.ComboOffset << 4); - if (comboData.NewCombo) type |= LegacyHitObjectType.NewCombo; + if (combo.NewCombo) + type |= LegacyHitObjectType.NewCombo; + } switch (hitObject) { @@ -261,7 +274,7 @@ namespace osu.Game.Beatmaps.Formats return type; } - private void addCurveData(TextWriter writer, IHasCurve curveData, IHasPosition positionData) + private void addCurveData(TextWriter writer, IHasCurve curveData, Vector2 position) { PathType? lastType = null; @@ -297,13 +310,13 @@ namespace osu.Game.Beatmaps.Formats else { // New segment with the same type - duplicate the control point - writer.Write(FormattableString.Invariant($"{positionData.X + point.Position.Value.X}:{positionData.Y + point.Position.Value.Y}|")); + writer.Write(FormattableString.Invariant($"{position.X + point.Position.Value.X}:{position.Y + point.Position.Value.Y}|")); } } if (i != 0) { - writer.Write(FormattableString.Invariant($"{positionData.X + point.Position.Value.X}:{positionData.Y + point.Position.Value.Y}")); + writer.Write(FormattableString.Invariant($"{position.X + point.Position.Value.X}:{position.Y + point.Position.Value.Y}")); writer.Write(i != curveData.Path.ControlPoints.Count - 1 ? "|" : ","); } } From d957614fc96fe142dccaeec1030de08e3f4c7453 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Apr 2020 16:33:19 +0900 Subject: [PATCH 454/655] Cleanup handling of mania samples --- .../Beatmaps/ManiaBeatmapConverter.cs | 21 +-------- .../Legacy/DistanceObjectPatternGenerator.cs | 10 ++-- .../Legacy/EndTimeObjectPatternGenerator.cs | 15 ++---- .../Objects/Drawables/DrawableHoldNote.cs | 5 ++ osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 46 +++++++++++++++---- 5 files changed, 52 insertions(+), 45 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 4187e39b43..b803caa1b7 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -238,9 +238,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps { StartTime = HitObject.StartTime, Duration = endTimeData.Duration, - Column = column, - Head = { Samples = sampleInfoListAt(HitObject.StartTime) }, - Tail = { Samples = sampleInfoListAt(endTimeData.EndTime) }, + Samples = HitObject.Samples, + NodeSamples = (HitObject as IHasRepeats)?.NodeSamples }); } else if (HitObject is IHasXPosition) @@ -255,22 +254,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps return pattern; } - - /// - /// Retrieves the sample info list at a point in time. - /// - /// The time to retrieve the sample info list from. - /// - private IList sampleInfoListAt(double time) - { - if (!(HitObject is IHasCurve curveData)) - return HitObject.Samples; - - double segmentTime = (curveData.EndTime - HitObject.StartTime) / curveData.SpanCount(); - - int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); - return curveData.NodeSamples[index]; - } } } } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 315ef96e49..d8d5b67c0e 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -505,16 +505,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy } else { - var holdNote = new HoldNote + newObject = new HoldNote { StartTime = startTime, - Column = column, Duration = endTime - startTime, - Head = { Samples = sampleInfoListAt(startTime) }, - Tail = { Samples = sampleInfoListAt(endTime) } + Column = column, + Samples = HitObject.Samples, + NodeSamples = (HitObject as IHasRepeats)?.NodeSamples }; - - newObject = holdNote; } pattern.Add(newObject); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs index b3be08e1f7..907bed0d65 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs @@ -64,21 +64,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (holdNote) { - var hold = new HoldNote + newObject = new HoldNote { StartTime = HitObject.StartTime, + Duration = endTime - HitObject.StartTime, Column = column, - Duration = endTime - HitObject.StartTime + Samples = HitObject.Samples, + NodeSamples = (HitObject as IHasRepeats)?.NodeSamples }; - - if (hold.Head.Samples == null) - hold.Head.Samples = new List(); - - hold.Head.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_NORMAL }); - - hold.Tail.Samples = HitObject.Samples; - - newObject = hold; } else { diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index a9ef661aaa..bc3a136a6a 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -127,6 +127,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables bodyPiece.Anchor = bodyPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; } + public override void PlaySamples() + { + // The hold note does not play samples itself. + } + protected override void Update() { base.Update(); diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index 049bf55f90..04f8fd8c99 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; @@ -28,7 +30,9 @@ namespace osu.Game.Rulesets.Mania.Objects set { duration = value; - Tail.StartTime = EndTime; + + if (Tail != null) + Tail.StartTime = EndTime; } } @@ -38,8 +42,12 @@ namespace osu.Game.Rulesets.Mania.Objects set { base.StartTime = value; - Head.StartTime = value; - Tail.StartTime = EndTime; + + if (Head != null) + Head.StartTime = value; + + if (Tail != null) + Tail.StartTime = EndTime; } } @@ -49,20 +57,26 @@ namespace osu.Game.Rulesets.Mania.Objects set { base.Column = value; - Head.Column = value; - Tail.Column = value; + + if (Head != null) + Head.Column = value; + + if (Tail != null) + Tail.Column = value; } } + public List> NodeSamples { get; set; } + /// /// The head note of the hold. /// - public readonly Note Head = new Note(); + public Note Head { get; private set; } /// /// The tail note of the hold. /// - public readonly TailNote Tail = new TailNote(); + public TailNote Tail { get; private set; } /// /// The time between ticks of this hold. @@ -83,8 +97,19 @@ namespace osu.Game.Rulesets.Mania.Objects createTicks(); - AddNested(Head); - AddNested(Tail); + AddNested(Head = new Note + { + StartTime = StartTime, + Column = Column, + Samples = getNodeSamples(0), + }); + + AddNested(Tail = new TailNote + { + StartTime = EndTime, + Column = Column, + Samples = getNodeSamples(1), + }); } private void createTicks() @@ -105,5 +130,8 @@ namespace osu.Game.Rulesets.Mania.Objects public override Judgement CreateJudgement() => new IgnoreJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; + + private IList getNodeSamples(int nodeIndex) => + nodeIndex < NodeSamples?.Count ? NodeSamples[nodeIndex] : Samples; } } From cc0c82aaebac82c2d3a7eb1844eac44dab47da31 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Apr 2020 16:44:26 +0900 Subject: [PATCH 455/655] Implement IHasXPosition on ManiaHitObject --- .../Beatmaps/ManiaBeatmapConverter.cs | 3 +-- osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs | 9 ++++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index b803caa1b7..cb583ac1a9 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -13,7 +13,6 @@ using osu.Game.Rulesets.Mania.Beatmaps.Patterns; using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy; using osuTK; -using osu.Game.Audio; namespace osu.Game.Rulesets.Mania.Beatmaps { @@ -67,7 +66,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps } } - public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition || h is ManiaHitObject); + public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition); protected override Beatmap ConvertBeatmap(IBeatmap original) { diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs index 995e1516cb..27bf50493d 100644 --- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs @@ -5,11 +5,12 @@ using osu.Framework.Bindables; using osu.Game.Rulesets.Mania.Objects.Types; using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Objects { - public abstract class ManiaHitObject : HitObject, IHasColumn + public abstract class ManiaHitObject : HitObject, IHasColumn, IHasXPosition { public readonly Bindable ColumnBindable = new Bindable(); @@ -20,5 +21,11 @@ namespace osu.Game.Rulesets.Mania.Objects } protected override HitWindows CreateHitWindows() => new ManiaHitWindows(); + + #region LegacyBeatmapEncoder + + float IHasXPosition.X => Column; + + #endregion } } From d8fdd73e170e8117961a3da8d1ef31e85738c9c2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Apr 2020 16:45:01 +0900 Subject: [PATCH 456/655] Implement IHasCurve on DrumRoll --- .../Beatmaps/TaikoBeatmapConverter.cs | 6 ++-- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 29 ++++++++++++++++++- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 8 +++-- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 695ada3a00..caf645d5a2 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps /// osu! is generally slower than taiko, so a factor is added to increase /// speed. This must be used everywhere slider length or beat length is used. /// - private const float legacy_velocity_multiplier = 1.4f; + public const float LEGACY_VELOCITY_MULTIPLIER = 1.4f; /// /// Because swells are easier in taiko than spinners are in osu!, @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps // Rewrite the beatmap info to add the slider velocity multiplier original.BeatmapInfo = original.BeatmapInfo.Clone(); original.BeatmapInfo.BaseDifficulty = original.BeatmapInfo.BaseDifficulty.Clone(); - original.BeatmapInfo.BaseDifficulty.SliderMultiplier *= legacy_velocity_multiplier; + original.BeatmapInfo.BaseDifficulty.SliderMultiplier *= LEGACY_VELOCITY_MULTIPLIER; Beatmap converted = base.ConvertBeatmap(original); @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps double speedAdjustedBeatLength = timingPoint.BeatLength / speedAdjustment; // The true distance, accounting for any repeats. This ends up being the drum roll distance later - double distance = distanceData.Distance * spans * legacy_velocity_multiplier; + double distance = distanceData.Distance * spans * LEGACY_VELOCITY_MULTIPLIER; // The velocity of the taiko hit object - calculated as the velocity of a drum roll double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength; diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index aacd78f176..ad0cd2f2a8 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -3,15 +3,20 @@ using osu.Game.Rulesets.Objects.Types; using System; +using System.Collections.Generic; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Judgements; +using osuTK; namespace osu.Game.Rulesets.Taiko.Objects { - public class DrumRoll : TaikoHitObject, IHasEndTime + public class DrumRoll : TaikoHitObject, IHasEndTime, IHasCurve { /// /// Drum roll distance that results in a duration of 1 speed-adjusted beat length. @@ -26,6 +31,11 @@ namespace osu.Game.Rulesets.Taiko.Objects public double Duration { get; set; } + /// + /// Velocity of this . + /// + public double Velocity { get; private set; } + /// /// Numer of ticks per beat length. /// @@ -54,6 +64,10 @@ namespace osu.Game.Rulesets.Taiko.Objects base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); + DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); + + double scoringDistance = base_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; + Velocity = scoringDistance / timingPoint.BeatLength; tickSpacing = timingPoint.BeatLength / TickRate; overallDifficulty = difficulty.OverallDifficulty; @@ -93,5 +107,18 @@ namespace osu.Game.Rulesets.Taiko.Objects public override Judgement CreateJudgement() => new TaikoDrumRollJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; + + #region LegacyBeatmapEncoder + + double IHasDistance.Distance => Duration * Velocity; + + int IHasRepeats.RepeatCount { get => 0; set { } } + + List> IHasRepeats.NodeSamples => new List>(); + + SliderPath IHasCurve.Path + => new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(1) }, ((IHasDistance)this).Distance / TaikoBeatmapConverter.LEGACY_VELOCITY_MULTIPLIER); + + #endregion } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index af0adb65a5..4b760f1983 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -125,7 +125,12 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"CircleSize: {beatmap.BeatmapInfo.BaseDifficulty.CircleSize}")); writer.WriteLine(FormattableString.Invariant($"OverallDifficulty: {beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty}")); writer.WriteLine(FormattableString.Invariant($"ApproachRate: {beatmap.BeatmapInfo.BaseDifficulty.ApproachRate}")); - writer.WriteLine(FormattableString.Invariant($"SliderMultiplier: {beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier}")); + + // Taiko adjusts the slider multiplier (see: TaikoBeatmapConverter.LEGACY_VELOCITY_MULTIPLIER) + writer.WriteLine(beatmap.BeatmapInfo.RulesetID == 1 + ? FormattableString.Invariant($"SliderMultiplier: {beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / 1.4f}") + : FormattableString.Invariant($"SliderMultiplier: {beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier}")); + writer.WriteLine(FormattableString.Invariant($"SliderTickRate: {beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate}")); } @@ -226,7 +231,6 @@ namespace osu.Game.Beatmaps.Formats writer.Write(FormattableString.Invariant($"{position.Y},")); writer.Write(FormattableString.Invariant($"{hitObject.StartTime},")); writer.Write(FormattableString.Invariant($"{(int)getObjectType(hitObject)},")); - writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(hitObject.Samples)},")); if (hitObject is IHasCurve curveData) From 1f962f5c563d80f40208aa8f5e8fa0d1b54389d9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Apr 2020 17:20:37 +0900 Subject: [PATCH 457/655] Reword comment --- osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index bc3a136a6a..770dd73e7a 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -129,7 +129,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public override void PlaySamples() { - // The hold note does not play samples itself. + // Samples are played by the head/tail notes. } protected override void Update() From 6da0872ae55d9b07d2d1fe55a4db02455b18a9a6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Apr 2020 17:29:23 +0900 Subject: [PATCH 458/655] Use the last node sample for the tail note --- osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index 04f8fd8c99..eea2c31260 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Mania.Objects { StartTime = EndTime, Column = Column, - Samples = getNodeSamples(1), + Samples = getNodeSamples((NodeSamples?.Count - 1) ?? 1), }); } From ba12e23d9eed0b48effa40f7734c1485adfa91cd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 21 Apr 2020 17:31:49 +0900 Subject: [PATCH 459/655] Fix inspection --- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index ad0cd2f2a8..dc2f277e58 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -16,7 +16,7 @@ using osuTK; namespace osu.Game.Rulesets.Taiko.Objects { - public class DrumRoll : TaikoHitObject, IHasEndTime, IHasCurve + public class DrumRoll : TaikoHitObject, IHasCurve { /// /// Drum roll distance that results in a duration of 1 speed-adjusted beat length. From 0c74f1aaa91c70e7984bbe9c93b68509d6b09d38 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Apr 2020 09:08:33 +0900 Subject: [PATCH 460/655] Fix now playing output showing empty brackets when no difficulty specified --- osu.Game/Beatmaps/BeatmapInfo.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 68d113ce40..90c100db05 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -149,7 +149,12 @@ namespace osu.Game.Beatmaps } } - public override string ToString() => $"{Metadata} [{Version}]".Trim(); + public override string ToString() + { + string version = string.IsNullOrEmpty(Version) ? string.Empty : $"[{Version}]"; + + return $"{Metadata} {version}".Trim(); + } public bool Equals(BeatmapInfo other) { From 360c9f8e387bffbb2d4d19d07465d412d0d94e75 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Apr 2020 09:19:34 +0900 Subject: [PATCH 461/655] Add test coverage and handle null creator --- .../Beatmaps/ToStringFormattingTest.cs | 61 +++++++++++++++++++ osu.Game/Beatmaps/BeatmapMetadata.cs | 6 +- 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs diff --git a/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs b/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs new file mode 100644 index 0000000000..c477bbd9cf --- /dev/null +++ b/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs @@ -0,0 +1,61 @@ +// 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.Beatmaps; +using osu.Game.Users; + +namespace osu.Game.Tests.Beatmaps +{ + [TestFixture] + public class ToStringFormattingTest + { + [Test] + public void TestArtistTitle() + { + var beatmap = new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Artist = "artist", + Title = "title" + } + }; + + Assert.That(beatmap.ToString(), Is.EqualTo("artist - title")); + } + + [Test] + public void TestArtistTitleCreator() + { + var beatmap = new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Artist = "artist", + Title = "title", + Author = new User { Username = "creator" } + } + }; + + Assert.That(beatmap.ToString(), Is.EqualTo("artist - title (creator)")); + } + + [Test] + public void TestArtistTitleCreatorDifficulty() + { + var beatmap = new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Artist = "artist", + Title = "title", + Author = new User { Username = "creator" } + }, + Version = "difficulty" + }; + + Assert.That(beatmap.ToString(), Is.EqualTo("artist - title (creator) [difficulty]")); + } + } +} diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 001f319307..775d78f1fb 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -53,7 +53,11 @@ namespace osu.Game.Beatmaps public string AudioFile { get; set; } public string BackgroundFile { get; set; } - public override string ToString() => $"{Artist} - {Title} ({Author})"; + public override string ToString() + { + string author = Author == null ? string.Empty : $"({Author})"; + return $"{Artist} - {Title} {author}".Trim(); + } [JsonIgnore] public string[] SearchableTerms => new[] From f841eb7e0607b19feb84b0cc896b027390128663 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 22 Apr 2020 07:27:15 +0300 Subject: [PATCH 462/655] Replace constructing a whole Catcher with static calculation methods --- .../Difficulty/CatchDifficultyCalculator.cs | 3 +- osu.Game.Rulesets.Catch/UI/Catcher.cs | 36 +++++++++++++++---- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 4d9dbbbc5f..d99325ff87 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -71,8 +71,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty protected override Skill[] CreateSkills(IBeatmap beatmap) { - using (var catcher = new Catcher(beatmap.BeatmapInfo.BaseDifficulty)) - halfCatcherWidth = catcher.CatchWidth * 0.5f; + halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) * 0.5f; return new Skill[] { diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 920d804e72..ee806b7b99 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -44,11 +44,6 @@ namespace osu.Game.Rulesets.Catch.UI /// private const float allowed_catch_range = 0.8f; - /// - /// Width of the area that can be used to attempt catches during gameplay. - /// - internal float CatchWidth => CatcherArea.CATCHER_SIZE * Math.Abs(Scale.X) * allowed_catch_range; - protected bool Dashing { get => dashing; @@ -79,6 +74,11 @@ namespace osu.Game.Rulesets.Catch.UI } } + /// + /// Width of the area that can be used to attempt catches during gameplay. + /// + private readonly float catchWidth; + private Container caughtFruit; private CatcherSprite catcherIdle; @@ -106,7 +106,9 @@ namespace osu.Game.Rulesets.Catch.UI Size = new Vector2(CatcherArea.CATCHER_SIZE); if (difficulty != null) - Scale = new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5); + Scale = CalculateScale(difficulty); + + catchWidth = CalculateCatchWidth(Scale); } [BackgroundDependencyLoader] @@ -139,6 +141,26 @@ namespace osu.Game.Rulesets.Catch.UI updateCatcher(); } + /// + /// Calculates the scale of the catcher based off the provided beatmap difficulty. + /// + internal static Vector2 CalculateScale(BeatmapDifficulty difficulty) + => new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5); + + /// + /// Calculates the width of the area used for attempting catches in gameplay. + /// + internal static float CalculateCatchWidth(Vector2 scale) + => CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * allowed_catch_range; + + /// + /// Calculates the width of the area used for attempting catches in gameplay. + /// + /// + /// + internal static float CalculateCatchWidth(BeatmapDifficulty difficulty) + => CalculateCatchWidth(CalculateScale(difficulty)); + /// /// Add a caught fruit to the catcher's stack. /// @@ -177,7 +199,7 @@ namespace osu.Game.Rulesets.Catch.UI /// Whether the catch is possible. public bool AttemptCatch(CatchHitObject fruit) { - var halfCatchWidth = CatchWidth * 0.5f; + var halfCatchWidth = catchWidth * 0.5f; // this stuff wil disappear once we move fruit to non-relative coordinate space in the future. var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH; From fccb30e031a40e08533e5ce2f862cbe81d6fc504 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 22 Apr 2020 07:36:59 +0300 Subject: [PATCH 463/655] Adjust documents *whoops* --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index ee806b7b99..4dace76008 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -150,14 +150,14 @@ namespace osu.Game.Rulesets.Catch.UI /// /// Calculates the width of the area used for attempting catches in gameplay. /// + /// The scale of the catcher. internal static float CalculateCatchWidth(Vector2 scale) => CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * allowed_catch_range; /// /// Calculates the width of the area used for attempting catches in gameplay. /// - /// - /// + /// The beatmap difficulty. internal static float CalculateCatchWidth(BeatmapDifficulty difficulty) => CalculateCatchWidth(CalculateScale(difficulty)); From 883788dd5aec1b228328812fb4ea14043c299118 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 22 Apr 2020 07:37:49 +0300 Subject: [PATCH 464/655] Privatize externally-unused methods --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 4dace76008..daf9456919 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -106,7 +106,7 @@ namespace osu.Game.Rulesets.Catch.UI Size = new Vector2(CatcherArea.CATCHER_SIZE); if (difficulty != null) - Scale = CalculateScale(difficulty); + Scale = calculateScale(difficulty); catchWidth = CalculateCatchWidth(Scale); } @@ -144,7 +144,7 @@ namespace osu.Game.Rulesets.Catch.UI /// /// Calculates the scale of the catcher based off the provided beatmap difficulty. /// - internal static Vector2 CalculateScale(BeatmapDifficulty difficulty) + private static Vector2 calculateScale(BeatmapDifficulty difficulty) => new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5); /// @@ -159,7 +159,7 @@ namespace osu.Game.Rulesets.Catch.UI /// /// The beatmap difficulty. internal static float CalculateCatchWidth(BeatmapDifficulty difficulty) - => CalculateCatchWidth(CalculateScale(difficulty)); + => CalculateCatchWidth(calculateScale(difficulty)); /// /// Add a caught fruit to the catcher's stack. From 58af75ad576396258285b2722dfb07d1176f7889 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Apr 2020 13:45:12 +0900 Subject: [PATCH 465/655] Add back missing line --- osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index cb583ac1a9..1c8116754f 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -237,6 +237,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps { StartTime = HitObject.StartTime, Duration = endTimeData.Duration, + Column = column, Samples = HitObject.Samples, NodeSamples = (HitObject as IHasRepeats)?.NodeSamples }); From 7cdc9a599c6ffb39c7635e63cbf8fa57bbcb2684 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Apr 2020 16:27:07 +0900 Subject: [PATCH 466/655] Fix mania holds written as spinners --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 4b760f1983..fc853a7a68 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -248,7 +248,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(); } - private static LegacyHitObjectType getObjectType(HitObject hitObject) + private LegacyHitObjectType getObjectType(HitObject hitObject) { LegacyHitObjectType type = 0; @@ -267,7 +267,10 @@ namespace osu.Game.Beatmaps.Formats break; case IHasEndTime _: - type |= LegacyHitObjectType.Spinner; + if (beatmap.BeatmapInfo.RulesetID == 3) + type |= LegacyHitObjectType.Hold; + else + type |= LegacyHitObjectType.Spinner; break; default: From 3b805daa0b1fedac05e46e26fa6e77ba9cc1b4dc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Apr 2020 16:40:07 +0900 Subject: [PATCH 467/655] Fix hold note end time being written incorrectly --- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index fc853a7a68..b5a8f1604c 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -240,8 +240,9 @@ namespace osu.Game.Beatmaps.Formats } else { - if (hitObject is IHasEndTime endTimeData) - writer.Write(FormattableString.Invariant($"{endTimeData.EndTime},")); + if (hitObject is IHasEndTime _) + addEndTimeData(writer, hitObject); + writer.Write(getSampleBank(hitObject.Samples)); } @@ -344,6 +345,20 @@ namespace osu.Game.Beatmaps.Formats } } + private void addEndTimeData(TextWriter writer, HitObject hitObject) + { + var endTimeData = (IHasEndTime)hitObject; + var type = getObjectType(hitObject); + + char suffix = ','; + + // Holds write the end time as if it's part of sample data. + if (type == LegacyHitObjectType.Hold) + suffix = ':'; + + writer.Write(FormattableString.Invariant($"{endTimeData.EndTime}{suffix}")); + } + private string getSampleBank(IList samples, bool banksOnly = false, bool zeroBanks = false) { LegacySampleBank normalBank = toLegacySampleBank(samples.SingleOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank); From 44405d4771b76649e64fd7bd81e13cef17977b71 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Wed, 22 Apr 2020 15:50:23 +0800 Subject: [PATCH 468/655] Moved result to load complete for flying hits --- .../Objects/Drawables/DrawableFlyingCentreHit.cs | 3 ++- .../Objects/Drawables/DrawableFlyingRimHit.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingCentreHit.cs index 826a4467f8..f70d940bd2 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingCentreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingCentreHit.cs @@ -9,8 +9,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { public class DrawableFlyingCentreHit : DrawableCentreHit { - protected override void CheckForResult(bool userTriggered, double timeOffset) + protected override void LoadComplete() { + base.LoadComplete(); ApplyResult(r => r.Type = HitResult.Good); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingRimHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingRimHit.cs index 4a6fed8302..9005dac653 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingRimHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingRimHit.cs @@ -9,8 +9,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { public class DrawableFlyingRimHit : DrawableRimHit { - protected override void CheckForResult(bool userTriggered, double timeOffset) + protected override void LoadComplete() { + base.LoadComplete(); ApplyResult(r => r.Type = HitResult.Good); } From 9c22d2f1dd167cd6b28ec2bdd7a3949219773209 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Apr 2020 17:41:24 +0900 Subject: [PATCH 469/655] Use platform bindings for editor actions --- osu.Game/Screens/Edit/Editor.cs | 49 +++++++++++++++++---------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 9a1f450dc6..5665f4b25d 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -22,6 +22,7 @@ using osu.Game.Screens.Edit.Design; using osuTK.Input; using System.Collections.Generic; using osu.Framework; +using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Logging; using osu.Game.Beatmaps; @@ -37,7 +38,7 @@ using osu.Game.Users; namespace osu.Game.Screens.Edit { [Cached(typeof(IBeatSnapProvider))] - public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler, IBeatSnapProvider + public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler, IKeyBindingHandler, IBeatSnapProvider { public override float BackgroundParallaxAmount => 0.1f; @@ -230,6 +231,30 @@ namespace osu.Game.Screens.Edit clock.ProcessFrame(); } + public bool OnPressed(PlatformAction action) + { + switch (action.ActionType) + { + case PlatformActionType.Undo: + undo(); + return true; + + case PlatformActionType.Redo: + redo(); + return true; + + case PlatformActionType.Save: + saveBeatmap(); + return true; + } + + return false; + } + + public void OnReleased(PlatformAction action) + { + } + protected override bool OnKeyDown(KeyDownEvent e) { switch (e.Key) @@ -241,28 +266,6 @@ namespace osu.Game.Screens.Edit case Key.Right: seek(e, 1); return true; - - case Key.S: - if (e.ControlPressed) - { - saveBeatmap(); - return true; - } - - break; - - case Key.Z: - if (e.ControlPressed) - { - if (e.ShiftPressed) - redo(); - else - undo(); - - return true; - } - - break; } return base.OnKeyDown(e); From 8b0274fedd51ff2e897930786b4c04187f541daa Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Apr 2020 17:55:50 +0900 Subject: [PATCH 470/655] Remove obsolete methods --- .../Objects/Drawables/DrawableHitObject.cs | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index e847dcec40..1316ac1156 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -180,11 +179,6 @@ namespace osu.Game.Rulesets.Objects.Drawables private void apply(HitObject hitObject) { -#pragma warning disable 618 // can be removed 20200417 - if (GetType().GetMethod(nameof(AddNested), BindingFlags.NonPublic | BindingFlags.Instance)?.DeclaringType != typeof(DrawableHitObject)) - return; -#pragma warning restore 618 - if (nestedHitObjects.IsValueCreated) { nestedHitObjects.Value.Clear(); @@ -194,8 +188,6 @@ namespace osu.Game.Rulesets.Objects.Drawables foreach (var h in hitObject.NestedHitObjects) { var drawableNested = CreateNestedHitObject(h) ?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}."); - - addNested(drawableNested); AddNestedHitObject(drawableNested); } } @@ -208,13 +200,6 @@ namespace osu.Game.Rulesets.Objects.Drawables { } - /// - /// Adds a nested . This should not be used except for legacy nested usages. - /// - /// - [Obsolete("Use AddNestedHitObject() / ClearNestedHitObjects() / CreateNestedHitObject() instead.")] // can be removed 20200417 - protected virtual void AddNested(DrawableHitObject h) => addNested(h); - /// /// Invoked by the base to remove all previously-added nested s. /// @@ -229,17 +214,6 @@ namespace osu.Game.Rulesets.Objects.Drawables /// The drawable representation for . protected virtual DrawableHitObject CreateNestedHitObject(HitObject hitObject) => null; - private void addNested(DrawableHitObject hitObject) - { - // Todo: Exists for legacy purposes, can be removed 20200417 - - hitObject.OnNewResult += (d, r) => OnNewResult?.Invoke(d, r); - hitObject.OnRevertResult += (d, r) => OnRevertResult?.Invoke(d, r); - hitObject.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j); - - nestedHitObjects.Value.Add(hitObject); - } - #region State / Transform Management /// From e1142b424d2f140dbb61521920c33336d5f0992e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Apr 2020 18:14:21 +0900 Subject: [PATCH 471/655] Fix test failures --- .../Editor/TestSceneEditorChangeStates.cs | 41 ++++--------------- osu.Game/Screens/Edit/Editor.cs | 12 +++--- 2 files changed, 15 insertions(+), 38 deletions(-) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs index dd1b6cf6aa..efc2a6f552 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs @@ -10,24 +10,22 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; -using osuTK.Input; namespace osu.Game.Tests.Visual.Editor { public class TestSceneEditorChangeStates : ScreenTestScene { private EditorBeatmap editorBeatmap; + private TestEditor editor; public override void SetUpSteps() { base.SetUpSteps(); - Screens.Edit.Editor editor = null; - AddStep("load editor", () => { Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - LoadScreen(editor = new Screens.Edit.Editor()); + LoadScreen(editor = new TestEditor()); }); AddUntilStep("wait for editor to load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true @@ -160,36 +158,15 @@ namespace osu.Game.Tests.Visual.Editor AddAssert("no hitobject added", () => addedObject == null); } - private void addUndoSteps() + private void addUndoSteps() => AddStep("undo", () => editor.Undo()); + + private void addRedoSteps() => AddStep("redo", () => editor.Redo()); + + private class TestEditor : Screens.Edit.Editor { - AddStep("press undo", () => - { - InputManager.PressKey(Key.LControl); - InputManager.PressKey(Key.Z); - }); + public new void Undo() => base.Undo(); - AddStep("release keys", () => - { - InputManager.ReleaseKey(Key.LControl); - InputManager.ReleaseKey(Key.Z); - }); - } - - private void addRedoSteps() - { - AddStep("press redo", () => - { - InputManager.PressKey(Key.LControl); - InputManager.PressKey(Key.LShift); - InputManager.PressKey(Key.Z); - }); - - AddStep("release keys", () => - { - InputManager.ReleaseKey(Key.LControl); - InputManager.ReleaseKey(Key.LShift); - InputManager.ReleaseKey(Key.Z); - }); + public new void Redo() => base.Redo(); } } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 5665f4b25d..54e4af94a4 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -158,8 +158,8 @@ namespace osu.Game.Screens.Edit { Items = new[] { - undoMenuItem = new EditorMenuItem("Undo", MenuItemType.Standard, undo), - redoMenuItem = new EditorMenuItem("Redo", MenuItemType.Standard, redo) + undoMenuItem = new EditorMenuItem("Undo", MenuItemType.Standard, Undo), + redoMenuItem = new EditorMenuItem("Redo", MenuItemType.Standard, Redo) } } } @@ -236,11 +236,11 @@ namespace osu.Game.Screens.Edit switch (action.ActionType) { case PlatformActionType.Undo: - undo(); + Undo(); return true; case PlatformActionType.Redo: - redo(); + Redo(); return true; case PlatformActionType.Save: @@ -329,9 +329,9 @@ namespace osu.Game.Screens.Edit return base.OnExiting(next); } - private void undo() => changeHandler.RestoreState(-1); + protected void Undo() => changeHandler.RestoreState(-1); - private void redo() => changeHandler.RestoreState(1); + protected void Redo() => changeHandler.RestoreState(1); private void resetTrack(bool seekToStart = false) { From 93151f761215c135ae625241532fa6b899e73747 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Apr 2020 18:32:59 +0900 Subject: [PATCH 472/655] Add back necessary events + addition to list --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 1316ac1156..0047142cbd 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -188,6 +188,12 @@ namespace osu.Game.Rulesets.Objects.Drawables foreach (var h in hitObject.NestedHitObjects) { var drawableNested = CreateNestedHitObject(h) ?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}."); + + drawableNested.OnNewResult += (d, r) => OnNewResult?.Invoke(d, r); + drawableNested.OnRevertResult += (d, r) => OnRevertResult?.Invoke(d, r); + drawableNested.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j); + + nestedHitObjects.Value.Add(drawableNested); AddNestedHitObject(drawableNested); } } From 0a34fddcc3cfda42bc40e393374b7c0decd824d8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Apr 2020 19:38:27 +0900 Subject: [PATCH 473/655] Fix TestBeatmap not setting appropriate ruleset ID --- osu.Game/Tests/Beatmaps/TestBeatmap.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index 96e3c037a3..a7c84bf692 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -23,6 +23,7 @@ namespace osu.Game.Tests.Beatmaps HitObjects = baseBeatmap.HitObjects; BeatmapInfo.Ruleset = ruleset; + BeatmapInfo.RulesetID = ruleset.ID ?? 0; BeatmapInfo.BeatmapSet.Metadata = BeatmapInfo.Metadata; BeatmapInfo.BeatmapSet.Beatmaps = new List { BeatmapInfo }; BeatmapInfo.BeatmapSet.OnlineInfo = new BeatmapSetOnlineInfo From 08982e0e00da8fe144793a8b7fa49713fdf98145 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Apr 2020 19:49:21 +0900 Subject: [PATCH 474/655] Ensure editor tests wait for load to complete --- .../Editor/TestSceneEditorChangeStates.cs | 21 +++++++------------ osu.Game/Tests/Visual/EditorTestScene.cs | 10 ++++++++- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs index dd1b6cf6aa..c68015d1a2 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs @@ -4,35 +4,28 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Testing; -using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK.Input; namespace osu.Game.Tests.Visual.Editor { - public class TestSceneEditorChangeStates : ScreenTestScene + public class TestSceneEditorChangeStates : EditorTestScene { + public TestSceneEditorChangeStates() + : base(new OsuRuleset()) + { + } + private EditorBeatmap editorBeatmap; public override void SetUpSteps() { base.SetUpSteps(); - Screens.Edit.Editor editor = null; - - AddStep("load editor", () => - { - Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - LoadScreen(editor = new Screens.Edit.Editor()); - }); - - AddUntilStep("wait for editor to load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true - && editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); - AddStep("get beatmap", () => editorBeatmap = editor.ChildrenOfType().Single()); + AddStep("get beatmap", () => editorBeatmap = Editor.ChildrenOfType().Single()); } [Test] diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 80bc3bdb87..88e50d4858 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -3,9 +3,13 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Testing; using osu.Game.Rulesets; +using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose.Components.Timeline; namespace osu.Game.Tests.Visual { @@ -13,6 +17,8 @@ namespace osu.Game.Tests.Visual { public override IReadOnlyList RequiredTypes => new[] { typeof(Editor), typeof(EditorScreen) }; + protected Editor Editor { get; private set; } + private readonly Ruleset ruleset; protected EditorTestScene(Ruleset ruleset) @@ -30,7 +36,9 @@ namespace osu.Game.Tests.Visual { base.SetUpSteps(); - AddStep("Load editor", () => LoadScreen(new Editor())); + AddStep("load editor", () => LoadScreen(Editor = new Editor())); + AddUntilStep("wait for editor to load", () => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true + && Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); } } } From 26779a57b4b211ab86e9bdd41d194c1d7271e5e5 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Wed, 22 Apr 2020 22:49:30 +0800 Subject: [PATCH 475/655] Exposed public ability to unproxy content --- .../Objects/Drawables/DrawableTaikoHitObject.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 5f892dd2fa..b2f9086184 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -76,6 +76,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// public Drawable CreateProxiedContent() => proxiedContent.CreateProxy(); + public void RemoveProxiedContent() => UnproxyContent(); + public abstract bool OnPressed(TaikoAction action); public virtual void OnReleased(TaikoAction action) From 2600518b1bfdfc39809dc5221a7b2e783e7fd0bb Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Wed, 22 Apr 2020 22:50:00 +0800 Subject: [PATCH 476/655] Moved drumroll container and removed rewound notes --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 31 ++++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index e947795fe5..70839ce806 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -119,7 +119,14 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, Masking = true, - Child = HitObjectContainer + Children = new Drawable[] + { + HitObjectContainer, + drumRollHitContainer = new ScrollingHitObjectContainer + { + Name = "Drumroll hit" + } + } }, kiaiExplosionContainer = new Container { @@ -135,14 +142,6 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Y, Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, Blending = BlendingParameters.Additive - }, - drumRollHitContainer = new ScrollingHitObjectContainer - { - Name = "Drumroll hit", - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Stretch, - Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Width = 1.0f } } }, @@ -282,6 +281,20 @@ namespace osu.Game.Rulesets.Taiko.UI } } + protected override void Update() + { + base.Update(); + + if (Time.Elapsed < 0) + { + foreach (DrawableHit taikoHit in drumRollHitContainer.Objects) + { + taikoHit.RemoveProxiedContent(); + drumRollHitContainer.Remove(taikoHit); + } + } + } + private class ProxyContainer : LifetimeManagementContainer { public new MarginPadding Padding From 40f11ed15c944dd0007a5d637a1215c2ac33c902 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 23 Apr 2020 10:37:05 +0900 Subject: [PATCH 477/655] Resolve broken test scene --- .../Visual/Editor/TestSceneEditorChangeStates.cs | 7 ++++--- osu.Game/Tests/Visual/EditorTestScene.cs | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs index 7b4747592a..efdcc6f78b 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs @@ -19,7 +19,6 @@ namespace osu.Game.Tests.Visual.Editor } private EditorBeatmap editorBeatmap; - private TestEditor editor; public override void SetUpSteps() { @@ -153,9 +152,11 @@ namespace osu.Game.Tests.Visual.Editor AddAssert("no hitobject added", () => addedObject == null); } - private void addUndoSteps() => AddStep("undo", () => editor.Undo()); + private void addUndoSteps() => AddStep("undo", () => ((TestEditor)Editor).Undo()); - private void addRedoSteps() => AddStep("redo", () => editor.Redo()); + private void addRedoSteps() => AddStep("redo", () => ((TestEditor)Editor).Redo()); + + protected override Screens.Edit.Editor CreateEditor() => new TestEditor(); private class TestEditor : Screens.Edit.Editor { diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 88e50d4858..caf2bc0ff1 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -36,9 +36,11 @@ namespace osu.Game.Tests.Visual { base.SetUpSteps(); - AddStep("load editor", () => LoadScreen(Editor = new Editor())); + AddStep("load editor", () => LoadScreen(Editor = CreateEditor())); AddUntilStep("wait for editor to load", () => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true && Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); } + + protected virtual Editor CreateEditor() => new Editor(); } } From 86ef73aa27934b377c5a931c0e66b2f96cc0fc9a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 23 Apr 2020 11:16:59 +0900 Subject: [PATCH 478/655] Implement HitObjectContainer.Clear() --- osu.Game/Rulesets/UI/HitObjectContainer.cs | 9 +++++++++ .../Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index dea981c3ad..f4f66f1272 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -43,6 +43,15 @@ namespace osu.Game.Rulesets.UI return true; } + public virtual void Clear(bool disposeChildren = true) + { + ClearInternal(disposeChildren); + + foreach (var kvp in startTimeMap) + kvp.Value.bindable.UnbindAll(); + startTimeMap.Clear(); + } + public int IndexOf(DrawableHitObject hitObject) => IndexOfInternal(hitObject); private void onStartTimeChanged(DrawableHitObject hitObject) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 108f98d5fc..57f58be55a 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -58,6 +58,14 @@ namespace osu.Game.Rulesets.UI.Scrolling return result; } + public override void Clear(bool disposeChildren = true) + { + base.Clear(disposeChildren); + + initialStateCache.Invalidate(); + hitObjectInitialStateCache.Clear(); + } + private float scrollLength; protected override void Update() From 6df45164face4fb8b31e3cb7a9cfaf9d97003ec7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 23 Apr 2020 11:17:16 +0900 Subject: [PATCH 479/655] Expose direction from scrolling test container --- osu.Game/Tests/Visual/ScrollingTestContainer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Tests/Visual/ScrollingTestContainer.cs b/osu.Game/Tests/Visual/ScrollingTestContainer.cs index 3b741fcf1d..994f23577d 100644 --- a/osu.Game/Tests/Visual/ScrollingTestContainer.cs +++ b/osu.Game/Tests/Visual/ScrollingTestContainer.cs @@ -30,6 +30,11 @@ namespace osu.Game.Tests.Visual set => scrollingInfo.TimeRange.Value = value; } + public ScrollingDirection Direction + { + set => scrollingInfo.Direction.Value = value; + } + public IScrollingInfo ScrollingInfo => scrollingInfo; [Cached(Type = typeof(IScrollingInfo))] From ca56e6c0d2a1fb09749bd8f4070cfeaed947048c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Apr 2020 12:10:26 +0900 Subject: [PATCH 480/655] Rename taiko HitTarget classes to avoid conflict with mania --- .../Skinning/TestSceneTaikoPlayfield.cs | 4 ++-- .../{LegacyHitTarget.cs => TaikoLegacyHitTarget.cs} | 8 +++++--- .../Skinning/TaikoLegacySkinTransformer.cs | 2 +- .../UI/{HitTarget.cs => TaikoHitTarget.cs} | 6 ++---- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) rename osu.Game.Rulesets.Taiko/Skinning/{LegacyHitTarget.cs => TaikoLegacyHitTarget.cs} (80%) rename osu.Game.Rulesets.Taiko/UI/{HitTarget.cs => TaikoHitTarget.cs} (95%) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs index 730eed0e0f..cdd6f9ab19 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs @@ -18,8 +18,8 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning { public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { - typeof(HitTarget), - typeof(LegacyHitTarget), + typeof(TaikoHitTarget), + typeof(TaikoLegacyHitTarget), }).ToList(); [Cached(typeof(IScrollingInfo))] diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitTarget.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacyHitTarget.cs similarity index 80% rename from osu.Game.Rulesets.Taiko/Skinning/LegacyHitTarget.cs rename to osu.Game.Rulesets.Taiko/Skinning/TaikoLegacyHitTarget.cs index 51aea9b9ab..b80f273d24 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitTarget.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacyHitTarget.cs @@ -10,7 +10,7 @@ using osuTK; namespace osu.Game.Rulesets.Taiko.Skinning { - public class LegacyHitTarget : CompositeDrawable + public class TaikoLegacyHitTarget : CompositeDrawable { [BackgroundDependencyLoader] private void load(ISkinSource skin) @@ -22,7 +22,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning new Sprite { Texture = skin.GetTexture("approachcircle"), - Scale = new Vector2(0.73f), + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.73f) * 0.625f, Alpha = 0.7f, Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -30,7 +31,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning new Sprite { Texture = skin.GetTexture("taikobigcircle"), - Scale = new Vector2(0.7f), + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.7f) * 0.625f, Alpha = 0.5f, Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs index 6b59718173..eaa69283e4 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning case TaikoSkinComponents.HitTarget: if (GetTexture("taikobigcircle") != null) - return new LegacyHitTarget(); + return new TaikoLegacyHitTarget(); return null; } diff --git a/osu.Game.Rulesets.Taiko/UI/HitTarget.cs b/osu.Game.Rulesets.Taiko/UI/TaikoHitTarget.cs similarity index 95% rename from osu.Game.Rulesets.Taiko/UI/HitTarget.cs rename to osu.Game.Rulesets.Taiko/UI/TaikoHitTarget.cs index 88886508af..7de1593ab6 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitTarget.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoHitTarget.cs @@ -13,14 +13,14 @@ namespace osu.Game.Rulesets.Taiko.UI /// /// A component that is displayed at the hit position in the taiko playfield. /// - internal class HitTarget : Container + internal class TaikoHitTarget : Container { /// /// Thickness of all drawn line pieces. /// private const float border_thickness = 2.5f; - public HitTarget() + public TaikoHitTarget() { RelativeSizeAxes = Axes.Both; @@ -41,7 +41,6 @@ namespace osu.Game.Rulesets.Taiko.UI Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, Scale = new Vector2(TaikoHitObject.DEFAULT_STRONG_SIZE), Masking = true, BorderColour = Color4.White, @@ -63,7 +62,6 @@ namespace osu.Game.Rulesets.Taiko.UI Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, Scale = new Vector2(TaikoHitObject.DEFAULT_SIZE), Masking = true, BorderColour = Color4.White, diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 375d9995c0..69c9003434 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Taiko.UI FillMode = FillMode.Fit, Blending = BlendingParameters.Additive, }, - HitTarget = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.HitTarget), _ => new HitTarget()) + HitTarget = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.HitTarget), _ => new TaikoHitTarget()) { Anchor = Anchor.CentreLeft, Origin = Anchor.Centre, From 8d5732aabd87662ec038358e63b630e23153605b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 23 Apr 2020 12:17:11 +0900 Subject: [PATCH 481/655] Make placements happen on mouse down --- .../TestSceneSliderPlacementBlueprint.cs | 17 ++++++++++++++++ .../HitCircles/HitCirclePlacementBlueprint.cs | 20 +++++++++++++------ .../Components/PathControlPointPiece.cs | 4 +++- .../Sliders/SliderPlacementBlueprint.cs | 10 +++++----- 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderPlacementBlueprint.cs index 9fc479953e..fe9973f4d8 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderPlacementBlueprint.cs @@ -259,6 +259,23 @@ namespace osu.Game.Rulesets.Osu.Tests assertControlPointType(2, PathType.PerfectCurve); } + [Test] + public void TestBeginPlacementWithoutReleasingMouse() + { + addMovementStep(new Vector2(200)); + AddStep("press left button", () => InputManager.PressButton(MouseButton.Left)); + + addMovementStep(new Vector2(400, 200)); + AddStep("release left button", () => InputManager.ReleaseButton(MouseButton.Left)); + + addClickStep(MouseButton.Right); + + assertPlaced(true); + assertLength(200); + assertControlPointCount(2); + assertControlPointType(0, PathType.Linear); + } + private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position))); private void addClickStep(MouseButton button) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs index 407f5f540e..2f400160b8 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs @@ -6,6 +6,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Objects; using osuTK; +using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles { @@ -28,16 +29,23 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles circlePiece.UpdateFrom(HitObject); } - protected override bool OnClick(ClickEvent e) + protected override bool OnMouseDown(MouseDownEvent e) { - EndPlacement(true); - return true; + if (e.Button == MouseButton.Left) + { + BeginPlacement(); + return true; + } + + return base.OnMouseDown(e); } - public override void UpdatePosition(Vector2 screenSpacePosition) + protected override void OnMouseUp(MouseUpEvent e) { - BeginPlacement(); - HitObject.Position = ToLocalSpace(screenSpacePosition); + if (e.Button == MouseButton.Left) + EndPlacement(true); } + + public override void UpdatePosition(Vector2 screenSpacePosition) => HitObject.Position = ToLocalSpace(screenSpacePosition); } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index fed149b5c5..d0c1eb5317 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -28,7 +28,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public Action RequestSelection; public readonly BindableBool IsSelected = new BindableBool(); - public readonly PathControlPoint ControlPoint; private readonly Slider slider; @@ -146,6 +145,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override bool OnDragStart(DragStartEvent e) { + if (RequestSelection == null) + return false; + if (e.Button == MouseButton.Left) { changeHandler?.BeginChange(); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 9af972dbce..ac30f5a762 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -82,8 +82,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } } - protected override bool OnClick(ClickEvent e) + protected override bool OnMouseDown(MouseDownEvent e) { + if (e.Button != MouseButton.Left) + return base.OnMouseDown(e); + switch (state) { case PlacementState.Initial: @@ -91,9 +94,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders break; case PlacementState.Body: - if (e.Button != MouseButton.Left) - break; - if (canPlaceNewControlPoint(out var lastPoint)) { // Place a new point by detatching the current cursor. @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders currentSegmentLength = 1; } - return true; + break; } return true; From 58bf288595a2fc16f3491c614480bd1d997d0afe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Apr 2020 12:17:48 +0900 Subject: [PATCH 482/655] Remove DrawableHit's custom sizing logic Turns out this was unnecessary and never actually being used. --- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 9333e5f144..fe9a89f2be 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -92,8 +92,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables // The input manager processes all input prior to us updating, so this is the perfect time // for us to remove the extra press blocking, before input is handled in the next frame pressHandledThisFrame = false; - - Size = BaseSize * Parent.RelativeChildSize; } protected override void UpdateStateTransforms(ArmedState state) From c59096a9419c3087d35137350bc480a90972d3c6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 23 Apr 2020 12:05:34 +0900 Subject: [PATCH 483/655] Fix note placement --- .../ManiaPlacementBlueprintTestScene.cs | 2 - .../TestSceneNotePlacementBlueprint.cs | 42 +++++++++++++++++++ .../Blueprints/ManiaPlacementBlueprint.cs | 2 +- .../Edit/Blueprints/NotePlacementBlueprint.cs | 9 ++++ 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs index afde1c9521..aac77c9c1c 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs @@ -41,8 +41,6 @@ namespace osu.Game.Rulesets.Mania.Tests AccentColour = Color4.OrangeRed, Clock = new FramedClock(new StopwatchClock()), // No scroll }); - - AddStep("change direction", () => ((ScrollingTestContainer)HitObjectContainer).Flip()); } protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both }; diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotePlacementBlueprint.cs index d7b539a2a0..2d97e61aa5 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotePlacementBlueprint.cs @@ -1,17 +1,59 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; +using NUnit.Framework; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Testing; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Input; namespace osu.Game.Rulesets.Mania.Tests { public class TestSceneNotePlacementBlueprint : ManiaPlacementBlueprintTestScene { + [SetUp] + public void Setup() => Schedule(() => + { + this.ChildrenOfType().ForEach(c => c.Clear()); + + ResetPlacement(); + + ((ScrollingTestContainer)HitObjectContainer).Direction = ScrollingDirection.Down; + }); + + [Test] + public void TestPlaceBeforeCurrentTimeDownwards() + { + AddStep("move mouse before current time", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single().ScreenSpaceDrawQuad.BottomLeft - new Vector2(0, 10))); + + AddStep("click", () => InputManager.Click(MouseButton.Left)); + + AddAssert("note start time < 0", () => getNote().StartTime < 0); + } + + [Test] + public void TestPlaceAfterCurrentTimeDownwards() + { + AddStep("move mouse after current time", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + + AddStep("click", () => InputManager.Click(MouseButton.Left)); + + AddAssert("note start time > 0", () => getNote().StartTime > 0); + } + + private Note getNote() => this.ChildrenOfType().FirstOrDefault()?.HitObject; + protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableNote((Note)hitObject); protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint(); } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index 6ddf212266..f228daa5e3 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints return base.OnMouseDown(e); HitObject.Column = Column.Index; - BeginPlacement(TimeAt(e.ScreenSpaceMousePosition)); + BeginPlacement(TimeAt(e.ScreenSpaceMousePosition), true); return true; } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs index 32c6a6fd07..fd8ef52cef 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs @@ -4,6 +4,7 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; +using osuTK; namespace osu.Game.Rulesets.Mania.Edit.Blueprints { @@ -26,5 +27,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints Width = SnappedWidth; Position = SnappedMousePosition; } + + public override void UpdatePosition(Vector2 screenSpacePosition) + { + base.UpdatePosition(screenSpacePosition); + + // Continue updating the position until placement is finished on mouse up. + BeginPlacement(TimeAt(screenSpacePosition), true); + } } } From 37f7e0a7349967077f701cf8c3e263573f2f31e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Apr 2020 12:33:34 +0900 Subject: [PATCH 484/655] Restructure TaikoPlayfield for better skin support --- .../Skinning/TestSceneTaikoPlayfield.cs | 6 +- .../Skinning/TaikoLegacySkinTransformer.cs | 18 ++ .../TaikoSkinComponents.cs | 4 +- osu.Game.Rulesets.Taiko/UI/HitExplosion.cs | 2 +- .../UI/PlayfieldBackground.cs | 61 ++++++ osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 187 ++++++++---------- 6 files changed, 165 insertions(+), 113 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/UI/PlayfieldBackground.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs index cdd6f9ab19..3c7360a6bd 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs @@ -5,7 +5,9 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Framework.Testing; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Taiko.Skinning; using osu.Game.Rulesets.Taiko.UI; @@ -20,6 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning { typeof(TaikoHitTarget), typeof(TaikoLegacyHitTarget), + typeof(PlayfieldBackground), }).ToList(); [Cached(typeof(IScrollingInfo))] @@ -33,10 +36,11 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning { AddStep("Load playfield", () => SetContents(() => new TaikoPlayfield(new ControlPointInfo()) { - Height = 0.4f, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, })); + + AddRepeatStep("change height", () => this.ChildrenOfType().ForEach(p => p.Height = Math.Max(0.2f, (p.Height + 0.2f) % 1f)), 50); } } } diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs index eaa69283e4..919788f08a 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Game.Audio; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Taiko.Skinning { @@ -55,6 +56,23 @@ namespace osu.Game.Rulesets.Taiko.Skinning return new TaikoLegacyHitTarget(); return null; + + case TaikoSkinComponents.PlayfieldBackgroundRight: + if (GetTexture("taiko-bar-right") != null) + return this.GetAnimation("taiko-bar-right", false, false).With(d => + { + d.RelativeSizeAxes = Axes.Both; + d.Size = Vector2.One; + }); + + return null; + + case TaikoSkinComponents.PlayfieldBackgroundLeft: + // This is displayed inside LegacyInputDrum. It is required to be there for layout purposes (can be seen on legacy skins). + if (GetTexture("taiko-bar-right") != null) + return Drawable.Empty(); + + return null; } return source.GetDrawableComponent(component); diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs index 775eeb4e38..60a2be7f99 100644 --- a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs +++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs @@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Taiko DrumRollBody, DrumRollTick, Swell, - HitTarget + HitTarget, + PlayfieldBackgroundLeft, + PlayfieldBackgroundRight } } diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs index 404960c26f..d4118d38b6 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Taiko.UI JudgedObject = judgedObject; - Anchor = Anchor.CentreLeft; + Anchor = Anchor.Centre; Origin = Anchor.Centre; RelativeSizeAxes = Axes.Both; diff --git a/osu.Game.Rulesets.Taiko/UI/PlayfieldBackground.cs b/osu.Game.Rulesets.Taiko/UI/PlayfieldBackground.cs new file mode 100644 index 0000000000..39fb6c0476 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/PlayfieldBackground.cs @@ -0,0 +1,61 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Taiko.UI +{ + public class PlayfieldBackground : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Name = "Transparent playfield background"; + RelativeSizeAxes = Axes.Both; + Masking = true; + BorderColour = colours.Gray1; + + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.2f), + Radius = 5, + }; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Gray0, + Alpha = 0.6f + }, + new Container + { + Name = "Border", + RelativeSizeAxes = Axes.Both, + Masking = true, + MaskingSmoothness = 0, + BorderThickness = 2, + AlwaysPresent = true, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + } + }; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 69c9003434..4587e896ba 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; @@ -25,162 +24,132 @@ namespace osu.Game.Rulesets.Taiko.UI { public class TaikoPlayfield : ScrollingPlayfield { + private readonly ControlPointInfo controlPoints; + /// /// Default height of a when inside a . /// public const float DEFAULT_HEIGHT = 178; - /// - /// The offset from which the center of the hit target lies at. - /// - public const float HIT_TARGET_OFFSET = 100; - /// /// The size of the left area of the playfield. This area contains the input drum. /// - private const float left_area_size = 240; + private const float left_area_size = 180; - private readonly Container hitExplosionContainer; - private readonly Container kiaiExplosionContainer; - private readonly JudgementContainer judgementContainer; - internal readonly Drawable HitTarget; + private Container hitExplosionContainer; + private Container kiaiExplosionContainer; + private JudgementContainer judgementContainer; + internal Drawable HitTarget; - private readonly ProxyContainer topLevelHitContainer; - private readonly ProxyContainer barlineContainer; + private ProxyContainer topLevelHitContainer; + private ProxyContainer barlineContainer; + private Container rightArea; + private Container leftArea; - private readonly Container overlayBackgroundContainer; - private readonly Container backgroundContainer; - - private readonly Box overlayBackground; - private readonly Box background; + private Container hitTargetOffsetContent; public TaikoPlayfield(ControlPointInfo controlPoints) + { + this.controlPoints = controlPoints; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) { InternalChildren = new Drawable[] { - backgroundContainer = new Container - { - Name = "Transparent playfield background", - RelativeSizeAxes = Axes.Both, - Masking = true, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.2f), - Radius = 5, - }, - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.6f - }, - } - }, - new Container + new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundRight), _ => new PlayfieldBackground()), + rightArea = new Container { Name = "Right area", RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = left_area_size }, + RelativePositionAxes = Axes.Both, + Masking = true, Children = new Drawable[] { new Container { Name = "Masked elements before hit objects", RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Masking = true, + FillMode = FillMode.Fit, Children = new[] { hitExplosionContainer = new Container { RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, Blending = BlendingParameters.Additive, }, HitTarget = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.HitTarget), _ => new TaikoHitTarget()) { - Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit } } }, - barlineContainer = new ProxyContainer + hitTargetOffsetContent = new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = HIT_TARGET_OFFSET } - }, - new Container - { - Name = "Hit objects", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Masking = true, - Child = HitObjectContainer - }, - kiaiExplosionContainer = new Container - { - Name = "Kiai hit explosions", - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Blending = BlendingParameters.Additive - }, - judgementContainer = new JudgementContainer - { - Name = "Judgements", - RelativeSizeAxes = Axes.Y, - Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Blending = BlendingParameters.Additive + Children = new Drawable[] + { + barlineContainer = new ProxyContainer + { + RelativeSizeAxes = Axes.Both, + }, + new Container + { + Name = "Hit objects", + RelativeSizeAxes = Axes.Both, + Child = HitObjectContainer + }, + kiaiExplosionContainer = new Container + { + Name = "Kiai hit explosions", + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Blending = BlendingParameters.Additive + }, + judgementContainer = new JudgementContainer + { + Name = "Judgements", + RelativeSizeAxes = Axes.Y, + Blending = BlendingParameters.Additive + }, + } }, } }, - overlayBackgroundContainer = new Container + leftArea = new Container { Name = "Left overlay", - RelativeSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + BorderColour = colours.Gray0, Size = new Vector2(left_area_size, 1), Children = new Drawable[] { - overlayBackground = new Box + new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new Container { RelativeSizeAxes = Axes.Both, - }, + Children = new Drawable[] + { + new Box + { + Colour = colours.Gray1, + RelativeSizeAxes = Axes.Both, + }, + new Box + { + Anchor = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = 10, + Colour = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)), + }, + } + }), new InputDrum(controlPoints) { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Scale = new Vector2(0.9f), - Margin = new MarginPadding { Right = 20 } + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, }, - new Box - { - Anchor = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Width = 10, - Colour = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)), - }, - } - }, - new Container - { - Name = "Border", - RelativeSizeAxes = Axes.Both, - Masking = true, - MaskingSmoothness = 0, - BorderThickness = 2, - AlwaysPresent = true, - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - } } }, topLevelHitContainer = new ProxyContainer @@ -191,14 +160,12 @@ namespace osu.Game.Rulesets.Taiko.UI }; } - [BackgroundDependencyLoader] - private void load(OsuColour colours) + protected override void Update() { - overlayBackgroundContainer.BorderColour = colours.Gray0; - overlayBackground.Colour = colours.Gray1; + base.Update(); - backgroundContainer.BorderColour = colours.Gray1; - background.Colour = colours.Gray0; + rightArea.Padding = new MarginPadding { Left = leftArea.DrawWidth }; + hitTargetOffsetContent.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 }; } public override void Add(DrawableHitObject h) From 49568a3d562884e8960b8ac5d728f476de872d37 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Apr 2020 12:19:30 +0900 Subject: [PATCH 485/655] Adjust input drum to work with new playfield changes --- osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs | 9 +++++++++ osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs index c61e35692b..276a9d76a8 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs @@ -78,6 +78,15 @@ namespace osu.Game.Rulesets.Taiko.Skinning } } + protected override void Update() + { + base.Update(); + + // Relying on RelativeSizeAxes.Both + FillMode.Fit doesn't work due to the precise pixel layout requirements. + // This is a bit ugly but makes the non-legacy implementations a lot cleaner to implement. + Scale = new Vector2(Parent.DrawHeight / Size.Y); + } + /// /// A half-drum. Contains one centre and one rim hit. /// diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 422ea2f929..38026517d9 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -31,7 +31,6 @@ namespace osu.Game.Rulesets.Taiko.UI sampleMapping = new DrumSampleMapping(controlPoints); RelativeSizeAxes = Axes.Both; - FillMode = FillMode.Fit; } [BackgroundDependencyLoader] @@ -40,6 +39,8 @@ namespace osu.Game.Rulesets.Taiko.UI Child = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.InputDrum), _ => new Container { RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Scale = new Vector2(0.9f), Children = new Drawable[] { new TaikoHalfDrum(false) From 2e022fbcb5e47519e57a3c973cbb58295e636788 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Apr 2020 12:47:57 +0900 Subject: [PATCH 486/655] Add comment about padding update computation --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 4587e896ba..4e0ef64ce1 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -164,6 +164,8 @@ namespace osu.Game.Rulesets.Taiko.UI { base.Update(); + // Padding is required to be updated for elements which are based on "absolute" X sized elements. + // This is basically allowing for correct alignment as relative pieces move around them. rightArea.Padding = new MarginPadding { Left = leftArea.DrawWidth }; hitTargetOffsetContent.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 }; } From 22d2607ff58eb51bfa38d04e08a097011caabf8e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 23 Apr 2020 12:53:09 +0900 Subject: [PATCH 487/655] Only commit if placement is active --- .../Edit/Blueprints/NotePlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs index fd8ef52cef..888ce695c2 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints base.UpdatePosition(screenSpacePosition); // Continue updating the position until placement is finished on mouse up. - BeginPlacement(TimeAt(screenSpacePosition), true); + BeginPlacement(TimeAt(screenSpacePosition), PlacementActive); } } } From 4f0b5a34d3a1325b77d9901cfd43d6af55a0e29c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 23 Apr 2020 12:53:23 +0900 Subject: [PATCH 488/655] Fix hold note placement body sized incorrectly --- .../Objects/Drawables/DrawableHoldNote.cs | 5 ++++- .../Objects/Drawables/Pieces/DefaultBodyPiece.cs | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index a9ef661aaa..d63c0326a7 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -51,7 +51,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables AddRangeInternal(new[] { - bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece()) + bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece + { + RelativeSizeAxes = Axes.Both + }) { RelativeSizeAxes = Axes.X }, diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs index 0ee0a14df3..bc4a095395 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs @@ -34,7 +34,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces public DefaultBodyPiece() { - RelativeSizeAxes = Axes.Both; Blending = BlendingParameters.Additive; AddLayout(subtractionCache); From 61d2580e1c847cde16cfb4bb023dcc978730e636 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Apr 2020 13:07:30 +0900 Subject: [PATCH 489/655] Fix gap to left of InputDrum on legacy skins --- .../Skinning/LegacyInputDrum.cs | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs index 276a9d76a8..1e8cade01d 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs @@ -20,36 +20,41 @@ namespace osu.Game.Rulesets.Taiko.Skinning { private LegacyHalfDrum left; private LegacyHalfDrum right; + private Container content; public LegacyInputDrum() { - Size = new Vector2(180, 200); + RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] private void load(ISkinSource skin) { - Children = new Drawable[] + Child = content = new Container { - new Sprite + Size = new Vector2(180, 200), + Children = new Drawable[] { - Texture = skin.GetTexture("taiko-bar-left") - }, - left = new LegacyHalfDrum(false) - { - Name = "Left Half", - RelativeSizeAxes = Axes.Both, - RimAction = TaikoAction.LeftRim, - CentreAction = TaikoAction.LeftCentre - }, - right = new LegacyHalfDrum(true) - { - Name = "Right Half", - RelativeSizeAxes = Axes.Both, - Origin = Anchor.TopRight, - Scale = new Vector2(-1, 1), - RimAction = TaikoAction.RightRim, - CentreAction = TaikoAction.RightCentre + new Sprite + { + Texture = skin.GetTexture("taiko-bar-left") + }, + left = new LegacyHalfDrum(false) + { + Name = "Left Half", + RelativeSizeAxes = Axes.Both, + RimAction = TaikoAction.LeftRim, + CentreAction = TaikoAction.LeftCentre + }, + right = new LegacyHalfDrum(true) + { + Name = "Right Half", + RelativeSizeAxes = Axes.Both, + Origin = Anchor.TopRight, + Scale = new Vector2(-1, 1), + RimAction = TaikoAction.RightRim, + CentreAction = TaikoAction.RightCentre + } } }; @@ -84,7 +89,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning // Relying on RelativeSizeAxes.Both + FillMode.Fit doesn't work due to the precise pixel layout requirements. // This is a bit ugly but makes the non-legacy implementations a lot cleaner to implement. - Scale = new Vector2(Parent.DrawHeight / Size.Y); + content.Scale = new Vector2(DrawHeight / content.Size.Y); } /// From 4032d669596a2031eca223c37bc2cddb9a102725 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Apr 2020 13:17:46 +0900 Subject: [PATCH 490/655] Apply same legacy scale adjust logic to TaikoLegacyHitTarget --- .../Skinning/TaikoLegacyHitTarget.cs | 52 ++++++++++++------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacyHitTarget.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacyHitTarget.cs index b80f273d24..7c1e65f569 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacyHitTarget.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacyHitTarget.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Taiko.UI; using osu.Game.Skinning; using osuTK; @@ -12,32 +13,47 @@ namespace osu.Game.Rulesets.Taiko.Skinning { public class TaikoLegacyHitTarget : CompositeDrawable { + private Container content; + [BackgroundDependencyLoader] private void load(ISkinSource skin) { RelativeSizeAxes = Axes.Both; - InternalChildren = new Drawable[] + InternalChild = content = new Container { - new Sprite + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] { - Texture = skin.GetTexture("approachcircle"), - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.73f) * 0.625f, - Alpha = 0.7f, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new Sprite - { - Texture = skin.GetTexture("taikobigcircle"), - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.7f) * 0.625f, - Alpha = 0.5f, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, + new Sprite + { + Texture = skin.GetTexture("approachcircle"), + Scale = new Vector2(0.73f), + Alpha = 0.7f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new Sprite + { + Texture = skin.GetTexture("taikobigcircle"), + Scale = new Vector2(0.7f), + Alpha = 0.5f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } }; } + + protected override void Update() + { + base.Update(); + + // Relying on RelativeSizeAxes.Both + FillMode.Fit doesn't work due to the precise pixel layout requirements. + // This is a bit ugly but makes the non-legacy implementations a lot cleaner to implement. + content.Scale = new Vector2(DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); + } } } From 559487b20583fa087760f0410da44d8d35927892 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Apr 2020 13:23:49 +0900 Subject: [PATCH 491/655] Move playfield background implementation to its own file --- .../Skinning/TestSceneTaikoPlayfield.cs | 2 +- .../Skinning/TaikoLegacySkinTransformer.cs | 2 + .../UI/PlayfieldBackgroundLeft.cs | 37 +++++++++++++++++++ ...kground.cs => PlayfieldBackgroundRight.cs} | 2 +- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 25 +------------ 5 files changed, 43 insertions(+), 25 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/UI/PlayfieldBackgroundLeft.cs rename osu.Game.Rulesets.Taiko/UI/{PlayfieldBackground.cs => PlayfieldBackgroundRight.cs} (96%) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs index 3c7360a6bd..16b3c036a3 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning { typeof(TaikoHitTarget), typeof(TaikoLegacyHitTarget), - typeof(PlayfieldBackground), + typeof(PlayfieldBackgroundRight), }).ToList(); [Cached(typeof(IScrollingInfo))] diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs index 919788f08a..15bbd32eb1 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs @@ -59,11 +59,13 @@ namespace osu.Game.Rulesets.Taiko.Skinning case TaikoSkinComponents.PlayfieldBackgroundRight: if (GetTexture("taiko-bar-right") != null) + { return this.GetAnimation("taiko-bar-right", false, false).With(d => { d.RelativeSizeAxes = Axes.Both; d.Size = Vector2.One; }); + } return null; diff --git a/osu.Game.Rulesets.Taiko/UI/PlayfieldBackgroundLeft.cs b/osu.Game.Rulesets.Taiko/UI/PlayfieldBackgroundLeft.cs new file mode 100644 index 0000000000..2a8890a95d --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/PlayfieldBackgroundLeft.cs @@ -0,0 +1,37 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Taiko.UI +{ + internal class PlayfieldBackgroundLeft : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.Both; + InternalChildren = new Drawable[] + { + new Box + { + Colour = colours.Gray1, + RelativeSizeAxes = Axes.Both, + }, + new Box + { + Anchor = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = 10, + Colour = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)), + }, + }; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/PlayfieldBackground.cs b/osu.Game.Rulesets.Taiko/UI/PlayfieldBackgroundRight.cs similarity index 96% rename from osu.Game.Rulesets.Taiko/UI/PlayfieldBackground.cs rename to osu.Game.Rulesets.Taiko/UI/PlayfieldBackgroundRight.cs index 39fb6c0476..44bfdacf37 100644 --- a/osu.Game.Rulesets.Taiko/UI/PlayfieldBackground.cs +++ b/osu.Game.Rulesets.Taiko/UI/PlayfieldBackgroundRight.cs @@ -12,7 +12,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Taiko.UI { - public class PlayfieldBackground : CompositeDrawable + public class PlayfieldBackgroundRight : CompositeDrawable { [BackgroundDependencyLoader] private void load(OsuColour colours) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 4e0ef64ce1..1e8d37e3e5 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -3,10 +3,8 @@ using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; @@ -18,7 +16,6 @@ using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Skinning; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Taiko.UI { @@ -58,7 +55,7 @@ namespace osu.Game.Rulesets.Taiko.UI { InternalChildren = new Drawable[] { - new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundRight), _ => new PlayfieldBackground()), + new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundRight), _ => new PlayfieldBackgroundRight()), rightArea = new Container { Name = "Right area", @@ -126,25 +123,7 @@ namespace osu.Game.Rulesets.Taiko.UI Size = new Vector2(left_area_size, 1), Children = new Drawable[] { - new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - Colour = colours.Gray1, - RelativeSizeAxes = Axes.Both, - }, - new Box - { - Anchor = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Width = 10, - Colour = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)), - }, - } - }), + new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()), new InputDrum(controlPoints) { Anchor = Anchor.CentreLeft, From 12c235027df835b189669ade2313f8128b4d31ea Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 23 Apr 2020 13:28:27 +0900 Subject: [PATCH 492/655] Remove stale file --- .../Edit/Masks/ManiaSelectionBlueprint.cs | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs diff --git a/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs deleted file mode 100644 index 433db79ae0..0000000000 --- a/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects.Drawables; - -namespace osu.Game.Rulesets.Mania.Edit.Masks -{ - public abstract class ManiaSelectionBlueprint : OverlaySelectionBlueprint - { - protected ManiaSelectionBlueprint(DrawableHitObject drawableObject) - : base(drawableObject) - { - RelativeSizeAxes = Axes.None; - } - } -} From f804be25d13e785dd7a0152f7b9b7c79d93d3138 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Apr 2020 13:36:33 +0900 Subject: [PATCH 493/655] Remove incorrect area sizing (now using fillmode / relative instead) --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 1e8d37e3e5..8402ebb4c4 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -15,7 +15,6 @@ using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Skinning; -using osuTK; namespace osu.Game.Rulesets.Taiko.UI { @@ -28,11 +27,6 @@ namespace osu.Game.Rulesets.Taiko.UI /// public const float DEFAULT_HEIGHT = 178; - /// - /// The size of the left area of the playfield. This area contains the input drum. - /// - private const float left_area_size = 180; - private Container hitExplosionContainer; private Container kiaiExplosionContainer; private JudgementContainer judgementContainer; @@ -120,7 +114,6 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, BorderColour = colours.Gray0, - Size = new Vector2(left_area_size, 1), Children = new Drawable[] { new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()), From b4e1ad81d07d89f4ea9b62dc684c1562629cb472 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Apr 2020 13:48:08 +0900 Subject: [PATCH 494/655] Fix alignment of right half of legacy input drum --- osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs index 1e8cade01d..81d645e294 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning const float ratio = 1.6f; // because the right half is flipped, we need to position using width - position to get the true "topleft" origin position - float negativeScaleAdjust = Width / ratio; + float negativeScaleAdjust = content.Width / ratio; if (skin.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.1m) { From 2a1cc35541f46c8bc36d8447fddce0d7c37589de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Apr 2020 14:01:50 +0900 Subject: [PATCH 495/655] Add test scene --- .../Skinning/TestSceneDrawableBarLine.cs | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs new file mode 100644 index 0000000000..a1a5c8c836 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs @@ -0,0 +1,90 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests.Skinning +{ + [TestFixture] + public class TestSceneDrawableBarLine : TaikoSkinnableTestScene + { + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] + { + typeof(DrawableBarLine), + }).ToList(); + + [Cached(typeof(IScrollingInfo))] + private ScrollingTestContainer.TestScrollingInfo info = new ScrollingTestContainer.TestScrollingInfo + { + Direction = { Value = ScrollingDirection.Left }, + TimeRange = { Value = 5000 }, + }; + + [BackgroundDependencyLoader] + private void load() + { + AddStep("Bar line ", () => SetContents(() => + { + var hoc = new ScrollingHitObjectContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Height = 0.8f + }; + + hoc.Add(new DrawableBarLine(createBarLineAtCurrentTime()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + + return hoc; + })); + + AddStep("Bar line (major)", () => SetContents(() => + { + var hoc = new ScrollingHitObjectContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Height = 0.8f + }; + + hoc.Add(new DrawableBarLineMajor(createBarLineAtCurrentTime(true)) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + + return hoc; + })); + } + + private BarLine createBarLineAtCurrentTime(bool major = false) + { + var drumroll = new BarLine + { + Major = major, + StartTime = Time.Current + 1000, + }; + + var cpi = new ControlPointInfo(); + cpi.Add(0, new TimingControlPoint { BeatLength = 500 }); + + drumroll.ApplyDefaults(cpi, new BeatmapDifficulty()); + + return drumroll; + } + } +} From 12f156dcecdb34228c7cb4209b09fbfc00d79821 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Apr 2020 14:32:48 +0900 Subject: [PATCH 496/655] Add taiko barline skinning support --- .../Objects/Drawables/DrawableBarLine.cs | 13 +++++---- .../Objects/Drawables/DrawableBarLineMajor.cs | 2 +- .../Skinning/LegacyBarLine.cs | 27 +++++++++++++++++++ .../Skinning/TaikoLegacySkinTransformer.cs | 6 +++++ .../TaikoSkinComponents.cs | 3 ++- 5 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/Skinning/LegacyBarLine.cs diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs index e9caabbcc8..1e08e921a6 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Objects; using osuTK; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -27,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// /// The visual line tracker. /// - protected Box Tracker; + protected SkinnableDrawable Line; /// /// The bar line. @@ -45,13 +46,15 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables RelativeSizeAxes = Axes.Y; Width = tracker_width; - AddInternal(Tracker = new Box + AddInternal(Line = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.BarLine), _ => new Box + { + RelativeSizeAxes = Axes.Both, + EdgeSmoothness = new Vector2(0.5f, 0), + }) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - EdgeSmoothness = new Vector2(0.5f, 0), - Alpha = 0.75f + Alpha = 0.75f, }); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs index 4d3a1a3f8a..62aab3524b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } }); - Tracker.Alpha = 1f; + Line.Alpha = 1f; } protected override void LoadComplete() diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyBarLine.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyBarLine.cs new file mode 100644 index 0000000000..7d08a21ab1 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyBarLine.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Taiko.Skinning +{ + public class LegacyBarLine : Sprite + { + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + Texture = skin.GetTexture("taiko-barline"); + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + RelativeSizeAxes = Axes.Both; + Size = new Vector2(1, 0.88f); + FillMode = FillMode.Fill; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs index 15bbd32eb1..447d6ae455 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs @@ -75,6 +75,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning return Drawable.Empty(); return null; + + case TaikoSkinComponents.BarLine: + if (GetTexture("taiko-barline") != null) + return new LegacyBarLine(); + + return null; } return source.GetDrawableComponent(component); diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs index 60a2be7f99..a90ce608b2 100644 --- a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs +++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs @@ -13,6 +13,7 @@ namespace osu.Game.Rulesets.Taiko Swell, HitTarget, PlayfieldBackgroundLeft, - PlayfieldBackgroundRight + PlayfieldBackgroundRight, + BarLine } } From 8f31846defad9ea469b4aedd5970355177c20d69 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Apr 2020 14:39:34 +0900 Subject: [PATCH 497/655] Add playfield background to test scene to better understand bounds --- .../Skinning/TestSceneDrawableBarLine.cs | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs index a1a5c8c836..e72f42a0b4 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs @@ -7,10 +7,12 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects.Drawables; +using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; @@ -36,11 +38,19 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning { AddStep("Bar line ", () => SetContents(() => { - var hoc = new ScrollingHitObjectContainer + ScrollingHitObjectContainer hoc; + + var cont = new Container { + RelativeSizeAxes = Axes.Both, + Height = 0.8f, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Height = 0.8f + Children = new Drawable[] + { + new TaikoPlayfield(new ControlPointInfo()), + hoc = new ScrollingHitObjectContainer() + } }; hoc.Add(new DrawableBarLine(createBarLineAtCurrentTime()) @@ -49,16 +59,24 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning Origin = Anchor.Centre, }); - return hoc; + return cont; })); AddStep("Bar line (major)", () => SetContents(() => { - var hoc = new ScrollingHitObjectContainer + ScrollingHitObjectContainer hoc; + + var cont = new Container { + RelativeSizeAxes = Axes.Both, + Height = 0.8f, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Height = 0.8f + Children = new Drawable[] + { + new TaikoPlayfield(new ControlPointInfo()), + hoc = new ScrollingHitObjectContainer() + } }; hoc.Add(new DrawableBarLineMajor(createBarLineAtCurrentTime(true)) @@ -67,7 +85,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning Origin = Anchor.Centre, }); - return hoc; + return cont; })); } @@ -76,7 +94,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning var drumroll = new BarLine { Major = major, - StartTime = Time.Current + 1000, + StartTime = Time.Current + 2000, }; var cpi = new ControlPointInfo(); From ab93c819b54f930211c507f28ea23b3ae9ca6cfa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Apr 2020 15:26:03 +0900 Subject: [PATCH 498/655] Add metric right background element --- .../metrics-skin/taiko-bar-right@2x.png | Bin 0 -> 7830 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-bar-right@2x.png diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-bar-right@2x.png b/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-bar-right@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..5ca8a40d88a13e447d26cd8c0e777173d8e60445 GIT binary patch literal 7830 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 Of{?|_1M@zM+V&ruzN7m9 literal 0 HcmV?d00001 From 712331a2fe524fb6a3c7a8d77a8095d50d7c83d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Apr 2020 15:26:20 +0900 Subject: [PATCH 499/655] Add metric barline element --- .../Resources/metrics-skin/taiko-barline@2x.png | Bin 0 -> 1551 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-barline@2x.png diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-barline@2x.png b/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-barline@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3e44f3309585d83ce100c59f08e880582b4feb70 GIT binary patch literal 1551 zcmbVMU5MON6wZ{kw7awwY(eVFC539K`JE)YxozB?o!NE*W;@Idbc)#aZgOWP&LlTY z?sjIkAY}zbL=i1mw)!H1|5kh|XzBV=MQ9PhC&357H&+pT5E1cae!{NQzg`HrKi~b% z`OZD(JX4uFzH4m%7{_tD>~g8f{yR7>cY4QmHoJE9^jGY+Gb*1-IBxvj^viLpuiVFR z+xGjlI;}g8K$ip}^2h=fdqKp|9Cvi87a_NeDOkWwKeYHCKf1vK-?R9Wx+6PL5x4yE za*XGf=W6b9+ciCY>S1uS2N^+tDFVHq6DF`{@qJ#%=IOS?gFb||ExwQ%1a+qZiX_HB z7Y)Hxv`H{wii)AhdE+6V%8Dt;lai7bWCh9w)F;4j@XT85HDI-LY-o$EEWSl)1SP54 z?TX#JNaCiXm?q;;B~=v|LP(ZEih4qr92hZ_aN@>(M12y1lo2hEMQZWP(}9E_%4ox6 z7$z1lsfQv-5#>};ALzLmE?SH`edC@h;SLUPNE3!tGHlc$lq4_npld$CJ(Ye!XoLAm>Fi?4u+JO@3uOj&em@wpRrbW#V!?Ut#8&}JTi7s-n;eFd&ciS@k0B%Kc0J_?4qB( z*TISbbBCY4^~B8d&Np-Cetmq}Ufa6&_8Z*%mSbms;PUc8@%f9RvG&gN?(Np^H=k6l ze|tfB^=o~-@X}2>{%rM=*J_o*wteBD`X%?&B}MrB@+uv7etTPg%f9f%>j%$#ywcvY s=a&z1@BMr=_vr4Y_SNCqmUoYQcxCnFFCUw_oVILxcCPgP%-NMc0h$5s%m4rY literal 0 HcmV?d00001 From e3a3144236fe6e2790dafb5198248392b9227145 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 23 Apr 2020 11:07:55 +0300 Subject: [PATCH 500/655] Rename editor tests namespace from "Editor" to "Editing" --- osu.Game.Tests/{Editor => Editing}/EditorChangeHandlerTest.cs | 2 +- .../{Editor => Editing}/LegacyEditorBeatmapPatcherTest.cs | 2 +- .../TestSceneHitObjectComposerDistanceSnapping.cs | 2 +- .../Visual/{Editor => Editing}/TestSceneBeatDivisorControl.cs | 2 +- .../Visual/{Editor => Editing}/TestSceneComposeScreen.cs | 2 +- .../Visual/{Editor => Editing}/TestSceneDistanceSnapGrid.cs | 2 +- .../Visual/{Editor => Editing}/TestSceneEditorChangeStates.cs | 4 ++-- .../{Editor => Editing}/TestSceneEditorComposeRadioButtons.cs | 2 +- .../Visual/{Editor => Editing}/TestSceneEditorMenuBar.cs | 2 +- .../Visual/{Editor => Editing}/TestSceneEditorSeekSnapping.cs | 2 +- .../{Editor => Editing}/TestSceneEditorSummaryTimeline.cs | 2 +- .../Visual/{Editor => Editing}/TestSceneHitObjectComposer.cs | 2 +- .../Visual/{Editor => Editing}/TestScenePlaybackControl.cs | 2 +- .../TestSceneTimelineBlueprintContainer.cs | 2 +- .../{Editor => Editing}/TestSceneTimelineTickDisplay.cs | 2 +- .../Visual/{Editor => Editing}/TestSceneTimingScreen.cs | 2 +- .../Visual/{Editor => Editing}/TestSceneWaveform.cs | 2 +- .../{Editor => Editing}/TestSceneZoomableScrollContainer.cs | 2 +- .../Visual/{Editor => Editing}/TimelineTestScene.cs | 2 +- 19 files changed, 20 insertions(+), 20 deletions(-) rename osu.Game.Tests/{Editor => Editing}/EditorChangeHandlerTest.cs (98%) rename osu.Game.Tests/{Editor => Editing}/LegacyEditorBeatmapPatcherTest.cs (99%) rename osu.Game.Tests/{Editor => Editing}/TestSceneHitObjectComposerDistanceSnapping.cs (99%) rename osu.Game.Tests/Visual/{Editor => Editing}/TestSceneBeatDivisorControl.cs (98%) rename osu.Game.Tests/Visual/{Editor => Editing}/TestSceneComposeScreen.cs (96%) rename osu.Game.Tests/Visual/{Editor => Editing}/TestSceneDistanceSnapGrid.cs (99%) rename osu.Game.Tests/Visual/{Editor => Editing}/TestSceneEditorChangeStates.cs (98%) rename osu.Game.Tests/Visual/{Editor => Editing}/TestSceneEditorComposeRadioButtons.cs (97%) rename osu.Game.Tests/Visual/{Editor => Editing}/TestSceneEditorMenuBar.cs (99%) rename osu.Game.Tests/Visual/{Editor => Editing}/TestSceneEditorSeekSnapping.cs (99%) rename osu.Game.Tests/Visual/{Editor => Editing}/TestSceneEditorSummaryTimeline.cs (95%) rename osu.Game.Tests/Visual/{Editor => Editing}/TestSceneHitObjectComposer.cs (98%) rename osu.Game.Tests/Visual/{Editor => Editing}/TestScenePlaybackControl.cs (96%) rename osu.Game.Tests/Visual/{Editor => Editing}/TestSceneTimelineBlueprintContainer.cs (95%) rename osu.Game.Tests/Visual/{Editor => Editing}/TestSceneTimelineTickDisplay.cs (95%) rename osu.Game.Tests/Visual/{Editor => Editing}/TestSceneTimingScreen.cs (96%) rename osu.Game.Tests/Visual/{Editor => Editing}/TestSceneWaveform.cs (98%) rename osu.Game.Tests/Visual/{Editor => Editing}/TestSceneZoomableScrollContainer.cs (99%) rename osu.Game.Tests/Visual/{Editor => Editing}/TimelineTestScene.cs (99%) diff --git a/osu.Game.Tests/Editor/EditorChangeHandlerTest.cs b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs similarity index 98% rename from osu.Game.Tests/Editor/EditorChangeHandlerTest.cs rename to osu.Game.Tests/Editing/EditorChangeHandlerTest.cs index 9613f250c4..feda1ae0e9 100644 --- a/osu.Game.Tests/Editor/EditorChangeHandlerTest.cs +++ b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs @@ -5,7 +5,7 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Screens.Edit; -namespace osu.Game.Tests.Editor +namespace osu.Game.Tests.Editing { [TestFixture] public class EditorChangeHandlerTest diff --git a/osu.Game.Tests/Editor/LegacyEditorBeatmapPatcherTest.cs b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs similarity index 99% rename from osu.Game.Tests/Editor/LegacyEditorBeatmapPatcherTest.cs rename to osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs index c24418d688..a3ab677d96 100644 --- a/osu.Game.Tests/Editor/LegacyEditorBeatmapPatcherTest.cs +++ b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs @@ -17,7 +17,7 @@ using osu.Game.Screens.Edit; using osuTK; using Decoder = osu.Game.Beatmaps.Formats.Decoder; -namespace osu.Game.Tests.Editor +namespace osu.Game.Tests.Editing { [TestFixture] public class LegacyEditorBeatmapPatcherTest diff --git a/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs similarity index 99% rename from osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs rename to osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index 3cb5909ba9..168ec0f09d 100644 --- a/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -14,7 +14,7 @@ using osu.Game.Rulesets.Osu.Edit; using osu.Game.Screens.Edit; using osu.Game.Tests.Visual; -namespace osu.Game.Tests.Editor +namespace osu.Game.Tests.Editing { [HeadlessTest] public class TestSceneHitObjectComposerDistanceSnapping : EditorClockTestScene diff --git a/osu.Game.Tests/Visual/Editor/TestSceneBeatDivisorControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs similarity index 98% rename from osu.Game.Tests/Visual/Editor/TestSceneBeatDivisorControl.cs rename to osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs index fd7a5980f3..f6e69fd8bf 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneBeatDivisorControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs @@ -14,7 +14,7 @@ using osu.Game.Screens.Edit.Compose.Components; using osuTK; using osuTK.Input; -namespace osu.Game.Tests.Visual.Editor +namespace osu.Game.Tests.Visual.Editing { public class TestSceneBeatDivisorControl : OsuManualInputManagerTestScene { diff --git a/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs similarity index 96% rename from osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs rename to osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs index a8830824c0..6f5655006e 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose; -namespace osu.Game.Tests.Visual.Editor +namespace osu.Game.Tests.Visual.Editing { [TestFixture] public class TestSceneComposeScreen : EditorClockTestScene diff --git a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs similarity index 99% rename from osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs rename to osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs index f49256a633..417d16fdb0 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs @@ -13,7 +13,7 @@ using osu.Game.Screens.Edit.Compose.Components; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual.Editor +namespace osu.Game.Tests.Visual.Editing { public class TestSceneDistanceSnapGrid : EditorClockTestScene { diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs similarity index 98% rename from osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs rename to osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs index efc2a6f552..d85bf15d52 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorChangeStates.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; -namespace osu.Game.Tests.Visual.Editor +namespace osu.Game.Tests.Visual.Editing { public class TestSceneEditorChangeStates : ScreenTestScene { @@ -162,7 +162,7 @@ namespace osu.Game.Tests.Visual.Editor private void addRedoSteps() => AddStep("redo", () => editor.Redo()); - private class TestEditor : Screens.Edit.Editor + private class TestEditor : Editor { public new void Undo() => base.Undo(); diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeRadioButtons.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorComposeRadioButtons.cs similarity index 97% rename from osu.Game.Tests/Visual/Editor/TestSceneEditorComposeRadioButtons.cs rename to osu.Game.Tests/Visual/Editing/TestSceneEditorComposeRadioButtons.cs index 1709067d5d..2deeaef1f6 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeRadioButtons.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorComposeRadioButtons.cs @@ -7,7 +7,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Screens.Edit.Components.RadioButtons; -namespace osu.Game.Tests.Visual.Editor +namespace osu.Game.Tests.Visual.Editing { [TestFixture] public class TestSceneEditorComposeRadioButtons : OsuTestScene diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorMenuBar.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorMenuBar.cs similarity index 99% rename from osu.Game.Tests/Visual/Editor/TestSceneEditorMenuBar.cs rename to osu.Game.Tests/Visual/Editing/TestSceneEditorMenuBar.cs index 53c2d62067..2cbdacb61c 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorMenuBar.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorMenuBar.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Edit.Components.Menus; -namespace osu.Game.Tests.Visual.Editor +namespace osu.Game.Tests.Visual.Editing { [TestFixture] public class TestSceneEditorMenuBar : OsuTestScene diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorSeekSnapping.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSeekSnapping.cs similarity index 99% rename from osu.Game.Tests/Visual/Editor/TestSceneEditorSeekSnapping.cs rename to osu.Game.Tests/Visual/Editing/TestSceneEditorSeekSnapping.cs index 3118e0cabe..41d1459103 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorSeekSnapping.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSeekSnapping.cs @@ -13,7 +13,7 @@ using osu.Game.Rulesets.Osu.Objects; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual.Editor +namespace osu.Game.Tests.Visual.Editing { [TestFixture] public class TestSceneEditorSeekSnapping : EditorClockTestScene diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorSummaryTimeline.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs similarity index 95% rename from osu.Game.Tests/Visual/Editor/TestSceneEditorSummaryTimeline.cs rename to osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs index 2e04eb50ca..c92423545d 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorSummaryTimeline.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit.Components.Timelines.Summary; using osuTK; -namespace osu.Game.Tests.Visual.Editor +namespace osu.Game.Tests.Visual.Editing { [TestFixture] public class TestSceneEditorSummaryTimeline : EditorClockTestScene diff --git a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs similarity index 98% rename from osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs rename to osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs index e41c2427fb..ddaca26220 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs @@ -20,7 +20,7 @@ using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components; using osuTK; -namespace osu.Game.Tests.Visual.Editor +namespace osu.Game.Tests.Visual.Editing { [TestFixture] public class TestSceneHitObjectComposer : EditorClockTestScene diff --git a/osu.Game.Tests/Visual/Editor/TestScenePlaybackControl.cs b/osu.Game.Tests/Visual/Editing/TestScenePlaybackControl.cs similarity index 96% rename from osu.Game.Tests/Visual/Editor/TestScenePlaybackControl.cs rename to osu.Game.Tests/Visual/Editing/TestScenePlaybackControl.cs index 0d4fe4366d..3af976cae0 100644 --- a/osu.Game.Tests/Visual/Editor/TestScenePlaybackControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePlaybackControl.cs @@ -9,7 +9,7 @@ using osu.Game.Beatmaps; using osu.Game.Screens.Edit.Components; using osuTK; -namespace osu.Game.Tests.Visual.Editor +namespace osu.Game.Tests.Visual.Editing { [TestFixture] public class TestScenePlaybackControl : OsuTestScene diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineBlueprintContainer.cs similarity index 95% rename from osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs rename to osu.Game.Tests/Visual/Editing/TestSceneTimelineBlueprintContainer.cs index 4d8f877575..5ab2f49b4a 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineBlueprintContainer.cs @@ -7,7 +7,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Screens.Edit.Compose.Components.Timeline; -namespace osu.Game.Tests.Visual.Editor +namespace osu.Game.Tests.Visual.Editing { [TestFixture] public class TestSceneTimelineBlueprintContainer : TimelineTestScene diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimelineTickDisplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineTickDisplay.cs similarity index 95% rename from osu.Game.Tests/Visual/Editor/TestSceneTimelineTickDisplay.cs rename to osu.Game.Tests/Visual/Editing/TestSceneTimelineTickDisplay.cs index 43a3cd6122..e33040acdc 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneTimelineTickDisplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineTickDisplay.cs @@ -8,7 +8,7 @@ using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; -namespace osu.Game.Tests.Visual.Editor +namespace osu.Game.Tests.Visual.Editing { [TestFixture] public class TestSceneTimelineTickDisplay : TimelineTestScene diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs similarity index 96% rename from osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs rename to osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs index ae09a7fa47..a6dbe9571e 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Timing; -namespace osu.Game.Tests.Visual.Editor +namespace osu.Game.Tests.Visual.Editing { [TestFixture] public class TestSceneTimingScreen : EditorClockTestScene diff --git a/osu.Game.Tests/Visual/Editor/TestSceneWaveform.cs b/osu.Game.Tests/Visual/Editing/TestSceneWaveform.cs similarity index 98% rename from osu.Game.Tests/Visual/Editor/TestSceneWaveform.cs rename to osu.Game.Tests/Visual/Editing/TestSceneWaveform.cs index e2762f3d5f..0c1296b82c 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneWaveform.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneWaveform.cs @@ -14,7 +14,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Osu; using osuTK.Graphics; -namespace osu.Game.Tests.Visual.Editor +namespace osu.Game.Tests.Visual.Editing { [TestFixture] public class TestSceneWaveform : OsuTestScene diff --git a/osu.Game.Tests/Visual/Editor/TestSceneZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs similarity index 99% rename from osu.Game.Tests/Visual/Editor/TestSceneZoomableScrollContainer.cs rename to osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs index 19d19c2759..082268d824 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneZoomableScrollContainer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs @@ -15,7 +15,7 @@ using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual.Editor +namespace osu.Game.Tests.Visual.Editing { public class TestSceneZoomableScrollContainer : OsuManualInputManagerTestScene { diff --git a/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs similarity index 99% rename from osu.Game.Tests/Visual/Editor/TimelineTestScene.cs rename to osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index 7081eb3af5..56b2860e96 100644 --- a/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -18,7 +18,7 @@ using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual.Editor +namespace osu.Game.Tests.Visual.Editing { public abstract class TimelineTestScene : EditorClockTestScene { From 0a840a26133b76a1bbd80b9d77782843460e28d9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 23 Apr 2020 17:41:33 +0900 Subject: [PATCH 501/655] Fix mania not getting its own selection handler --- osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs index d744036b4c..cea27498c3 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs @@ -30,5 +30,7 @@ namespace osu.Game.Rulesets.Mania.Edit return base.CreateBlueprintFor(hitObject); } + + protected override SelectionHandler CreateSelectionHandler() => new ManiaSelectionHandler(); } } From 4ebb28d3e7fdc0a412643f5e4a3effce353e9ebe Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 23 Apr 2020 17:52:54 +0900 Subject: [PATCH 502/655] wip --- .../Edit/Blueprints/HoldNoteSelectionBlueprint.cs | 2 ++ osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs | 4 ++-- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 7 +++++-- osu.Game/Tests/Beatmaps/TestBeatmap.cs | 1 + 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index d569d68b59..43d43ef252 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -76,5 +76,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints } public override Quad SelectionQuad => ScreenSpaceDrawQuad; + + public override Vector2 SelectionPoint => DrawableObject.Head.ScreenSpaceDrawQuad.Centre; } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 9069a636a8..78f159b733 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.Edit var maniaBlueprint = (ManiaSelectionBlueprint)moveEvent.Blueprint; int lastColumn = maniaBlueprint.DrawableObject.HitObject.Column; - adjustOrigins(maniaBlueprint); + // adjustOrigins(maniaBlueprint); performDragMovement(moveEvent); performColumnMovement(lastColumn, moveEvent); @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Mania.Edit // When scrolling downwards the anchor position is at the bottom of the screen, however the movement event assumes the anchor is at the top of the screen. // This causes the delta to assume a positive hitobject position, and which can be corrected for by subtracting the parent height. if (scrollingInfo.Direction.Value == ScrollingDirection.Down) - delta -= moveEvent.Blueprint.Parent.DrawHeight; // todo: probably wrong + delta -= moveEvent.Blueprint.Parent.DrawHeight; // todo: definitely wrong foreach (var selectionBlueprint in SelectedBlueprints) { diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index ad16e22e5e..0823be01f8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -401,15 +401,18 @@ namespace osu.Game.Screens.Edit.Compose.Components HitObject draggedObject = movementBlueprint.HitObject; - // The final movement position, relative to screenSpaceMovementStartPosition + // The final movement position, relative to movementBlueprintOriginalPosition Vector2 movePosition = movementBlueprintOriginalPosition.Value + e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition; - (Vector2 snappedPosition, double snappedTime) = snapProvider.GetSnappedPosition(ToLocalSpace(movePosition), draggedObject.StartTime); + (Vector2 snappedPosition, _) = snapProvider.GetSnappedPosition(ToLocalSpace(movePosition), draggedObject.StartTime); // Move the hitobjects if (!selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, ToScreenSpace(snappedPosition)))) return true; + // Todo: Temp + (_, double snappedTime) = snapProvider.GetSnappedPosition(ToLocalSpace(snappedPosition), draggedObject.StartTime); + // Apply the start time at the newly snapped-to position double offset = snappedTime - draggedObject.StartTime; foreach (HitObject obj in selectionHandler.SelectedHitObjects) diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index 96e3c037a3..a7c84bf692 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -23,6 +23,7 @@ namespace osu.Game.Tests.Beatmaps HitObjects = baseBeatmap.HitObjects; BeatmapInfo.Ruleset = ruleset; + BeatmapInfo.RulesetID = ruleset.ID ?? 0; BeatmapInfo.BeatmapSet.Metadata = BeatmapInfo.Metadata; BeatmapInfo.BeatmapSet.Beatmaps = new List { BeatmapInfo }; BeatmapInfo.BeatmapSet.OnlineInfo = new BeatmapSetOnlineInfo From b471a240cc9f1510a945352937ddd6b3834fb8f7 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Thu, 23 Apr 2020 16:59:56 +0800 Subject: [PATCH 503/655] Fixed merge typo in playfield members --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index a13a818f68..d44de496f4 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Container kiaiExplosionContainer; private readonly JudgementContainer judgementContainer; private readonly ScrollingHitObjectContainer drumRollHitContainer; - internal readonly HitTarget HitTarget; + internal readonly Drawable HitTarget; private readonly ProxyContainer topLevelHitContainer; private readonly ProxyContainer barlineContainer; From a9897ba627311977186f50a9db0b97326e8351b4 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Thu, 23 Apr 2020 18:15:12 +0800 Subject: [PATCH 504/655] Moved proxy behaviour to drumroll container --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index d44de496f4..07f3fe4e01 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -195,7 +195,8 @@ namespace osu.Game.Rulesets.Taiko.UI { Name = "Top level hit objects", RelativeSizeAxes = Axes.Both, - } + }, + drumRollHitContainer.CreateProxy() }; } @@ -241,7 +242,7 @@ namespace osu.Game.Rulesets.Taiko.UI drawableHit = new DrawableFlyingCentreHit(Time.Current, isStrong); drumRollHitContainer.Add(drawableHit); - topLevelHitContainer.Add(drawableHit.CreateProxiedContent()); + } internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result) From f1ae8af5818958f67b057151ed3c58726fccfa44 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Thu, 23 Apr 2020 18:16:05 +0800 Subject: [PATCH 505/655] Removed un-needed using directives --- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs index d4d073ee94..f767403c65 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs @@ -1,11 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osu.Game.Skinning; From dded4f8176265a4bc5571856424edd9eef883943 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Thu, 23 Apr 2020 18:17:31 +0800 Subject: [PATCH 506/655] Fixed syntax warnings in Taiko playfield --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 07f3fe4e01..f4982fa7e6 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -242,7 +242,6 @@ namespace osu.Game.Rulesets.Taiko.UI drawableHit = new DrawableFlyingCentreHit(Time.Current, isStrong); drumRollHitContainer.Add(drawableHit); - } internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result) @@ -289,8 +288,9 @@ namespace osu.Game.Rulesets.Taiko.UI if (Time.Elapsed < 0) { - foreach (DrawableHit taikoHit in drumRollHitContainer.Objects) + foreach (var o in drumRollHitContainer.Objects) { + var taikoHit = (DrawableHit)o; taikoHit.RemoveProxiedContent(); drumRollHitContainer.Remove(taikoHit); } From c059588a09a761d6c92304d71b77218c836c915f Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Thu, 23 Apr 2020 18:26:40 +0800 Subject: [PATCH 507/655] Removed un-needed unproxy method --- .../Objects/Drawables/DrawableTaikoHitObject.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index e0ff236297..1be04f1760 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -76,8 +76,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// public Drawable CreateProxiedContent() => proxiedContent.CreateProxy(); - public void RemoveProxiedContent() => UnproxyContent(); - public abstract bool OnPressed(TaikoAction action); public virtual void OnReleased(TaikoAction action) From 1fa3764a1db26d70a124437d3c6ae6e70c780674 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Thu, 23 Apr 2020 18:26:53 +0800 Subject: [PATCH 508/655] Cleaned up Update method in Taiko Playfield --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index f4982fa7e6..3d371d5260 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -286,14 +286,12 @@ namespace osu.Game.Rulesets.Taiko.UI { base.Update(); + // When rewinding, make sure to remove any auxilliary hit notes that were + // spawned and played during a drumroll. if (Time.Elapsed < 0) { foreach (var o in drumRollHitContainer.Objects) - { - var taikoHit = (DrawableHit)o; - taikoHit.RemoveProxiedContent(); - drumRollHitContainer.Remove(taikoHit); - } + drumRollHitContainer.Remove(o); } } From 029d15f2a2f19ed2f88bb61d58af947df3411702 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Thu, 23 Apr 2020 20:14:39 +0800 Subject: [PATCH 509/655] Fixed syntax warning for playfield children --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 3d371d5260..66894fe883 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Taiko.UI public TaikoPlayfield(ControlPointInfo controlPoints) { - InternalChildren = new Drawable[] + InternalChildren = new[] { backgroundContainer = new Container { From 085b6ae25f0f6bd4c01b4077090d01be35ef3b67 Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 23 Apr 2020 20:24:03 +0200 Subject: [PATCH 510/655] Add background video for showcase scene (Tournament Client) --- .../Screens/Showcase/ShowcaseScreen.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs index d809dfc994..1bf67fe607 100644 --- a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs +++ b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Tournament.Components; namespace osu.Game.Tournament.Screens.Showcase { @@ -10,7 +12,14 @@ namespace osu.Game.Tournament.Screens.Showcase [BackgroundDependencyLoader] private void load() { - AddInternal(new TournamentLogo()); + AddRangeInternal(new Drawable[] { + new TournamentLogo(), + new TourneyVideo("showcase") + { + Loop = true, + RelativeSizeAxes = Axes.Both, + } + }); } } } From 28dcfe867c3afc866791d2d43934ae0a7626586d Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 23 Apr 2020 21:09:12 +0200 Subject: [PATCH 511/655] Add Chroma keying to the background of the showcase video. --- .../Screens/Showcase/ShowcaseScreen.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs index 1bf67fe607..85cf8d2e1f 100644 --- a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs +++ b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs @@ -4,11 +4,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Tournament.Components; +using osu.Framework.Graphics.Shapes; +using osuTK.Graphics; namespace osu.Game.Tournament.Screens.Showcase { - public class ShowcaseScreen : BeatmapInfoScreen + public class ShowcaseScreen : BeatmapInfoScreen, IProvideVideo { + private Box chroma; [BackgroundDependencyLoader] private void load() { @@ -18,6 +21,16 @@ namespace osu.Game.Tournament.Screens.Showcase { Loop = true, RelativeSizeAxes = Axes.Both, + }, + chroma = new Box + { + // chroma key area for stable gameplay + Name = "chroma", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Height = 695, + Width = 1366, + Colour = new Color4(0, 255, 0, 255), } }); } From dba737105ee9d74050d55fdd61e525a4cc388973 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Apr 2020 08:57:01 +0900 Subject: [PATCH 512/655] Update test scene for dynamic compilation --- .../Skinning/TestSceneDrawableBarLine.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs index e72f42a0b4..58d69fc32a 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs @@ -12,6 +12,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Skinning; using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; @@ -24,6 +25,8 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(DrawableBarLine), + typeof(LegacyBarLine), + typeof(BarLine), }).ToList(); [Cached(typeof(IScrollingInfo))] From 1c13fa6c6199ed5a51d985f140b209fb804ddd4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Apr 2020 10:27:32 +0900 Subject: [PATCH 513/655] Fix editor crashing when entering with no beatmap selected --- .../Edit/Compose/Components/Timeline/Timeline.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 1cb4f737c1..8e6b3d5424 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -60,9 +60,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline waveform.Waveform = b.NewValue.Waveform; track = b.NewValue.Track; - MaxZoom = getZoomLevelForVisibleMilliseconds(500); - MinZoom = getZoomLevelForVisibleMilliseconds(10000); - Zoom = getZoomLevelForVisibleMilliseconds(2000); + if (track.Length > 0) + { + MaxZoom = getZoomLevelForVisibleMilliseconds(500); + MinZoom = getZoomLevelForVisibleMilliseconds(10000); + Zoom = getZoomLevelForVisibleMilliseconds(2000); + } }, true); } @@ -135,7 +138,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void scrollToTrackTime() { - if (!track.IsLoaded) + if (!track.IsLoaded || track.Length == 0) return; ScrollTo((float)(adjustableClock.CurrentTime / track.Length) * Content.DrawWidth, false); From ca692f30e66fabf102579803561c96ef2d176d9b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 24 Apr 2020 12:27:56 +0900 Subject: [PATCH 514/655] Do full placement on mouse down --- .../Blueprints/HitCircles/HitCirclePlacementBlueprint.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs index 2f400160b8..dad199715e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs @@ -33,19 +33,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles { if (e.Button == MouseButton.Left) { - BeginPlacement(); + EndPlacement(true); return true; } return base.OnMouseDown(e); } - protected override void OnMouseUp(MouseUpEvent e) - { - if (e.Button == MouseButton.Left) - EndPlacement(true); - } - public override void UpdatePosition(Vector2 screenSpacePosition) => HitObject.Position = ToLocalSpace(screenSpacePosition); } } From 2b0deec491f5260a3c68763cfaccf45384ec32a0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 24 Apr 2020 13:20:41 +0900 Subject: [PATCH 515/655] Finish note placement on mouse down --- .../Edit/Blueprints/HoldNotePlacementBlueprint.cs | 7 +++++++ .../Edit/Blueprints/ManiaPlacementBlueprint.cs | 6 ------ .../Edit/Blueprints/NotePlacementBlueprint.cs | 12 +++++++----- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index 7bbde400ea..b3dd392202 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Graphics; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; using osuTK; @@ -46,6 +47,12 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints bodyPiece.Height = (bottomPosition - topPosition).Y; } + protected override void OnMouseUp(MouseUpEvent e) + { + base.OnMouseUp(e); + EndPlacement(true); + } + private double originalStartTime; public override void UpdatePosition(Vector2 screenSpacePosition) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index f228daa5e3..400abb6380 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -54,12 +54,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints return true; } - protected override void OnMouseUp(MouseUpEvent e) - { - EndPlacement(true); - base.OnMouseUp(e); - } - public override void UpdatePosition(Vector2 screenSpacePosition) { if (!PlacementActive) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs index 888ce695c2..2b7b383dbe 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs @@ -2,9 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; -using osuTK; namespace osu.Game.Rulesets.Mania.Edit.Blueprints { @@ -28,12 +28,14 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints Position = SnappedMousePosition; } - public override void UpdatePosition(Vector2 screenSpacePosition) + protected override bool OnMouseDown(MouseDownEvent e) { - base.UpdatePosition(screenSpacePosition); + base.OnMouseDown(e); - // Continue updating the position until placement is finished on mouse up. - BeginPlacement(TimeAt(screenSpacePosition), PlacementActive); + // Place the note immediately. + EndPlacement(true); + + return true; } } } From abb687286be341b609ab92705f012f5c9a6a4496 Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 23 Apr 2020 22:34:00 -0700 Subject: [PATCH 516/655] Fix score multiplier being cut off in mod select at higher ui scales --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index e9b3598625..b94f5cb570 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -103,7 +103,7 @@ namespace osu.Game.Overlays.Mods { new Dimension(GridSizeMode.Absolute, 90), new Dimension(GridSizeMode.Distributed), - new Dimension(GridSizeMode.Absolute, 70), + new Dimension(GridSizeMode.AutoSize), }, Content = new[] { @@ -197,7 +197,8 @@ namespace osu.Game.Overlays.Mods // Footer new Container { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, Children = new Drawable[] @@ -215,7 +216,6 @@ namespace osu.Game.Overlays.Mods AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Width = content_width, - Direction = FillDirection.Horizontal, Padding = new MarginPadding { Vertical = 15, From 118db03b56ec35eba67b900b930a73d61838e191 Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 23 Apr 2020 22:41:38 -0700 Subject: [PATCH 517/655] Fix vertical spacing and score multiplier splitting apart Also cleans up margin and its hacks (alignment done with anchor/origin now). --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 73 +++++++++++----------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index b94f5cb570..6ab72bc02a 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -57,6 +57,8 @@ namespace osu.Game.Overlays.Mods protected Color4 HighMultiplierColour; private const float content_width = 0.8f; + private const float footer_button_spacing = 20; + private readonly FillFlowContainer footerContainer; private SampleChannel sampleOn, sampleOff; @@ -216,6 +218,7 @@ namespace osu.Game.Overlays.Mods AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Width = content_width, + Spacing = new Vector2(footer_button_spacing, footer_button_spacing / 2), Padding = new MarginPadding { Vertical = 15, @@ -228,10 +231,8 @@ namespace osu.Game.Overlays.Mods Width = 180, Text = "Deselect All", Action = DeselectAll, - Margin = new MarginPadding - { - Right = 20 - } + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, }, CustomiseButton = new TriangleButton { @@ -239,49 +240,47 @@ namespace osu.Game.Overlays.Mods Text = "Customisation", Action = () => ModSettingsContainer.Alpha = ModSettingsContainer.Alpha == 1 ? 0 : 1, Enabled = { Value = false }, - Margin = new MarginPadding - { - Right = 20 - } + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, }, CloseButton = new TriangleButton { Width = 180, Text = "Close", Action = Hide, - Margin = new MarginPadding - { - Right = 20 - } + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, }, - new OsuSpriteText + new FillFlowContainer { - Text = @"Score Multiplier:", - Font = OsuFont.GetFont(size: 30), - Margin = new MarginPadding + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(footer_button_spacing / 2, 0), + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Children = new Drawable[] { - Top = 5, - Right = 10 - } + new OsuSpriteText + { + Text = @"Score Multiplier:", + Font = OsuFont.GetFont(size: 30), + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + }, + MultiplierLabel = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold, fixedWidth: true), + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + }, + UnrankedLabel = new OsuSpriteText + { + Text = @"(Unranked)", + Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold), + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + }, + }, }, - MultiplierLabel = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold), - Margin = new MarginPadding - { - Top = 5 - } - }, - UnrankedLabel = new OsuSpriteText - { - Text = @"(Unranked)", - Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold), - Margin = new MarginPadding - { - Top = 5, - Left = 10 - } - } } } }, From 0f6ec274f9547881484b73559e6d27a0e709eaad Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 23 Apr 2020 22:44:17 -0700 Subject: [PATCH 518/655] Add transitions to footer when flowing to another row --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 6ab72bc02a..a7b7e50422 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -219,6 +219,8 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.X, Width = content_width, Spacing = new Vector2(footer_button_spacing, footer_button_spacing / 2), + LayoutDuration = 100, + LayoutEasing = Easing.OutQuint, Padding = new MarginPadding { Vertical = 15, From 05b3db0147aadad9a8114d9f74f4cfdfd045aa14 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Apr 2020 16:56:18 +0900 Subject: [PATCH 519/655] Remove masking --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 8402ebb4c4..6f7d4a1854 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -55,7 +55,6 @@ namespace osu.Game.Rulesets.Taiko.UI Name = "Right area", RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, - Masking = true, Children = new Drawable[] { new Container From cbcd915ec8489caf449c02f42f3e3abf801a69fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Apr 2020 18:18:10 +0900 Subject: [PATCH 520/655] Fix crash on switching comments page at an inopportune time --- osu.Game/Overlays/Comments/CommentsContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 591a9dc86e..e7bfeaf968 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -153,7 +153,7 @@ namespace osu.Game.Overlays.Comments request?.Cancel(); loadCancellation?.Cancel(); request = new GetCommentsRequest(id.Value, type.Value, Sort.Value, currentPage++, 0); - request.Success += onSuccess; + request.Success += res => Schedule(() => onSuccess(res)); api.PerformAsync(request); } From 39a593120cbf84493263f6b1ec7cc99a8f7c390c Mon Sep 17 00:00:00 2001 From: Shivam Date: Fri, 24 Apr 2020 17:00:35 +0200 Subject: [PATCH 521/655] Fixed CodeInspect errors --- .../Screens/Showcase/ShowcaseScreen.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs index 85cf8d2e1f..e48ec63d01 100644 --- a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs +++ b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs @@ -11,26 +11,26 @@ namespace osu.Game.Tournament.Screens.Showcase { public class ShowcaseScreen : BeatmapInfoScreen, IProvideVideo { - private Box chroma; [BackgroundDependencyLoader] private void load() { - AddRangeInternal(new Drawable[] { + AddRangeInternal(new Drawable[] + { new TournamentLogo(), new TourneyVideo("showcase") { Loop = true, RelativeSizeAxes = Axes.Both, }, - chroma = new Box - { - // chroma key area for stable gameplay - Name = "chroma", - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Height = 695, - Width = 1366, - Colour = new Color4(0, 255, 0, 255), + new Box + { + // chroma key area for stable gameplay + Name = "chroma", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Height = 695, + Width = 1366, + Colour = new Color4(0, 255, 0, 255), } }); } From 2be3a8184d0c2e3726a29683d3c18cd2d96ca9fe Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Sat, 25 Apr 2020 00:15:37 +0800 Subject: [PATCH 522/655] Removed modifications to drum roll object --- .../Objects/Drawables/DrawableDrumRoll.cs | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index e416c23c30..5e731e5ad6 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -36,14 +36,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private Color4 colourIdle; private Color4 colourEngaged; - private bool judgingStarted; - - /// - /// A handler action for when the drumroll has been hit, - /// regardless of any judgement. - /// - public Action OnHit; - public DrawableDrumRoll(DrumRoll drumRoll) : base(drumRoll) { @@ -103,27 +95,15 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.DrumRollBody), _ => new ElongatedCirclePiece()); - public override bool OnPressed(TaikoAction action) - { - if (judgingStarted) - OnHit.Invoke(action, HitObject.IsStrong); - - return false; - } + public override bool OnPressed(TaikoAction action) => false; private void onNewResult(DrawableHitObject obj, JudgementResult result) { if (!(obj is DrawableDrumRollTick)) return; - DrawableDrumRollTick drumRollTick = (DrawableDrumRollTick)obj; - if (result.Type > HitResult.Miss) - { - OnHit.Invoke(drumRollTick.JudgedAction, HitObject.IsStrong); - judgingStarted = true; rollingHits++; - } else rollingHits--; From 477fe72fcf8d4966ea25f7ee476305b0a2019112 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Sat, 25 Apr 2020 00:15:59 +0800 Subject: [PATCH 523/655] Changed note playback to happen on new result --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 66894fe883..a03010af00 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -12,6 +12,8 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.Taiko.Objects.Drawables; @@ -222,24 +224,23 @@ namespace osu.Game.Rulesets.Taiko.UI barlineContainer.Add(barline.CreateProxy()); break; - case DrawableDrumRoll drumRoll: - drumRoll.OnHit += onDrumrollArbitraryHit; - break; - case DrawableTaikoHitObject taikoObject: topLevelHitContainer.Add(taikoObject.CreateProxiedContent()); break; } } - private void onDrumrollArbitraryHit(TaikoAction action, bool isStrong) + private void playDrumrollHit(DrawableDrumRollTick drumrollTick) { - DrawableHit drawableHit; + TaikoAction action = drumrollTick.JudgedAction; + bool isStrong = drumrollTick.HitObject.IsStrong; + double time = drumrollTick.HitObject.GetEndTime(); + DrawableHit drawableHit; if (action == TaikoAction.LeftRim || action == TaikoAction.RightRim) - drawableHit = new DrawableFlyingRimHit(Time.Current, isStrong); + drawableHit = new DrawableFlyingRimHit(time, isStrong); else - drawableHit = new DrawableFlyingCentreHit(Time.Current, isStrong); + drawableHit = new DrawableFlyingCentreHit(time, isStrong); drumRollHitContainer.Add(drawableHit); } @@ -249,6 +250,9 @@ namespace osu.Game.Rulesets.Taiko.UI if (!DisplayJudgements.Value) return; + if ((judgedObject is DrawableDrumRollTick) && result.Type != HitResult.Miss) + playDrumrollHit((DrawableDrumRollTick)judgedObject); + if (!judgedObject.DisplayResult) return; From 4b60be87b591a2bb6d8b1b5378790e7a0509e478 Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 24 Apr 2020 16:34:41 -0700 Subject: [PATCH 524/655] Move unranked label under multiplier number to avoid width changes --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 30 +++++++++++++++------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index a7b7e50422..36d21c8b46 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -268,18 +268,30 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, }, - MultiplierLabel = new OsuSpriteText + new FillFlowContainer { - Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold, fixedWidth: true), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - }, - UnrankedLabel = new OsuSpriteText - { - Text = @"(Unranked)", - Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold), + AutoSizeAxes = Axes.Both, Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, + Direction = FillDirection.Vertical, + LayoutDuration = 100, + LayoutEasing = Easing.OutQuint, + Children = new Drawable[] + { + MultiplierLabel = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 25, weight: FontWeight.Bold, fixedWidth: true), + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + }, + UnrankedLabel = new OsuSpriteText + { + Text = @"(Unranked)", + Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold), + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + }, + } }, }, }, From 3cc0b21eaef64e58730d8a7732230290a1dc10b1 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Sat, 25 Apr 2020 13:18:02 +0800 Subject: [PATCH 525/655] Added more smart checking to removing rewound drumroll hits --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index a03010af00..cae6b1c80b 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -295,7 +295,10 @@ namespace osu.Game.Rulesets.Taiko.UI if (Time.Elapsed < 0) { foreach (var o in drumRollHitContainer.Objects) - drumRollHitContainer.Remove(o); + { + if (o.HitObject.StartTime >= Time.Current) + drumRollHitContainer.Remove(o); + } } } From c1c930c472208bd5b0fddde8389863863c06de93 Mon Sep 17 00:00:00 2001 From: Tim Oliver Date: Sat, 25 Apr 2020 13:47:20 +0800 Subject: [PATCH 526/655] Fixed linting warnings --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 203d37006a..d1d2571ec7 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load(OsuColour colours) { - InternalChildren = new Drawable[] + InternalChildren = new[] { new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundRight), _ => new PlayfieldBackgroundRight()), rightArea = new Container diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index b5a8f1604c..7727f25967 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -240,7 +240,7 @@ namespace osu.Game.Beatmaps.Formats } else { - if (hitObject is IHasEndTime _) + if (hitObject is IHasEndTime) addEndTimeData(writer, hitObject); writer.Write(getSampleBank(hitObject.Samples)); From 37cc1ed5a244cae4d2cddbf2c8dc54e9c2ea5c34 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 25 Apr 2020 09:45:11 +0300 Subject: [PATCH 527/655] Fix potential null reference while hiding toolbar --- osu.Game/Overlays/Toolbar/Toolbar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 227347112c..bac73c4379 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -148,7 +148,7 @@ namespace osu.Game.Overlays.Toolbar protected override void PopOut() { - userButton?.StateContainer.Hide(); + userButton.StateContainer?.Hide(); this.MoveToY(-DrawSize.Y, transition_time, Easing.OutQuint); this.FadeOut(transition_time); From 1953c8fc107d87a9941597ec94235a180f05b339 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 25 Apr 2020 09:53:09 +0300 Subject: [PATCH 528/655] Fix ruleset selector not receiving key input on toolbar absence --- osu.Game/Overlays/Toolbar/Toolbar.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index bac73c4379..301747acbc 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -33,6 +33,10 @@ namespace osu.Game.Overlays.Toolbar private readonly Bindable overlayActivationMode = new Bindable(OverlayActivation.All); + // Required for toolbar components that need to listen for key input + // to invoke specific actions while toolbar is in hidden state. + public override bool PropagateNonPositionalInputSubTree => true; + public Toolbar() { RelativeSizeAxes = Axes.X; From f0ebbb1807aaf293751bce16c6c08c7bae15ea6d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 25 Apr 2020 09:54:37 +0300 Subject: [PATCH 529/655] Rewrite toolbar test scene and add test cases --- .../Visual/Menus/TestSceneToolbar.cs | 62 ++++++++++++++++--- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs index f24589ed35..8fbbc8ebd8 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs @@ -5,13 +5,17 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; using osu.Game.Overlays.Toolbar; +using osu.Game.Rulesets; +using osuTK.Input; namespace osu.Game.Tests.Visual.Menus { [TestFixture] - public class TestSceneToolbar : OsuTestScene + public class TestSceneToolbar : OsuManualInputManagerTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -21,24 +25,62 @@ namespace osu.Game.Tests.Visual.Menus typeof(ToolbarNotificationButton), }; - public TestSceneToolbar() + private Toolbar toolbar; + + [Resolved] + private RulesetStore rulesets { get; set; } + + [SetUp] + public void SetUp() => Schedule(() => + { + Child = toolbar = new Toolbar { State = { Value = Visibility.Visible } }; + }); + + [Test] + public void TestNotificationCounter() { - var toolbar = new Toolbar { State = { Value = Visibility.Visible } }; ToolbarNotificationButton notificationButton = null; - AddStep("create toolbar", () => - { - Add(toolbar); - notificationButton = toolbar.Children.OfType().Last().Children.OfType().First(); - }); - - void setNotifications(int count) => AddStep($"set notification count to {count}", () => notificationButton.NotificationCount.Value = count); + AddStep("retrieve notification button", () => notificationButton = toolbar.ChildrenOfType().Single()); setNotifications(1); setNotifications(2); setNotifications(3); setNotifications(0); setNotifications(144); + + void setNotifications(int count) + => AddStep($"set notification count to {count}", + () => notificationButton.NotificationCount.Value = count); + } + + [TestCase(false)] + [TestCase(true)] + public void TestRulesetSwitchingShortcut(bool toolbarHidden) + { + ToolbarRulesetSelector rulesetSelector = null; + + if (toolbarHidden) + AddStep("hide toolbar", () => toolbar.Hide()); + + AddStep("retrieve ruleset selector", () => rulesetSelector = toolbar.ChildrenOfType().Single()); + + for (int i = 0; i < 4; i++) + { + var expected = rulesets.AvailableRulesets.ElementAt(i); + var numberKey = Key.Number1 + i; + + AddStep($"switch to ruleset {i} via shortcut", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.PressKey(numberKey); + + InputManager.ReleaseKey(Key.ControlLeft); + InputManager.ReleaseKey(numberKey); + }); + + AddUntilStep("ruleset switched", () => rulesetSelector.Current.Value.Equals(expected)); + } } } } From b50e8471d20fd113db31027692d472524b0d02f5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 25 Apr 2020 18:23:09 +0900 Subject: [PATCH 530/655] Reword comment --- osu.Game/Overlays/Toolbar/Toolbar.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 301747acbc..1b748cb672 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -33,8 +33,7 @@ namespace osu.Game.Overlays.Toolbar private readonly Bindable overlayActivationMode = new Bindable(OverlayActivation.All); - // Required for toolbar components that need to listen for key input - // to invoke specific actions while toolbar is in hidden state. + // Toolbar components like RulesetSelector should receive keyboard input events even when the toolbar is hidden. public override bool PropagateNonPositionalInputSubTree => true; public Toolbar() From a756486a4d74073f7e131ff47235ba91d892649f Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 25 Apr 2020 20:35:46 +0200 Subject: [PATCH 531/655] Make settings section icons actual drawables. --- osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs | 8 +++++++- osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs | 8 +++++++- osu.Game/Overlays/Settings/Sections/AudioSection.cs | 8 ++++++-- osu.Game/Overlays/Settings/Sections/DebugSection.cs | 7 ++++++- osu.Game/Overlays/Settings/Sections/GameplaySection.cs | 7 ++++++- osu.Game/Overlays/Settings/Sections/GeneralSection.cs | 7 ++++++- osu.Game/Overlays/Settings/Sections/GraphicsSection.cs | 7 ++++++- osu.Game/Overlays/Settings/Sections/InputSection.cs | 7 ++++++- osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs | 7 ++++++- osu.Game/Overlays/Settings/Sections/OnlineSection.cs | 7 ++++++- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 6 +++++- osu.Game/Overlays/Settings/SettingsSection.cs | 3 +-- osu.Game/Overlays/Settings/SidebarButton.cs | 7 ++++--- 13 files changed, 72 insertions(+), 17 deletions(-) diff --git a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs index 56e93b6a1e..214da69dfb 100644 --- a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs +++ b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Input.Bindings; using osu.Game.Overlays.Settings; @@ -9,7 +10,12 @@ namespace osu.Game.Overlays.KeyBinding { public class GlobalKeyBindingsSection : SettingsSection { - public override IconUsage Icon => FontAwesome.Solid.Globe; + public override Drawable CreateIcon() => new SpriteIcon + { + RelativeSizeAxes = Axes.Both, + Icon = FontAwesome.Solid.Globe + }; + public override string Header => "Global"; public GlobalKeyBindingsSection(GlobalActionContainer manager) diff --git a/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs b/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs index 1f4042c57c..b2814907eb 100644 --- a/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs +++ b/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Overlays.Settings; @@ -10,7 +11,12 @@ namespace osu.Game.Overlays.KeyBinding { public class RulesetBindingsSection : SettingsSection { - public override IconUsage Icon => (ruleset.CreateInstance().CreateIcon() as SpriteIcon)?.Icon ?? OsuIcon.Hot; + public override Drawable CreateIcon() => ruleset?.CreateInstance()?.CreateIcon() ?? new SpriteIcon + { + RelativeSizeAxes = Axes.Both, + Icon = OsuIcon.Hot + }; + public override string Header => ruleset.Name; private readonly RulesetInfo ruleset; diff --git a/osu.Game/Overlays/Settings/Sections/AudioSection.cs b/osu.Game/Overlays/Settings/Sections/AudioSection.cs index b18488b616..5f55b268d0 100644 --- a/osu.Game/Overlays/Settings/Sections/AudioSection.cs +++ b/osu.Game/Overlays/Settings/Sections/AudioSection.cs @@ -13,9 +13,13 @@ namespace osu.Game.Overlays.Settings.Sections { public override string Header => "Audio"; - public override IEnumerable FilterTerms => base.FilterTerms.Concat(new[] { "sound" }); + public override Drawable CreateIcon() => new SpriteIcon + { + RelativeSizeAxes = Axes.Both, + Icon = FontAwesome.Solid.VolumeUp + }; - public override IconUsage Icon => FontAwesome.Solid.VolumeUp; + public override IEnumerable FilterTerms => base.FilterTerms.Concat(new[] { "sound" }); public AudioSection() { diff --git a/osu.Game/Overlays/Settings/Sections/DebugSection.cs b/osu.Game/Overlays/Settings/Sections/DebugSection.cs index f62de0b243..a3ca6cf6de 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSection.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSection.cs @@ -10,7 +10,12 @@ namespace osu.Game.Overlays.Settings.Sections public class DebugSection : SettingsSection { public override string Header => "Debug"; - public override IconUsage Icon => FontAwesome.Solid.Bug; + + public override Drawable CreateIcon() => new SpriteIcon + { + RelativeSizeAxes = Axes.Both, + Icon = FontAwesome.Solid.Bug + }; public DebugSection() { diff --git a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs index 97d9d3c697..38f68b97ba 100644 --- a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs +++ b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs @@ -13,7 +13,12 @@ namespace osu.Game.Overlays.Settings.Sections public class GameplaySection : SettingsSection { public override string Header => "Gameplay"; - public override IconUsage Icon => FontAwesome.Regular.Circle; + + public override Drawable CreateIcon() => new SpriteIcon + { + RelativeSizeAxes = Axes.Both, + Icon = FontAwesome.Regular.Circle + }; public GameplaySection() { diff --git a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs index d9947f16cc..bc87281f20 100644 --- a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs @@ -10,7 +10,12 @@ namespace osu.Game.Overlays.Settings.Sections public class GeneralSection : SettingsSection { public override string Header => "General"; - public override IconUsage Icon => FontAwesome.Solid.Cog; + + public override Drawable CreateIcon() => new SpriteIcon + { + RelativeSizeAxes = Axes.Both, + Icon = FontAwesome.Solid.Cog + }; public GeneralSection() { diff --git a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs index 89caa3dc8f..3ac6686e36 100644 --- a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs @@ -10,7 +10,12 @@ namespace osu.Game.Overlays.Settings.Sections public class GraphicsSection : SettingsSection { public override string Header => "Graphics"; - public override IconUsage Icon => FontAwesome.Solid.Laptop; + + public override Drawable CreateIcon() => new SpriteIcon + { + RelativeSizeAxes = Axes.Both, + Icon = FontAwesome.Solid.Laptop + }; public GraphicsSection() { diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs index 2a348b4e03..2c395d325a 100644 --- a/osu.Game/Overlays/Settings/Sections/InputSection.cs +++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs @@ -10,7 +10,12 @@ namespace osu.Game.Overlays.Settings.Sections public class InputSection : SettingsSection { public override string Header => "Input"; - public override IconUsage Icon => FontAwesome.Regular.Keyboard; + + public override Drawable CreateIcon() => new SpriteIcon + { + RelativeSizeAxes = Axes.Both, + Icon = FontAwesome.Solid.Keyboard + }; public InputSection(KeyBindingPanel keyConfig) { diff --git a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs index 0f3acd5b7f..69f00d1549 100644 --- a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs +++ b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs @@ -11,7 +11,12 @@ namespace osu.Game.Overlays.Settings.Sections public class MaintenanceSection : SettingsSection { public override string Header => "Maintenance"; - public override IconUsage Icon => FontAwesome.Solid.Wrench; + + public override Drawable CreateIcon() => new SpriteIcon + { + RelativeSizeAxes = Axes.Both, + Icon = FontAwesome.Solid.Wrench + }; public MaintenanceSection() { diff --git a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs index 80295690c0..c22174f050 100644 --- a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs +++ b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs @@ -10,7 +10,12 @@ namespace osu.Game.Overlays.Settings.Sections public class OnlineSection : SettingsSection { public override string Header => "Online"; - public override IconUsage Icon => FontAwesome.Solid.GlobeAsia; + + public override Drawable CreateIcon() => new SpriteIcon + { + RelativeSizeAxes = Axes.Both, + Icon = FontAwesome.Solid.GlobeAsia + }; public OnlineSection() { diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index b229014c84..3edfc4346f 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -19,7 +19,11 @@ namespace osu.Game.Overlays.Settings.Sections public override string Header => "Skin"; - public override IconUsage Icon => FontAwesome.Solid.PaintBrush; + public override Drawable CreateIcon() => new SpriteIcon + { + RelativeSizeAxes = Axes.Both, + Icon = FontAwesome.Solid.PaintBrush + }; private readonly Bindable dropdownBindable = new Bindable { Default = SkinInfo.Default }; private readonly Bindable configBindable = new Bindable(); diff --git a/osu.Game/Overlays/Settings/SettingsSection.cs b/osu.Game/Overlays/Settings/SettingsSection.cs index be3696029e..97e4ba9da7 100644 --- a/osu.Game/Overlays/Settings/SettingsSection.cs +++ b/osu.Game/Overlays/Settings/SettingsSection.cs @@ -11,7 +11,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using System.Collections.Generic; using System.Linq; -using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Settings { @@ -20,7 +19,7 @@ namespace osu.Game.Overlays.Settings protected FillFlowContainer FlowContent; protected override Container Content => FlowContent; - public abstract IconUsage Icon { get; } + public abstract Drawable CreateIcon(); public abstract string Header { get; } public IEnumerable FilterableChildren => Children.OfType(); diff --git a/osu.Game/Overlays/Settings/SidebarButton.cs b/osu.Game/Overlays/Settings/SidebarButton.cs index 68836bc6b3..30a53b351d 100644 --- a/osu.Game/Overlays/Settings/SidebarButton.cs +++ b/osu.Game/Overlays/Settings/SidebarButton.cs @@ -11,12 +11,13 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Settings { public class SidebarButton : OsuButton { - private readonly SpriteIcon drawableIcon; + private readonly ConstrainedIconContainer iconContainer; private readonly SpriteText headerText; private readonly Box selectionIndicator; private readonly Container text; @@ -30,7 +31,7 @@ namespace osu.Game.Overlays.Settings { section = value; headerText.Text = value.Header; - drawableIcon.Icon = value.Icon; + iconContainer.Icon = value.CreateIcon(); } } @@ -78,7 +79,7 @@ namespace osu.Game.Overlays.Settings Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, - drawableIcon = new SpriteIcon + iconContainer = new ConstrainedIconContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, From 801968ed515e32990af009c4bfa2ee4a04c97b5e Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 26 Apr 2020 21:17:40 +0200 Subject: [PATCH 532/655] Remove un-needed RelativeSizeAxes specifications. --- osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs | 1 - osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs | 1 - osu.Game/Overlays/Settings/Sections/AudioSection.cs | 1 - osu.Game/Overlays/Settings/Sections/DebugSection.cs | 1 - osu.Game/Overlays/Settings/Sections/GraphicsSection.cs | 1 - osu.Game/Overlays/Settings/Sections/InputSection.cs | 1 - osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs | 1 - osu.Game/Overlays/Settings/Sections/OnlineSection.cs | 1 - osu.Game/Overlays/Settings/Sections/SkinSection.cs | 1 - 9 files changed, 9 deletions(-) diff --git a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs index 214da69dfb..5b44c486a3 100644 --- a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs +++ b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs @@ -12,7 +12,6 @@ namespace osu.Game.Overlays.KeyBinding { public override Drawable CreateIcon() => new SpriteIcon { - RelativeSizeAxes = Axes.Both, Icon = FontAwesome.Solid.Globe }; diff --git a/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs b/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs index b2814907eb..332fb6c8fc 100644 --- a/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs +++ b/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs @@ -13,7 +13,6 @@ namespace osu.Game.Overlays.KeyBinding { public override Drawable CreateIcon() => ruleset?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { - RelativeSizeAxes = Axes.Both, Icon = OsuIcon.Hot }; diff --git a/osu.Game/Overlays/Settings/Sections/AudioSection.cs b/osu.Game/Overlays/Settings/Sections/AudioSection.cs index 5f55b268d0..69538358f1 100644 --- a/osu.Game/Overlays/Settings/Sections/AudioSection.cs +++ b/osu.Game/Overlays/Settings/Sections/AudioSection.cs @@ -15,7 +15,6 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - RelativeSizeAxes = Axes.Both, Icon = FontAwesome.Solid.VolumeUp }; diff --git a/osu.Game/Overlays/Settings/Sections/DebugSection.cs b/osu.Game/Overlays/Settings/Sections/DebugSection.cs index a3ca6cf6de..44d4088972 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSection.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSection.cs @@ -13,7 +13,6 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - RelativeSizeAxes = Axes.Both, Icon = FontAwesome.Solid.Bug }; diff --git a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs index 3ac6686e36..c1b4b0bbcb 100644 --- a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs @@ -13,7 +13,6 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - RelativeSizeAxes = Axes.Both, Icon = FontAwesome.Solid.Laptop }; diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs index 2c395d325a..b43453f53d 100644 --- a/osu.Game/Overlays/Settings/Sections/InputSection.cs +++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs @@ -13,7 +13,6 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - RelativeSizeAxes = Axes.Both, Icon = FontAwesome.Solid.Keyboard }; diff --git a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs index 69f00d1549..73c88b8e71 100644 --- a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs +++ b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs @@ -14,7 +14,6 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - RelativeSizeAxes = Axes.Both, Icon = FontAwesome.Solid.Wrench }; diff --git a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs index c22174f050..150cddb388 100644 --- a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs +++ b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs @@ -13,7 +13,6 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - RelativeSizeAxes = Axes.Both, Icon = FontAwesome.Solid.GlobeAsia }; diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 3edfc4346f..75c8db1612 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -21,7 +21,6 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - RelativeSizeAxes = Axes.Both, Icon = FontAwesome.Solid.PaintBrush }; From a436f8e6d42165e5bf392fb2cf2be3a9378e72b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 26 Apr 2020 22:54:01 +0200 Subject: [PATCH 533/655] Trim other leftover RelativeSizeAxes --- osu.Game/Overlays/Settings/Sections/GameplaySection.cs | 1 - osu.Game/Overlays/Settings/Sections/GeneralSection.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs index 38f68b97ba..aca507f20a 100644 --- a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs +++ b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs @@ -16,7 +16,6 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - RelativeSizeAxes = Axes.Both, Icon = FontAwesome.Regular.Circle }; diff --git a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs index bc87281f20..fefc3fe6a7 100644 --- a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs @@ -13,7 +13,6 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - RelativeSizeAxes = Axes.Both, Icon = FontAwesome.Solid.Cog }; From 3c1730d0caaa933263b950a20ba1aadbde16522a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 26 Apr 2020 23:59:24 +0200 Subject: [PATCH 534/655] Expose SongBar's height --- osu.Game.Tournament/Components/SongBar.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index 8d766ec9ba..e86fd890c1 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tournament.Components { private BeatmapInfo beatmap; - private const float height = 145; + public const float HEIGHT = 145 / 2f; [Resolved] private IBindable ruleset { get; set; } @@ -157,7 +157,7 @@ namespace osu.Game.Tournament.Components new Container { RelativeSizeAxes = Axes.X, - Height = height / 2, + Height = HEIGHT, Width = 0.5f, Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, @@ -229,7 +229,7 @@ namespace osu.Game.Tournament.Components { RelativeSizeAxes = Axes.X, Width = 0.5f, - Height = height / 2, + Height = HEIGHT, Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, } From b9e0fed4679d5b7c6193cb1c48ef8659eaa59f3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 27 Apr 2020 00:02:58 +0200 Subject: [PATCH 535/655] Use SongBar height instead of hard-coded dimensions --- .../Screens/Showcase/ShowcaseScreen.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs index e48ec63d01..aea531e88d 100644 --- a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs +++ b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Tournament.Components; using osu.Framework.Graphics.Shapes; using osuTK.Graphics; @@ -22,15 +23,19 @@ namespace osu.Game.Tournament.Screens.Showcase Loop = true, RelativeSizeAxes = Axes.Both, }, - new Box + new Container { - // chroma key area for stable gameplay - Name = "chroma", - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Height = 695, - Width = 1366, - Colour = new Color4(0, 255, 0, 255), + Padding = new MarginPadding { Bottom = SongBar.HEIGHT }, + RelativeSizeAxes = Axes.Both, + Child = new Box + { + // chroma key area for stable gameplay + Name = "chroma", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Both, + Colour = new Color4(0, 255, 0, 255), + } } }); } From 743b4f05b3640e728319a1eed500e73bf760de63 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Apr 2020 08:36:36 +0900 Subject: [PATCH 536/655] Rename out of place variable --- .../Skinning/TestSceneDrawableBarLine.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs index 58d69fc32a..cffe47f62a 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs @@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning private BarLine createBarLineAtCurrentTime(bool major = false) { - var drumroll = new BarLine + var barline = new BarLine { Major = major, StartTime = Time.Current + 2000, @@ -103,9 +103,9 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning var cpi = new ControlPointInfo(); cpi.Add(0, new TimingControlPoint { BeatLength = 500 }); - drumroll.ApplyDefaults(cpi, new BeatmapDifficulty()); + barline.ApplyDefaults(cpi, new BeatmapDifficulty()); - return drumroll; + return barline; } } } From 75c588c59d8f8548b8c1b307bc7a5dc3daaf91e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Apr 2020 08:36:48 +0900 Subject: [PATCH 537/655] Remove stray space --- .../Skinning/TestSceneDrawableBarLine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs index cffe47f62a..70493aa69a 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning [BackgroundDependencyLoader] private void load() { - AddStep("Bar line ", () => SetContents(() => + AddStep("Bar line", () => SetContents(() => { ScrollingHitObjectContainer hoc; From fcded206559d688f0d363d12508b666ee779abd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Apr 2020 08:58:01 +0900 Subject: [PATCH 538/655] Don't specify IProvideVideo interface for now --- osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs index aea531e88d..9785b7e647 100644 --- a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs +++ b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs @@ -10,7 +10,7 @@ using osuTK.Graphics; namespace osu.Game.Tournament.Screens.Showcase { - public class ShowcaseScreen : BeatmapInfoScreen, IProvideVideo + public class ShowcaseScreen : BeatmapInfoScreen // IProvideVideo { [BackgroundDependencyLoader] private void load() From 48168dddcec916967327057454f649fe884a02d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Apr 2020 10:54:32 +0900 Subject: [PATCH 539/655] Adjust editor timeline current marker to promote tick visibility --- .../Compose/Components/Timeline/CentreMarker.cs | 13 ++++++++++--- .../Edit/Compose/Components/Timeline/Timeline.cs | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs index 0d4c48b5f6..0d2b35132e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs @@ -12,14 +12,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class CentreMarker : CompositeDrawable { - private const float triangle_width = 20; + private const float triangle_width = 15; private const float triangle_height = 10; private const float bar_width = 2; public CentreMarker() { RelativeSizeAxes = Axes.Y; - Size = new Vector2(20, 1); + Size = new Vector2(triangle_width, 1); Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -39,6 +39,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Origin = Anchor.BottomCentre, Size = new Vector2(triangle_width, triangle_height), Scale = new Vector2(1, -1) + }, + new Triangle + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = new Vector2(triangle_width, triangle_height), + Scale = new Vector2(1, 1) } }; } @@ -46,7 +53,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [BackgroundDependencyLoader] private void load(OsuColour colours) { - Colour = colours.Red; + Colour = colours.RedDark; } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 8e6b3d5424..25f3cfc285 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }); // We don't want the centre marker to scroll - AddInternal(new CentreMarker()); + AddInternal(new CentreMarker { Depth = float.MaxValue }); WaveformVisible.ValueChanged += visible => waveform.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint); From 104c61d622589b199dd859abf3dfb2e6f0d0e7f0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 27 Apr 2020 11:06:23 +0900 Subject: [PATCH 540/655] Remove unnecessary scale --- .../Screens/Edit/Compose/Components/Timeline/CentreMarker.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs index 0d2b35132e..8c8b38d9ea 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs @@ -45,7 +45,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Size = new Vector2(triangle_width, triangle_height), - Scale = new Vector2(1, 1) } }; } From 8a47a615dbb44c3e236f8b4b61ebe1609a9bcefe Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 26 Apr 2020 19:29:22 -0700 Subject: [PATCH 541/655] Remove unranked label from footer --- .../TestSceneModSelectOverlay.cs | 12 -------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 29 ++----------------- 2 files changed, 2 insertions(+), 39 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 2294cd6966..769c660f48 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -117,8 +117,6 @@ namespace osu.Game.Tests.Visual.UserInterface public void TestManiaMods() { changeRuleset(3); - - testRankedText(new ManiaRuleset().GetModsFor(ModType.Conversion).First(m => m is ManiaModRandom)); } [Test] @@ -217,15 +215,6 @@ namespace osu.Game.Tests.Visual.UserInterface checkLabelColor(() => Color4.White); } - private void testRankedText(Mod mod) - { - AddUntilStep("check for ranked", () => modSelect.UnrankedLabel.Alpha == 0); - selectNext(mod); - AddUntilStep("check for unranked", () => modSelect.UnrankedLabel.Alpha != 0); - selectPrevious(mod); - AddUntilStep("check for ranked", () => modSelect.UnrankedLabel.Alpha == 0); - } - private void selectNext(Mod mod) => AddStep($"left click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext(1)); private void selectPrevious(Mod mod) => AddStep($"right click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext(-1)); @@ -272,7 +261,6 @@ namespace osu.Game.Tests.Visual.UserInterface } public new OsuSpriteText MultiplierLabel => base.MultiplierLabel; - public new OsuSpriteText UnrankedLabel => base.UnrankedLabel; public new TriangleButton DeselectAllButton => base.DeselectAllButton; public new Color4 LowMultiplierColour => base.LowMultiplierColour; diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 36d21c8b46..914e730c27 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -37,7 +37,6 @@ namespace osu.Game.Overlays.Mods protected readonly TriangleButton CloseButton; protected readonly OsuSpriteText MultiplierLabel; - protected readonly OsuSpriteText UnrankedLabel; protected override bool BlockNonPositionalInput => false; @@ -268,30 +267,11 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, }, - new FillFlowContainer + MultiplierLabel = new OsuSpriteText { - AutoSizeAxes = Axes.Both, + Font = OsuFont.GetFont(size: 25, weight: FontWeight.Bold, fixedWidth: true), Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, - Direction = FillDirection.Vertical, - LayoutDuration = 100, - LayoutEasing = Easing.OutQuint, - Children = new Drawable[] - { - MultiplierLabel = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 25, weight: FontWeight.Bold, fixedWidth: true), - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - }, - UnrankedLabel = new OsuSpriteText - { - Text = @"(Unranked)", - Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold), - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - }, - } }, }, }, @@ -340,7 +320,6 @@ namespace osu.Game.Overlays.Mods { LowMultiplierColour = colours.Red; HighMultiplierColour = colours.Green; - UnrankedLabel.Colour = colours.Blue; availableMods = osu.AvailableMods.GetBoundCopy(); @@ -444,12 +423,10 @@ namespace osu.Game.Overlays.Mods private void updateMods() { var multiplier = 1.0; - var ranked = true; foreach (var mod in SelectedMods.Value) { multiplier *= mod.ScoreMultiplier; - ranked &= mod.Ranked; } MultiplierLabel.Text = $"{multiplier:N2}x"; @@ -459,8 +436,6 @@ namespace osu.Game.Overlays.Mods MultiplierLabel.FadeColour(LowMultiplierColour, 200); else MultiplierLabel.FadeColour(Color4.White, 200); - - UnrankedLabel.FadeTo(ranked ? 0 : 1, 200); } private void updateModSettings(ValueChangedEvent> selectedMods) From 1b9362041a4e4019c84f41f8230aca4f3d194459 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 26 Apr 2020 19:50:11 -0700 Subject: [PATCH 542/655] Revert multiplier number changes and set width Safe arbitrary width taken from "0.00x" (highest width of 67), rounded to the nearest tenth. --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 914e730c27..09f4befbc1 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -269,9 +269,10 @@ namespace osu.Game.Overlays.Mods }, MultiplierLabel = new OsuSpriteText { - Font = OsuFont.GetFont(size: 25, weight: FontWeight.Bold, fixedWidth: true), + Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold), Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, + Width = 70, // to avoid footer from flowing when clicking mods }, }, }, From dd36b839b9bb4bd558a7218e8e86c37334f86b5a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 27 Apr 2020 12:01:31 +0900 Subject: [PATCH 543/655] Refactor --- .../Objects/Drawables/DrawableDrumRollTick.cs | 2 - osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 38 ++++++++++--------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index 536dd1209c..e12d1a0ba9 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -24,8 +24,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables FillMode = FillMode.Fit; } - public override bool DisplayResult => false; - protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.DrumRollTick), _ => new TickPiece { diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index d1d2571ec7..6103dea1fd 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -181,29 +181,11 @@ namespace osu.Game.Rulesets.Taiko.UI } } - private void playDrumrollHit(DrawableDrumRollTick drumrollTick) - { - TaikoAction action = drumrollTick.JudgedAction; - bool isStrong = drumrollTick.HitObject.IsStrong; - double time = drumrollTick.HitObject.GetEndTime(); - - DrawableHit drawableHit; - if (action == TaikoAction.LeftRim || action == TaikoAction.RightRim) - drawableHit = new DrawableFlyingRimHit(time, isStrong); - else - drawableHit = new DrawableFlyingCentreHit(time, isStrong); - - drumRollHitContainer.Add(drawableHit); - } - internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result) { if (!DisplayJudgements.Value) return; - if ((judgedObject is DrawableDrumRollTick) && result.Type != HitResult.Miss) - playDrumrollHit((DrawableDrumRollTick)judgedObject); - if (!judgedObject.DisplayResult) return; @@ -214,6 +196,11 @@ namespace osu.Game.Rulesets.Taiko.UI hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == ((DrawableStrongNestedHit)judgedObject).MainObject)?.VisualiseSecondHit(); break; + case TaikoDrumRollTickJudgement _: + if (result.IsHit) + playDrumrollHit((DrawableDrumRollTick)judgedObject); + break; + default: judgementContainer.Add(new DrawableTaikoJudgement(result, judgedObject) { @@ -237,6 +224,21 @@ namespace osu.Game.Rulesets.Taiko.UI } } + private void playDrumrollHit(DrawableDrumRollTick drumrollTick) + { + TaikoAction action = drumrollTick.JudgedAction; + bool isStrong = drumrollTick.HitObject.IsStrong; + double time = drumrollTick.HitObject.GetEndTime(); + + DrawableHit drawableHit; + if (action == TaikoAction.LeftRim || action == TaikoAction.RightRim) + drawableHit = new DrawableFlyingRimHit(time, isStrong); + else + drawableHit = new DrawableFlyingCentreHit(time, isStrong); + + drumRollHitContainer.Add(drawableHit); + } + private class ProxyContainer : LifetimeManagementContainer { public new MarginPadding Padding From 81df22d2a77b1b85d4c14ade4da13532ede7a3f3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 27 Apr 2020 12:17:36 +0900 Subject: [PATCH 544/655] Improve test scene --- osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs index c2ca578dfa..1822180795 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Tests [TestFixture] public class TestSceneHits : OsuTestScene { - private const double default_duration = 1000; + private const double default_duration = 3000; private const float scroll_time = 1000; protected override double TimePerAction => default_duration * 2; @@ -45,6 +45,7 @@ namespace osu.Game.Rulesets.Taiko.Tests AddStep("Miss :(", addMissJudgement); AddStep("DrumRoll", () => addDrumRoll(false)); AddStep("Strong DrumRoll", () => addDrumRoll(true)); + AddStep("Kiai DrumRoll", () => addDrumRoll(true, kiai: true)); AddStep("Swell", () => addSwell()); AddStep("Centre", () => addCentreHit(false)); AddStep("Strong Centre", () => addCentreHit(true)); @@ -192,7 +193,7 @@ namespace osu.Game.Rulesets.Taiko.Tests drawableRuleset.Playfield.Add(new DrawableSwell(swell)); } - private void addDrumRoll(bool strong, double duration = default_duration) + private void addDrumRoll(bool strong, double duration = default_duration, bool kiai = false) { addBarLine(true); addBarLine(true, scroll_time + duration); @@ -202,9 +203,13 @@ namespace osu.Game.Rulesets.Taiko.Tests StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, IsStrong = strong, Duration = duration, + TickRate = 8, }; - d.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + var cpi = new ControlPointInfo(); + cpi.Add(-10000, new EffectControlPoint { KiaiMode = kiai }); + + d.ApplyDefaults(cpi, new BeatmapDifficulty()); drawableRuleset.Playfield.Add(new DrawableDrumRoll(d)); } From 7dc090cc24ccd64f19aa5f010ded2c99871f176d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 27 Apr 2020 12:23:53 +0900 Subject: [PATCH 545/655] Add support for hit explosions --- .../Objects/Drawables/DrawableDrumRollTick.cs | 7 ++-- osu.Game.Rulesets.Taiko/UI/HitExplosion.cs | 10 +++--- .../UI/KiaiHitExplosion.cs | 10 +++--- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 34 +++++++++++-------- 4 files changed, 31 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index e12d1a0ba9..62405cf047 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -13,10 +13,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public class DrawableDrumRollTick : DrawableTaikoHitObject { /// - /// The action type that the user took which caused this tick to - /// have been judged as "hit" + /// The hit type corresponding to the that the user pressed to hit this . /// - public TaikoAction JudgedAction; + public HitType JudgementType; public DrawableDrumRollTick(DrumRollTick tick) : base(tick) @@ -57,7 +56,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public override bool OnPressed(TaikoAction action) { - JudgedAction = action; + JudgementType = action == TaikoAction.LeftRim || action == TaikoAction.RightRim ? HitType.Rim : HitType.Centre; return UpdateResult(true); } diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs index d4118d38b6..fbaae7e322 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs @@ -21,16 +21,14 @@ namespace osu.Game.Rulesets.Taiko.UI public override bool RemoveWhenNotAlive => true; public readonly DrawableHitObject JudgedObject; + private readonly HitType type; private readonly Box innerFill; - private readonly bool isRim; - - public HitExplosion(DrawableHitObject judgedObject, bool isRim) + public HitExplosion(DrawableHitObject judgedObject, HitType type) { - this.isRim = isRim; - JudgedObject = judgedObject; + this.type = type; Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -58,7 +56,7 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load(OsuColour colours) { - innerFill.Colour = isRim ? colours.BlueDarker : colours.PinkDarker; + innerFill.Colour = type == HitType.Rim ? colours.BlueDarker : colours.PinkDarker; } protected override void LoadComplete() diff --git a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs index e80b463481..3a307bb3bb 100644 --- a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs @@ -18,14 +18,12 @@ namespace osu.Game.Rulesets.Taiko.UI public override bool RemoveWhenNotAlive => true; public readonly DrawableHitObject JudgedObject; + private readonly HitType type; - private readonly bool isRim; - - public KiaiHitExplosion(DrawableHitObject judgedObject, bool isRim) + public KiaiHitExplosion(DrawableHitObject judgedObject, HitType type) { - this.isRim = isRim; - JudgedObject = judgedObject; + this.type = type; Anchor = Anchor.CentreLeft; Origin = Anchor.Centre; @@ -53,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.UI EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, - Colour = isRim ? colours.BlueDarker : colours.PinkDarker, + Colour = type == HitType.Rim ? colours.BlueDarker : colours.PinkDarker, Radius = 60, }; } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 6103dea1fd..a19c08ae7e 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -10,7 +10,6 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.Taiko.Objects.Drawables; @@ -197,8 +196,13 @@ namespace osu.Game.Rulesets.Taiko.UI break; case TaikoDrumRollTickJudgement _: - if (result.IsHit) - playDrumrollHit((DrawableDrumRollTick)judgedObject); + if (!result.IsHit) + return; + + var drawableTick = (DrawableDrumRollTick)judgedObject; + + addDrumRollHit(drawableTick); + addExplosion(drawableTick, drawableTick.JudgementType); break; default: @@ -213,25 +217,19 @@ namespace osu.Game.Rulesets.Taiko.UI if (!result.IsHit) break; - bool isRim = (judgedObject.HitObject as Hit)?.Type == HitType.Rim; - - hitExplosionContainer.Add(new HitExplosion(judgedObject, isRim)); - - if (judgedObject.HitObject.Kiai) - kiaiExplosionContainer.Add(new KiaiHitExplosion(judgedObject, isRim)); + addExplosion(judgedObject, (judgedObject.HitObject as Hit)?.Type ?? HitType.Centre); break; } } - private void playDrumrollHit(DrawableDrumRollTick drumrollTick) + private void addDrumRollHit(DrawableDrumRollTick drawableTick) { - TaikoAction action = drumrollTick.JudgedAction; - bool isStrong = drumrollTick.HitObject.IsStrong; - double time = drumrollTick.HitObject.GetEndTime(); + bool isStrong = drawableTick.HitObject.IsStrong; + double time = drawableTick.HitObject.GetEndTime(); DrawableHit drawableHit; - if (action == TaikoAction.LeftRim || action == TaikoAction.RightRim) + if (drawableTick.JudgementType == HitType.Rim) drawableHit = new DrawableFlyingRimHit(time, isStrong); else drawableHit = new DrawableFlyingCentreHit(time, isStrong); @@ -239,6 +237,14 @@ namespace osu.Game.Rulesets.Taiko.UI drumRollHitContainer.Add(drawableHit); } + private void addExplosion(DrawableHitObject drawableObject, HitType type) + { + hitExplosionContainer.Add(new HitExplosion(drawableObject, type)); + + if (drawableObject.HitObject.Kiai) + kiaiExplosionContainer.Add(new KiaiHitExplosion(drawableObject, type)); + } + private class ProxyContainer : LifetimeManagementContainer { public new MarginPadding Padding From 2630fc1405260ce60ad9d7aa533c483d90a91147 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 27 Apr 2020 12:27:43 +0900 Subject: [PATCH 546/655] Break instead of return for consistency --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index a19c08ae7e..1fce70122d 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -197,7 +197,7 @@ namespace osu.Game.Rulesets.Taiko.UI case TaikoDrumRollTickJudgement _: if (!result.IsHit) - return; + break; var drawableTick = (DrawableDrumRollTick)judgedObject; @@ -218,7 +218,6 @@ namespace osu.Game.Rulesets.Taiko.UI break; addExplosion(judgedObject, (judgedObject.HitObject as Hit)?.Type ?? HitType.Centre); - break; } } From 20ae973e4afba59b5e87645dffbf7964b0ea0676 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 27 Apr 2020 12:29:11 +0900 Subject: [PATCH 547/655] Use max result instead of GOOD --- .../Objects/Drawables/DrawableFlyingCentreHit.cs | 3 +-- .../Objects/Drawables/DrawableFlyingRimHit.cs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingCentreHit.cs index f70d940bd2..105ff62812 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingCentreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingCentreHit.cs @@ -3,7 +3,6 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -12,7 +11,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override void LoadComplete() { base.LoadComplete(); - ApplyResult(r => r.Type = HitResult.Good); + ApplyResult(r => r.Type = r.Judgement.MaxResult); } public DrawableFlyingCentreHit(double time, bool isStrong = false) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingRimHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingRimHit.cs index 9005dac653..e8cbd6e54f 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingRimHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingRimHit.cs @@ -3,7 +3,6 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -12,7 +11,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override void LoadComplete() { base.LoadComplete(); - ApplyResult(r => r.Type = HitResult.Good); + ApplyResult(r => r.Type = r.Judgement.MaxResult); } public DrawableFlyingRimHit(double time, bool isStrong = false) From 7731d45f13ce88392716c640dbdaa79b23d965ce Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 26 Apr 2020 20:30:56 -0700 Subject: [PATCH 548/655] Remove unnecessary usings --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 769c660f48..ec6ee6bc83 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -14,8 +14,6 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; using osu.Game.Overlays.Mods.Sections; using osu.Game.Rulesets; -using osu.Game.Rulesets.Mania; -using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; From ff736a22dd9f4e1835b07ec8f25c7507045ed8b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Apr 2020 14:41:19 +0900 Subject: [PATCH 549/655] Fix typos in comment --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 1fce70122d..cd05d8e2f9 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -150,8 +150,8 @@ namespace osu.Game.Rulesets.Taiko.UI rightArea.Padding = new MarginPadding { Left = leftArea.DrawWidth }; hitTargetOffsetContent.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 }; - // When rewinding, make sure to remove any auxilliary hit notes that were - // spawned and played during a drumroll. + // When rewinding, make sure to remove any auxiliary hit notes that were + // spawned and played during a drum roll. if (Time.Elapsed < 0) { foreach (var o in drumRollHitContainer.Objects) From b9f28c83731a4ca823c0090f654fab57d9da979f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Apr 2020 16:13:28 +0900 Subject: [PATCH 550/655] Combine hit types and remove old drumroll hits using a more efficient method --- .../Skinning/TestSceneDrawableHit.cs | 10 +++--- .../TestSceneHits.cs | 4 +-- .../Objects/Drawables/DrawableCentreHit.cs | 21 ----------- ...lyingCentreHit.cs => DrawableFlyingHit.cs} | 7 ++-- .../Objects/Drawables/DrawableFlyingRimHit.cs | 23 ------------ .../Objects/Drawables/DrawableHit.cs | 26 +++++++++----- .../Objects/Drawables/DrawableRimHit.cs | 21 ----------- .../UI/DrawableTaikoRuleset.cs | 5 +-- .../UI/DrumRollHitContainer.cs | 35 +++++++++++++++++++ osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 28 ++------------- 10 files changed, 67 insertions(+), 113 deletions(-) delete mode 100644 osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs rename osu.Game.Rulesets.Taiko/Objects/Drawables/{DrawableFlyingCentreHit.cs => DrawableFlyingHit.cs} (72%) delete mode 100644 osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingRimHit.cs delete mode 100644 osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs create mode 100644 osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs index 6d6da1fb5b..6a3c98a514 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs @@ -21,8 +21,6 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(DrawableHit), - typeof(DrawableCentreHit), - typeof(DrawableRimHit), typeof(LegacyHit), typeof(LegacyCirclePiece), }).ToList(); @@ -30,25 +28,25 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning [BackgroundDependencyLoader] private void load() { - AddStep("Centre hit", () => SetContents(() => new DrawableCentreHit(createHitAtCurrentTime()) + AddStep("Centre hit", () => SetContents(() => new DrawableHit(createHitAtCurrentTime()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, })); - AddStep("Centre hit (strong)", () => SetContents(() => new DrawableCentreHit(createHitAtCurrentTime(true)) + AddStep("Centre hit (strong)", () => SetContents(() => new DrawableHit(createHitAtCurrentTime(true)) { Anchor = Anchor.Centre, Origin = Anchor.Centre, })); - AddStep("Rim hit", () => SetContents(() => new DrawableRimHit(createHitAtCurrentTime()) + AddStep("Rim hit", () => SetContents(() => new DrawableHit(createHitAtCurrentTime()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, })); - AddStep("Rim hit (strong)", () => SetContents(() => new DrawableRimHit(createHitAtCurrentTime(true)) + AddStep("Rim hit (strong)", () => SetContents(() => new DrawableHit(createHitAtCurrentTime(true)) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs index 1822180795..23adb79083 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs @@ -224,7 +224,7 @@ namespace osu.Game.Rulesets.Taiko.Tests h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - drawableRuleset.Playfield.Add(new DrawableCentreHit(h)); + drawableRuleset.Playfield.Add(new DrawableHit(h)); } private void addRimHit(bool strong) @@ -237,7 +237,7 @@ namespace osu.Game.Rulesets.Taiko.Tests h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - drawableRuleset.Playfield.Add(new DrawableRimHit(h)); + drawableRuleset.Playfield.Add(new DrawableHit(h)); } private class TestStrongNestedHit : DrawableStrongNestedHit diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs deleted file mode 100644 index a87da44415..0000000000 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; -using osu.Game.Skinning; - -namespace osu.Game.Rulesets.Taiko.Objects.Drawables -{ - public class DrawableCentreHit : DrawableHit - { - public override TaikoAction[] HitActions { get; } = { TaikoAction.LeftCentre, TaikoAction.RightCentre }; - - public DrawableCentreHit(Hit hit) - : base(hit) - { - } - - protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.CentreHit), - _ => new CentreHitCirclePiece(), confineMode: ConfineMode.ScaleToFit); - } -} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs similarity index 72% rename from osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingCentreHit.cs rename to osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs index 105ff62812..86822e3ae8 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingCentreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs @@ -6,7 +6,10 @@ using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { - public class DrawableFlyingCentreHit : DrawableCentreHit + /// + /// A hit used specifically for drum rolls, where spawning flying hits is required. + /// + public class DrawableFlyingHit : DrawableHit { protected override void LoadComplete() { @@ -14,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables ApplyResult(r => r.Type = r.Judgement.MaxResult); } - public DrawableFlyingCentreHit(double time, bool isStrong = false) + public DrawableFlyingHit(double time, bool isStrong = false) : base(new IgnoreHit { StartTime = time, IsStrong = isStrong }) { HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingRimHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingRimHit.cs deleted file mode 100644 index e8cbd6e54f..0000000000 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingRimHit.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; - -namespace osu.Game.Rulesets.Taiko.Objects.Drawables -{ - public class DrawableFlyingRimHit : DrawableRimHit - { - protected override void LoadComplete() - { - base.LoadComplete(); - ApplyResult(r => r.Type = r.Judgement.MaxResult); - } - - public DrawableFlyingRimHit(double time, bool isStrong = false) - : base(new IgnoreHit { StartTime = time, IsStrong = isStrong }) - { - HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - } - } -} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index fe9a89f2be..d2671eadda 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -8,31 +8,45 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { - public abstract class DrawableHit : DrawableTaikoHitObject + public class DrawableHit : DrawableTaikoHitObject { /// /// A list of keys which can result in hits for this HitObject. /// - public abstract TaikoAction[] HitActions { get; } + public TaikoAction[] HitActions { get; } /// /// The action that caused this to be hit. /// - public TaikoAction? HitAction { get; private set; } + public TaikoAction? HitAction + { + get; + private set; + } private bool validActionPressed; private bool pressHandledThisFrame; - protected DrawableHit(Hit hit) + public DrawableHit(Hit hit) : base(hit) { FillMode = FillMode.Fit; + + HitActions = + HitObject.Type == HitType.Centre + ? new[] { TaikoAction.LeftCentre, TaikoAction.RightCentre } + : new[] { TaikoAction.LeftRim, TaikoAction.RightRim }; } + protected override SkinnableDrawable CreateMainPiece() => HitObject.Type == HitType.Centre + ? new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.CentreHit), _ => new CentreHitCirclePiece(), confineMode: ConfineMode.ScaleToFit) + : new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.RimHit), _ => new RimHitCirclePiece(), confineMode: ConfineMode.ScaleToFit); + protected override void CheckForResult(bool userTriggered, double timeOffset) { Debug.Assert(HitObject.HitWindows != null); @@ -58,7 +72,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { if (pressHandledThisFrame) return true; - if (Judged) return false; @@ -66,14 +79,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables // Only count this as handled if the new judgement is a hit var result = UpdateResult(true); - if (IsHit) HitAction = action; // Regardless of whether we've hit or not, any secondary key presses in the same frame should be discarded // E.g. hitting a non-strong centre as a strong should not fall through and perform a hit on the next note pressHandledThisFrame = true; - return result; } @@ -81,7 +92,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { if (action == HitAction) HitAction = null; - base.OnReleased(action); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs deleted file mode 100644 index f767403c65..0000000000 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; -using osu.Game.Skinning; - -namespace osu.Game.Rulesets.Taiko.Objects.Drawables -{ - public class DrawableRimHit : DrawableHit - { - public override TaikoAction[] HitActions { get; } = { TaikoAction.LeftRim, TaikoAction.RightRim }; - - public DrawableRimHit(Hit hit) - : base(hit) - { - } - - protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.RimHit), - _ => new RimHitCirclePiece(), confineMode: ConfineMode.ScaleToFit); - } -} diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index e4a4b555a7..a6a00fe242 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -49,10 +49,7 @@ namespace osu.Game.Rulesets.Taiko.UI switch (h) { case Hit hit: - if (hit.Type == HitType.Centre) - return new DrawableCentreHit(hit); - else - return new DrawableRimHit(hit); + return new DrawableHit(hit); case DrumRoll drumRoll: return new DrawableDrumRoll(drumRoll); diff --git a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs new file mode 100644 index 0000000000..9ef944b412 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs @@ -0,0 +1,35 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; + +namespace osu.Game.Rulesets.Taiko.UI +{ + internal class DrumRollHitContainer : ScrollingHitObjectContainer + { + protected override void Update() + { + base.Update(); + + // Remove any auxiliary hit notes that were spawned during a drum roll but subsequently rewound. + for (var i = AliveInternalChildren.Count - 1; i >= 0; i--) + { + var flyingHit = (DrawableFlyingHit)AliveInternalChildren[i]; + if (Time.Current < flyingHit.HitObject.StartTime) + Remove(flyingHit); + } + } + + protected override void OnChildLifetimeBoundaryCrossed(LifetimeBoundaryCrossedEvent e) + { + base.OnChildLifetimeBoundaryCrossed(e); + + // ensure all old hits are removed on becoming alive (may miss being in the AliveInternalChildren list above). + if (e.Kind == LifetimeBoundaryKind.Start && e.Direction == LifetimeBoundaryCrossingDirection.Backward) + Remove((DrawableHitObject)e.Child); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index cd05d8e2f9..28706ef0d3 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -93,10 +93,7 @@ namespace osu.Game.Rulesets.Taiko.UI Children = new Drawable[] { HitObjectContainer, - drumRollHitContainer = new ScrollingHitObjectContainer - { - Name = "Drumroll hit" - } + drumRollHitContainer = new DrumRollHitContainer() } }, kiaiExplosionContainer = new Container @@ -149,23 +146,11 @@ namespace osu.Game.Rulesets.Taiko.UI // This is basically allowing for correct alignment as relative pieces move around them. rightArea.Padding = new MarginPadding { Left = leftArea.DrawWidth }; hitTargetOffsetContent.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 }; - - // When rewinding, make sure to remove any auxiliary hit notes that were - // spawned and played during a drum roll. - if (Time.Elapsed < 0) - { - foreach (var o in drumRollHitContainer.Objects) - { - if (o.HitObject.StartTime >= Time.Current) - drumRollHitContainer.Remove(o); - } - } } public override void Add(DrawableHitObject h) { h.OnNewResult += OnNewResult; - base.Add(h); switch (h) @@ -184,7 +169,6 @@ namespace osu.Game.Rulesets.Taiko.UI { if (!DisplayJudgements.Value) return; - if (!judgedObject.DisplayResult) return; @@ -226,20 +210,12 @@ namespace osu.Game.Rulesets.Taiko.UI { bool isStrong = drawableTick.HitObject.IsStrong; double time = drawableTick.HitObject.GetEndTime(); - - DrawableHit drawableHit; - if (drawableTick.JudgementType == HitType.Rim) - drawableHit = new DrawableFlyingRimHit(time, isStrong); - else - drawableHit = new DrawableFlyingCentreHit(time, isStrong); - - drumRollHitContainer.Add(drawableHit); + drumRollHitContainer.Add(new DrawableFlyingHit(time, isStrong)); } private void addExplosion(DrawableHitObject drawableObject, HitType type) { hitExplosionContainer.Add(new HitExplosion(drawableObject, type)); - if (drawableObject.HitObject.Kiai) kiaiExplosionContainer.Add(new KiaiHitExplosion(drawableObject, type)); } From 52cf1e18590fbef3e2f221e19a2dc126af2f4ad5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Apr 2020 16:48:17 +0900 Subject: [PATCH 551/655] Fix hit type not being provided and hit time offset not being considered --- .../Objects/Drawables/DrawableFlyingHit.cs | 17 +++++++++++------ .../UI/DrumRollHitContainer.cs | 2 +- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 9 ++------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs index 86822e3ae8..460e760629 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs @@ -11,16 +11,21 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// public class DrawableFlyingHit : DrawableHit { + public DrawableFlyingHit(DrawableDrumRollTick drumRollTick) + : base(new IgnoreHit + { + StartTime = drumRollTick.HitObject.StartTime + drumRollTick.Result.TimeOffset, + IsStrong = drumRollTick.HitObject.IsStrong, + Type = drumRollTick.JudgementType + }) + { + HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + } + protected override void LoadComplete() { base.LoadComplete(); ApplyResult(r => r.Type = r.Judgement.MaxResult); } - - public DrawableFlyingHit(double time, bool isStrong = false) - : base(new IgnoreHit { StartTime = time, IsStrong = isStrong }) - { - HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - } } } diff --git a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs index 9ef944b412..fde42bec04 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.UI for (var i = AliveInternalChildren.Count - 1; i >= 0; i--) { var flyingHit = (DrawableFlyingHit)AliveInternalChildren[i]; - if (Time.Current < flyingHit.HitObject.StartTime) + if (Time.Current <= flyingHit.HitObject.StartTime) Remove(flyingHit); } } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 28706ef0d3..4bc6cb8e4b 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -9,7 +9,6 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.Taiko.Objects.Drawables; @@ -206,12 +205,8 @@ namespace osu.Game.Rulesets.Taiko.UI } } - private void addDrumRollHit(DrawableDrumRollTick drawableTick) - { - bool isStrong = drawableTick.HitObject.IsStrong; - double time = drawableTick.HitObject.GetEndTime(); - drumRollHitContainer.Add(new DrawableFlyingHit(time, isStrong)); - } + private void addDrumRollHit(DrawableDrumRollTick drawableTick) => + drumRollHitContainer.Add(new DrawableFlyingHit(drawableTick)); private void addExplosion(DrawableHitObject drawableObject, HitType type) { From acf95fca9cdd8b12027d907838738fa0216845b4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 27 Apr 2020 17:14:00 +0900 Subject: [PATCH 552/655] Remove old, now unnecessary method --- .../Edit/ManiaSelectionHandler.cs | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 78f159b733..d290e4ec24 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -35,35 +35,12 @@ namespace osu.Game.Rulesets.Mania.Edit var maniaBlueprint = (ManiaSelectionBlueprint)moveEvent.Blueprint; int lastColumn = maniaBlueprint.DrawableObject.HitObject.Column; - // adjustOrigins(maniaBlueprint); performDragMovement(moveEvent); performColumnMovement(lastColumn, moveEvent); return true; } - /// - /// Ensures that the position of hitobjects remains centred to the mouse position. - /// E.g. The hitobject position will change if the editor scrolls while a hitobject is dragged. - /// - /// The that received the drag event. - private void adjustOrigins(ManiaSelectionBlueprint reference) - { - var referenceParent = (HitObjectContainer)reference.DrawableObject.Parent; - - float offsetFromReferenceOrigin = reference.DragPosition.Y - reference.DrawableObject.OriginPosition.Y; - float targetPosition = referenceParent.ToLocalSpace(reference.ScreenSpaceDragPosition).Y - offsetFromReferenceOrigin; - - // Flip the vertical coordinate space when scrolling downwards - if (scrollingInfo.Direction.Value == ScrollingDirection.Down) - targetPosition -= referenceParent.DrawHeight; - - float movementDelta = targetPosition - reference.DrawableObject.Position.Y; - - foreach (var b in SelectedBlueprints.OfType()) - b.DrawableObject.Y += movementDelta; - } - private void performDragMovement(MoveSelectionEvent moveEvent) { float delta = moveEvent.InstantDelta.Y; From 03863d901b221e31baa22af9f65de9490ae276d4 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2020 08:59:09 +0000 Subject: [PATCH 553/655] Bump Microsoft.NET.Test.Sdk from 16.5.0 to 16.6.1 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 16.5.0 to 16.6.1. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v16.5.0...v16.6.1) Signed-off-by: dependabot-preview[bot] --- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 8c371db257..cbd3dc5518 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index 6855b99f28..77c871718b 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index 217707b180..2fcfa1deb7 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index f6054a5d6f..28b8476a22 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 35eb3fa161..5ee887cb64 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 3b45fc83fd..aa37326a49 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -5,7 +5,7 @@ - + From c0b225ffc8e5c27bc4c624468994f61b698daab5 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2020 08:59:34 +0000 Subject: [PATCH 554/655] Bump ppy.osu.Game.Resources from 2020.412.0 to 2020.427.0 Bumps [ppy.osu.Game.Resources](https://github.com/ppy/osu-resources) from 2020.412.0 to 2020.427.0. - [Release notes](https://github.com/ppy/osu-resources/releases) - [Commits](https://github.com/ppy/osu-resources/compare/2020.412.0...2020.427.0) Signed-off-by: dependabot-preview[bot] --- 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 25942863c5..73fbe3ab2e 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 9c17c453a6..3b05eb82d7 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 07ea4b9c2a..f3202693f3 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From ca055581af91975ed44438b25863ad0b33c7e9c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Apr 2020 18:18:11 +0900 Subject: [PATCH 555/655] Fix taiko hit target alpha on legacy skins --- osu.Game.Rulesets.Taiko/Skinning/TaikoLegacyHitTarget.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacyHitTarget.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacyHitTarget.cs index 7c1e65f569..e522fb7c10 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacyHitTarget.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacyHitTarget.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning { Texture = skin.GetTexture("approachcircle"), Scale = new Vector2(0.73f), - Alpha = 0.7f, + Alpha = 0.47f, // eyeballed to match stable Anchor = Anchor.Centre, Origin = Anchor.Centre, }, @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning { Texture = skin.GetTexture("taikobigcircle"), Scale = new Vector2(0.7f), - Alpha = 0.5f, + Alpha = 0.22f, // eyeballed to match stable Anchor = Anchor.Centre, Origin = Anchor.Centre, }, From b88dd442526c8a58a9bd992101b5d7b5da33e3a6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 27 Apr 2020 19:47:25 +0900 Subject: [PATCH 556/655] Fix movement not working correctly in down-scroll --- .../Blueprints/ManiaSelectionBlueprint.cs | 20 +++++++++++++++++++ .../Edit/ManiaSelectionHandler.cs | 19 +++++++----------- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs index 9f57160f99..14d52dd08e 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -74,5 +74,25 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints DrawableObject.AlwaysAlive = false; base.Hide(); } + + public override Vector2 GetInstantDelta(Vector2 screenSpacePosition) + { + var baseDelta = base.GetInstantDelta(screenSpacePosition); + + if (scrollingInfo.Direction.Value == ScrollingDirection.Down) + { + // The parent of DrawableObject is the scrolling hitobject container (SHOC). + // In the coordinate-space of the SHOC, the screen-space position at the hit target is equal to the height of the SHOC, + // but this is not what we want as it means a slight movement downwards results in a delta greater than the height of the SHOC. + // To get around this issue, the height of the SHOC is subtracted from the delta. + // + // Ideally this should be a _negative_ value in the case described above, however this code gives a _positive_ delta. + // This is intentional as the delta is added to the hitobject's position (see: ManiaSelectionHandler) and a negative delta would move them towards the top of the screen instead, + // which would cause the delta to get increasingly larger as additional movements are performed. + return new Vector2(baseDelta.X, baseDelta.Y - DrawableObject.Parent.DrawHeight); + } + + return baseDelta; + } } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index d290e4ec24..8dfc97f87a 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -45,11 +45,6 @@ namespace osu.Game.Rulesets.Mania.Edit { float delta = moveEvent.InstantDelta.Y; - // When scrolling downwards the anchor position is at the bottom of the screen, however the movement event assumes the anchor is at the top of the screen. - // This causes the delta to assume a positive hitobject position, and which can be corrected for by subtracting the parent height. - if (scrollingInfo.Direction.Value == ScrollingDirection.Down) - delta -= moveEvent.Blueprint.Parent.DrawHeight; // todo: definitely wrong - foreach (var selectionBlueprint in SelectedBlueprints) { var b = (OverlaySelectionBlueprint)selectionBlueprint; @@ -57,24 +52,24 @@ namespace osu.Game.Rulesets.Mania.Edit var hitObject = b.DrawableObject; var objectParent = (HitObjectContainer)hitObject.Parent; - // StartTime could be used to adjust the position if only one movement event was received per frame. - // However this is not the case and ScrollingHitObjectContainer performs movement in UpdateAfterChildren() so the position must also be updated to be valid for further movement events + // We receive multiple movement events per frame such that we can't rely on updating the start time + // since the scrolling hitobject container requires at least one update frame to update the position. + // However the position needs to be valid for future movement events to calculate the correct deltas. hitObject.Y += delta; float targetPosition = hitObject.Position.Y; - // The scrolling algorithm always assumes an anchor at the top of the screen, so the position must be flipped when scrolling downwards to reflect a top anchor if (scrollingInfo.Direction.Value == ScrollingDirection.Down) + { + // When scrolling downwards, the position is _negative_ when the object's start time is after the current time (e.g. in the middle of the stage). + // However all scrolling algorithms upwards scrolling, meaning that a positive (inverse) position is expected in the same scenario. targetPosition = -targetPosition; - - objectParent.Remove(hitObject); + } hitObject.HitObject.StartTime = scrollingInfo.Algorithm.TimeAt(targetPosition, editorClock.CurrentTime, scrollingInfo.TimeRange.Value, objectParent.DrawHeight); - - objectParent.Add(hitObject); } } From cebc0fc0466d61db6f22c19358c8d22ce5f12d09 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 27 Apr 2020 20:35:24 +0900 Subject: [PATCH 557/655] Attempt to fix multiple selection movements --- .../Edit/ManiaHitObjectComposer.cs | 28 ++++++++++++++++++- .../Edit/ManiaSelectionHandler.cs | 14 ---------- osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 16 +++++++++++ .../Compose/Components/BlueprintContainer.cs | 10 +++---- 4 files changed, 47 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 62b609610f..8d012c63ac 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -10,6 +10,7 @@ using osu.Framework.Allocation; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -37,7 +38,32 @@ namespace osu.Game.Rulesets.Mania.Edit protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - public int TotalColumns => ((ManiaPlayfield)drawableRuleset.Playfield).TotalColumns; + public ManiaPlayfield Playfield => ((ManiaPlayfield)drawableRuleset.Playfield); + + public int TotalColumns => Playfield.TotalColumns; + + public override (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) + { + var hoc = Playfield.GetColumn(0).HitObjectContainer; + + position.Y -= ToLocalSpace(hoc.ScreenSpaceDrawQuad.TopLeft).Y; + + float targetPosition = position.Y; + + if (drawableRuleset.ScrollingInfo.Direction.Value == ScrollingDirection.Down) + { + // When scrolling downwards, the position is _negative_ when the object's start time is after the current time (e.g. in the middle of the stage). + // However all scrolling algorithms upwards scrolling, meaning that a positive (inverse) position is expected in the same scenario. + targetPosition = -targetPosition; + } + + double targetTime = drawableRuleset.ScrollingInfo.Algorithm.TimeAt(targetPosition, + EditorClock.CurrentTime, + drawableRuleset.ScrollingInfo.TimeRange.Value, + hoc.DrawHeight); + + return base.GetSnappedPosition(position, targetTime); + } protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) { diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 8dfc97f87a..989e0f5b01 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -56,20 +56,6 @@ namespace osu.Game.Rulesets.Mania.Edit // since the scrolling hitobject container requires at least one update frame to update the position. // However the position needs to be valid for future movement events to calculate the correct deltas. hitObject.Y += delta; - - float targetPosition = hitObject.Position.Y; - - if (scrollingInfo.Direction.Value == ScrollingDirection.Down) - { - // When scrolling downwards, the position is _negative_ when the object's start time is after the current time (e.g. in the middle of the stage). - // However all scrolling algorithms upwards scrolling, meaning that a positive (inverse) position is expected in the same scenario. - targetPosition = -targetPosition; - } - - hitObject.HitObject.StartTime = scrollingInfo.Algorithm.TimeAt(targetPosition, - editorClock.CurrentTime, - scrollingInfo.TimeRange.Value, - objectParent.DrawHeight); } } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 2dec468654..f9faa262ed 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -87,6 +87,22 @@ namespace osu.Game.Rulesets.Mania.UI return found; } + public Column GetColumn(int index) + { + foreach (var stage in stages) + { + if (index >= stage.Columns.Count) + { + index -= stage.Columns.Count; + continue; + } + + return stage.Columns[index]; + } + + throw new ArgumentOutOfRangeException(nameof(index)); + } + /// /// Retrieves the total amount of columns across all stages in this playfield. /// diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 0823be01f8..8910684463 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -401,18 +401,16 @@ namespace osu.Game.Screens.Edit.Compose.Components HitObject draggedObject = movementBlueprint.HitObject; - // The final movement position, relative to movementBlueprintOriginalPosition + // The final movement position, relative to movementBlueprintOriginalPosition. Vector2 movePosition = movementBlueprintOriginalPosition.Value + e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition; - (Vector2 snappedPosition, _) = snapProvider.GetSnappedPosition(ToLocalSpace(movePosition), draggedObject.StartTime); + // Retrieve a snapped position. + (Vector2 snappedPosition, double snappedTime) = snapProvider.GetSnappedPosition(ToLocalSpace(movePosition), draggedObject.StartTime); - // Move the hitobjects + // Move the hitobjects. if (!selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, ToScreenSpace(snappedPosition)))) return true; - // Todo: Temp - (_, double snappedTime) = snapProvider.GetSnappedPosition(ToLocalSpace(snappedPosition), draggedObject.StartTime); - // Apply the start time at the newly snapped-to position double offset = snappedTime - draggedObject.StartTime; foreach (HitObject obj in selectionHandler.SelectedHitObjects) From be59ee945a5ead6fe5d8d7cbeaad058e8180bf7f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Apr 2020 22:22:32 +0900 Subject: [PATCH 558/655] Add taiko hit explosion skinning support --- .../Skinning/TestSceneHitExplosion.cs | 79 +++++++++++++++++++ .../Skinning/LegacyHitExplosion.cs | 29 +++++++ .../Skinning/TaikoLegacySkinTransformer.cs | 27 +++++++ .../TaikoSkinComponents.cs | 5 +- .../UI/DefaultHitExplosion.cs | 54 +++++++++++++ osu.Game.Rulesets.Taiko/UI/HitExplosion.cs | 50 ++++++------ osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 7 +- 7 files changed, 221 insertions(+), 30 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs create mode 100644 osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs create mode 100644 osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs new file mode 100644 index 0000000000..ca0c60b67a --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs @@ -0,0 +1,79 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Skinning; +using osu.Game.Rulesets.Taiko.UI; + +namespace osu.Game.Rulesets.Taiko.Tests.Skinning +{ + [TestFixture] + public class TestSceneHitExplosion : TaikoSkinnableTestScene + { + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] + { + typeof(HitExplosion), + typeof(LegacyHitExplosion), + typeof(DefaultHitExplosion), + }).ToList(); + + [BackgroundDependencyLoader] + private void load() + { + AddStep("Great", () => SetContents(() => getContentFor(HitResult.Great))); + AddStep("Good", () => SetContents(() => getContentFor(HitResult.Good))); + AddStep("Miss", () => SetContents(() => getContentFor(HitResult.Miss))); + } + + private Drawable getContentFor(HitResult type) + { + DrawableTaikoHitObject hit; + + return new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + hit = createHit(type), + new HitExplosion(hit) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + } + }; + } + + private DrawableTaikoHitObject createHit(HitResult type) => new TestDrawableHit(new Hit { StartTime = Time.Current }, type); + + private class TestDrawableHit : DrawableTaikoHitObject + { + private readonly HitResult type; + + public TestDrawableHit(Hit hit, HitResult type) + : base(hit) + { + this.type = type; + } + + [BackgroundDependencyLoader] + private void load() + { + Result.Type = type; + } + + public override bool OnPressed(TaikoAction action) => false; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs new file mode 100644 index 0000000000..d29b574866 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Rulesets.Taiko.Skinning +{ + public class LegacyHitExplosion : CompositeDrawable + { + public LegacyHitExplosion(Drawable sprite) + { + InternalChild = sprite; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + AutoSizeAxes = Axes.Both; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + this.FadeIn(120); + this.ScaleTo(0.6f).Then().ScaleTo(1, 240, Easing.OutElastic); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs index 447d6ae455..bea1c6bdcf 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs @@ -81,11 +81,38 @@ namespace osu.Game.Rulesets.Taiko.Skinning return new LegacyBarLine(); return null; + + case TaikoSkinComponents.TaikoExplosionGood: + case TaikoSkinComponents.TaikoExplosionGreat: + case TaikoSkinComponents.TaikoExplosionMiss: + + var sprite = this.GetAnimation(getHitname(taikoComponent.Component), true, false); + if (sprite != null) + return new LegacyHitExplosion(sprite); + + return null; } return source.GetDrawableComponent(component); } + private string getHitname(TaikoSkinComponents component) + { + switch (component) + { + case TaikoSkinComponents.TaikoExplosionMiss: + return "taiko-hit0"; + + case TaikoSkinComponents.TaikoExplosionGood: + return "taiko-hit100"; + + case TaikoSkinComponents.TaikoExplosionGreat: + return "taiko-hit300"; + } + + return string.Empty; + } + public Texture GetTexture(string componentName) => source.GetTexture(componentName); public SampleChannel GetSample(ISampleInfo sampleInfo) => source.GetSample(new LegacyTaikoSampleInfo(sampleInfo)); diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs index a90ce608b2..fd091f97d0 100644 --- a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs +++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs @@ -14,6 +14,9 @@ namespace osu.Game.Rulesets.Taiko HitTarget, PlayfieldBackgroundLeft, PlayfieldBackgroundRight, - BarLine + BarLine, + TaikoExplosionMiss, + TaikoExplosionGood, + TaikoExplosionGreat, } } diff --git a/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs new file mode 100644 index 0000000000..aa444d0494 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs @@ -0,0 +1,54 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Objects; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Taiko.UI +{ + internal class DefaultHitExplosion : CircularContainer + { + [Resolved] + private DrawableHitObject judgedObject { get; set; } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.Both; + + BorderColour = Color4.White; + BorderThickness = 1; + + Alpha = 0.15f; + Masking = true; + + if (judgedObject.Result.Type == HitResult.Miss) + return; + + bool isRim = (judgedObject.HitObject as Hit)?.Type == HitType.Rim; + + InternalChildren = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = isRim ? colours.BlueDarker : colours.PinkDarker, + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + this.ScaleTo(3f, 1000, Easing.OutQuint); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs index fbaae7e322..9bef93d834 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs @@ -1,15 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osuTK; -using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Taiko.UI { @@ -20,15 +20,12 @@ namespace osu.Game.Rulesets.Taiko.UI { public override bool RemoveWhenNotAlive => true; + [Cached(typeof(DrawableHitObject))] public readonly DrawableHitObject JudgedObject; - private readonly HitType type; - private readonly Box innerFill; - - public HitExplosion(DrawableHitObject judgedObject, HitType type) + public HitExplosion(DrawableHitObject judgedObject) { JudgedObject = judgedObject; - this.type = type; Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -37,35 +34,36 @@ namespace osu.Game.Rulesets.Taiko.UI Size = new Vector2(TaikoHitObject.DEFAULT_SIZE); RelativePositionAxes = Axes.Both; - - BorderColour = Color4.White; - BorderThickness = 1; - - Alpha = 0.15f; - Masking = true; - - Children = new[] - { - innerFill = new Box - { - RelativeSizeAxes = Axes.Both, - } - }; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { - innerFill.Colour = type == HitType.Rim ? colours.BlueDarker : colours.PinkDarker; + Child = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(JudgedObject.Result?.Type ?? HitResult.Great)), _ => new DefaultHitExplosion()); + } + + private TaikoSkinComponents getComponentName(HitResult resultType) + { + switch (resultType) + { + case HitResult.Miss: + return TaikoSkinComponents.TaikoExplosionMiss; + + case HitResult.Good: + return TaikoSkinComponents.TaikoExplosionGood; + + case HitResult.Great: + return TaikoSkinComponents.TaikoExplosionGreat; + } + + throw new ArgumentException("Invalid result type", nameof(resultType)); } protected override void LoadComplete() { base.LoadComplete(); - this.ScaleTo(3f, 1000, Easing.OutQuint); this.FadeOut(500); - Expire(true); } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 4bc6cb8e4b..6a78c0a1fb 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -185,7 +185,6 @@ namespace osu.Game.Rulesets.Taiko.UI var drawableTick = (DrawableDrumRollTick)judgedObject; addDrumRollHit(drawableTick); - addExplosion(drawableTick, drawableTick.JudgementType); break; default: @@ -200,7 +199,9 @@ namespace osu.Game.Rulesets.Taiko.UI if (!result.IsHit) break; - addExplosion(judgedObject, (judgedObject.HitObject as Hit)?.Type ?? HitType.Centre); + var type = (judgedObject.HitObject as Hit)?.Type ?? HitType.Centre; + + addExplosion(judgedObject, type); break; } } @@ -210,7 +211,7 @@ namespace osu.Game.Rulesets.Taiko.UI private void addExplosion(DrawableHitObject drawableObject, HitType type) { - hitExplosionContainer.Add(new HitExplosion(drawableObject, type)); + hitExplosionContainer.Add(new HitExplosion(drawableObject)); if (drawableObject.HitObject.Kiai) kiaiExplosionContainer.Add(new KiaiHitExplosion(drawableObject, type)); } From df55439f8b15d167491fba706987278f50bd4ac0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Apr 2020 23:24:12 +0900 Subject: [PATCH 559/655] Remove undetected usings --- osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs index ca0c60b67a..3a9779c93b 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs @@ -8,8 +8,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects.Drawables; From a34ec03efc685c706004fb578da9fddb1f5855d2 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 27 Apr 2020 12:44:20 -0700 Subject: [PATCH 560/655] Reword width comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Bartłomiej Dach --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 09f4befbc1..b32875f723 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -272,7 +272,7 @@ namespace osu.Game.Overlays.Mods Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold), Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, - Width = 70, // to avoid footer from flowing when clicking mods + Width = 70, // to prevent footer from flowing when clicking mods }, }, }, From f387fe310f04cf87f182961e6209477bb6a9394a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Apr 2020 11:07:31 +0900 Subject: [PATCH 561/655] Fix regressing hits test --- .../DrawableTestHit.cs | 29 +++++++++++++++++++ .../Skinning/TestSceneHitExplosion.cs | 21 +------------- .../TestSceneHits.cs | 12 +++----- 3 files changed, 34 insertions(+), 28 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs new file mode 100644 index 0000000000..1db07b3244 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Objects.Drawables; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + internal class DrawableTestHit : DrawableTaikoHitObject + { + private readonly HitResult type; + + public DrawableTestHit(Hit hit, HitResult type = HitResult.Great) + : base(hit) + { + this.type = type; + } + + [BackgroundDependencyLoader] + private void load() + { + Result.Type = type; + } + + public override bool OnPressed(TaikoAction action) => false; + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs index 3a9779c93b..791c438c94 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs @@ -53,25 +53,6 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning }; } - private DrawableTaikoHitObject createHit(HitResult type) => new TestDrawableHit(new Hit { StartTime = Time.Current }, type); - - private class TestDrawableHit : DrawableTaikoHitObject - { - private readonly HitResult type; - - public TestDrawableHit(Hit hit, HitResult type) - : base(hit) - { - this.type = type; - } - - [BackgroundDependencyLoader] - private void load() - { - Result.Type = type; - } - - public override bool OnPressed(TaikoAction action) => false; - } + private DrawableTaikoHitObject createHit(HitResult type) => new DrawableTestHit(new Hit { StartTime = Time.Current }, type); } } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs index 23adb79083..44452d70c1 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs @@ -149,6 +149,8 @@ namespace osu.Game.Rulesets.Taiko.Tests var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; + Add(h); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult }); } @@ -164,6 +166,8 @@ namespace osu.Game.Rulesets.Taiko.Tests var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; + Add(h); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult }); ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great }); } @@ -249,13 +253,5 @@ namespace osu.Game.Rulesets.Taiko.Tests public override bool OnPressed(TaikoAction action) => false; } - - private class DrawableTestHit : DrawableHitObject - { - public DrawableTestHit(TaikoHitObject hitObject) - : base(hitObject) - { - } - } } } From 84641765c5858976fc2fa43606e77f60d4f7e7f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Apr 2020 11:08:19 +0900 Subject: [PATCH 562/655] Adjust exceptions and fix capitalisation --- .../Skinning/TaikoLegacySkinTransformer.cs | 7 ++++--- osu.Game.Rulesets.Taiko/UI/HitExplosion.cs | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs index bea1c6bdcf..f0df612e18 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; @@ -86,7 +87,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning case TaikoSkinComponents.TaikoExplosionGreat: case TaikoSkinComponents.TaikoExplosionMiss: - var sprite = this.GetAnimation(getHitname(taikoComponent.Component), true, false); + var sprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false); if (sprite != null) return new LegacyHitExplosion(sprite); @@ -96,7 +97,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning return source.GetDrawableComponent(component); } - private string getHitname(TaikoSkinComponents component) + private string getHitName(TaikoSkinComponents component) { switch (component) { @@ -110,7 +111,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning return "taiko-hit300"; } - return string.Empty; + throw new ArgumentOutOfRangeException(nameof(component), "Invalid result type"); } public Texture GetTexture(string componentName) => source.GetTexture(componentName); diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs index 9bef93d834..35a54d6ea7 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Taiko.UI return TaikoSkinComponents.TaikoExplosionGreat; } - throw new ArgumentException("Invalid result type", nameof(resultType)); + throw new ArgumentOutOfRangeException(nameof(resultType), "Invalid result type"); } protected override void LoadComplete() From 62be138aa912ca3cd77e53d3df39cff877b3f7f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Apr 2020 11:46:08 +0900 Subject: [PATCH 563/655] Avoid calls on MusicController executing before it may have finished loading --- .../TestSceneNowPlayingOverlay.cs | 17 +++++++++-------- osu.Game/Overlays/MusicController.cs | 18 +++++++++++------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs index 2ea9aec50a..e2913833a7 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs @@ -47,18 +47,19 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestPrevTrackBehavior() { - AddStep(@"Play track", () => - { - musicController.NextTrack(); - currentBeatmap = Beatmap.Value; - }); + AddStep(@"Next track", () => musicController.NextTrack()); + AddStep("Store track", () => currentBeatmap = Beatmap.Value); AddStep(@"Seek track to 6 second", () => musicController.SeekTo(6000)); AddUntilStep(@"Wait for current time to update", () => currentBeatmap.Track.CurrentTime > 5000); - AddAssert(@"Check action is restart track", () => musicController.PreviousTrack() == PreviousTrackResult.Restart); - AddUntilStep("Wait for current time to update", () => Precision.AlmostEquals(currentBeatmap.Track.CurrentTime, 0)); + + AddStep(@"Set previous", () => musicController.PreviousTrack()); + AddAssert(@"Check track didn't change", () => currentBeatmap == Beatmap.Value); - AddAssert(@"Check action is not restart", () => musicController.PreviousTrack() != PreviousTrackResult.Restart); + AddUntilStep("Wait for current time to update", () => currentBeatmap.Track.CurrentTime < 5000); + + AddStep(@"Set previous", () => musicController.PreviousTrack()); + AddAssert(@"Check track did change", () => currentBeatmap != Beatmap.Value); } } } diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index d788929739..6d269aa944 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -172,10 +172,15 @@ namespace osu.Game.Overlays } /// - /// Play the previous track or restart the current track if it's current time below + /// Play the previous track or restart the current track if it's current time below . /// - /// The that indicate the decided action - public PreviousTrackResult PreviousTrack() + public void PreviousTrack() => Schedule(() => prev()); + + /// + /// Play the previous track or restart the current track if it's current time below . + /// + /// The that indicate the decided action. + private PreviousTrackResult prev() { var currentTrackPosition = current?.Track.CurrentTime; @@ -204,8 +209,7 @@ namespace osu.Game.Overlays /// /// Play the next random or playlist track. /// - /// Whether the operation was successful. - public bool NextTrack() => next(); + public void NextTrack() => Schedule(() => next()); private bool next(bool instant = false) { @@ -319,13 +323,13 @@ namespace osu.Game.Overlays return true; case GlobalAction.MusicNext: - if (NextTrack()) + if (next()) onScreenDisplay?.Display(new MusicControllerToast("Next track")); return true; case GlobalAction.MusicPrev: - switch (PreviousTrack()) + switch (prev()) { case PreviousTrackResult.Restart: onScreenDisplay?.Display(new MusicControllerToast("Restart track")); From 4fff08d241a2158da662863426e07fa1f1621f8a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 28 Apr 2020 12:19:39 +0900 Subject: [PATCH 564/655] Remove unused using --- .../Visual/UserInterface/TestSceneNowPlayingOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs index e2913833a7..9944e6f9d0 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs @@ -4,7 +4,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Game.Rulesets.Osu; From 81e73acb1a05863fbda05603e3505a8fe5c79931 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Apr 2020 12:14:22 +0900 Subject: [PATCH 565/655] Fix tests failing when not run in certain order --- .../TestSceneNowPlayingOverlay.cs | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs index 9944e6f9d0..a9c52bd189 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -43,9 +44,29 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep(@"hide", () => nowPlayingOverlay.Hide()); } + [Resolved] + private BeatmapManager manager { get; set; } + [Test] public void TestPrevTrackBehavior() { + // ensure we have at least two beatmaps available. + AddRepeatStep("import beatmap", () => manager.Import(new BeatmapSetInfo + { + Beatmaps = new List + { + new BeatmapInfo + { + BaseDifficulty = new BeatmapDifficulty(), + } + }, + Metadata = new BeatmapMetadata + { + Artist = "a test map", + Title = "title", + } + }).Wait(), 5); + AddStep(@"Next track", () => musicController.NextTrack()); AddStep("Store track", () => currentBeatmap = Beatmap.Value); @@ -54,11 +75,11 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep(@"Set previous", () => musicController.PreviousTrack()); - AddAssert(@"Check track didn't change", () => currentBeatmap == Beatmap.Value); + AddAssert(@"Check beatmap didn't change", () => currentBeatmap == Beatmap.Value); AddUntilStep("Wait for current time to update", () => currentBeatmap.Track.CurrentTime < 5000); AddStep(@"Set previous", () => musicController.PreviousTrack()); - AddAssert(@"Check track did change", () => currentBeatmap != Beatmap.Value); + AddAssert(@"Check beatmap did change", () => currentBeatmap != Beatmap.Value); } } } From 743a92bbbedcd696d7df90bb07dd1bdb049e0b79 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Apr 2020 12:40:56 +0900 Subject: [PATCH 566/655] Use a local database for now playing test --- .../UserInterface/TestSceneNowPlayingOverlay.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs index a9c52bd189..ee922a073a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs @@ -4,9 +4,12 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Graphics; +using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Overlays; +using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; namespace osu.Game.Tests.Visual.UserInterface @@ -21,9 +24,14 @@ namespace osu.Game.Tests.Visual.UserInterface private NowPlayingOverlay nowPlayingOverlay; + private RulesetStore rulesets; + [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio, GameHost host) { + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default)); + Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); nowPlayingOverlay = new NowPlayingOverlay @@ -44,9 +52,10 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep(@"hide", () => nowPlayingOverlay.Hide()); } - [Resolved] private BeatmapManager manager { get; set; } + private int importId = 0; + [Test] public void TestPrevTrackBehavior() { @@ -62,7 +71,7 @@ namespace osu.Game.Tests.Visual.UserInterface }, Metadata = new BeatmapMetadata { - Artist = "a test map", + Artist = $"a test map {importId++}", Title = "title", } }).Wait(), 5); From 0d752dc7b847cc162a3076f72c20e487833e724e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Apr 2020 12:55:57 +0900 Subject: [PATCH 567/655] Remove redundant initialisation --- .../Visual/UserInterface/TestSceneNowPlayingOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs index ee922a073a..532744a0fc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.UserInterface private BeatmapManager manager { get; set; } - private int importId = 0; + private int importId; [Test] public void TestPrevTrackBehavior() From 832fa74a5e06146fc7572b841f7d957c9d44afdc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Apr 2020 13:26:42 +0900 Subject: [PATCH 568/655] Reword comment slightly --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index b32875f723..3d0ad1a594 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -272,7 +272,7 @@ namespace osu.Game.Overlays.Mods Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold), Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, - Width = 70, // to prevent footer from flowing when clicking mods + Width = 70, // make width fixed so reflow doesn't occur when multiplier number changes. }, }, }, From 7342e0015161a7b7e339370ac4aa6aadcee6f3ce Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Apr 2020 14:00:14 +0900 Subject: [PATCH 569/655] Convert positions to local HOC coordinate space --- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 8d012c63ac..b415c9f0c9 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -46,9 +46,7 @@ namespace osu.Game.Rulesets.Mania.Edit { var hoc = Playfield.GetColumn(0).HitObjectContainer; - position.Y -= ToLocalSpace(hoc.ScreenSpaceDrawQuad.TopLeft).Y; - - float targetPosition = position.Y; + float targetPosition = hoc.ToLocalSpace(ToScreenSpace(position)).Y; if (drawableRuleset.ScrollingInfo.Direction.Value == ScrollingDirection.Down) { From da30eafa3020d3c1479e32ca4e37f43e114098ad Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Apr 2020 14:47:45 +0900 Subject: [PATCH 570/655] Prevent potential exception --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 5062c92afe..7170d425e2 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.UI /// public PassThroughInputManager KeyBindingInputManager; - public override double GameplayStartTime => Objects.First().StartTime - 2000; + public override double GameplayStartTime => Objects.FirstOrDefault()?.StartTime - 2000 ?? 0; private readonly Lazy playfield; From d905ef53b37ac287c2072fb8bcb26c8704211c27 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Apr 2020 14:47:52 +0900 Subject: [PATCH 571/655] Add test scene for mania composer --- .../TestSceneManiaHitObjectComposer.cs | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs new file mode 100644 index 0000000000..3cd2530f36 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs @@ -0,0 +1,69 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Edit; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Screens.Edit; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Mania.Tests +{ + public class TestSceneManiaHitObjectComposer : EditorClockTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ManiaBlueprintContainer) + }; + + [Cached(typeof(EditorBeatmap))] + [Cached(typeof(IBeatSnapProvider))] + private readonly EditorBeatmap editorBeatmap; + + protected override Container Content { get; } + + private ManiaHitObjectComposer composer; + + public TestSceneManiaHitObjectComposer() + { + base.Content.Add(new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 })) + { + BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo } + }, + Content = new Container + { + RelativeSizeAxes = Axes.Both, + } + }, + }); + + for (int i = 0; i < 10; i++) + { + editorBeatmap.Add(new Note { StartTime = 100 * i }); + } + } + + [SetUp] + public void Setup() => Schedule(() => + { + Children = new Drawable[] + { + composer = new ManiaHitObjectComposer(new ManiaRuleset()) + }; + + BeatDivisor.Value = 8; + }); + } +} From 330521a2ae7f268b1645c03fc7feab010c48ce74 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Apr 2020 15:34:10 +0900 Subject: [PATCH 572/655] Fix lifetime override not working --- .../Drawables/DrawableManiaHitObject.cs | 62 +++++++++++++++++-- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 88888001b4..a44d8b09aa 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -13,11 +13,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { public abstract class DrawableManiaHitObject : DrawableHitObject { - /// - /// Whether this should always remain alive. - /// - internal bool AlwaysAlive; - /// /// The which causes this to be hit. /// @@ -54,7 +49,62 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Direction.BindValueChanged(OnDirectionChanged, true); } - protected override bool ShouldBeAlive => AlwaysAlive || base.ShouldBeAlive; + private double computedLifetimeStart; + + public override double LifetimeStart + { + get => base.LifetimeStart; + set + { + computedLifetimeStart = value; + + if (!AlwaysAlive) + base.LifetimeStart = value; + } + } + + private double computedLifetimeEnd; + + public override double LifetimeEnd + { + get => base.LifetimeEnd; + set + { + computedLifetimeEnd = value; + + if (!AlwaysAlive) + base.LifetimeEnd = value; + } + } + + private bool alwaysAlive; + + /// + /// Whether this should always remain alive. + /// + internal bool AlwaysAlive + { + get => alwaysAlive; + set + { + if (alwaysAlive == value) + return; + + alwaysAlive = value; + + if (value) + { + // Set the base lifetimes directly, to avoid mangling the computed lifetimes + base.LifetimeStart = double.MinValue; + base.LifetimeEnd = double.MaxValue; + } + else + { + LifetimeStart = computedLifetimeStart; + LifetimeEnd = computedLifetimeEnd; + } + } + } protected virtual void OnDirectionChanged(ValueChangedEvent e) { From 3eb7c8755c1ed9e21a36ab0cee16047aab20f6e1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Apr 2020 15:34:41 +0900 Subject: [PATCH 573/655] Cleanup --- .../TestSceneManiaHitObjectComposer.cs | 4 +-- .../Blueprints/ManiaSelectionBlueprint.cs | 29 ------------------- .../Edit/ManiaSelectionHandler.cs | 11 ------- 3 files changed, 1 insertion(+), 43 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs index 3cd2530f36..180ceb94f4 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs @@ -29,8 +29,6 @@ namespace osu.Game.Rulesets.Mania.Tests protected override Container Content { get; } - private ManiaHitObjectComposer composer; - public TestSceneManiaHitObjectComposer() { base.Content.Add(new Container @@ -60,7 +58,7 @@ namespace osu.Game.Rulesets.Mania.Tests { Children = new Drawable[] { - composer = new ManiaHitObjectComposer(new ManiaRuleset()) + new ManiaHitObjectComposer(new ManiaRuleset()) }; BeatDivisor.Value = 8; diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs index 14d52dd08e..b03bf7c078 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -3,8 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Input.Events; -using osu.Framework.Timing; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables; @@ -15,13 +13,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { public class ManiaSelectionBlueprint : OverlaySelectionBlueprint { - public Vector2 ScreenSpaceDragPosition { get; private set; } - public Vector2 DragPosition { get; private set; } - public new DrawableManiaHitObject DrawableObject => (DrawableManiaHitObject)base.DrawableObject; - protected IClock EditorClock { get; private set; } - [Resolved] private IScrollingInfo scrollingInfo { get; set; } @@ -34,12 +27,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints RelativeSizeAxes = Axes.None; } - [BackgroundDependencyLoader] - private void load(IAdjustableClock clock) - { - EditorClock = clock; - } - protected override void Update() { base.Update(); @@ -47,22 +34,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints Position = Parent.ToLocalSpace(DrawableObject.ToScreenSpace(Vector2.Zero)); } - protected override bool OnMouseDown(MouseDownEvent e) - { - ScreenSpaceDragPosition = e.ScreenSpaceMousePosition; - DragPosition = DrawableObject.ToLocalSpace(e.ScreenSpaceMousePosition); - - return base.OnMouseDown(e); - } - - protected override void OnDrag(DragEvent e) - { - base.OnDrag(e); - - ScreenSpaceDragPosition = e.ScreenSpaceMousePosition; - DragPosition = DrawableObject.ToLocalSpace(e.ScreenSpaceMousePosition); - } - public override void Show() { DrawableObject.AlwaysAlive = true; diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 989e0f5b01..11e9b56a53 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -4,11 +4,9 @@ using System; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Timing; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit.Compose.Components; @@ -22,14 +20,6 @@ namespace osu.Game.Rulesets.Mania.Edit [Resolved] private IManiaHitObjectComposer composer { get; set; } - private IClock editorClock; - - [BackgroundDependencyLoader] - private void load(IAdjustableClock clock) - { - editorClock = clock; - } - public override bool HandleMovement(MoveSelectionEvent moveEvent) { var maniaBlueprint = (ManiaSelectionBlueprint)moveEvent.Blueprint; @@ -50,7 +40,6 @@ namespace osu.Game.Rulesets.Mania.Edit var b = (OverlaySelectionBlueprint)selectionBlueprint; var hitObject = b.DrawableObject; - var objectParent = (HitObjectContainer)hitObject.Parent; // We receive multiple movement events per frame such that we can't rely on updating the start time // since the scrolling hitobject container requires at least one update frame to update the position. From 31c3fd86b9ffc1117cff3628a4441309255c504b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Apr 2020 16:22:00 +0900 Subject: [PATCH 574/655] Avoid using internal EF methods in tests --- osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs b/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs index 2d4587341d..b7b48ec06a 100644 --- a/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs +++ b/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs @@ -1,9 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; -using Microsoft.EntityFrameworkCore.Internal; using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Rulesets.Objects; @@ -137,7 +137,7 @@ namespace osu.Game.Tests.Beatmaps var hitCircle = new HitCircle { StartTime = 1000 }; editorBeatmap.Add(hitCircle); Assert.That(editorBeatmap.HitObjects.Count(h => h == hitCircle), Is.EqualTo(1)); - Assert.That(editorBeatmap.HitObjects.IndexOf(hitCircle), Is.EqualTo(3)); + Assert.That(Array.IndexOf(editorBeatmap.HitObjects.ToArray(), hitCircle), Is.EqualTo(3)); } /// @@ -161,7 +161,7 @@ namespace osu.Game.Tests.Beatmaps hitCircle.StartTime = 0; Assert.That(editorBeatmap.HitObjects.Count(h => h == hitCircle), Is.EqualTo(1)); - Assert.That(editorBeatmap.HitObjects.IndexOf(hitCircle), Is.EqualTo(1)); + Assert.That(Array.IndexOf(editorBeatmap.HitObjects.ToArray(), hitCircle), Is.EqualTo(1)); } /// From f3fbb3cdc6595bda5e66382b01027bcdf4498ce1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Apr 2020 16:49:43 +0900 Subject: [PATCH 575/655] Add to banned symbols --- CodeAnalysis/BannedSymbols.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index a92191a439..4d7135a195 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -4,3 +4,4 @@ M:System.ValueType.Equals(System.Object)~System.Boolean;Don't use object.Equals( M:System.Nullable`1.Equals(System.Object)~System.Boolean;Use == instead. T:System.IComparable;Don't use non-generic IComparable. Use generic version instead. M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText. +T:Microsoft.EntityFrameworkCore.Internal.EnumerableExtensions;Don't use internal extension methods. \ No newline at end of file From c3a41c8476d97a5710e90b6da19e72787b026449 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Apr 2020 16:56:36 +0900 Subject: [PATCH 576/655] Also avoid using internal TypeExtensions --- CodeAnalysis/BannedSymbols.txt | 3 ++- osu.Game/Screens/OsuScreen.cs | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index 4d7135a195..e34626a59e 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -4,4 +4,5 @@ M:System.ValueType.Equals(System.Object)~System.Boolean;Don't use object.Equals( M:System.Nullable`1.Equals(System.Object)~System.Boolean;Use == instead. T:System.IComparable;Don't use non-generic IComparable. Use generic version instead. M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText. -T:Microsoft.EntityFrameworkCore.Internal.EnumerableExtensions;Don't use internal extension methods. \ No newline at end of file +T:Microsoft.EntityFrameworkCore.Internal.EnumerableExtensions;Don't use internal extension methods. +T:Microsoft.EntityFrameworkCore.Internal.TypeExtensions;Don't use internal extension methods. diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 61e94ae969..2124a66a75 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using Microsoft.EntityFrameworkCore.Internal; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -31,7 +30,7 @@ namespace osu.Game.Screens /// /// A user-facing title for this screen. /// - public virtual string Title => GetType().ShortDisplayName(); + public virtual string Title => GetType().Name; public string Description => Title; From e5131400e77d7ba9c213259ad883fc715206b9f7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Apr 2020 18:34:39 +0900 Subject: [PATCH 577/655] Remove now unnecessary position manipulation --- .../Edit/ManiaSelectionHandler.cs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 11e9b56a53..55245198c8 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using osu.Framework.Allocation; -using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.UI.Scrolling; @@ -25,29 +24,11 @@ namespace osu.Game.Rulesets.Mania.Edit var maniaBlueprint = (ManiaSelectionBlueprint)moveEvent.Blueprint; int lastColumn = maniaBlueprint.DrawableObject.HitObject.Column; - performDragMovement(moveEvent); performColumnMovement(lastColumn, moveEvent); return true; } - private void performDragMovement(MoveSelectionEvent moveEvent) - { - float delta = moveEvent.InstantDelta.Y; - - foreach (var selectionBlueprint in SelectedBlueprints) - { - var b = (OverlaySelectionBlueprint)selectionBlueprint; - - var hitObject = b.DrawableObject; - - // We receive multiple movement events per frame such that we can't rely on updating the start time - // since the scrolling hitobject container requires at least one update frame to update the position. - // However the position needs to be valid for future movement events to calculate the correct deltas. - hitObject.Y += delta; - } - } - private void performColumnMovement(int lastColumn, MoveSelectionEvent moveEvent) { var currentColumn = composer.ColumnAt(moveEvent.ScreenSpacePosition); From a7a680b4862e5adf6d0579b81534f07b169488c4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Apr 2020 18:34:55 +0900 Subject: [PATCH 578/655] Fix horizontal drag not working --- osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index f9faa262ed..6960d15f31 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Mania.UI { foreach (var column in stage.Columns) { - if (column.ReceivePositionalInputAt(screenSpacePosition)) + if (column.ReceivePositionalInputAt(new Vector2(screenSpacePosition.X, column.ScreenSpaceDrawQuad.Centre.Y))) { found = column; break; From f93291e25b4967edd658d3e309d33905684d5343 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Apr 2020 18:35:22 +0900 Subject: [PATCH 579/655] Remove unused override --- .../Blueprints/ManiaSelectionBlueprint.cs | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs index b03bf7c078..b8574b804e 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -45,25 +45,5 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints DrawableObject.AlwaysAlive = false; base.Hide(); } - - public override Vector2 GetInstantDelta(Vector2 screenSpacePosition) - { - var baseDelta = base.GetInstantDelta(screenSpacePosition); - - if (scrollingInfo.Direction.Value == ScrollingDirection.Down) - { - // The parent of DrawableObject is the scrolling hitobject container (SHOC). - // In the coordinate-space of the SHOC, the screen-space position at the hit target is equal to the height of the SHOC, - // but this is not what we want as it means a slight movement downwards results in a delta greater than the height of the SHOC. - // To get around this issue, the height of the SHOC is subtracted from the delta. - // - // Ideally this should be a _negative_ value in the case described above, however this code gives a _positive_ delta. - // This is intentional as the delta is added to the hitobject's position (see: ManiaSelectionHandler) and a negative delta would move them towards the top of the screen instead, - // which would cause the delta to get increasingly larger as additional movements are performed. - return new Vector2(baseDelta.X, baseDelta.Y - DrawableObject.Parent.DrawHeight); - } - - return baseDelta; - } } } From 7d54d4b800234bb017602dc49e8b2c4a5134193e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Apr 2020 18:35:37 +0900 Subject: [PATCH 580/655] Improve test scene --- .../TestSceneManiaHitObjectComposer.cs | 171 ++++++++++++++---- .../Edit/ManiaHitObjectComposer.cs | 2 + osu.Game/Tests/Visual/EditorClockTestScene.cs | 2 +- 3 files changed, 140 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs index 180ceb94f4..4ce2424ffa 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs @@ -3,16 +3,23 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Edit; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit; using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Input; namespace osu.Game.Rulesets.Mania.Tests { @@ -23,45 +30,141 @@ namespace osu.Game.Rulesets.Mania.Tests typeof(ManiaBlueprintContainer) }; - [Cached(typeof(EditorBeatmap))] - [Cached(typeof(IBeatSnapProvider))] - private readonly EditorBeatmap editorBeatmap; - - protected override Container Content { get; } - - public TestSceneManiaHitObjectComposer() - { - base.Content.Add(new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 })) - { - BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo } - }, - Content = new Container - { - RelativeSizeAxes = Axes.Both, - } - }, - }); - - for (int i = 0; i < 10; i++) - { - editorBeatmap.Add(new Note { StartTime = 100 * i }); - } - } + private TestComposer composer; [SetUp] public void Setup() => Schedule(() => { - Children = new Drawable[] - { - new ManiaHitObjectComposer(new ManiaRuleset()) - }; - BeatDivisor.Value = 8; + Clock.Seek(0); + + Child = composer = new TestComposer { RelativeSizeAxes = Axes.Both }; }); + + [Test] + public void TestDragOffscreenSelectionVerticallyUpScroll() + { + DrawableHitObject lastObject = null; + Vector2 originalPosition = Vector2.Zero; + + AddStep("seek to last object", () => + { + lastObject = this.ChildrenOfType().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last()); + Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime); + }); + + AddStep("select all objects", () => composer.EditorBeatmap.SelectedHitObjects.AddRange(composer.EditorBeatmap.HitObjects)); + + AddStep("click last object", () => + { + originalPosition = lastObject.DrawPosition; + + InputManager.MoveMouseTo(lastObject); + InputManager.PressButton(MouseButton.Left); + }); + + AddStep("move mouse downwards", () => + { + InputManager.MoveMouseTo(lastObject, new Vector2(0, 20)); + InputManager.ReleaseButton(MouseButton.Left); + }); + + AddAssert("hitobjects not moved columns", () => composer.EditorBeatmap.HitObjects.All(h => ((ManiaHitObject)h).Column == 0)); + AddAssert("hitobjects moved downwards", () => lastObject.DrawPosition.Y - originalPosition.Y > 0); + AddAssert("hitobjects not moved too far", () => lastObject.DrawPosition.Y - originalPosition.Y < 50); + } + + [Test] + public void TestDragOffscreenSelectionVerticallyDownScroll() + { + DrawableHitObject lastObject = null; + Vector2 originalPosition = Vector2.Zero; + + AddStep("set down scroll", () => ((Bindable)composer.Composer.ScrollingInfo.Direction).Value = ScrollingDirection.Down); + + AddStep("seek to last object", () => + { + lastObject = this.ChildrenOfType().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last()); + Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime); + }); + + AddStep("select all objects", () => composer.EditorBeatmap.SelectedHitObjects.AddRange(composer.EditorBeatmap.HitObjects)); + + AddStep("click last object", () => + { + originalPosition = lastObject.DrawPosition; + + InputManager.MoveMouseTo(lastObject); + InputManager.PressButton(MouseButton.Left); + }); + + AddStep("move mouse upwards", () => + { + InputManager.MoveMouseTo(lastObject, new Vector2(0, -20)); + InputManager.ReleaseButton(MouseButton.Left); + }); + + AddAssert("hitobjects not moved columns", () => composer.EditorBeatmap.HitObjects.All(h => ((ManiaHitObject)h).Column == 0)); + AddAssert("hitobjects moved upwards", () => originalPosition.Y - lastObject.DrawPosition.Y > 0); + AddAssert("hitobjects not moved too far", () => originalPosition.Y - lastObject.DrawPosition.Y < 50); + } + + [Test] + public void TestDragOffscreenSelectionHorizontally() + { + DrawableHitObject lastObject = null; + Vector2 originalPosition = Vector2.Zero; + + AddStep("seek to last object", () => + { + lastObject = this.ChildrenOfType().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last()); + originalPosition = lastObject.DrawPosition; + + Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime); + }); + + AddStep("select all objects", () => composer.EditorBeatmap.SelectedHitObjects.AddRange(composer.EditorBeatmap.HitObjects)); + + AddStep("click last object", () => + { + InputManager.MoveMouseTo(lastObject); + InputManager.PressButton(MouseButton.Left); + }); + + AddStep("move mouse right", () => + { + InputManager.MoveMouseTo(lastObject, new Vector2(40, 0)); + InputManager.ReleaseButton(MouseButton.Left); + }); + + AddAssert("hitobjects moved columns", () => composer.EditorBeatmap.HitObjects.All(h => ((ManiaHitObject)h).Column == 1)); + + // Todo: They'll have moved vertically by half the height of a note. Probably a problem. + AddAssert("hitobjects not moved vertically", () => lastObject.DrawPosition.Y - originalPosition.Y < 10); + } + + private class TestComposer : CompositeDrawable + { + [Cached(typeof(EditorBeatmap))] + [Cached(typeof(IBeatSnapProvider))] + public readonly EditorBeatmap EditorBeatmap; + + public readonly ManiaHitObjectComposer Composer; + + public TestComposer() + { + InternalChildren = new Drawable[] + { + EditorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 })) + { + BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo } + }, + Composer = new ManiaHitObjectComposer(new ManiaRuleset()) + }; + + for (int i = 0; i < 10; i++) + EditorBeatmap.Add(new Note { StartTime = 100 * i }); + } + } } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index b415c9f0c9..fba80f92d2 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -40,6 +40,8 @@ namespace osu.Game.Rulesets.Mania.Edit public ManiaPlayfield Playfield => ((ManiaPlayfield)drawableRuleset.Playfield); + public IScrollingInfo ScrollingInfo => drawableRuleset.ScrollingInfo; + public int TotalColumns => Playfield.TotalColumns; public override (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) diff --git a/osu.Game/Tests/Visual/EditorClockTestScene.cs b/osu.Game/Tests/Visual/EditorClockTestScene.cs index 58a443ed3d..830e6ed363 100644 --- a/osu.Game/Tests/Visual/EditorClockTestScene.cs +++ b/osu.Game/Tests/Visual/EditorClockTestScene.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tests.Visual /// Provides a clock, beat-divisor, and scrolling capability for test cases of editor components that /// are preferrably tested within the presence of a clock and seek controls. /// - public abstract class EditorClockTestScene : OsuTestScene + public abstract class EditorClockTestScene : OsuManualInputManagerTestScene { protected readonly BindableBeatDivisor BeatDivisor = new BindableBeatDivisor(); protected new readonly EditorClock Clock; From ff24a15760167b1db112239b6c3418870654f5a1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Apr 2020 18:36:24 +0900 Subject: [PATCH 581/655] Fix vertical drag in down-scroll scenarios --- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index fba80f92d2..af465af16a 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -52,9 +52,9 @@ namespace osu.Game.Rulesets.Mania.Edit if (drawableRuleset.ScrollingInfo.Direction.Value == ScrollingDirection.Down) { - // When scrolling downwards, the position is _negative_ when the object's start time is after the current time (e.g. in the middle of the stage). - // However all scrolling algorithms upwards scrolling, meaning that a positive (inverse) position is expected in the same scenario. - targetPosition = -targetPosition; + // We're dealing with screen coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time. + // The scrolling algorithm assumes a top anchor meaning an increase in time corresponds to an increase in position, so when scrolling downwards the coordinates need to be flipped. + targetPosition = hoc.DrawHeight - targetPosition; } double targetTime = drawableRuleset.ScrollingInfo.Algorithm.TimeAt(targetPosition, From db12fafc2c3187220488e6235acc4f93cfa97687 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Apr 2020 18:53:30 +0900 Subject: [PATCH 582/655] Update comment --- .../TestSceneManiaHitObjectComposer.cs | 5 +++-- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs index 4ce2424ffa..a84fe83245 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs @@ -14,6 +14,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Edit; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit; @@ -139,8 +140,8 @@ namespace osu.Game.Rulesets.Mania.Tests AddAssert("hitobjects moved columns", () => composer.EditorBeatmap.HitObjects.All(h => ((ManiaHitObject)h).Column == 1)); - // Todo: They'll have moved vertically by half the height of a note. Probably a problem. - AddAssert("hitobjects not moved vertically", () => lastObject.DrawPosition.Y - originalPosition.Y < 10); + // Todo: They'll move vertically by the height of a note since there's no snapping and the selection point is the middle of the note. + AddAssert("hitobjects not moved vertically", () => lastObject.DrawPosition.Y - originalPosition.Y <= DefaultNotePiece.NOTE_HEIGHT); } private class TestComposer : CompositeDrawable diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index af465af16a..dfa933baad 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -53,7 +53,8 @@ namespace osu.Game.Rulesets.Mania.Edit if (drawableRuleset.ScrollingInfo.Direction.Value == ScrollingDirection.Down) { // We're dealing with screen coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time. - // The scrolling algorithm assumes a top anchor meaning an increase in time corresponds to an increase in position, so when scrolling downwards the coordinates need to be flipped. + // The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position, + // so when scrolling downwards the coordinates need to be flipped. targetPosition = hoc.DrawHeight - targetPosition; } From ff3928465c06a7e578afe1088a9b0a045ea2eca7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Apr 2020 18:55:58 +0900 Subject: [PATCH 583/655] Add xmldoc --- osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 6960d15f31..1af7d06998 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -87,8 +87,17 @@ namespace osu.Game.Rulesets.Mania.UI return found; } + /// + /// Retrieves a by index. + /// + /// The index of the column. + /// The corresponding to the given index. + /// If is less than 0 or greater than . public Column GetColumn(int index) { + if (index < 0 || index > TotalColumns - 1) + throw new ArgumentOutOfRangeException(nameof(index)); + foreach (var stage in stages) { if (index >= stage.Columns.Count) From 1aaab40228baa33ccb6cadadf07e84805fa48f66 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Apr 2020 19:23:33 +0900 Subject: [PATCH 584/655] Fix mods affecting mania scroll speed --- .../UI/DrawableManiaRuleset.cs | 25 ++++++++++++++++--- osu.Game/Rulesets/UI/DrawableRuleset.cs | 8 +++--- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 14cad39b04..39f3331fbb 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; @@ -48,6 +50,10 @@ namespace osu.Game.Rulesets.Mania.UI protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config; private readonly Bindable configDirection = new Bindable(); + private readonly Bindable configTimeRange = new Bindable(); + + // Stores the current speed adjustment active in gameplay. + private readonly Track speedAdjustmentTrack = new TrackVirtual(1000); public DrawableManiaRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset, beatmap, mods) @@ -58,6 +64,9 @@ namespace osu.Game.Rulesets.Mania.UI [BackgroundDependencyLoader] private void load() { + foreach (var mod in Mods.OfType()) + mod.ApplyToTrack(speedAdjustmentTrack); + bool isForCurrentRuleset = Beatmap.BeatmapInfo.Ruleset.Equals(Ruleset.RulesetInfo); foreach (var p in ControlPoints) @@ -76,7 +85,8 @@ namespace osu.Game.Rulesets.Mania.UI Config.BindWith(ManiaRulesetSetting.ScrollDirection, configDirection); configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true); - Config.BindWith(ManiaRulesetSetting.ScrollTime, TimeRange); + Config.BindWith(ManiaRulesetSetting.ScrollTime, configTimeRange); + configTimeRange.BindValueChanged(_ => updateTimeRange()); } protected override void AdjustScrollSpeed(int amount) @@ -86,10 +96,19 @@ namespace osu.Game.Rulesets.Mania.UI private double relativeTimeRange { - get => MAX_TIME_RANGE / TimeRange.Value; - set => TimeRange.Value = MAX_TIME_RANGE / value; + get => MAX_TIME_RANGE / configTimeRange.Value; + set => configTimeRange.Value = MAX_TIME_RANGE / value; } + protected override void Update() + { + base.Update(); + + updateTimeRange(); + } + + private void updateTimeRange() => TimeRange.Value = configTimeRange.Value * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value; + /// /// Retrieves the column that intersects a screen-space position. /// diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 5062c92afe..0a1c35c7c6 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.UI /// The mods which are to be applied. /// [Cached(typeof(IReadOnlyList))] - private readonly IReadOnlyList mods; + protected readonly IReadOnlyList Mods; private FrameStabilityContainer frameStabilityContainer; @@ -129,7 +129,7 @@ namespace osu.Game.Rulesets.UI throw new ArgumentException($"{GetType()} expected the beatmap to contain hitobjects of type {typeof(TObject)}.", nameof(beatmap)); Beatmap = tBeatmap; - this.mods = mods?.ToArray() ?? Array.Empty(); + Mods = mods?.ToArray() ?? Array.Empty(); RelativeSizeAxes = Axes.Both; @@ -204,7 +204,7 @@ namespace osu.Game.Rulesets.UI .WithChild(ResumeOverlay))); } - applyRulesetMods(mods, config); + applyRulesetMods(Mods, config); loadObjects(cancellationToken); } @@ -224,7 +224,7 @@ namespace osu.Game.Rulesets.UI Playfield.PostProcess(); - foreach (var mod in mods.OfType()) + foreach (var mod in Mods.OfType()) mod.ApplyToDrawableHitObjects(Playfield.AllHitObjects); } From 7868c0dad5cff824fe5bf7c90c0a8d4dea71d5eb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Apr 2020 20:15:56 +0900 Subject: [PATCH 585/655] Fix test case failures --- .../TestSceneManiaHitObjectComposer.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs index a84fe83245..286e3f6e50 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs @@ -119,8 +119,6 @@ namespace osu.Game.Rulesets.Mania.Tests AddStep("seek to last object", () => { lastObject = this.ChildrenOfType().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last()); - originalPosition = lastObject.DrawPosition; - Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime); }); @@ -128,13 +126,18 @@ namespace osu.Game.Rulesets.Mania.Tests AddStep("click last object", () => { + originalPosition = lastObject.DrawPosition; + InputManager.MoveMouseTo(lastObject); InputManager.PressButton(MouseButton.Left); }); AddStep("move mouse right", () => { - InputManager.MoveMouseTo(lastObject, new Vector2(40, 0)); + var firstColumn = composer.Composer.Playfield.GetColumn(0); + var secondColumn = composer.Composer.Playfield.GetColumn(1); + + InputManager.MoveMouseTo(lastObject, new Vector2(secondColumn.ScreenSpaceDrawQuad.Centre.X - firstColumn.ScreenSpaceDrawQuad.Centre.X + 1, 0)); InputManager.ReleaseButton(MouseButton.Left); }); From 119000f1ab2157762863743d25d111b936f6a4a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Apr 2020 21:43:35 +0900 Subject: [PATCH 586/655] Reduce database includes where possible --- osu.Game/Beatmaps/BeatmapManager.cs | 54 ++++++++++++++++++++-- osu.Game/Beatmaps/BeatmapStore.cs | 12 +++++ osu.Game/Overlays/MusicController.cs | 2 +- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- 6 files changed, 67 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 6542866936..5651d07566 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -246,6 +246,12 @@ namespace osu.Game.Beatmaps if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo) return DefaultBeatmap; + if (beatmapInfo.BeatmapSet.Files == null) + { + var info = beatmapInfo; + beatmapInfo = QueryBeatmap(b => b.ID == info.ID); + } + lock (workingCache) { var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID); @@ -287,13 +293,34 @@ namespace osu.Game.Beatmaps /// Returns a list of all usable s. /// /// A list of available . - public List GetAllUsableBeatmapSets() => GetAllUsableBeatmapSetsEnumerable().ToList(); + public List GetAllUsableBeatmapSets(IncludedDetails includes = IncludedDetails.All) => GetAllUsableBeatmapSetsEnumerable(includes).ToList(); /// - /// Returns a list of all usable s. + /// Returns a list of all usable s. Note that files are not populated. /// + /// The level of detail to include in the returned objects. /// A list of available . - public IQueryable GetAllUsableBeatmapSetsEnumerable() => beatmaps.ConsumableItems.Where(s => !s.DeletePending && !s.Protected); + public IQueryable GetAllUsableBeatmapSetsEnumerable(IncludedDetails includes) + { + IQueryable queryable; + + switch (includes) + { + case IncludedDetails.Minimal: + queryable = beatmaps.BeatmapSetsOverview; + break; + + case IncludedDetails.AllButFiles: + queryable = beatmaps.BeatmapSetsWithoutFiles; + break; + + default: + queryable = beatmaps.ConsumableItems; + break; + } + + return queryable.Where(s => !s.DeletePending && !s.Protected); + } /// /// Perform a lookup query on available s. @@ -482,4 +509,25 @@ namespace osu.Game.Beatmaps } } } + + /// + /// The level of detail to include in database results. + /// + public enum IncludedDetails + { + /// + /// Only include beatmap difficulties and set level metadata. + /// + Minimal, + + /// + /// Include all difficulties, rulesets, difficulty metadata but no files. + /// + AllButFiles, + + /// + /// Include everything. + /// + All + } } diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index a2279fdb14..642bafd2ac 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -87,6 +87,18 @@ namespace osu.Game.Beatmaps base.Purge(items, context); } + public IQueryable BeatmapSetsOverview => ContextFactory.Get().BeatmapSetInfo + .Include(s => s.Metadata) + .Include(s => s.Beatmaps) + .AsNoTracking(); + + public IQueryable BeatmapSetsWithoutFiles => ContextFactory.Get().BeatmapSetInfo + .Include(s => s.Metadata) + .Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset) + .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) + .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) + .AsNoTracking(); + public IQueryable Beatmaps => ContextFactory.Get().BeatmapInfo .Include(b => b.BeatmapSet).ThenInclude(s => s.Metadata) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 6d269aa944..c872f82b32 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -66,7 +66,7 @@ namespace osu.Game.Overlays beatmaps.ItemAdded += handleBeatmapAdded; beatmaps.ItemRemoved += handleBeatmapRemoved; - beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next())); + beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets(IncludedDetails.Minimal).OrderBy(_ => RNG.Next())); } protected override void LoadComplete() diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 26455b1dbd..d2296573a6 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Menu if (!MenuMusic.Value) { - var sets = beatmaps.GetAllUsableBeatmapSets(); + var sets = beatmaps.GetAllUsableBeatmapSets(IncludedDetails.Minimal); if (sets.Count > 0) setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID); } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index f989ab2787..5a4a03662a 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -169,7 +169,7 @@ namespace osu.Game.Screens.Select loadBeatmapSets(GetLoadableBeatmaps()); } - protected virtual IEnumerable GetLoadableBeatmaps() => beatmaps.GetAllUsableBeatmapSetsEnumerable(); + protected virtual IEnumerable GetLoadableBeatmaps() => beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.AllButFiles); public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => { diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 0d07a335cf..c07465ca44 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -286,7 +286,7 @@ namespace osu.Game.Screens.Select Schedule(() => { // if we have no beatmaps but osu-stable is found, let's prompt the user to import. - if (!beatmaps.GetAllUsableBeatmapSetsEnumerable().Any() && beatmaps.StableInstallationAvailable) + if (!beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.Minimal).Any() && beatmaps.StableInstallationAvailable) { dialogOverlay.Push(new ImportFromStablePopup(() => { From 00918ecb6d6c47f6c131fca8991448825e9297e3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 29 Apr 2020 04:43:49 +0300 Subject: [PATCH 587/655] Replace interval collection with a more-specific immutable component Covers all small changes into one commit: - Remove generics and use `double` type instead. - Make the component immutable and not enumerable for simplicity of it's worth. - Make the component more-specific (to time period tracking) - Apply small adjustments to the component --- osu.Game/Lists/IntervalList.cs | 111 -------------------------------- osu.Game/Utils/PeriodTracker.cs | 90 ++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 111 deletions(-) delete mode 100644 osu.Game/Lists/IntervalList.cs create mode 100644 osu.Game/Utils/PeriodTracker.cs diff --git a/osu.Game/Lists/IntervalList.cs b/osu.Game/Lists/IntervalList.cs deleted file mode 100644 index 580015bb96..0000000000 --- a/osu.Game/Lists/IntervalList.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections; -using System.Collections.Generic; -using osu.Framework.Lists; - -namespace osu.Game.Lists -{ - /// - /// Represents a list of intervals that can be used for whether a specific value falls into one of them. - /// - /// The type of interval values. - public class IntervalList : IEnumerable> - where T : struct, IConvertible - { - private static readonly IComparer type_comparer = Comparer.Default; - - private readonly SortedList> intervals = new SortedList>((x, y) => type_comparer.Compare(x.Start, y.Start)); - private int nearestIndex; - - public Interval this[int i] - { - get => intervals[i]; - set => intervals[i] = value; - } - - /// - /// Whether the provided value is in any interval added to this list. - /// - /// The value to check for. - public bool IsInAnyInterval(T value) - { - if (intervals.Count == 0) - return false; - - // Clamp the nearest index in case there were intervals - // removed from the list causing the index to go out of range. - nearestIndex = Math.Clamp(nearestIndex, 0, intervals.Count - 1); - - if (type_comparer.Compare(value, this[nearestIndex].End) > 0) - { - while (type_comparer.Compare(value, this[nearestIndex].End) > 0 && nearestIndex < intervals.Count - 1) - nearestIndex++; - } - else - { - while (type_comparer.Compare(value, this[nearestIndex].Start) < 0 && nearestIndex > 0) - nearestIndex--; - } - - var nearestInterval = this[nearestIndex]; - - return type_comparer.Compare(value, nearestInterval.Start) >= 0 && - type_comparer.Compare(value, nearestInterval.End) <= 0; - } - - /// - /// Adds a new interval to the list. - /// - /// The start value of the interval. - /// The end value of the interval. - public void Add(T start, T end) => Add(new Interval(start, end)); - - /// - /// Adds a new interval to the list - /// - /// The interval to add. - public void Add(Interval interval) => intervals.Add(interval); - - /// - /// Removes an existing interval from the list. - /// - /// The interval to remove. - /// Whether the provided interval exists in the list and has been removed. - public bool Remove(Interval interval) => intervals.Remove(interval); - - /// - /// Removes all intervals from the list. - /// - public void Clear() => intervals.Clear(); - - public IEnumerator> GetEnumerator() => intervals.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } - - public readonly struct Interval - where T : struct, IConvertible - { - /// - /// The start value of this interval. - /// - public readonly T Start; - - /// - /// The end value of this interval. - /// - public readonly T End; - - public Interval(T start, T end) - { - if (Comparer.Default.Compare(start, end) >= 0) - throw new ArgumentException($"Invalid interval, {nameof(start)} must be less than {nameof(end)}", nameof(start)); - - Start = start; - End = end; - } - } -} diff --git a/osu.Game/Utils/PeriodTracker.cs b/osu.Game/Utils/PeriodTracker.cs new file mode 100644 index 0000000000..589f061c1d --- /dev/null +++ b/osu.Game/Utils/PeriodTracker.cs @@ -0,0 +1,90 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace osu.Game.Utils +{ + /// + /// Represents a tracking component used for whether a + /// specific time falls into any of the provided periods. + /// + public class PeriodTracker + { + private readonly List periods = new List(); + private int nearestIndex; + + /// + /// The list of periods to add to the tracker for using the required check methods. + /// + public IEnumerable Periods + { + set + { + var sortedValue = value?.ToList(); + sortedValue?.Sort(); + + if (sortedValue != null && periods.SequenceEqual(sortedValue)) + return; + + periods.Clear(); + nearestIndex = 0; + + if (value?.Any() != true) + return; + + periods.AddRange(sortedValue); + } + } + + /// + /// Whether the provided time is in any of the added periods. + /// + /// The time value to check for. + public bool Contains(double time) + { + if (periods.Count == 0) + return false; + + if (time > periods[nearestIndex].End) + { + while (time > periods[nearestIndex].End && nearestIndex < periods.Count - 1) + nearestIndex++; + } + else + { + while (time < periods[nearestIndex].Start && nearestIndex > 0) + nearestIndex--; + } + + var nearest = periods[nearestIndex]; + return time >= nearest.Start && time <= nearest.End; + } + } + + public readonly struct Period : IComparable + { + /// + /// The start time of this period. + /// + public readonly double Start; + + /// + /// The end time of this period. + /// + public readonly double End; + + public Period(double start, double end) + { + if (start >= end) + throw new ArgumentException($"Invalid period provided, {nameof(start)} must be less than {nameof(end)}", nameof(start)); + + Start = start; + End = end; + } + + public int CompareTo(Period other) => Start.CompareTo(other.Start); + } +} From 587f946dc63ba8315b8f90b8b1cd9b5d42a9fc06 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 29 Apr 2020 04:58:08 +0300 Subject: [PATCH 588/655] Adjust, simplify and make the test code more flexible to some degree --- osu.Game.Tests/Lists/IntervalListTest.cs | 115 ------------------ osu.Game.Tests/NonVisual/PeriodTrackerTest.cs | 103 ++++++++++++++++ 2 files changed, 103 insertions(+), 115 deletions(-) delete mode 100644 osu.Game.Tests/Lists/IntervalListTest.cs create mode 100644 osu.Game.Tests/NonVisual/PeriodTrackerTest.cs diff --git a/osu.Game.Tests/Lists/IntervalListTest.cs b/osu.Game.Tests/Lists/IntervalListTest.cs deleted file mode 100644 index 0958f0fa7c..0000000000 --- a/osu.Game.Tests/Lists/IntervalListTest.cs +++ /dev/null @@ -1,115 +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 NUnit.Framework; -using osu.Game.Lists; - -namespace osu.Game.Tests.Lists -{ - [TestFixture] - public class IntervalListTest - { - // this is intended to be unordered to test adding intervals in unordered way. - private static readonly (double, double)[] test_intervals = - { - (-9.1d, -8.3d), - (-3.4d, 2.1d), - (9.0d, 50.0d), - (5.25d, 10.50d), - }; - - [Test] - public void TestCheckValueInsideSingleInterval() - { - var list = new IntervalList { { 1.0d, 2.0d } }; - - Assert.IsTrue(list.IsInAnyInterval(1.0d)); - Assert.IsTrue(list.IsInAnyInterval(1.5d)); - Assert.IsTrue(list.IsInAnyInterval(2.0d)); - } - - [Test] - public void TestCheckValuesInsideIntervals() - { - var list = new IntervalList(); - - foreach (var (start, end) in test_intervals) - list.Add(start, end); - - Assert.IsTrue(list.IsInAnyInterval(-8.75d)); - Assert.IsTrue(list.IsInAnyInterval(1.0d)); - Assert.IsTrue(list.IsInAnyInterval(7.89d)); - Assert.IsTrue(list.IsInAnyInterval(9.8d)); - Assert.IsTrue(list.IsInAnyInterval(15.83d)); - } - - [Test] - public void TestCheckValuesInRandomOrder() - { - var list = new IntervalList(); - - foreach (var (start, end) in test_intervals) - list.Add(start, end); - - Assert.IsTrue(list.IsInAnyInterval(9.8d)); - Assert.IsTrue(list.IsInAnyInterval(7.89d)); - Assert.IsTrue(list.IsInAnyInterval(1.0d)); - Assert.IsTrue(list.IsInAnyInterval(15.83d)); - Assert.IsTrue(list.IsInAnyInterval(-8.75d)); - } - - [Test] - public void TestCheckValuesOutOfIntervals() - { - var list = new IntervalList(); - - foreach (var (start, end) in test_intervals) - list.Add(start, end); - - Assert.IsFalse(list.IsInAnyInterval(-9.2d)); - Assert.IsFalse(list.IsInAnyInterval(2.2d)); - Assert.IsFalse(list.IsInAnyInterval(5.15d)); - Assert.IsFalse(list.IsInAnyInterval(51.2d)); - } - - [Test] - public void TestCheckValueAfterRemovedInterval() - { - var list = new IntervalList { { 50, 100 }, { 150, 200 }, { 250, 300 } }; - - Assert.IsTrue(list.IsInAnyInterval(75)); - Assert.IsTrue(list.IsInAnyInterval(175)); - Assert.IsTrue(list.IsInAnyInterval(275)); - - list.Remove(list[1]); - - Assert.IsFalse(list.IsInAnyInterval(175)); - Assert.IsTrue(list.IsInAnyInterval(75)); - Assert.IsTrue(list.IsInAnyInterval(275)); - } - - [Test] - public void TestCheckValueAfterAddedInterval() - { - var list = new IntervalList { { 50, 100 }, { 250, 300 } }; - - Assert.IsFalse(list.IsInAnyInterval(175)); - Assert.IsTrue(list.IsInAnyInterval(75)); - Assert.IsTrue(list.IsInAnyInterval(275)); - - list.Add(150, 200); - - Assert.IsTrue(list.IsInAnyInterval(175)); - } - - [Test] - public void TestReversedIntervalThrows() - { - var list = new IntervalList(); - - Assert.Throws(() => list.Add(50, 25)); - Assert.Throws(() => list.Add(new Interval(50, 25))); - } - } -} diff --git a/osu.Game.Tests/NonVisual/PeriodTrackerTest.cs b/osu.Game.Tests/NonVisual/PeriodTrackerTest.cs new file mode 100644 index 0000000000..39eea2b386 --- /dev/null +++ b/osu.Game.Tests/NonVisual/PeriodTrackerTest.cs @@ -0,0 +1,103 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Utils; +using osu.Game.Utils; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class PeriodTrackerTest + { + private static readonly Period[] test_single_period = { new Period(1.0, 2.0) }; + + // this is intended to be unordered to test adding periods in unordered way. + private static readonly Period[] test_periods = + { + new Period(-9.1, -8.3), + new Period(-3.4, 2.1), + new Period(9.0, 50.0), + new Period(5.25, 10.50) + }; + + [Test] + public void TestCheckValueInsideSinglePeriod() + { + var tracker = new PeriodTracker { Periods = test_single_period }; + + var period = test_single_period.Single(); + Assert.IsTrue(tracker.Contains(period.Start)); + Assert.IsTrue(tracker.Contains(getMidTime(period))); + Assert.IsTrue(tracker.Contains(period.End)); + } + + [Test] + public void TestCheckValuesInsidePeriods() + { + var tracker = new PeriodTracker { Periods = test_periods }; + + foreach (var period in test_periods) + Assert.IsTrue(tracker.Contains(getMidTime(period))); + } + + [Test] + public void TestCheckValuesInRandomOrder() + { + var tracker = new PeriodTracker { Periods = test_periods }; + + foreach (var period in test_periods.OrderBy(_ => RNG.Next())) + Assert.IsTrue(tracker.Contains(getMidTime(period))); + } + + [Test] + public void TestCheckValuesOutOfPeriods() + { + var tracker = new PeriodTracker + { + Periods = new[] + { + new Period(1.0, 2.0), + new Period(3.0, 4.0) + } + }; + + Assert.IsFalse(tracker.Contains(0.9), "Time before first period is being considered inside"); + + Assert.IsFalse(tracker.Contains(2.1), "Time right after first period is being considered inside"); + Assert.IsFalse(tracker.Contains(2.9), "Time right before second period is being considered inside"); + + Assert.IsFalse(tracker.Contains(4.1), "Time after last period is being considered inside"); + } + + [Test] + public void TestNullRemovesExistingPeriods() + { + var tracker = new PeriodTracker { Periods = test_single_period }; + + var period = test_single_period.Single(); + Assert.IsTrue(tracker.Contains(getMidTime(period))); + + tracker.Periods = null; + Assert.IsFalse(tracker.Contains(getMidTime(period))); + } + + [Test] + public void TestReversedPeriodHandling() + { + var tracker = new PeriodTracker(); + + Assert.Throws(() => + { + tracker.Periods = new[] + { + new Period(2.0, 1.0) + }; + }); + } + + private double getMidTime(Period period) => period.Start + (period.End - period.Start) / 2; + } +} From 8d899f4e7742e93b8afa0432e865685e3b92e71f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 29 Apr 2020 05:07:58 +0300 Subject: [PATCH 589/655] Apply changes to the BreakTracker and more adjustment --- osu.Game/Screens/Play/BreakTracker.cs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Play/BreakTracker.cs b/osu.Game/Screens/Play/BreakTracker.cs index c2eb069ee6..e30c0c6dec 100644 --- a/osu.Game/Screens/Play/BreakTracker.cs +++ b/osu.Game/Screens/Play/BreakTracker.cs @@ -2,20 +2,22 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps.Timing; -using osu.Game.Lists; using osu.Game.Rulesets.Scoring; +using osu.Game.Utils; namespace osu.Game.Screens.Play { public class BreakTracker : Component { private readonly ScoreProcessor scoreProcessor; - private readonly double gameplayStartTime; + private readonly PeriodTracker tracker = new PeriodTracker(); + /// /// Whether the gameplay is currently in a break. /// @@ -23,22 +25,14 @@ namespace osu.Game.Screens.Play private readonly BindableBool isBreakTime = new BindableBool(); - private readonly IntervalList breakIntervals = new IntervalList(); - public IReadOnlyList Breaks { set { isBreakTime.Value = false; - breakIntervals.Clear(); - foreach (var b in value) - { - if (!b.HasEffect) - continue; - - breakIntervals.Add(b.StartTime, b.EndTime - BreakOverlay.BREAK_FADE_DURATION); - } + tracker.Periods = value?.Where(b => b.HasEffect) + .Select(b => new Period(b.StartTime, b.EndTime - BreakOverlay.BREAK_FADE_DURATION)); } } @@ -54,7 +48,7 @@ namespace osu.Game.Screens.Play var time = Clock.CurrentTime; - isBreakTime.Value = breakIntervals.IsInAnyInterval(time) + isBreakTime.Value = tracker.Contains(time) || time < gameplayStartTime || scoreProcessor?.HasCompleted == true; } From 6e76e5900a4d965eddecb7dc2223d47cf8d2c38b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 29 Apr 2020 05:08:38 +0300 Subject: [PATCH 590/655] Rename is-in-any check method to a more legible name --- osu.Game.Tests/NonVisual/PeriodTrackerTest.cs | 22 +++++++++---------- osu.Game/Screens/Play/BreakTracker.cs | 2 +- osu.Game/Utils/PeriodTracker.cs | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/NonVisual/PeriodTrackerTest.cs b/osu.Game.Tests/NonVisual/PeriodTrackerTest.cs index 39eea2b386..f033672576 100644 --- a/osu.Game.Tests/NonVisual/PeriodTrackerTest.cs +++ b/osu.Game.Tests/NonVisual/PeriodTrackerTest.cs @@ -29,9 +29,9 @@ namespace osu.Game.Tests.NonVisual var tracker = new PeriodTracker { Periods = test_single_period }; var period = test_single_period.Single(); - Assert.IsTrue(tracker.Contains(period.Start)); - Assert.IsTrue(tracker.Contains(getMidTime(period))); - Assert.IsTrue(tracker.Contains(period.End)); + Assert.IsTrue(tracker.IsInAny(period.Start)); + Assert.IsTrue(tracker.IsInAny(getMidTime(period))); + Assert.IsTrue(tracker.IsInAny(period.End)); } [Test] @@ -40,7 +40,7 @@ namespace osu.Game.Tests.NonVisual var tracker = new PeriodTracker { Periods = test_periods }; foreach (var period in test_periods) - Assert.IsTrue(tracker.Contains(getMidTime(period))); + Assert.IsTrue(tracker.IsInAny(getMidTime(period))); } [Test] @@ -49,7 +49,7 @@ namespace osu.Game.Tests.NonVisual var tracker = new PeriodTracker { Periods = test_periods }; foreach (var period in test_periods.OrderBy(_ => RNG.Next())) - Assert.IsTrue(tracker.Contains(getMidTime(period))); + Assert.IsTrue(tracker.IsInAny(getMidTime(period))); } [Test] @@ -64,12 +64,12 @@ namespace osu.Game.Tests.NonVisual } }; - Assert.IsFalse(tracker.Contains(0.9), "Time before first period is being considered inside"); + Assert.IsFalse(tracker.IsInAny(0.9), "Time before first period is being considered inside"); - Assert.IsFalse(tracker.Contains(2.1), "Time right after first period is being considered inside"); - Assert.IsFalse(tracker.Contains(2.9), "Time right before second period is being considered inside"); + Assert.IsFalse(tracker.IsInAny(2.1), "Time right after first period is being considered inside"); + Assert.IsFalse(tracker.IsInAny(2.9), "Time right before second period is being considered inside"); - Assert.IsFalse(tracker.Contains(4.1), "Time after last period is being considered inside"); + Assert.IsFalse(tracker.IsInAny(4.1), "Time after last period is being considered inside"); } [Test] @@ -78,10 +78,10 @@ namespace osu.Game.Tests.NonVisual var tracker = new PeriodTracker { Periods = test_single_period }; var period = test_single_period.Single(); - Assert.IsTrue(tracker.Contains(getMidTime(period))); + Assert.IsTrue(tracker.IsInAny(getMidTime(period))); tracker.Periods = null; - Assert.IsFalse(tracker.Contains(getMidTime(period))); + Assert.IsFalse(tracker.IsInAny(getMidTime(period))); } [Test] diff --git a/osu.Game/Screens/Play/BreakTracker.cs b/osu.Game/Screens/Play/BreakTracker.cs index e30c0c6dec..eb77cb9369 100644 --- a/osu.Game/Screens/Play/BreakTracker.cs +++ b/osu.Game/Screens/Play/BreakTracker.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Play var time = Clock.CurrentTime; - isBreakTime.Value = tracker.Contains(time) + isBreakTime.Value = tracker.IsInAny(time) || time < gameplayStartTime || scoreProcessor?.HasCompleted == true; } diff --git a/osu.Game/Utils/PeriodTracker.cs b/osu.Game/Utils/PeriodTracker.cs index 589f061c1d..49b372bb27 100644 --- a/osu.Game/Utils/PeriodTracker.cs +++ b/osu.Game/Utils/PeriodTracker.cs @@ -43,7 +43,7 @@ namespace osu.Game.Utils /// Whether the provided time is in any of the added periods. /// /// The time value to check for. - public bool Contains(double time) + public bool IsInAny(double time) { if (periods.Count == 0) return false; From 024f10a494f124e632652cae331bbcce1dbf3c39 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 29 Apr 2020 13:24:31 +0900 Subject: [PATCH 591/655] Use non-generic bindable 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/UI/DrawableManiaRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 39f3331fbb..fa26e6d713 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Mania.UI protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config; private readonly Bindable configDirection = new Bindable(); - private readonly Bindable configTimeRange = new Bindable(); + private readonly Bindable configTimeRange = new BindableDouble(); // Stores the current speed adjustment active in gameplay. private readonly Track speedAdjustmentTrack = new TrackVirtual(1000); From 0c95d11fdb0db19fc1610774961ffe376241d3e5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 29 Apr 2020 13:27:33 +0900 Subject: [PATCH 592/655] Remove unnecessary value change binding --- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index fa26e6d713..00fa68d088 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -86,7 +86,6 @@ namespace osu.Game.Rulesets.Mania.UI configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true); Config.BindWith(ManiaRulesetSetting.ScrollTime, configTimeRange); - configTimeRange.BindValueChanged(_ => updateTimeRange()); } protected override void AdjustScrollSpeed(int amount) From 4f332ace1426b1c6840d0b4af3aad2e4a8e58e14 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 29 Apr 2020 14:27:21 +0900 Subject: [PATCH 593/655] Use 0 length --- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 00fa68d088..f3f843f366 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Mania.UI private readonly Bindable configTimeRange = new BindableDouble(); // Stores the current speed adjustment active in gameplay. - private readonly Track speedAdjustmentTrack = new TrackVirtual(1000); + private readonly Track speedAdjustmentTrack = new TrackVirtual(0); public DrawableManiaRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset, beatmap, mods) From c73d45bc01de6d2d2175c028400bb4c6d727c2f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Apr 2020 15:23:28 +0900 Subject: [PATCH 594/655] Reduce initial channel load overhead by only loading history on active channel --- osu.Game/Online/Chat/ChannelManager.cs | 12 ++++++------ osu.Game/Overlays/Chat/DrawableChannel.cs | 8 ++++++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 822f628dd2..53872ddcba 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -93,6 +93,12 @@ namespace osu.Game.Online.Chat { if (!(e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel)) JoinChannel(e.NewValue); + + if (e.NewValue?.MessagesLoaded == false) + { + // let's fetch a small number of messages to bring us up-to-date with the backlog. + fetchInitalMessages(e.NewValue); + } } /// @@ -375,12 +381,6 @@ namespace osu.Game.Online.Chat if (CurrentChannel.Value == null) CurrentChannel.Value = channel; - if (!channel.MessagesLoaded) - { - // let's fetch a small number of messages to bring us up-to-date with the backlog. - fetchInitalMessages(channel); - } - return channel; } diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 6019657cf0..d63faebae4 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -105,6 +105,14 @@ namespace osu.Game.Overlays.Chat private void newMessagesArrived(IEnumerable newMessages) { + if (newMessages.Min(m => m.Id) < chatLines.Max(c => c.Message.Id)) + { + // there is a case (on initial population) that we may receive past messages and need to reorder. + // easiest way is to just combine messages and recreate drawables (less worrying about day separators etc.) + newMessages = newMessages.Concat(chatLines.Select(c => c.Message)).OrderBy(m => m.Id).ToList(); + ChatLineFlow.Clear(); + } + bool shouldScrollToEnd = scroll.IsScrolledToEnd(10) || !chatLines.Any() || newMessages.Any(m => m is LocalMessage); // Add up to last Channel.MAX_HISTORY messages From d1ec99ffd9c790bf57f10a9592bd9e06b7b4df53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Apr 2020 16:51:22 +0900 Subject: [PATCH 595/655] Further improve beatmap carousel load performance by avoiding incorrect query construction --- osu.Game/Beatmaps/BeatmapManager.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 5651d07566..b8dfac0342 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -300,7 +300,7 @@ namespace osu.Game.Beatmaps /// /// The level of detail to include in the returned objects. /// A list of available . - public IQueryable GetAllUsableBeatmapSetsEnumerable(IncludedDetails includes) + public IEnumerable GetAllUsableBeatmapSetsEnumerable(IncludedDetails includes) { IQueryable queryable; @@ -319,7 +319,10 @@ namespace osu.Game.Beatmaps break; } - return queryable.Where(s => !s.DeletePending && !s.Protected); + // AsEnumerable used here to avoid applying the WHERE in sql. When done so, ef core 2.x uses an incorrect ORDER BY + // clause which causes queries to take 5-10x longer. + // TODO: remove if upgrading to EF core 3.x. + return queryable.AsEnumerable().Where(s => !s.DeletePending && !s.Protected); } /// From 48733a7e2f5c4d0d02666950e5d61fe04d435f25 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Apr 2020 17:53:25 +0900 Subject: [PATCH 596/655] Change taiko hit explosion animation to match stable for skins --- .../Skinning/LegacyHitExplosion.cs | 10 ++++++++-- .../UI/DefaultHitExplosion.cs | 3 +++ osu.Game.Rulesets.Taiko/UI/HitExplosion.cs | 16 +++++++--------- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs index d29b574866..42d4a34b9d 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs @@ -22,8 +22,14 @@ namespace osu.Game.Rulesets.Taiko.Skinning { base.LoadComplete(); - this.FadeIn(120); - this.ScaleTo(0.6f).Then().ScaleTo(1, 240, Easing.OutElastic); + const double animation_time = 120; + + this.FadeInFromZero(animation_time).Then().FadeOut(animation_time * 1.5); + + this.ScaleTo(0.6f) + .Then().ScaleTo(1.1f, animation_time * 0.8) + .Then().ScaleTo(0.9f, animation_time * 0.4) + .Then().ScaleTo(1f, animation_time * 0.2); } } } diff --git a/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs index aa444d0494..a0ca5f1c39 100644 --- a/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs @@ -49,6 +49,9 @@ namespace osu.Game.Rulesets.Taiko.UI base.LoadComplete(); this.ScaleTo(3f, 1000, Easing.OutQuint); + this.FadeOut(500); + + Expire(true); } } } diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs index 35a54d6ea7..f0585b9c50 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs @@ -23,6 +23,12 @@ namespace osu.Game.Rulesets.Taiko.UI [Cached(typeof(DrawableHitObject))] public readonly DrawableHitObject JudgedObject; + private SkinnableDrawable skinnable; + + public override double LifetimeStart => skinnable.Drawable.LifetimeStart; + + public override double LifetimeEnd => skinnable.Drawable.LifetimeEnd; + public HitExplosion(DrawableHitObject judgedObject) { JudgedObject = judgedObject; @@ -39,7 +45,7 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load() { - Child = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(JudgedObject.Result?.Type ?? HitResult.Great)), _ => new DefaultHitExplosion()); + Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(JudgedObject.Result?.Type ?? HitResult.Great)), _ => new DefaultHitExplosion()); } private TaikoSkinComponents getComponentName(HitResult resultType) @@ -59,14 +65,6 @@ namespace osu.Game.Rulesets.Taiko.UI throw new ArgumentOutOfRangeException(nameof(resultType), "Invalid result type"); } - protected override void LoadComplete() - { - base.LoadComplete(); - - this.FadeOut(500); - Expire(true); - } - /// /// Transforms this hit explosion to visualise a secondary hit. /// From 783dc58ef0f600073ef115f4e4868902f96473ea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Apr 2020 09:41:56 +0900 Subject: [PATCH 597/655] Move taiko additive blending locally to avoid applying to legacy skins --- osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs | 2 ++ osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs | 2 ++ osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 3 --- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs index 42d4a34b9d..c44da9ce1e 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs @@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning Origin = Anchor.Centre; AutoSizeAxes = Axes.Both; + + Blending = BlendingParameters.Additive; } protected override void LoadComplete() diff --git a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs index 3a307bb3bb..067d390894 100644 --- a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs @@ -31,6 +31,8 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both; Size = new Vector2(TaikoHitObject.DEFAULT_SIZE, 1); + Blending = BlendingParameters.Additive; + Masking = true; Alpha = 0.25f; diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 6a78c0a1fb..5c763cb332 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -68,7 +68,6 @@ namespace osu.Game.Rulesets.Taiko.UI hitExplosionContainer = new Container { RelativeSizeAxes = Axes.Both, - Blending = BlendingParameters.Additive, }, HitTarget = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.HitTarget), _ => new TaikoHitTarget()) { @@ -100,13 +99,11 @@ namespace osu.Game.Rulesets.Taiko.UI Name = "Kiai hit explosions", RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, - Blending = BlendingParameters.Additive }, judgementContainer = new JudgementContainer { Name = "Judgements", RelativeSizeAxes = Axes.Y, - Blending = BlendingParameters.Additive }, } }, From 49a98fde7374055a8b2aa5286b733ec3673f55ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Apr 2020 09:57:14 +0900 Subject: [PATCH 598/655] Move to non-legacy class --- osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs | 2 -- osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs index c44da9ce1e..42d4a34b9d 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs @@ -16,8 +16,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning Origin = Anchor.Centre; AutoSizeAxes = Axes.Both; - - Blending = BlendingParameters.Additive; } protected override void LoadComplete() diff --git a/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs index a0ca5f1c39..9943a58e3e 100644 --- a/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs @@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Taiko.UI BorderColour = Color4.White; BorderThickness = 1; + Blending = BlendingParameters.Additive; + Alpha = 0.15f; Masking = true; From cf4e79cf38490ecb85f213b154dc7eef3ffb14ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Apr 2020 11:51:06 +0900 Subject: [PATCH 599/655] Show loading spinner when carousel is not ready to be displayed --- .../SongSelect/TestScenePlaySongSelect.cs | 1 + osu.Game/Screens/Select/SongSelect.cs | 33 +++++++++++-------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 39e04ed39a..aed8e19fb2 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -797,6 +797,7 @@ namespace osu.Game.Tests.Visual.SongSelect { AddStep("create song select", () => LoadScreen(songSelect = new TestSongSelect())); AddUntilStep("wait for present", () => songSelect.IsCurrentScreen()); + AddUntilStep("wait for carousel loaded", () => songSelect.Carousel.IsAlive); } private void addManyTestMaps() diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index c07465ca44..6b896694ea 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -34,6 +34,7 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; +using osu.Game.Graphics.UserInterface; using osu.Game.Scoring; namespace osu.Game.Screens.Select @@ -92,6 +93,8 @@ namespace osu.Game.Screens.Select private SampleChannel sampleChangeDifficulty; private SampleChannel sampleChangeBeatmap; + private Container carouselContainer; + protected BeatmapDetailArea BeatmapDetails { get; private set; } private readonly Bindable decoupledRuleset = new Bindable(); @@ -105,9 +108,22 @@ namespace osu.Game.Screens.Select // initial value transfer is required for FilterControl (it uses our re-cached bindables in its async load for the initial filter). transferRulesetValue(); + LoadComponentAsync(Carousel = new BeatmapCarousel + { + AllowSelection = false, // delay any selection until our bindables are ready to make a good choice. + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + BleedTop = FilterControl.HEIGHT, + BleedBottom = Footer.HEIGHT, + SelectionChanged = updateSelectedBeatmap, + BeatmapSetsChanged = carouselBeatmapsLoaded, + GetRecommendedBeatmap = (recommender = new DifficultyRecommender()).GetRecommendedBeatmap, + }, c => carouselContainer.Child = c); + AddRangeInternal(new Drawable[] { - recommender = new DifficultyRecommender(), + recommender, new ResetScrollContainer(() => Carousel.ScrollToSelected()) { RelativeSizeAxes = Axes.Y, @@ -139,7 +155,7 @@ namespace osu.Game.Screens.Select Padding = new MarginPadding { Right = -150 }, }, }, - new Container + carouselContainer = new Container { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding @@ -147,18 +163,7 @@ namespace osu.Game.Screens.Select Top = FilterControl.HEIGHT, Bottom = Footer.HEIGHT }, - Child = Carousel = new BeatmapCarousel - { - AllowSelection = false, // delay any selection until our bindables are ready to make a good choice. - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.Both, - BleedTop = FilterControl.HEIGHT, - BleedBottom = Footer.HEIGHT, - SelectionChanged = updateSelectedBeatmap, - BeatmapSetsChanged = carouselBeatmapsLoaded, - GetRecommendedBeatmap = recommender.GetRecommendedBeatmap, - }, + Child = new LoadingSpinner(true) { State = { Value = Visibility.Visible } } } }, } From 21c6ac8c43725f8739dcaea3a2b6105e7feab142 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Apr 2020 12:12:28 +0900 Subject: [PATCH 600/655] Allow closing the game during intro --- osu.Game/OsuGame.cs | 5 +---- osu.Game/Screens/Menu/IntroScreen.cs | 2 -- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index f5f7d0cef4..8e62819c95 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -914,10 +914,7 @@ namespace osu.Game if (ScreenStack.CurrentScreen is Loader) return false; - if (introScreen == null) - return true; - - if (!introScreen.DidLoadMenu || !(ScreenStack.CurrentScreen is IntroScreen)) + if (introScreen.DidLoadMenu && !(ScreenStack.CurrentScreen is IntroScreen)) { Scheduler.Add(introScreen.MakeCurrent); return true; diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index d2296573a6..736202ee52 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -96,8 +96,6 @@ namespace osu.Game.Screens.Menu Track = introBeatmap.Track; } - public override bool OnExiting(IScreen next) => !DidLoadMenu; - public override void OnResuming(IScreen last) { this.FadeIn(300); From 48af4d4eb4fc3ef8abf757ab512211defddee949 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Apr 2020 16:18:15 +0900 Subject: [PATCH 601/655] Fix skinned taiko hit explosions not being removed on rewind --- osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs index 42d4a34b9d..b5ec2e8def 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs @@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning .Then().ScaleTo(1.1f, animation_time * 0.8) .Then().ScaleTo(0.9f, animation_time * 0.4) .Then().ScaleTo(1f, animation_time * 0.2); + + Expire(true); } } } From d0a8c0fa71bfe372a535380d258eb259b7f60dd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Apr 2020 16:42:38 +0900 Subject: [PATCH 602/655] Add kiai support to osu!taiko skinned playfields --- .../Skinning/TestSceneTaikoPlayfield.cs | 18 ++++++ .../TaikoLegacyPlayfieldBackgroundRight.cs | 57 +++++++++++++++++++ .../Skinning/TaikoLegacySkinTransformer.cs | 9 +-- osu.Game/Beatmaps/WorkingBeatmap.cs | 4 +- osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs | 4 ++ 5 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/Skinning/TaikoLegacyPlayfieldBackgroundRight.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs index 16b3c036a3..ae5dd1e622 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs @@ -9,6 +9,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Skinning; using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.UI.Scrolling; @@ -34,6 +35,18 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning public TestSceneTaikoPlayfield() { + TaikoBeatmap beatmap; + bool kiai = false; + + AddStep("set beatmap", () => + { + Beatmap.Value = CreateWorkingBeatmap(beatmap = new TaikoBeatmap()); + + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }); + + Beatmap.Value.Track.Start(); + }); + AddStep("Load playfield", () => SetContents(() => new TaikoPlayfield(new ControlPointInfo()) { Anchor = Anchor.CentreLeft, @@ -41,6 +54,11 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning })); AddRepeatStep("change height", () => this.ChildrenOfType().ForEach(p => p.Height = Math.Max(0.2f, (p.Height + 0.2f) % 1f)), 50); + + AddStep("Toggle kiai", () => + { + Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new EffectControlPoint { KiaiMode = (kiai = !kiai) }); + }); } } } diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacyPlayfieldBackgroundRight.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacyPlayfieldBackgroundRight.cs new file mode 100644 index 0000000000..7508c75231 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacyPlayfieldBackgroundRight.cs @@ -0,0 +1,57 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Audio.Track; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Containers; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Taiko.Skinning +{ + public class TaikoLegacyPlayfieldBackgroundRight : BeatSyncedContainer + { + private Sprite kiai; + + private bool kiaiDisplayed; + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new Sprite + { + Texture = skin.GetTexture("taiko-bar-right"), + RelativeSizeAxes = Axes.Both, + Size = Vector2.One, + }, + kiai = new Sprite + { + Texture = skin.GetTexture("taiko-bar-right-glow"), + RelativeSizeAxes = Axes.Both, + Size = Vector2.One, + Alpha = 0, + } + }; + } + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + { + base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + + if (effectPoint.KiaiMode != kiaiDisplayed) + { + kiaiDisplayed = effectPoint.KiaiMode; + + kiai.ClearTransforms(); + kiai.FadeTo(kiaiDisplayed ? 1 : 0, 200); + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs index f0df612e18..5dfc7ec0df 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Game.Audio; using osu.Game.Skinning; -using osuTK; namespace osu.Game.Rulesets.Taiko.Skinning { @@ -60,13 +59,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning case TaikoSkinComponents.PlayfieldBackgroundRight: if (GetTexture("taiko-bar-right") != null) - { - return this.GetAnimation("taiko-bar-right", false, false).With(d => - { - d.RelativeSizeAxes = Axes.Both; - d.Size = Vector2.One; - }); - } + return new TaikoLegacyPlayfieldBackgroundRight(); return null; diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index f30340956a..d2804bdc05 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -197,7 +197,7 @@ namespace osu.Game.Beatmaps public override string ToString() => BeatmapInfo.ToString(); - public bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false; + public virtual bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false; public IBeatmap Beatmap { @@ -233,7 +233,7 @@ namespace osu.Game.Beatmaps protected abstract Texture GetBackground(); private readonly RecyclableLazy background; - public bool TrackLoaded => track.IsResultAvailable; + public virtual bool TrackLoaded => track.IsResultAvailable; public Track Track => track.Value; protected abstract Track GetTrack(); private RecyclableLazy track; diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs index 8f8afb87d4..cdf9170701 100644 --- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs @@ -27,6 +27,10 @@ namespace osu.Game.Tests.Beatmaps this.storyboard = storyboard; } + public override bool TrackLoaded => true; + + public override bool BeatmapLoaded => true; + protected override IBeatmap GetBeatmap() => beatmap; protected override Storyboard GetStoryboard() => storyboard ?? base.GetStoryboard(); From 9bec42bc7ee8e59a1e9faebdc9388350259a7212 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 30 Apr 2020 20:03:46 +0900 Subject: [PATCH 603/655] Fix mania crashing on undo/redo --- osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index 17eba87076..45a0a5c485 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -84,7 +84,11 @@ namespace osu.Game.Screens.Edit { using (var stream = new MemoryStream(state)) using (var reader = new LineBufferedReader(stream, true)) - return new PassThroughWorkingBeatmap(Decoder.GetDecoder(reader).Decode(reader)).GetPlayableBeatmap(editorBeatmap.BeatmapInfo.Ruleset); + { + var decoded = Decoder.GetDecoder(reader).Decode(reader); + decoded.BeatmapInfo.Ruleset = editorBeatmap.BeatmapInfo.Ruleset; + return new PassThroughWorkingBeatmap(decoded).GetPlayableBeatmap(editorBeatmap.BeatmapInfo.Ruleset); + } } private class PassThroughWorkingBeatmap : WorkingBeatmap From c96bc5c51cdfe0f607ebd9925ab160358fb10a81 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 30 Apr 2020 20:39:41 +0900 Subject: [PATCH 604/655] Fix undo/redo behaving poorly with simultaneous objects --- .../Editing/LegacyEditorBeatmapPatcherTest.cs | 25 +++++++++++++++++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 20 ++++++++++++--- .../Edit/LegacyEditorBeatmapPatcher.cs | 6 +++-- 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs index a3ab677d96..ff17f23d50 100644 --- a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs +++ b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs @@ -304,6 +304,31 @@ namespace osu.Game.Tests.Editing runTest(patch); } + [Test] + public void TestChangeHitObjectAtSameTime() + { + current.AddRange(new[] + { + new HitCircle { StartTime = 500, Position = new Vector2(50) }, + new HitCircle { StartTime = 500, Position = new Vector2(100) }, + new HitCircle { StartTime = 500, Position = new Vector2(150) }, + new HitCircle { StartTime = 500, Position = new Vector2(200) }, + }); + + var patch = new OsuBeatmap + { + HitObjects = + { + new HitCircle { StartTime = 500, Position = new Vector2(150) }, + new HitCircle { StartTime = 500, Position = new Vector2(100) }, + new HitCircle { StartTime = 500, Position = new Vector2(50) }, + new HitCircle { StartTime = 500, Position = new Vector2(200) }, + } + }; + + runTest(patch); + } + private void runTest(IBeatmap patch) { // Due to the method of testing, "patch" comes in without having been decoded via a beatmap decoder. diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index a2d2f08ce9..2e8e03bc73 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -136,14 +136,26 @@ namespace osu.Game.Screens.Edit /// The to add. public void Add(HitObject hitObject) { - trackStartTime(hitObject); - // Preserve existing sorting order in the beatmap var insertionIndex = findInsertionIndex(PlayableBeatmap.HitObjects, hitObject.StartTime); - mutableHitObjects.Insert(insertionIndex + 1, hitObject); + Insert(insertionIndex + 1, hitObject); + } + + /// + /// Inserts a into this . + /// + /// + /// It is the invoker's responsibility to make sure that sorting order is maintained. + /// + /// The index to insert the at. + /// The to insert. + public void Insert(int index, HitObject hitObject) + { + trackStartTime(hitObject); + + mutableHitObjects.Insert(index, hitObject); HitObjectAdded?.Invoke(hitObject); - updateHitObject(hitObject, true); } diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index 17eba87076..04faba6478 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -63,8 +63,10 @@ namespace osu.Game.Screens.Edit } } - // Make the removal indices are sorted so that iteration order doesn't get messed up post-removal. + // Sort the indices to ensure that removal + insertion indices don't get jumbled up post-removal or post-insertion. + // This isn't strictly required, but the differ makes no guarantees about order. toRemove.Sort(); + toAdd.Sort(); // Apply the changes. for (int i = toRemove.Count - 1; i >= 0; i--) @@ -74,7 +76,7 @@ namespace osu.Game.Screens.Edit { IBeatmap newBeatmap = readBeatmap(newState); foreach (var i in toAdd) - editorBeatmap.Add(newBeatmap.HitObjects[i]); + editorBeatmap.Insert(i, newBeatmap.HitObjects[i]); } } From 000c34dc26ea9d815f0f76c32510f1a0b5f600a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Apr 2020 21:01:53 +0900 Subject: [PATCH 605/655] Move recommender to field construction --- osu.Game/Screens/Select/SongSelect.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 6b896694ea..a7e27c27ba 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -78,7 +78,7 @@ namespace osu.Game.Screens.Select protected BeatmapCarousel Carousel { get; private set; } - private DifficultyRecommender recommender; + private readonly DifficultyRecommender recommender = new DifficultyRecommender(); private BeatmapInfoWedge beatmapInfoWedge; private DialogOverlay dialogOverlay; @@ -118,7 +118,7 @@ namespace osu.Game.Screens.Select BleedBottom = Footer.HEIGHT, SelectionChanged = updateSelectedBeatmap, BeatmapSetsChanged = carouselBeatmapsLoaded, - GetRecommendedBeatmap = (recommender = new DifficultyRecommender()).GetRecommendedBeatmap, + GetRecommendedBeatmap = recommender.GetRecommendedBeatmap, }, c => carouselContainer.Child = c); AddRangeInternal(new Drawable[] From e0ae9f791c357a040f0ff06180451bf20dca2428 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 May 2020 11:56:40 +0900 Subject: [PATCH 606/655] 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 73fbe3ab2e..336479c40a 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3b05eb82d7..acb7fe5fbe 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index f3202693f3..6662e57dcd 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 700214d249d2938977c6161e4dcea22b712b1025 Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 30 Apr 2020 22:13:38 -0700 Subject: [PATCH 607/655] Truncate beatmap title and artist on score panel --- .../Ranking/TestSceneExpandedPanelMiddleContent.cs | 5 ++++- .../Ranking/Expanded/ExpandedPanelMiddleContent.cs | 10 ++++++++-- osu.Game/Screens/Ranking/ScorePanel.cs | 4 ++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs index 52d8ea0480..328a0e0c27 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs @@ -20,6 +20,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking.Expanded; using osu.Game.Screens.Ranking.Expanded.Accuracy; using osu.Game.Screens.Ranking.Expanded.Statistics; @@ -74,6 +75,8 @@ namespace osu.Game.Tests.Visual.Ranking { var beatmap = new TestBeatmap(rulesetStore.GetRuleset(0)); beatmap.Metadata.Author = author; + beatmap.Metadata.Title = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap title"; + beatmap.Metadata.Artist = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap artist"; return new TestWorkingBeatmap(beatmap); } @@ -114,7 +117,7 @@ namespace osu.Game.Tests.Visual.Ranking Anchor = Anchor.Centre; Origin = Anchor.Centre; - Size = new Vector2(500, 700); + Size = new Vector2(ScorePanel.EXPANDED_WIDTH, 700); Children = new Drawable[] { new Box diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index b058cc142b..fd8ac33aef 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -35,6 +35,8 @@ namespace osu.Game.Screens.Ranking.Expanded private RollingCounter scoreCounter; + private const float padding = 10; + /// /// Creates a new . /// @@ -46,7 +48,7 @@ namespace osu.Game.Screens.Ranking.Expanded RelativeSizeAxes = Axes.Both; Masking = true; - Padding = new MarginPadding { Vertical = 10, Horizontal = 10 }; + Padding = new MarginPadding(padding); } [BackgroundDependencyLoader] @@ -92,13 +94,17 @@ namespace osu.Game.Screens.Ranking.Expanded Origin = Anchor.TopCentre, Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)), Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold), + MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2, + Truncate = true, }, new OsuSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist)), - Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold) + Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold), + MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2, + Truncate = true, }, new Container { diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index a1adfcc500..c055df7ccc 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.Ranking /// /// Width of the panel when expanded. /// - private const float expanded_width = 360; + public const float EXPANDED_WIDTH = 360; /// /// Height of the panel when expanded. @@ -183,7 +183,7 @@ namespace osu.Game.Screens.Ranking switch (state) { case PanelState.Expanded: - this.ResizeTo(new Vector2(expanded_width, expanded_height), resize_duration, Easing.OutQuint); + this.ResizeTo(new Vector2(EXPANDED_WIDTH, expanded_height), resize_duration, Easing.OutQuint); topLayerBackground.FadeColour(expanded_top_layer_colour, resize_duration, Easing.OutQuint); middleLayerBackground.FadeColour(expanded_middle_layer_colour, resize_duration, Easing.OutQuint); From 811874773288186d4e93620e44024331ebb59615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 2 May 2020 01:33:33 +0200 Subject: [PATCH 608/655] Make PeriodTracker actually immutable --- osu.Game.Tests/NonVisual/PeriodTrackerTest.cs | 54 +++++++------------ osu.Game/Screens/Play/BreakTracker.cs | 8 +-- osu.Game/Utils/PeriodTracker.cs | 35 +++--------- 3 files changed, 29 insertions(+), 68 deletions(-) diff --git a/osu.Game.Tests/NonVisual/PeriodTrackerTest.cs b/osu.Game.Tests/NonVisual/PeriodTrackerTest.cs index f033672576..62c7732b66 100644 --- a/osu.Game.Tests/NonVisual/PeriodTrackerTest.cs +++ b/osu.Game.Tests/NonVisual/PeriodTrackerTest.cs @@ -12,10 +12,9 @@ namespace osu.Game.Tests.NonVisual [TestFixture] public class PeriodTrackerTest { - private static readonly Period[] test_single_period = { new Period(1.0, 2.0) }; + private static readonly Period[] single_period = { new Period(1.0, 2.0) }; - // this is intended to be unordered to test adding periods in unordered way. - private static readonly Period[] test_periods = + private static readonly Period[] unordered_periods = { new Period(-9.1, -8.3), new Period(-3.4, 2.1), @@ -26,43 +25,40 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestCheckValueInsideSinglePeriod() { - var tracker = new PeriodTracker { Periods = test_single_period }; + var tracker = new PeriodTracker(single_period); - var period = test_single_period.Single(); + var period = single_period.Single(); Assert.IsTrue(tracker.IsInAny(period.Start)); - Assert.IsTrue(tracker.IsInAny(getMidTime(period))); + Assert.IsTrue(tracker.IsInAny(getMidpoint(period))); Assert.IsTrue(tracker.IsInAny(period.End)); } [Test] public void TestCheckValuesInsidePeriods() { - var tracker = new PeriodTracker { Periods = test_periods }; + var tracker = new PeriodTracker(unordered_periods); - foreach (var period in test_periods) - Assert.IsTrue(tracker.IsInAny(getMidTime(period))); + foreach (var period in unordered_periods) + Assert.IsTrue(tracker.IsInAny(getMidpoint(period))); } [Test] public void TestCheckValuesInRandomOrder() { - var tracker = new PeriodTracker { Periods = test_periods }; + var tracker = new PeriodTracker(unordered_periods); - foreach (var period in test_periods.OrderBy(_ => RNG.Next())) - Assert.IsTrue(tracker.IsInAny(getMidTime(period))); + foreach (var period in unordered_periods.OrderBy(_ => RNG.Next())) + Assert.IsTrue(tracker.IsInAny(getMidpoint(period))); } [Test] public void TestCheckValuesOutOfPeriods() { - var tracker = new PeriodTracker + var tracker = new PeriodTracker(new[] { - Periods = new[] - { - new Period(1.0, 2.0), - new Period(3.0, 4.0) - } - }; + new Period(1.0, 2.0), + new Period(3.0, 4.0) + }); Assert.IsFalse(tracker.IsInAny(0.9), "Time before first period is being considered inside"); @@ -72,32 +68,18 @@ namespace osu.Game.Tests.NonVisual Assert.IsFalse(tracker.IsInAny(4.1), "Time after last period is being considered inside"); } - [Test] - public void TestNullRemovesExistingPeriods() - { - var tracker = new PeriodTracker { Periods = test_single_period }; - - var period = test_single_period.Single(); - Assert.IsTrue(tracker.IsInAny(getMidTime(period))); - - tracker.Periods = null; - Assert.IsFalse(tracker.IsInAny(getMidTime(period))); - } - [Test] public void TestReversedPeriodHandling() { - var tracker = new PeriodTracker(); - Assert.Throws(() => { - tracker.Periods = new[] + _ = new PeriodTracker(new[] { new Period(2.0, 1.0) - }; + }); }); } - private double getMidTime(Period period) => period.Start + (period.End - period.Start) / 2; + private double getMidpoint(Period period) => period.Start + (period.End - period.Start) / 2; } } diff --git a/osu.Game/Screens/Play/BreakTracker.cs b/osu.Game/Screens/Play/BreakTracker.cs index 79da548336..51e21656e1 100644 --- a/osu.Game/Screens/Play/BreakTracker.cs +++ b/osu.Game/Screens/Play/BreakTracker.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Play private readonly ScoreProcessor scoreProcessor; private readonly double gameplayStartTime; - private readonly PeriodTracker tracker = new PeriodTracker(); + private PeriodTracker breaks; /// /// Whether the gameplay is currently in a break. @@ -31,8 +31,8 @@ namespace osu.Game.Screens.Play { isBreakTime.Value = false; - tracker.Periods = value?.Where(b => b.HasEffect) - .Select(b => new Period(b.StartTime, b.EndTime - BreakOverlay.BREAK_FADE_DURATION)); + breaks = new PeriodTracker(value.Where(b => b.HasEffect) + .Select(b => new Period(b.StartTime, b.EndTime - BreakOverlay.BREAK_FADE_DURATION))); } } @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Play var time = Clock.CurrentTime; - isBreakTime.Value = tracker.IsInAny(time) + isBreakTime.Value = breaks?.IsInAny(time) == true || time < gameplayStartTime || scoreProcessor?.HasCompleted.Value == true; } diff --git a/osu.Game/Utils/PeriodTracker.cs b/osu.Game/Utils/PeriodTracker.cs index 49b372bb27..ba77702247 100644 --- a/osu.Game/Utils/PeriodTracker.cs +++ b/osu.Game/Utils/PeriodTracker.cs @@ -8,41 +8,22 @@ using System.Linq; namespace osu.Game.Utils { /// - /// Represents a tracking component used for whether a - /// specific time falls into any of the provided periods. + /// Represents a tracking component used for whether a specific time instant falls into any of the provided periods. /// public class PeriodTracker { - private readonly List periods = new List(); + private readonly List periods; private int nearestIndex; - /// - /// The list of periods to add to the tracker for using the required check methods. - /// - public IEnumerable Periods + public PeriodTracker(IEnumerable periods) { - set - { - var sortedValue = value?.ToList(); - sortedValue?.Sort(); - - if (sortedValue != null && periods.SequenceEqual(sortedValue)) - return; - - periods.Clear(); - nearestIndex = 0; - - if (value?.Any() != true) - return; - - periods.AddRange(sortedValue); - } + this.periods = periods.OrderBy(period => period.Start).ToList(); } /// /// Whether the provided time is in any of the added periods. /// - /// The time value to check for. + /// The time value to check. public bool IsInAny(double time) { if (periods.Count == 0) @@ -64,7 +45,7 @@ namespace osu.Game.Utils } } - public readonly struct Period : IComparable + public readonly struct Period { /// /// The start time of this period. @@ -79,12 +60,10 @@ namespace osu.Game.Utils public Period(double start, double end) { if (start >= end) - throw new ArgumentException($"Invalid period provided, {nameof(start)} must be less than {nameof(end)}", nameof(start)); + throw new ArgumentException($"Invalid period provided, {nameof(start)} must be less than {nameof(end)}"); Start = start; End = end; } - - public int CompareTo(Period other) => Start.CompareTo(other.Start); } } From deb87517d01019bca4b9a121118d52172600e74f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 May 2020 14:35:12 +0900 Subject: [PATCH 609/655] Add local beatmap lookup cache --- osu.Game/Beatmaps/BeatmapManager.cs | 68 +------ .../Beatmaps/BeatmapManager_UpdateQueue.cs | 180 ++++++++++++++++++ osu.Game/Database/ArchiveModelManager.cs | 2 +- osu.Game/osu.Game.csproj | 1 + 4 files changed, 183 insertions(+), 68 deletions(-) create mode 100644 osu.Game/Beatmaps/BeatmapManager_UpdateQueue.cs diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index b8dfac0342..22451382a3 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -17,7 +17,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Lists; using osu.Framework.Logging; using osu.Framework.Platform; -using osu.Framework.Threading; using osu.Game.Beatmaps.Formats; using osu.Game.Database; using osu.Game.IO; @@ -78,7 +77,7 @@ namespace osu.Game.Beatmaps beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b); beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b); - updateQueue = new BeatmapUpdateQueue(api); + updateQueue = new BeatmapUpdateQueue(api, storage); exportStorage = storage.GetStorageForDirectory("exports"); } @@ -446,71 +445,6 @@ namespace osu.Game.Beatmaps protected override Texture GetBackground() => null; protected override Track GetTrack() => null; } - - private class BeatmapUpdateQueue - { - private readonly IAPIProvider api; - - private const int update_queue_request_concurrency = 4; - - private readonly ThreadedTaskScheduler updateScheduler = new ThreadedTaskScheduler(update_queue_request_concurrency, nameof(BeatmapUpdateQueue)); - - public BeatmapUpdateQueue(IAPIProvider api) - { - this.api = api; - } - - public Task UpdateAsync(BeatmapSetInfo beatmapSet, CancellationToken cancellationToken) - { - if (api?.State != APIState.Online) - return Task.CompletedTask; - - LogForModel(beatmapSet, "Performing online lookups..."); - return Task.WhenAll(beatmapSet.Beatmaps.Select(b => UpdateAsync(beatmapSet, b, cancellationToken)).ToArray()); - } - - // todo: expose this when we need to do individual difficulty lookups. - protected Task UpdateAsync(BeatmapSetInfo beatmapSet, BeatmapInfo beatmap, CancellationToken cancellationToken) - => Task.Factory.StartNew(() => update(beatmapSet, beatmap), cancellationToken, TaskCreationOptions.HideScheduler, updateScheduler); - - private void update(BeatmapSetInfo set, BeatmapInfo beatmap) - { - if (api?.State != APIState.Online) - return; - - var req = new GetBeatmapRequest(beatmap); - - req.Failure += fail; - - try - { - // intentionally blocking to limit web request concurrency - api.Perform(req); - - var res = req.Result; - - if (res != null) - { - beatmap.Status = res.Status; - beatmap.BeatmapSet.Status = res.BeatmapSet.Status; - beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID; - beatmap.OnlineBeatmapID = res.OnlineBeatmapID; - - LogForModel(set, $"Online retrieval mapped {beatmap} to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}."); - } - } - catch (Exception e) - { - fail(e); - } - - void fail(Exception e) - { - beatmap.OnlineBeatmapID = null; - LogForModel(set, $"Online retrieval failed for {beatmap} ({e.Message})"); - } - } - } } /// diff --git a/osu.Game/Beatmaps/BeatmapManager_UpdateQueue.cs b/osu.Game/Beatmaps/BeatmapManager_UpdateQueue.cs new file mode 100644 index 0000000000..aa8be823f7 --- /dev/null +++ b/osu.Game/Beatmaps/BeatmapManager_UpdateQueue.cs @@ -0,0 +1,180 @@ +// 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.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Dapper; +using Microsoft.Data.Sqlite; +using osu.Framework.IO.Network; +using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Framework.Threading; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using SharpCompress.Compressors; +using SharpCompress.Compressors.BZip2; + +namespace osu.Game.Beatmaps +{ + public partial class BeatmapManager + { + private class BeatmapUpdateQueue + { + private readonly IAPIProvider api; + private readonly Storage storage; + + private const int update_queue_request_concurrency = 4; + + private readonly ThreadedTaskScheduler updateScheduler = new ThreadedTaskScheduler(update_queue_request_concurrency, nameof(BeatmapUpdateQueue)); + + private FileWebRequest cacheDownloadRequest; + + private const string cache_database_name = "online.db"; + + public BeatmapUpdateQueue(IAPIProvider api, Storage storage) + { + this.api = api; + this.storage = storage; + + if (!storage.Exists(cache_database_name)) + prepareLocalCache(); + } + + public Task UpdateAsync(BeatmapSetInfo beatmapSet, CancellationToken cancellationToken) + { + if (api?.State != APIState.Online) + return Task.CompletedTask; + + LogForModel(beatmapSet, "Performing online lookups..."); + return Task.WhenAll(beatmapSet.Beatmaps.Select(b => UpdateAsync(beatmapSet, b, cancellationToken)).ToArray()); + } + + // todo: expose this when we need to do individual difficulty lookups. + protected Task UpdateAsync(BeatmapSetInfo beatmapSet, BeatmapInfo beatmap, CancellationToken cancellationToken) + => Task.Factory.StartNew(() => update(beatmapSet, beatmap), cancellationToken, TaskCreationOptions.HideScheduler, updateScheduler); + + private void update(BeatmapSetInfo set, BeatmapInfo beatmap) + { + if (cacheDownloadRequest == null && storage.Exists(cache_database_name)) + { + try + { + using (var db = new SqliteConnection(storage.GetDatabaseConnectionString("online"))) + { + var found = db.QueryFirstOrDefault( + "SELECT * FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineBeatmapID OR filename = @Path", beatmap); + + if (found != null) + { + var status = (BeatmapSetOnlineStatus)found.approved; + + beatmap.Status = status; + beatmap.BeatmapSet.Status = status; + beatmap.BeatmapSet.OnlineBeatmapSetID = found.beatmapset_id; + beatmap.OnlineBeatmapID = found.beatmap_id; + + LogForModel(set, $"Cached local retrieval for {beatmap}."); + return; + } + } + } + catch (Exception ex) + { + LogForModel(set, $"Cached local retrieval for {beatmap} failed with {ex}."); + } + } + + if (api?.State != APIState.Online) + return; + + var req = new GetBeatmapRequest(beatmap); + + req.Failure += fail; + + try + { + // intentionally blocking to limit web request concurrency + api.Perform(req); + + var res = req.Result; + + if (res != null) + { + beatmap.Status = res.Status; + beatmap.BeatmapSet.Status = res.BeatmapSet.Status; + beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID; + beatmap.OnlineBeatmapID = res.OnlineBeatmapID; + + LogForModel(set, $"Online retrieval mapped {beatmap} to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}."); + } + } + catch (Exception e) + { + fail(e); + } + + void fail(Exception e) + { + beatmap.OnlineBeatmapID = null; + LogForModel(set, $"Online retrieval failed for {beatmap} ({e.Message})"); + } + } + + private void prepareLocalCache() + { + string cacheFilePath = storage.GetFullPath(cache_database_name); + string compressedCacheFilePath = $"{cacheFilePath}.bz2"; + + cacheDownloadRequest = new FileWebRequest(compressedCacheFilePath, $"https://assets.ppy.sh/client-resources/{cache_database_name}.bz2"); + + cacheDownloadRequest.Failed += ex => + { + File.Delete(compressedCacheFilePath); + File.Delete(cacheFilePath); + + Logger.Log($"{nameof(BeatmapUpdateQueue)}'s online cache download failed: {ex}", LoggingTarget.Database); + }; + + cacheDownloadRequest.Finished += () => + { + try + { + using (var stream = File.OpenRead(cacheDownloadRequest.Filename)) + using (var outStream = File.OpenWrite(cacheFilePath)) + using (var bz2 = new BZip2Stream(stream, CompressionMode.Decompress, false)) + bz2.CopyTo(outStream); + + // set to null on completion to allow lookups to begin using the new source + cacheDownloadRequest = null; + } + catch (Exception ex) + { + Logger.Log($"{nameof(BeatmapUpdateQueue)}'s online cache extraction failed: {ex}", LoggingTarget.Database); + } + finally + { + File.Delete(compressedCacheFilePath); + File.Delete(cacheFilePath); + } + }; + + cacheDownloadRequest.PerformAsync(); + } + + [Serializable] + [SuppressMessage("ReSharper", "InconsistentNaming")] + private class CachedOnlineBeatmapLookup + { + public int approved { get; set; } + + public int? beatmapset_id { get; set; } + + public int? beatmap_id { get; set; } + } + } + } +} diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 5e237d2ecb..839f9075e5 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -245,7 +245,7 @@ namespace osu.Game.Database /// protected abstract string[] HashableFileTypes { get; } - protected static void LogForModel(TModel model, string message, Exception e = null) + internal static void LogForModel(TModel model, string message, Exception e = null) { string prefix = $"[{(model?.Hash ?? "?????").Substring(0, 5)}]"; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index acb7fe5fbe..81818360a4 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -18,6 +18,7 @@ + From 917393697cf36c61c3356719bd39fbaac2208947 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 May 2020 14:38:46 +0900 Subject: [PATCH 610/655] 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 336479c40a..8214fa2f2c 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index acb7fe5fbe..eae763c412 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 6662e57dcd..9ff7e3fc02 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From b9b57792514d43a92dc5d2de7b84b248a487ad28 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 3 May 2020 09:31:56 +0900 Subject: [PATCH 611/655] Move deletion to catch instead of finally --- osu.Game/Beatmaps/BeatmapManager_UpdateQueue.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager_UpdateQueue.cs b/osu.Game/Beatmaps/BeatmapManager_UpdateQueue.cs index aa8be823f7..be4bd0d30d 100644 --- a/osu.Game/Beatmaps/BeatmapManager_UpdateQueue.cs +++ b/osu.Game/Beatmaps/BeatmapManager_UpdateQueue.cs @@ -154,11 +154,11 @@ namespace osu.Game.Beatmaps catch (Exception ex) { Logger.Log($"{nameof(BeatmapUpdateQueue)}'s online cache extraction failed: {ex}", LoggingTarget.Database); + File.Delete(cacheFilePath); } finally { File.Delete(compressedCacheFilePath); - File.Delete(cacheFilePath); } }; From 035b513b68347b6144fa06c9727a2bb404c46b29 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 3 May 2020 09:32:33 +0900 Subject: [PATCH 612/655] Use QuerySingle instead of QueryFirst --- osu.Game/Beatmaps/BeatmapManager_UpdateQueue.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager_UpdateQueue.cs b/osu.Game/Beatmaps/BeatmapManager_UpdateQueue.cs index be4bd0d30d..84901f9b50 100644 --- a/osu.Game/Beatmaps/BeatmapManager_UpdateQueue.cs +++ b/osu.Game/Beatmaps/BeatmapManager_UpdateQueue.cs @@ -65,7 +65,7 @@ namespace osu.Game.Beatmaps { using (var db = new SqliteConnection(storage.GetDatabaseConnectionString("online"))) { - var found = db.QueryFirstOrDefault( + var found = db.QuerySingleOrDefault( "SELECT * FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineBeatmapID OR filename = @Path", beatmap); if (found != null) From 6fef4eeb8f9fd1e46e3b8553b03496c201d911b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 3 May 2020 09:35:48 +0900 Subject: [PATCH 613/655] Rename class and extract out lookup method --- osu.Game/Beatmaps/BeatmapManager.cs | 6 +- ...eatmapManager_BeatmapOnlineLookupQueue.cs} | 83 +++++++++++-------- 2 files changed, 51 insertions(+), 38 deletions(-) rename osu.Game/Beatmaps/{BeatmapManager_UpdateQueue.cs => BeatmapManager_BeatmapOnlineLookupQueue.cs} (71%) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 22451382a3..19d1162d23 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -60,7 +60,7 @@ namespace osu.Game.Beatmaps private readonly BeatmapStore beatmaps; private readonly AudioManager audioManager; private readonly GameHost host; - private readonly BeatmapUpdateQueue updateQueue; + private readonly BeatmapOnlineLookupQueue onlineLookupQueue; private readonly Storage exportStorage; public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, AudioManager audioManager, GameHost host = null, @@ -77,7 +77,7 @@ namespace osu.Game.Beatmaps beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b); beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b); - updateQueue = new BeatmapUpdateQueue(api, storage); + onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage); exportStorage = storage.GetStorageForDirectory("exports"); } @@ -104,7 +104,7 @@ namespace osu.Game.Beatmaps bool hadOnlineBeatmapIDs = beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0); - await updateQueue.UpdateAsync(beatmapSet, cancellationToken); + await onlineLookupQueue.UpdateAsync(beatmapSet, cancellationToken); // ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID. if (hadOnlineBeatmapIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0)) diff --git a/osu.Game/Beatmaps/BeatmapManager_UpdateQueue.cs b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs similarity index 71% rename from osu.Game/Beatmaps/BeatmapManager_UpdateQueue.cs rename to osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs index 84901f9b50..2bd7529ab0 100644 --- a/osu.Game/Beatmaps/BeatmapManager_UpdateQueue.cs +++ b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs @@ -22,20 +22,20 @@ namespace osu.Game.Beatmaps { public partial class BeatmapManager { - private class BeatmapUpdateQueue + private class BeatmapOnlineLookupQueue { private readonly IAPIProvider api; private readonly Storage storage; private const int update_queue_request_concurrency = 4; - private readonly ThreadedTaskScheduler updateScheduler = new ThreadedTaskScheduler(update_queue_request_concurrency, nameof(BeatmapUpdateQueue)); + private readonly ThreadedTaskScheduler updateScheduler = new ThreadedTaskScheduler(update_queue_request_concurrency, nameof(BeatmapOnlineLookupQueue)); private FileWebRequest cacheDownloadRequest; private const string cache_database_name = "online.db"; - public BeatmapUpdateQueue(IAPIProvider api, Storage storage) + public BeatmapOnlineLookupQueue(IAPIProvider api, Storage storage) { this.api = api; this.storage = storage; @@ -55,38 +55,12 @@ namespace osu.Game.Beatmaps // todo: expose this when we need to do individual difficulty lookups. protected Task UpdateAsync(BeatmapSetInfo beatmapSet, BeatmapInfo beatmap, CancellationToken cancellationToken) - => Task.Factory.StartNew(() => update(beatmapSet, beatmap), cancellationToken, TaskCreationOptions.HideScheduler, updateScheduler); + => Task.Factory.StartNew(() => lookup(beatmapSet, beatmap), cancellationToken, TaskCreationOptions.HideScheduler, updateScheduler); - private void update(BeatmapSetInfo set, BeatmapInfo beatmap) + private void lookup(BeatmapSetInfo set, BeatmapInfo beatmap) { - if (cacheDownloadRequest == null && storage.Exists(cache_database_name)) - { - try - { - using (var db = new SqliteConnection(storage.GetDatabaseConnectionString("online"))) - { - var found = db.QuerySingleOrDefault( - "SELECT * FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineBeatmapID OR filename = @Path", beatmap); - - if (found != null) - { - var status = (BeatmapSetOnlineStatus)found.approved; - - beatmap.Status = status; - beatmap.BeatmapSet.Status = status; - beatmap.BeatmapSet.OnlineBeatmapSetID = found.beatmapset_id; - beatmap.OnlineBeatmapID = found.beatmap_id; - - LogForModel(set, $"Cached local retrieval for {beatmap}."); - return; - } - } - } - catch (Exception ex) - { - LogForModel(set, $"Cached local retrieval for {beatmap} failed with {ex}."); - } - } + if (checkLocalCache(set, beatmap)) + return; if (api?.State != APIState.Online) return; @@ -136,7 +110,7 @@ namespace osu.Game.Beatmaps File.Delete(compressedCacheFilePath); File.Delete(cacheFilePath); - Logger.Log($"{nameof(BeatmapUpdateQueue)}'s online cache download failed: {ex}", LoggingTarget.Database); + Logger.Log($"{nameof(BeatmapOnlineLookupQueue)}'s online cache download failed: {ex}", LoggingTarget.Database); }; cacheDownloadRequest.Finished += () => @@ -153,7 +127,7 @@ namespace osu.Game.Beatmaps } catch (Exception ex) { - Logger.Log($"{nameof(BeatmapUpdateQueue)}'s online cache extraction failed: {ex}", LoggingTarget.Database); + Logger.Log($"{nameof(BeatmapOnlineLookupQueue)}'s online cache extraction failed: {ex}", LoggingTarget.Database); File.Delete(cacheFilePath); } finally @@ -165,6 +139,45 @@ namespace osu.Game.Beatmaps cacheDownloadRequest.PerformAsync(); } + private bool checkLocalCache(BeatmapSetInfo set, BeatmapInfo beatmap) + { + // download is in progress (or was, and failed). + if (cacheDownloadRequest != null) + return false; + + // database is unavailable. + if (!storage.Exists(cache_database_name)) + return false; + + try + { + using (var db = new SqliteConnection(storage.GetDatabaseConnectionString("online"))) + { + var found = db.QuerySingleOrDefault( + "SELECT * FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineBeatmapID OR filename = @Path", beatmap); + + if (found != null) + { + var status = (BeatmapSetOnlineStatus)found.approved; + + beatmap.Status = status; + beatmap.BeatmapSet.Status = status; + beatmap.BeatmapSet.OnlineBeatmapSetID = found.beatmapset_id; + beatmap.OnlineBeatmapID = found.beatmap_id; + + LogForModel(set, $"Cached local retrieval for {beatmap}."); + return true; + } + } + } + catch (Exception ex) + { + LogForModel(set, $"Cached local retrieval for {beatmap} failed with {ex}."); + } + + return false; + } + [Serializable] [SuppressMessage("ReSharper", "InconsistentNaming")] private class CachedOnlineBeatmapLookup From 68d40cf79064efc39f89631a80873f0886df99f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 3 May 2020 13:25:57 +0900 Subject: [PATCH 614/655] Fix test failures due to online cache download --- osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs index 2bd7529ab0..2c79a664c5 100644 --- a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Dapper; using Microsoft.Data.Sqlite; +using osu.Framework.Development; using osu.Framework.IO.Network; using osu.Framework.Logging; using osu.Framework.Platform; @@ -40,7 +41,8 @@ namespace osu.Game.Beatmaps this.api = api; this.storage = storage; - if (!storage.Exists(cache_database_name)) + // avoid downloading / using cache for unit tests. + if (!DebugUtils.IsNUnitRunning && !storage.Exists(cache_database_name)) prepareLocalCache(); } From a1cd007cadfdb120f3a977e258676fedad854081 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 May 2020 14:43:47 +0900 Subject: [PATCH 615/655] Fix song select tests potentially failing due to difficulty panels not yet displayed --- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 12 +++++++++--- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index aed8e19fb2..802c324c90 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -110,7 +110,7 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); - AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault); + waitForInitialSelection(); WorkingBeatmap selected = null; @@ -135,7 +135,7 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); - AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault); + waitForInitialSelection(); WorkingBeatmap selected = null; @@ -189,7 +189,7 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); - AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault); + waitForInitialSelection(); WorkingBeatmap selected = null; @@ -769,6 +769,12 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("Check first item in group selected", () => Beatmap.Value.BeatmapInfo == groupIcon.Items.First().Beatmap); } + private void waitForInitialSelection() + { + AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault); + AddUntilStep("wait for difficulty panels visible", () => songSelect.Carousel.ChildrenOfType().Any()); + } + private int getBeatmapIndex(BeatmapSetInfo set, BeatmapInfo info) => set.Beatmaps.FindIndex(b => b == info); private int getCurrentBeatmapIndex() => getBeatmapIndex(songSelect.Carousel.SelectedBeatmapSet, songSelect.Carousel.SelectedBeatmap); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 19d1162d23..1b29f14c9b 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -140,7 +140,7 @@ namespace osu.Game.Beatmaps { var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineBeatmapID.HasValue).Select(b => b.OnlineBeatmapID).ToList(); - LogForModel(beatmapSet, "Validating online IDs..."); + LogForModel(beatmapSet, $"Validating online IDs for {beatmapSet.Beatmaps.Count} beatmaps..."); // ensure all IDs are unique if (beatmapIds.GroupBy(b => b).Any(g => g.Count() > 1)) From 02b9f51bdd4c2e0c2b07cefea40b54b4f0bbc0b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 May 2020 13:31:49 +0900 Subject: [PATCH 616/655] Add failing test --- .../SongSelect/TestScenePlaySongSelect.cs | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 802c324c90..851801d38a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -24,10 +24,12 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Taiko; +using osu.Game.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Filter; +using osu.Game.Users; using osuTK.Input; namespace osu.Game.Tests.Visual.SongSelect @@ -769,6 +771,70 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("Check first item in group selected", () => Beatmap.Value.BeatmapInfo == groupIcon.Items.First().Beatmap); } + [Test] + public void TestChangeRulesetWhilePresentingScore() + { + changeRuleset(0); + + createSongSelect(); + + addRulesetImportStep(0); + addRulesetImportStep(1); + + AddStep("present score", () => + { + var presentBeatmap = manager.QueryBeatmap(b => b.RulesetID == 0); + var switchBeatmap = manager.QueryBeatmap(b => b.RulesetID == 1); + + // this ruleset change should be overridden by the present. + Ruleset.Value = switchBeatmap.Ruleset; + + songSelect.PresentScore(new ScoreInfo + { + User = new User { Username = "woo" }, + Beatmap = presentBeatmap, + Ruleset = presentBeatmap.Ruleset + }); + }); + + AddUntilStep("wait for results screen presented", () => !songSelect.IsCurrentScreen()); + + AddAssert("check beatmap is correct for score", () => Beatmap.Value.BeatmapInfo.Equals(manager.QueryBeatmap(b => b.RulesetID == 0))); + AddAssert("check ruleset is correct for score", () => Ruleset.Value.ID == 0); + } + + [Test] + public void TestChangeBeatmapWhilePresentingScore() + { + changeRuleset(0); + + createSongSelect(); + + addRulesetImportStep(0); + addRulesetImportStep(1); + + AddStep("present score", () => + { + var presentBeatmap = manager.QueryBeatmap(b => b.RulesetID == 0); + var switchBeatmap = manager.QueryBeatmap(b => b.RulesetID == 1); + + // this beatmap change should be overridden by the present. + Beatmap.Value = manager.GetWorkingBeatmap(switchBeatmap); + + songSelect.PresentScore(new ScoreInfo + { + User = new User { Username = "woo" }, + Beatmap = presentBeatmap, + Ruleset = presentBeatmap.Ruleset + }); + }); + + AddUntilStep("wait for results screen presented", () => !songSelect.IsCurrentScreen()); + + AddAssert("check beatmap is correct for score", () => Beatmap.Value.BeatmapInfo.Equals(manager.QueryBeatmap(b => b.RulesetID == 0))); + AddAssert("check ruleset is correct for score", () => Ruleset.Value.ID == 0); + } + private void waitForInitialSelection() { AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault); @@ -882,6 +948,8 @@ namespace osu.Game.Tests.Visual.SongSelect public WorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap; public new BeatmapCarousel Carousel => base.Carousel; + public new void PresentScore(ScoreInfo score) => base.PresentScore(score); + protected override bool OnStart() { StartRequested?.Invoke(); From 06f58dd3e35a6d233845dbd73482e2638d22156f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 May 2020 14:04:30 +0900 Subject: [PATCH 617/655] Ensure correct beatmap and ruleset when presenting a score from song select --- osu.Game/Screens/Select/PlaySongSelect.cs | 6 +++++- osu.Game/Screens/Select/SongSelect.cs | 15 ++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 179aab54a3..21ddc5685d 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Screens; using osu.Game.Graphics; +using osu.Game.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Users; @@ -32,9 +33,12 @@ namespace osu.Game.Screens.Select Edit(); }, Key.Number4); - ((PlayBeatmapDetailArea)BeatmapDetails).Leaderboard.ScoreSelected += score => this.Push(new ResultsScreen(score)); + ((PlayBeatmapDetailArea)BeatmapDetails).Leaderboard.ScoreSelected += PresentScore; } + protected void PresentScore(ScoreInfo score) => + FinaliseSelection(score.Beatmap, score.Ruleset, () => this.Push(new ResultsScreen(score))); + protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); public override void OnResuming(IScreen last) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index a7e27c27ba..2b373ab7e0 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -342,13 +342,17 @@ namespace osu.Game.Screens.Select /// Call to make a selection and perform the default action for this SongSelect. /// /// An optional beatmap to override the current carousel selection. - /// Whether to trigger . - public void FinaliseSelection(BeatmapInfo beatmap = null, bool performStartAction = true) + /// An optional ruleset to override the current carousel selection. + /// An optional custom action to perform instead of . + public void FinaliseSelection(BeatmapInfo beatmap = null, RulesetInfo ruleset = null, Action customStartAction = null) { // This is very important as we have not yet bound to screen-level bindables before the carousel load is completed. if (!Carousel.BeatmapSetsLoaded) return; + if (ruleset != null) + Ruleset.Value = ruleset; + transferRulesetValue(); // while transferRulesetValue will flush, it only does so if the ruleset changes. @@ -369,7 +373,12 @@ namespace osu.Game.Screens.Select selectionChangedDebounce = null; } - if (performStartAction && OnStart()) + if (customStartAction != null) + { + customStartAction(); + Carousel.AllowSelection = false; + } + else if (OnStart()) Carousel.AllowSelection = false; } From 81889e0034115e5a22f4882b1f6e98703d9acf50 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 May 2020 15:19:36 +0900 Subject: [PATCH 618/655] Fix tests potentially selecting a deleted beatmap --- .../SongSelect/TestScenePlaySongSelect.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 851801d38a..81fd1b66e5 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -774,6 +774,9 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestChangeRulesetWhilePresentingScore() { + BeatmapInfo getPresentBeatmap() => manager.QueryBeatmap(b => !b.BeatmapSet.DeletePending && b.RulesetID == 0); + BeatmapInfo getSwitchBeatmap() => manager.QueryBeatmap(b => !b.BeatmapSet.DeletePending && b.RulesetID == 1); + changeRuleset(0); createSongSelect(); @@ -783,55 +786,52 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("present score", () => { - var presentBeatmap = manager.QueryBeatmap(b => b.RulesetID == 0); - var switchBeatmap = manager.QueryBeatmap(b => b.RulesetID == 1); - // this ruleset change should be overridden by the present. - Ruleset.Value = switchBeatmap.Ruleset; + Ruleset.Value = getSwitchBeatmap().Ruleset; songSelect.PresentScore(new ScoreInfo { User = new User { Username = "woo" }, - Beatmap = presentBeatmap, - Ruleset = presentBeatmap.Ruleset + Beatmap = getPresentBeatmap(), + Ruleset = getPresentBeatmap().Ruleset }); }); AddUntilStep("wait for results screen presented", () => !songSelect.IsCurrentScreen()); - AddAssert("check beatmap is correct for score", () => Beatmap.Value.BeatmapInfo.Equals(manager.QueryBeatmap(b => b.RulesetID == 0))); + AddAssert("check beatmap is correct for score", () => Beatmap.Value.BeatmapInfo.Equals(getPresentBeatmap())); AddAssert("check ruleset is correct for score", () => Ruleset.Value.ID == 0); } [Test] public void TestChangeBeatmapWhilePresentingScore() { - changeRuleset(0); + BeatmapInfo getPresentBeatmap() => manager.QueryBeatmap(b => !b.BeatmapSet.DeletePending && b.RulesetID == 0); + BeatmapInfo getSwitchBeatmap() => manager.QueryBeatmap(b => !b.BeatmapSet.DeletePending && b.RulesetID == 1); - createSongSelect(); + changeRuleset(0); addRulesetImportStep(0); addRulesetImportStep(1); + createSongSelect(); + AddStep("present score", () => { - var presentBeatmap = manager.QueryBeatmap(b => b.RulesetID == 0); - var switchBeatmap = manager.QueryBeatmap(b => b.RulesetID == 1); - // this beatmap change should be overridden by the present. - Beatmap.Value = manager.GetWorkingBeatmap(switchBeatmap); + Beatmap.Value = manager.GetWorkingBeatmap(getSwitchBeatmap()); songSelect.PresentScore(new ScoreInfo { User = new User { Username = "woo" }, - Beatmap = presentBeatmap, - Ruleset = presentBeatmap.Ruleset + Beatmap = getPresentBeatmap(), + Ruleset = getPresentBeatmap().Ruleset }); }); AddUntilStep("wait for results screen presented", () => !songSelect.IsCurrentScreen()); - AddAssert("check beatmap is correct for score", () => Beatmap.Value.BeatmapInfo.Equals(manager.QueryBeatmap(b => b.RulesetID == 0))); + AddAssert("check beatmap is correct for score", () => Beatmap.Value.BeatmapInfo.Equals(getPresentBeatmap())); AddAssert("check ruleset is correct for score", () => Ruleset.Value.ID == 0); } From 46b0526db7afd25e1a962420ee8235df1f109801 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 4 May 2020 15:27:04 +0900 Subject: [PATCH 619/655] Remove hack limiting max number of ticks --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 19219cc1ba..01011645bd 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -58,8 +58,6 @@ namespace osu.Game.Rulesets.Catch.Objects SliderEventDescriptor? lastEvent = null; - int ticksGenerated = 0; - foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset)) { // generate tiny droplets since the last point @@ -75,9 +73,6 @@ namespace osu.Game.Rulesets.Catch.Objects for (double t = timeBetweenTiny; t < sinceLastTick; t += timeBetweenTiny) { - if (ticksGenerated++ >= 10000) - break; - AddNested(new TinyDroplet { StartTime = t + lastEvent.Value.Time, From 6d3a24ff01cdeea9bf52d164087279989b277b6d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 4 May 2020 15:55:42 +0900 Subject: [PATCH 620/655] Reorder tick hit results --- osu.Game/Rulesets/Scoring/HitResult.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 0c895bd086..b057af2a50 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -44,9 +44,24 @@ namespace osu.Game.Rulesets.Scoring [Description(@"Perfect")] Perfect, - SmallTickHit, + /// + /// Indicates small tick miss. + /// SmallTickMiss, - LargeTickHit, + + /// + /// Indicates a small tick hit. + /// + SmallTickHit, + + /// + /// Indicates a large tick miss. + /// LargeTickMiss, + + /// + /// Indicates a large tick hit. + /// + LargeTickHit } } From 62d433c9c57b2de866d579f4bfbad543eadc9649 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 4 May 2020 17:01:07 +0900 Subject: [PATCH 621/655] Adjust diffcalc test value --- osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs index 51fe0b035d..ee416e5a38 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Catch"; - [TestCase(4.2058561036909863d, "diffcalc-test")] + [TestCase(4.050601681491468d, "diffcalc-test")] public void Test(double expected, string name) => base.Test(expected, name); From e2593ac3e77e82d14c776ef4cf6c19c60ac697b3 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 4 May 2020 08:56:18 +0000 Subject: [PATCH 622/655] Bump Microsoft.CodeAnalysis.FxCopAnalyzers from 2.9.8 to 3.0.0 Bumps [Microsoft.CodeAnalysis.FxCopAnalyzers](https://github.com/dotnet/roslyn-analyzers) from 2.9.8 to 3.0.0. - [Release notes](https://github.com/dotnet/roslyn-analyzers/releases) - [Changelog](https://github.com/dotnet/roslyn-analyzers/blob/master/PostReleaseActivities.md) - [Commits](https://github.com/dotnet/roslyn-analyzers/compare/v2.9.8...v3.0.0) Signed-off-by: dependabot-preview[bot] --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 21b8b402e0..fbe300458e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -18,7 +18,7 @@ - + $(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset From 4ee2e6cd47add8c964d61445cca16a271b6236dc Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 4 May 2020 08:57:09 +0000 Subject: [PATCH 623/655] Bump Humanizer from 2.8.2 to 2.8.11 Bumps [Humanizer](https://github.com/Humanizr/Humanizer) from 2.8.2 to 2.8.11. - [Release notes](https://github.com/Humanizr/Humanizer/releases) - [Changelog](https://github.com/Humanizr/Humanizer/blob/master/release_notes.md) - [Commits](https://github.com/Humanizr/Humanizer/compare/v2.8.2...v2.8.11) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 91c89cbc20..9db5fe562c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -20,7 +20,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 9ff7e3fc02..82253a0418 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -76,7 +76,7 @@ - + From fe31bac505bb59891325fc59afb4ba597df3e052 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 4 May 2020 18:20:20 +0900 Subject: [PATCH 624/655] Fix build error --- osu.Game/Overlays/SearchableList/DisplayStyleControl.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs b/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs index a33f4eb30d..5ecb477a2f 100644 --- a/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs +++ b/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs @@ -91,6 +91,8 @@ namespace osu.Game.Overlays.SearchableList protected override void Dispose(bool isDisposing) { + base.Dispose(isDisposing); + bindable.ValueChanged -= Bindable_ValueChanged; } } From 969412a4265aa3c94f0a6112552b5ad9287f4908 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 4 May 2020 10:28:42 +0000 Subject: [PATCH 625/655] Bump Microsoft.CodeAnalysis.BannedApiAnalyzers from 2.9.8 to 3.0.0 Bumps [Microsoft.CodeAnalysis.BannedApiAnalyzers](https://github.com/dotnet/roslyn-analyzers) from 2.9.8 to 3.0.0. - [Release notes](https://github.com/dotnet/roslyn-analyzers/releases) - [Changelog](https://github.com/dotnet/roslyn-analyzers/blob/master/PostReleaseActivities.md) - [Commits](https://github.com/dotnet/roslyn-analyzers/compare/v2.9.8...v3.0.0) Signed-off-by: dependabot-preview[bot] --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 21b8b402e0..5d011dfdc5 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -16,7 +16,7 @@ - + From c987af988c98745a8039372563b87717a7691e46 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 4 May 2020 18:26:12 -0700 Subject: [PATCH 626/655] Fix typo --- osu.Game/Screens/OsuScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 2124a66a75..35bb4fa34f 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens { /// /// The amount of negative padding that should be applied to game background content which touches both the left and right sides of the screen. - /// This allows for the game content to be pushed byt he options/notification overlays without causing black areas to appear. + /// This allows for the game content to be pushed by the options/notification overlays without causing black areas to appear. /// public const float HORIZONTAL_OVERFLOW_PADDING = 50; From 0e2ccac33b916abc63e9aa4e8cb474e6761fb22e Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 4 May 2020 18:31:11 -0700 Subject: [PATCH 627/655] Add spaces to comments --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 10 ++++----- osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs | 2 +- .../Replays/CatchAutoGenerator.cs | 6 ++--- .../Beatmaps/OsuBeatmapProcessor.cs | 18 +++++++-------- osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs | 2 +- .../Drawables/Pieces/MainCirclePiece.cs | 2 +- .../Beatmaps/IO/ImportBeatmapTest.cs | 22 +++++++++---------- .../NonVisual/ControlPointInfoTest.cs | 2 +- .../NonVisual/FramedReplayInputHandlerTest.cs | 6 ++--- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/Graphics/Backgrounds/Triangles.cs | 4 ++-- .../Graphics/Containers/OsuScrollContainer.cs | 2 +- osu.Game/IPC/ArchiveImportIPCChannel.cs | 2 +- osu.Game/Online/API/APIAccess.cs | 8 +++---- osu.Game/Online/API/APIRequest.cs | 2 +- osu.Game/Online/Chat/Channel.cs | 2 +- osu.Game/Online/Chat/MessageFormatter.cs | 4 ++-- osu.Game/OsuGame.cs | 4 ++-- .../BeatmapListing/Panels/GridBeatmapPanel.cs | 2 +- osu.Game/Overlays/BeatmapSet/Header.cs | 2 +- osu.Game/Overlays/ChatOverlay.cs | 2 +- osu.Game/Overlays/DialogOverlay.cs | 2 +- osu.Game/Overlays/Music/PlaylistItem.cs | 2 +- osu.Game/Overlays/MusicController.cs | 2 +- osu.Game/Overlays/News/NewsArticleCover.cs | 2 +- .../Notifications/ProgressNotification.cs | 2 +- osu.Game/Overlays/NowPlayingOverlay.cs | 2 +- osu.Game/Overlays/OSD/Toast.cs | 2 +- .../Components/OverlinedInfoContainer.cs | 2 +- .../Profile/Header/MedalHeaderContainer.cs | 2 +- .../SearchableListFilterControl.cs | 2 +- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 2 +- osu.Game/Overlays/VolumeOverlay.cs | 2 +- .../Replays/FramedReplayInputHandler.cs | 6 ++--- osu.Game/Rulesets/RulesetStore.cs | 6 ++--- osu.Game/Screens/BackgroundScreen.cs | 2 +- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- osu.Game/Screens/Menu/LogoVisualisation.cs | 10 ++++----- osu.Game/Screens/Menu/MainMenu.cs | 2 +- osu.Game/Screens/Play/BreakOverlay.cs | 2 +- .../Screens/Play/HUD/PlayerSettingsOverlay.cs | 2 +- osu.Game/Screens/Play/KeyCounter.cs | 4 ++-- osu.Game/Screens/Play/PlayerLoader.cs | 6 ++--- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- .../Select/BeatmapDetailAreaTabControl.cs | 2 +- osu.Game/Screens/Select/BeatmapDetails.cs | 4 ++-- .../Screens/Select/Details/AdvancedStats.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- 48 files changed, 92 insertions(+), 92 deletions(-) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 60b47a8b3a..ade8460dd7 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -43,7 +43,7 @@ namespace osu.Desktop.Updater private async void checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null) { - //should we schedule a retry on completion of this check? + // should we schedule a retry on completion of this check? bool scheduleRecheck = true; try @@ -52,7 +52,7 @@ namespace osu.Desktop.Updater var info = await updateManager.CheckForUpdate(!useDeltaPatching); if (info.ReleasesToApply.Count == 0) - //no updates available. bail and retry later. + // no updates available. bail and retry later. return; if (notification == null) @@ -81,8 +81,8 @@ namespace osu.Desktop.Updater { logger.Add(@"delta patching failed; will attempt full download!"); - //could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959) - //try again without deltas. + // could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959) + // try again without deltas. checkForUpdateAsync(false, notification); scheduleRecheck = false; } @@ -101,7 +101,7 @@ namespace osu.Desktop.Updater { if (scheduleRecheck) { - //check again in 30 minutes. + // check again in 30 minutes. Scheduler.AddDelayed(() => checkForUpdateAsync(), 60000 * 30); } } diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs index 16414261a5..c1d24395e4 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Catch.Mods RelativeSizeAxes = Axes.Both; } - //disable keyboard controls + // disable keyboard controls public bool OnPressed(CatchAction action) => true; public void OnReleased(CatchAction action) diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index b90b5812a6..7a33cb0577 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.Replays if (lastPosition - catcher_width_half < h.X && lastPosition + catcher_width_half > h.X) { - //we are already in the correct range. + // we are already in the correct range. lastTime = h.StartTime; addFrame(h.StartTime, lastPosition); return; @@ -72,14 +72,14 @@ namespace osu.Game.Rulesets.Catch.Replays } else if (dashRequired) { - //we do a movement in two parts - the dash part then the normal part... + // we do a movement in two parts - the dash part then the normal part... double timeAtNormalSpeed = positionChange / movement_speed; double timeWeNeedToSave = timeAtNormalSpeed - timeAvailable; double timeAtDashSpeed = timeWeNeedToSave / 2; float midPosition = (float)Interpolation.Lerp(lastPosition, h.X, (float)timeAtDashSpeed / timeAvailable); - //dash movement + // dash movement addFrame(h.StartTime - timeAvailable + 1, lastPosition, true); addFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition); addFrame(h.StartTime, h.X); diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index 3a829f72fa..f51f04bf87 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo.StackLeniency; if (objectN.StartTime - endTime > stackThreshold) - //We are no longer within stacking range of the next object. + // We are no longer within stacking range of the next object. break; if (Vector2Extensions.Distance(stackBaseObject.Position, objectN.Position) < stack_distance @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps } } - //Reverse pass for stack calculation. + // Reverse pass for stack calculation. int extendedStartIndex = startIndex; for (int i = extendedEndIndex; i > startIndex; i--) @@ -124,7 +124,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps double endTime = objectN.GetEndTime(); if (objectI.StartTime - endTime > stackThreshold) - //We are no longer within stacking range of the previous object. + // We are no longer within stacking range of the previous object. break; // HitObjects before the specified update range haven't been reset yet @@ -145,20 +145,20 @@ namespace osu.Game.Rulesets.Osu.Beatmaps for (int j = n + 1; j <= i; j++) { - //For each object which was declared under this slider, we will offset it to appear *below* the slider end (rather than above). + // For each object which was declared under this slider, we will offset it to appear *below* the slider end (rather than above). OsuHitObject objectJ = beatmap.HitObjects[j]; if (Vector2Extensions.Distance(objectN.EndPosition, objectJ.Position) < stack_distance) objectJ.StackHeight -= offset; } - //We have hit a slider. We should restart calculation using this as the new base. - //Breaking here will mean that the slider still has StackCount of 0, so will be handled in the i-outer-loop. + // We have hit a slider. We should restart calculation using this as the new base. + // Breaking here will mean that the slider still has StackCount of 0, so will be handled in the i-outer-loop. break; } if (Vector2Extensions.Distance(objectN.Position, objectI.Position) < stack_distance) { - //Keep processing as if there are no sliders. If we come across a slider, this gets cancelled out. + // Keep processing as if there are no sliders. If we come across a slider, this gets cancelled out. //NOTE: Sliders with start positions stacking are a special case that is also handled here. objectN.StackHeight = objectI.StackHeight + 1; @@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps if (objectN is Spinner) continue; if (objectI.StartTime - objectN.StartTime > stackThreshold) - //We are no longer within stacking range of the previous object. + // We are no longer within stacking range of the previous object. break; if (Vector2Extensions.Distance(objectN.EndPosition, objectI.Position) < stack_distance) @@ -221,7 +221,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps } else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, position2) < stack_distance) { - //Case for sliders - bump notes down and right, rather than up and left. + // Case for sliders - bump notes down and right, rather than up and left. sliderStack++; beatmap.HitObjects[j].StackHeight -= sliderStack; startTime = beatmap.HitObjects[j].GetEndTime(); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs index 44dba7715a..5e80d08667 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Mods Vector2 originalPosition = drawable.Position; Vector2 appearOffset = new Vector2(MathF.Cos(theta), MathF.Sin(theta)) * appearDistance; - //the - 1 and + 1 prevents the hit objects to appear in the wrong position. + // the - 1 and + 1 prevents the hit objects to appear in the wrong position. double appearTime = hitObject.StartTime - hitObject.TimePreempt - 1; double moveDuration = hitObject.TimePreempt + 1; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs index e364c96426..cb3787a493 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces using (BeginDelayedSequence(flash_in, true)) { - //after the flash, we can hide some elements that were behind it + // after the flash, we can hide some elements that were behind it ring.FadeOut(); circle.FadeOut(); number.FadeOut(); diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index c6095ae404..ba6f5fc85c 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportWhenClosed() { - //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWhenClosed))) { try @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportThenDelete() { - //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenDelete))) { try @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportThenImport() { - //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImport))) { try @@ -96,7 +96,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportCorruptThenImport() { - //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportCorruptThenImport))) { try @@ -138,7 +138,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestRollbackOnFailure() { - //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestRollbackOnFailure))) { try @@ -215,7 +215,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportThenImportDifferentHash() { - //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportDifferentHash))) { try @@ -246,7 +246,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportThenDeleteThenImport() { - //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenDeleteThenImport))) { try @@ -274,7 +274,7 @@ namespace osu.Game.Tests.Beatmaps.IO [TestCase(false)] public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set) { - //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(TestImportThenDeleteThenImportWithOnlineIDMismatch)}-{set}")) { try @@ -308,7 +308,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportWithDuplicateBeatmapIDs() { - //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithDuplicateBeatmapIDs))) { try @@ -695,12 +695,12 @@ namespace osu.Game.Tests.Beatmaps.IO waitForOrAssert(() => (resultSets = store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526)).Any(), @"BeatmapSet did not import to the database in allocated time.", timeout); - //ensure we were stored to beatmap database backing... + // ensure we were stored to beatmap database backing... Assert.IsTrue(resultSets.Count() == 1, $@"Incorrect result count found ({resultSets.Count()} but should be 1)."); IEnumerable queryBeatmaps() => store.QueryBeatmaps(s => s.BeatmapSet.OnlineBeatmapSetID == 241526 && s.BaseDifficultyID > 0); IEnumerable queryBeatmapSets() => store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526); - //if we don't re-check here, the set will be inserted but the beatmaps won't be present yet. + // if we don't re-check here, the set will be inserted but the beatmaps won't be present yet. waitForOrAssert(() => queryBeatmaps().Count() == 12, @"Beatmaps did not import to the database in allocated time", timeout); waitForOrAssert(() => queryBeatmapSets().Count() == 1, diff --git a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs index 158954106d..830e4bc603 100644 --- a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs +++ b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.NonVisual Assert.That(cpi.TimingPoints.Count, Is.EqualTo(2)); Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2)); - cpi.Add(1000, new TimingControlPoint()); //is redundant + cpi.Add(1000, new TimingControlPoint()); // is redundant Assert.That(cpi.Groups.Count, Is.EqualTo(2)); Assert.That(cpi.TimingPoints.Count, Is.EqualTo(2)); diff --git a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs index 7df7df22ea..92a60663de 100644 --- a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs +++ b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs @@ -46,12 +46,12 @@ namespace osu.Game.Tests.NonVisual confirmCurrentFrame(0); confirmNextFrame(1); - //if we hit the first frame perfectly, time should progress to it. + // if we hit the first frame perfectly, time should progress to it. setTime(1000, 1000); confirmCurrentFrame(1); confirmNextFrame(2); - //in between non-important frames should progress based on input. + // in between non-important frames should progress based on input. setTime(1200, 1200); confirmCurrentFrame(1); @@ -144,7 +144,7 @@ namespace osu.Game.Tests.NonVisual confirmCurrentFrame(2); confirmNextFrame(1); - //ensure each frame plays out until start + // ensure each frame plays out until start setTime(-500, 1000); confirmCurrentFrame(1); confirmNextFrame(0); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 19d1162d23..d7c30dc9ff 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -381,7 +381,7 @@ namespace osu.Game.Beatmaps foreach (var file in files.Where(f => f.Filename.EndsWith(".osu"))) { using (var raw = Files.Store.GetStream(file.FileInfo.StoragePath)) - using (var ms = new MemoryStream()) //we need a memory stream so we can seek + using (var ms = new MemoryStream()) // we need a memory stream so we can seek using (var sr = new LineBufferedReader(ms)) { raw.CopyTo(ms); diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 590e4b2a5c..27027202ce 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -193,8 +193,8 @@ namespace osu.Game.Graphics.Backgrounds float u1 = 1 - RNG.NextSingle(); //uniform(0,1] random floats float u2 = 1 - RNG.NextSingle(); - float randStdNormal = (float)(Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2)); //random normal(0,1) - var scale = Math.Max(triangleScale * (mean + std_dev * randStdNormal), 0.1f); //random normal(mean,stdDev^2) + float randStdNormal = (float)(Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2)); // random normal(0,1) + var scale = Math.Max(triangleScale * (mean + std_dev * randStdNormal), 0.1f); // random normal(mean,stdDev^2) return new TriangleParticle { Scale = scale }; } diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs index 1824fcd878..d504a11b22 100644 --- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs +++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs @@ -158,7 +158,7 @@ namespace osu.Game.Graphics.Containers { if (!base.OnMouseDown(e)) return false; - //note that we are changing the colour of the box here as to not interfere with the hover effect. + // note that we are changing the colour of the box here as to not interfere with the hover effect. box.FadeColour(highlightColour, 100); return true; } diff --git a/osu.Game/IPC/ArchiveImportIPCChannel.cs b/osu.Game/IPC/ArchiveImportIPCChannel.cs index 484db932f8..029908ec9d 100644 --- a/osu.Game/IPC/ArchiveImportIPCChannel.cs +++ b/osu.Game/IPC/ArchiveImportIPCChannel.cs @@ -32,7 +32,7 @@ namespace osu.Game.IPC { if (importer == null) { - //we want to contact a remote osu! to handle the import. + // we want to contact a remote osu! to handle the import. await SendMessageAsync(new ArchiveImportMessage { Path = path }); return; } diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index adfef1d11f..4945f7f185 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -127,7 +127,7 @@ namespace osu.Game.Online.API case APIState.Offline: case APIState.Connecting: - //work to restore a connection... + // work to restore a connection... if (!HasLogin) { State = APIState.Offline; @@ -180,7 +180,7 @@ namespace osu.Game.Online.API break; } - //hard bail if we can't get a valid access token. + // hard bail if we can't get a valid access token. if (authentication.RequestAccessToken() == null) { Logout(); @@ -274,7 +274,7 @@ namespace osu.Game.Online.API { req.Perform(this); - //we could still be in initialisation, at which point we don't want to say we're Online yet. + // we could still be in initialisation, at which point we don't want to say we're Online yet. if (IsLoggedIn) State = APIState.Online; failureCount = 0; @@ -339,7 +339,7 @@ namespace osu.Game.Online.API log.Add($@"API failure count is now {failureCount}"); if (failureCount < 3) - //we might try again at an api level. + // we might try again at an api level. return false; if (State == APIState.Online) diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 0bba04cac3..0f8acbb7af 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -98,7 +98,7 @@ namespace osu.Game.Online.API if (checkAndScheduleFailure()) return; - if (!WebRequest.Aborted) //could have been aborted by a Cancel() call + if (!WebRequest.Aborted) // could have been aborted by a Cancel() call { Logger.Log($@"Performing request {this}", LoggingTarget.Network); WebRequest.Perform(); diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index 6f67a95f53..dbb2da5c03 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -61,7 +61,7 @@ namespace osu.Game.Online.Chat /// public event Action MessageRemoved; - public bool ReadOnly => false; //todo not yet used. + public bool ReadOnly => false; // todo: not yet used. public override string ToString() => Name; diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 717de18c14..6af2561c89 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -78,13 +78,13 @@ namespace osu.Game.Online.Chat { result.Text = result.Text.Remove(index, m.Length).Insert(index, displayText); - //since we just changed the line display text, offset any already processed links. + // since we just changed the line display text, offset any already processed links. result.Links.ForEach(l => l.Index -= l.Index > index ? m.Length - displayText.Length : 0); var details = GetLinkDetails(linkText); result.Links.Add(new Link(linkText, index, displayText.Length, linkActionOverride ?? details.Action, details.Argument)); - //adjust the offset for processing the current matches group. + // adjust the offset for processing the current matches group. captureOffset += m.Length - displayText.Length; } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 8e62819c95..fdc8d94352 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -609,7 +609,7 @@ namespace osu.Game loadComponentSingleFile(screenshotManager, Add); - //overlay elements + // overlay elements loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true); loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true); var rankingsOverlay = loadComponentSingleFile(new RankingsOverlay(), overlayContent.Add, true); @@ -781,7 +781,7 @@ namespace osu.Game { var previousLoadStream = asyncLoadStream; - //chain with existing load stream + // chain with existing load stream asyncLoadStream = Task.Run(async () => { if (previousLoadStream != null) diff --git a/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs index 84d35da096..28c36e6c56 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs @@ -35,7 +35,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels : base(beatmap) { Width = 380; - Height = 140 + vertical_padding; //full height of all the elements plus vertical padding (autosize uses the image) + Height = 140 + vertical_padding; // full height of all the elements plus vertical padding (autosize uses the image) } protected override void LoadComplete() diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index 17fa689cd2..1ff08aab2c 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -140,7 +140,7 @@ namespace osu.Game.Overlays.BeatmapSet { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Margin = new MarginPadding { Left = 3, Bottom = 4 }, //To better lineup with the font + Margin = new MarginPadding { Left = 3, Bottom = 4 }, // To better lineup with the font }, } }, diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 34afc3c431..5ba55f6d45 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -358,7 +358,7 @@ namespace osu.Game.Overlays protected override void OnFocus(FocusEvent e) { - //this is necessary as textbox is masked away and therefore can't get focus :( + // this is necessary as textbox is masked away and therefore can't get focus :( textbox.TakeFocus(); base.OnFocus(e); } diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 59d748bc5d..9f9dbdbaf1 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -50,7 +50,7 @@ namespace osu.Game.Overlays { if (v != Visibility.Hidden) return; - //handle the dialog being dismissed. + // handle the dialog being dismissed. dialog.Delay(PopupDialog.EXIT_DURATION).Expire(); if (dialog == CurrentDialog) diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index de2f916946..840fa51b4f 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -78,7 +78,7 @@ namespace osu.Game.Overlays.Music { text.Clear(); - //space after the title to put a space between the title and artist + // space after the title to put a space between the title and artist titleSprites = text.AddText(title.Value + @" ", sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)).OfType(); text.AddText(artist.Value, sprite => diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index c872f82b32..ded641b262 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -250,7 +250,7 @@ namespace osu.Game.Overlays } else { - //figure out the best direction based on order in playlist. + // figure out the best direction based on order in playlist. var last = BeatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count(); var next = beatmap.NewValue == null ? -1 : BeatmapSets.TakeWhile(b => b.ID != beatmap.NewValue.BeatmapSetInfo?.ID).Count(); diff --git a/osu.Game/Overlays/News/NewsArticleCover.cs b/osu.Game/Overlays/News/NewsArticleCover.cs index cca0cfb4a0..e3f5a8cea3 100644 --- a/osu.Game/Overlays/News/NewsArticleCover.cs +++ b/osu.Game/Overlays/News/NewsArticleCover.cs @@ -162,7 +162,7 @@ namespace osu.Game.Overlays.News public string TooltipText => date.ToString("dddd dd MMMM yyyy hh:mm:ss UTCz").ToUpper(); } - //fake API data struct to use for now as a skeleton for data, as there is no API struct for news article info for now + // fake API data struct to use for now as a skeleton for data, as there is no API struct for news article info for now public class ArticleInfo { public string Title { get; set; } diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 99836705c4..3105ecd742 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Notifications { base.LoadComplete(); - //we may have received changes before we were displayed. + // we may have received changes before we were displayed. updateState(); } diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 118cb037cb..ebb4a96d14 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -261,7 +261,7 @@ namespace osu.Game.Overlays // todo: this can likely be replaced with WorkingBeatmap.GetBeatmapAsync() Task.Run(() => { - if (beatmap?.Beatmap == null) //this is not needed if a placeholder exists + if (beatmap?.Beatmap == null) // this is not needed if a placeholder exists { title.Text = @"Nothing to play"; artist.Text = @"Nothing to play"; diff --git a/osu.Game/Overlays/OSD/Toast.cs b/osu.Game/Overlays/OSD/Toast.cs index 5d36cac20e..1497ca8fa8 100644 --- a/osu.Game/Overlays/OSD/Toast.cs +++ b/osu.Game/Overlays/OSD/Toast.cs @@ -31,7 +31,7 @@ namespace osu.Game.Overlays.OSD InternalChildren = new Drawable[] { - new Container //this container exists just to set a minimum width for the toast + new Container // this container exists just to set a minimum width for the toast { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs b/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs index b11e41f90f..9f56a34aa6 100644 --- a/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs +++ b/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs @@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { Font = OsuFont.GetFont(size: big ? 40 : 18, weight: FontWeight.Light) }, - new Container //Add a minimum size to the FillFlowContainer + new Container // Add a minimum size to the FillFlowContainer { Width = minimumWidth, } diff --git a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs index a5938a3fe7..e7df4eb5eb 100644 --- a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs @@ -35,7 +35,7 @@ namespace osu.Game.Overlays.Profile.Header RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background5, }, - new Container //artificial shadow + new Container // artificial shadow { RelativeSizeAxes = Axes.X, Height = 3, diff --git a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs index 117f905de4..d31470e685 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs @@ -94,7 +94,7 @@ namespace osu.Game.Overlays.SearchableList RelativeSizeAxes = Axes.X, }, }, - new Box //keep the tab strip part of autosize, but don't put it in the flow container + new Box // keep the tab strip part of autosize, but don't put it in the flow container { RelativeSizeAxes = Axes.X, Height = 1, diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index d6b810366d..3d66d3c28e 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -110,7 +110,7 @@ namespace osu.Game.Overlays.Toolbar tooltipContainer = new FillFlowContainer { Direction = FillDirection.Vertical, - RelativeSizeAxes = Axes.Both, //stops us being considered in parent's autosize + RelativeSizeAxes = Axes.Both, // stops us being considered in parent's autosize Anchor = TooltipAnchor.HasFlag(Anchor.x0) ? Anchor.BottomLeft : Anchor.BottomRight, Origin = TooltipAnchor, Position = new Vector2(TooltipAnchor.HasFlag(Anchor.x0) ? 5 : -5, 5), diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index b484921cce..676d2c941a 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -58,7 +58,7 @@ namespace osu.Game.Overlays { volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker) { - Margin = new MarginPadding { Top = 100 + MuteButton.HEIGHT } //to counter the mute button and re-center the volume meters + Margin = new MarginPadding { Top = 100 + MuteButton.HEIGHT } // to counter the mute button and re-center the volume meters }, volumeMeterMaster = new VolumeMeter("MASTER", 150, colours.PinkDarker), volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker), diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index 7e17396fde..55d82c4083 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Replays { int newFrame = nextFrameIndex; - //ensure we aren't at an extent. + // ensure we aren't at an extent. if (newFrame == currentFrameIndex) return false; currentFrameIndex = newFrame; @@ -99,8 +99,8 @@ namespace osu.Game.Rulesets.Replays if (frame == null) return false; - return IsImportant(frame) && //a button is in a pressed state - Math.Abs(CurrentTime - NextFrame?.Time ?? 0) <= AllowedImportantTimeSpan; //the next frame is within an allowable time span + return IsImportant(frame) && // a button is in a pressed state + Math.Abs(CurrentTime - NextFrame?.Time ?? 0) <= AllowedImportantTimeSpan; // the next frame is within an allowable time span } } diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 543134cfb4..f302f8700f 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets var instances = loadedAssemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r)).ToList(); - //add all legacy rulesets first to ensure they have exclusive choice of primary key. + // add all legacy rulesets first to ensure they have exclusive choice of primary key. foreach (var r in instances.Where(r => r is ILegacyRuleset)) { if (context.RulesetInfo.SingleOrDefault(dbRuleset => dbRuleset.ID == r.RulesetInfo.ID) == null) @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets context.SaveChanges(); - //add any other modes + // add any other modes foreach (var r in instances.Where(r => !(r is ILegacyRuleset))) { if (context.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo == r.RulesetInfo.InstantiationInfo) == null) @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets context.SaveChanges(); - //perform a consistency check + // perform a consistency check foreach (var r in context.RulesetInfo) { try diff --git a/osu.Game/Screens/BackgroundScreen.cs b/osu.Game/Screens/BackgroundScreen.cs index 5dfaceccf5..0f3615b7a9 100644 --- a/osu.Game/Screens/BackgroundScreen.cs +++ b/osu.Game/Screens/BackgroundScreen.cs @@ -30,7 +30,7 @@ namespace osu.Game.Screens protected override bool OnKeyDown(KeyDownEvent e) { - //we don't want to handle escape key. + // we don't want to handle escape key. return false; } diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 736202ee52..0d5f3d1142 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Menu this.FadeIn(300); double fadeOutTime = exit_delay; - //we also handle the exit transition. + // we also handle the exit transition. if (MenuVoice.Value) seeya.Play(); else diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 67537fa9df..0db7f2a2dc 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -162,7 +162,7 @@ namespace osu.Game.Screens.Menu private IShader shader; private Texture texture; - //Assuming the logo is a circle, we don't need a second dimension. + // Assuming the logo is a circle, we don't need a second dimension. private float size; private Color4 colour; @@ -209,13 +209,13 @@ namespace osu.Game.Screens.Menu float rotation = MathUtils.DegreesToRadians(i / (float)bars_per_visualiser * 360 + j * 360 / visualiser_rounds); float rotationCos = MathF.Cos(rotation); float rotationSin = MathF.Sin(rotation); - //taking the cos and sin to the 0..1 range + // taking the cos and sin to the 0..1 range var barPosition = new Vector2(rotationCos / 2 + 0.5f, rotationSin / 2 + 0.5f) * size; var barSize = new Vector2(size * MathF.Sqrt(2 * (1 - MathF.Cos(MathUtils.DegreesToRadians(360f / bars_per_visualiser)))) / 2f, bar_length * audioData[i]); - //The distance between the position and the sides of the bar. + // The distance between the position and the sides of the bar. var bottomOffset = new Vector2(-rotationSin * barSize.X / 2, rotationCos * barSize.X / 2); - //The distance between the bottom side of the bar and the top side. + // The distance between the bottom side of the bar and the top side. var amplitudeOffset = new Vector2(rotationCos * barSize.Y, rotationSin * barSize.Y); var rectangle = new Quad( @@ -231,7 +231,7 @@ namespace osu.Game.Screens.Menu colourInfo, null, vertexBatch.AddAction, - //barSize by itself will make it smooth more in the X axis than in the Y axis, this reverts that. + // barSize by itself will make it smooth more in the X axis than in the Y axis, this reverts that. Vector2.Divide(inflation, barSize.Yx)); } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 0589e4d12b..f0da2482d6 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -250,7 +250,7 @@ namespace osu.Game.Screens.Menu (Background as BackgroundScreenDefault)?.Next(); - //we may have consumed our preloaded instance, so let's make another. + // we may have consumed our preloaded instance, so let's make another. preloadSongSelect(); if (Beatmap.Value.Track != null && music?.IsUserPaused != true) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index c978f4e96d..36f825b8f6 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -119,7 +119,7 @@ namespace osu.Game.Screens.Play FinishTransforms(true); Scheduler.CancelDelayedTasks(); - if (breaks == null) return; //we need breaks. + if (breaks == null) return; // we need breaks. foreach (var b in breaks) { diff --git a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs index d201b5d30e..fc80983834 100644 --- a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs +++ b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Play.HUD protected override void PopIn() => this.FadeIn(fade_duration); protected override void PopOut() => this.FadeOut(fade_duration); - //We want to handle keyboard inputs all the time in order to trigger ToggleVisibility() when not visible + // We want to handle keyboard inputs all the time in order to trigger ToggleVisibility() when not visible public override bool PropagateNonPositionalInputSubTree => true; protected override bool OnKeyDown(KeyDownEvent e) diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/KeyCounter.cs index f4109a63d0..98df73a5e6 100644 --- a/osu.Game/Screens/Play/KeyCounter.cs +++ b/osu.Game/Screens/Play/KeyCounter.cs @@ -124,8 +124,8 @@ namespace osu.Game.Screens.Play } } }; - //Set this manually because an element with Alpha=0 won't take it size to AutoSizeContainer, - //so the size can be changing between buttonSprite and glowSprite. + // Set this manually because an element with Alpha=0 won't take it size to AutoSizeContainer, + // so the size can be changing between buttonSprite and glowSprite. Height = buttonSprite.DrawHeight; Width = buttonSprite.DrawWidth; } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index c0d88feda2..93a734589c 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -314,8 +314,8 @@ namespace osu.Game.Screens.Play LoadTask = null; - //By default, we want to load the player and never be returned to. - //Note that this may change if the player we load requested a re-run. + // By default, we want to load the player and never be returned to. + // Note that this may change if the player we load requested a re-run. ValidForResume = false; if (player.LoadedBeatmapSuccessfully) @@ -360,7 +360,7 @@ namespace osu.Game.Screens.Play { if (!muteWarningShownOnce.Value) { - //Checks if the notification has not been shown yet and also if master volume is muted, track/music volume is muted or if the whole game is muted. + // Checks if the notification has not been shown yet and also if master volume is muted, track/music volume is muted or if the whole game is muted. if (volumeOverlay?.IsMuted.Value == true || audioManager.Volume.Value <= audioManager.Volume.MinValue || audioManager.VolumeTrack.Value <= audioManager.VolumeTrack.MinValue) { notificationOverlay?.Post(new MutedNotification()); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 5a4a03662a..96b779cd20 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -208,7 +208,7 @@ namespace osu.Game.Screens.Select // without this, during a large beatmap import it is impossible to navigate the carousel. applyActiveCriteria(false, alwaysResetScrollPosition: false); - //check if we can/need to maintain our current selection. + // check if we can/need to maintain our current selection. if (previouslySelectedID != null) select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.Beatmap.ID == previouslySelectedID) ?? newSet); diff --git a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs index f4bf1ab059..63711e3e50 100644 --- a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs +++ b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Select set => tabs.Current = value; } - public Action OnFilter; //passed the selected tab and if mods is checked + public Action OnFilter; // passed the selected tab and if mods is checked public IReadOnlyList TabItems { diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index aebb8e9d87..9669a1391c 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -201,7 +201,7 @@ namespace osu.Game.Screens.Select Schedule(() => { if (beatmap != requestedBeatmap) - //the beatmap has been changed since we started the lookup. + // the beatmap has been changed since we started the lookup. return; var b = res.ToBeatmap(rulesets); @@ -222,7 +222,7 @@ namespace osu.Game.Screens.Select Schedule(() => { if (beatmap != requestedBeatmap) - //the beatmap has been changed since we started the lookup. + // the beatmap has been changed since we started the lookup. return; updateMetrics(); diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index af0d36ea9a..02822ea608 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -52,7 +52,7 @@ namespace osu.Game.Screens.Select.Details AutoSizeAxes = Axes.Y, Children = new[] { - FirstValue = new StatisticRow(), //circle size/key amount + FirstValue = new StatisticRow(), // circle size/key amount HpDrain = new StatisticRow { Title = "HP Drain" }, Accuracy = new StatisticRow { Title = "Accuracy" }, ApproachRate = new StatisticRow { Title = "Approach Rate" }, diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index a7e27c27ba..5b7c9082f3 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -798,7 +798,7 @@ namespace osu.Game.Screens.Select Masking = true; Anchor = Anchor.Centre; Origin = Anchor.Centre; - Width = panel_overflow; //avoid horizontal masking so the panels don't clip when screen stack is pushed. + Width = panel_overflow; // avoid horizontal masking so the panels don't clip when screen stack is pushed. InternalChild = Content = new Container { RelativeSizeAxes = Axes.Both, From aff74db80da54a35a438a4e6443569107e5403a1 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Tue, 5 May 2020 10:40:10 +0200 Subject: [PATCH 628/655] Publicly expose HUDOverlay in Player. --- osu.Game/Screens/Play/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ece4c6307e..af724d97a2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -89,7 +89,7 @@ namespace osu.Game.Screens.Play protected DrawableRuleset DrawableRuleset { get; private set; } - protected HUDOverlay HUDOverlay { get; private set; } + public HUDOverlay HUDOverlay { get; private set; } public bool LoadedBeatmapSuccessfully => DrawableRuleset?.Objects.Any() == true; From 5186da8412ded39b4216cab60c0858fe05b42f71 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 May 2020 11:37:04 +0900 Subject: [PATCH 629/655] Fix potential song select nullref --- 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 6d760df065..1e4f6aeda1 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -61,7 +61,7 @@ namespace osu.Game.Screens.Select.Carousel terms.Add(Beatmap.Version); foreach (var criteriaTerm in criteria.SearchTerms) - match &= terms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0); + match &= terms.Any(term => term?.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0); } Filtered.Value = !match; From e91e4a73af89e719e05c1f349836433079a2523e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 May 2020 12:22:07 +0900 Subject: [PATCH 630/655] Fix catch crashing when finishing maps --- .../Scoring/Legacy/ScoreInfoExtensions.cs | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs b/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs index 9745d1abef..6f73a284a2 100644 --- a/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs +++ b/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs @@ -12,7 +12,7 @@ namespace osu.Game.Scoring.Legacy switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) { case 3: - return scoreInfo.Statistics[HitResult.Perfect]; + return getCount(scoreInfo, HitResult.Perfect); } return null; @@ -35,10 +35,10 @@ namespace osu.Game.Scoring.Legacy case 0: case 1: case 3: - return scoreInfo.Statistics[HitResult.Great]; + return getCount(scoreInfo, HitResult.Great); case 2: - return scoreInfo.Statistics[HitResult.Perfect]; + return getCount(scoreInfo, HitResult.Perfect); } return null; @@ -65,10 +65,10 @@ namespace osu.Game.Scoring.Legacy switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) { case 3: - return scoreInfo.Statistics[HitResult.Good]; + return getCount(scoreInfo, HitResult.Good); case 2: - return scoreInfo.Statistics[HitResult.SmallTickMiss]; + return getCount(scoreInfo, HitResult.SmallTickMiss); } return null; @@ -94,13 +94,13 @@ namespace osu.Game.Scoring.Legacy { case 0: case 1: - return scoreInfo.Statistics[HitResult.Good]; + return getCount(scoreInfo, HitResult.Good); case 3: - return scoreInfo.Statistics[HitResult.Ok]; + return getCount(scoreInfo, HitResult.Ok); case 2: - return scoreInfo.Statistics[HitResult.LargeTickHit]; + return getCount(scoreInfo, HitResult.LargeTickHit); } return null; @@ -131,10 +131,10 @@ namespace osu.Game.Scoring.Legacy { case 0: case 3: - return scoreInfo.Statistics[HitResult.Meh]; + return getCount(scoreInfo, HitResult.Meh); case 2: - return scoreInfo.Statistics[HitResult.SmallTickHit]; + return getCount(scoreInfo, HitResult.SmallTickHit); } return null; @@ -156,9 +156,17 @@ namespace osu.Game.Scoring.Legacy } public static int? GetCountMiss(this ScoreInfo scoreInfo) => - scoreInfo.Statistics[HitResult.Miss]; + getCount(scoreInfo, HitResult.Miss); public static void SetCountMiss(this ScoreInfo scoreInfo, int value) => scoreInfo.Statistics[HitResult.Miss] = value; + + private static int? getCount(ScoreInfo scoreInfo, HitResult result) + { + if (scoreInfo.Statistics.TryGetValue(result, out var existing)) + return existing; + + return null; + } } } From 401c516503239f341c73396e5539e7b78f2b1078 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 May 2020 13:04:08 +0900 Subject: [PATCH 631/655] Expose searchable terms from beatmap info instead --- osu.Game/Beatmaps/BeatmapInfo.cs | 5 +++++ osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 8 ++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 90c100db05..3860f12baa 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -149,6 +149,11 @@ namespace osu.Game.Beatmaps } } + public string[] SearchableTerms => new[] + { + Version + }.Concat(Metadata?.SearchableTerms ?? Enumerable.Empty()).Where(s => !string.IsNullOrEmpty(s)).ToArray(); + public override string ToString() { string version = string.IsNullOrEmpty(Version) ? string.Empty : $"[{Version}]"; diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 1e4f6aeda1..ed54c158db 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Screens.Select.Filter; @@ -55,13 +54,10 @@ namespace osu.Game.Screens.Select.Carousel if (match) { - var terms = new List(); - - terms.AddRange(Beatmap.Metadata.SearchableTerms); - terms.Add(Beatmap.Version); + var terms = Beatmap.SearchableTerms; foreach (var criteriaTerm in criteria.SearchTerms) - match &= terms.Any(term => term?.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0); + match &= terms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0); } Filtered.Value = !match; From 259ef688110bfbc6e9eed2f7b8779af8b65bbb3e Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 6 May 2020 22:29:37 -0700 Subject: [PATCH 632/655] Fix date tooltip not showing in 24-hour format --- osu.Game/Graphics/DrawableDate.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/DrawableDate.cs b/osu.Game/Graphics/DrawableDate.cs index 8c520f4e10..8b6df4a834 100644 --- a/osu.Game/Graphics/DrawableDate.cs +++ b/osu.Game/Graphics/DrawableDate.cs @@ -139,7 +139,7 @@ namespace osu.Game.Graphics return false; dateText.Text = $"{date:d MMMM yyyy} "; - timeText.Text = $"{date:hh:mm:ss \"UTC\"z}"; + timeText.Text = $"{date:HH:mm:ss \"UTC\"z}"; return true; } From 09759565faee8f9f96fbc48aacaa7a8ad7842eb1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 May 2020 14:49:58 +0900 Subject: [PATCH 633/655] Add support for 3v3 tournament chroma key layout --- osu.Game.Tournament/Models/LadderInfo.cs | 6 ++ .../Screens/Gameplay/GameplayScreen.cs | 83 +++++++++++++++++-- 2 files changed, 82 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tournament/Models/LadderInfo.cs b/osu.Game.Tournament/Models/LadderInfo.cs index c2e6da9ca5..7794019437 100644 --- a/osu.Game.Tournament/Models/LadderInfo.cs +++ b/osu.Game.Tournament/Models/LadderInfo.cs @@ -32,5 +32,11 @@ namespace osu.Game.Tournament.Models MinValue = 640, MaxValue = 1366, }; + + public Bindable PlayersPerTeam = new BindableInt(4) + { + MinValue = 3, + MaxValue = 4, + }; } } diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs index 64a5cd6dec..e4e3842369 100644 --- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs +++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tournament.Screens.Gameplay [Resolved] private TournamentMatchChatDisplay chat { get; set; } - private Box chroma; + private Drawable chroma; [BackgroundDependencyLoader] private void load(LadderInfo ladder, MatchIPCInfo ipc, Storage storage) @@ -61,16 +61,30 @@ namespace osu.Game.Tournament.Screens.Gameplay Y = 110, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Children = new Drawable[] + Children = new[] { - chroma = new Box + chroma = new Container { - // chroma key area for stable gameplay - Name = "chroma", Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Height = 512, - Colour = new Color4(0, 255, 0, 255), + Children = new Drawable[] + { + new ChromaArea + { + Name = "Left chroma", + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + }, + new ChromaArea + { + Name = "Right chroma", + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Width = 0.5f, + } + } }, } }, @@ -98,9 +112,15 @@ namespace osu.Game.Tournament.Screens.Gameplay }, new SettingsSlider { - LabelText = "Chroma Width", + LabelText = "Chroma width", Bindable = LadderInfo.ChromaKeyWidth, KeyboardStep = 1, + }, + new SettingsSlider + { + LabelText = "Players per team", + Bindable = LadderInfo.PlayersPerTeam, + KeyboardStep = 1, } } } @@ -201,5 +221,54 @@ namespace osu.Game.Tournament.Screens.Gameplay lastState = state.NewValue; } } + + private class ChromaArea : CompositeDrawable + { + [Resolved] + private LadderInfo ladder { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + // chroma key area for stable gameplay + Colour = new Color4(0, 255, 0, 255); + + ladder.PlayersPerTeam.BindValueChanged(performLayout, true); + } + + private void performLayout(ValueChangedEvent playerCount) + { + switch (playerCount.NewValue) + { + case 3: + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Height = 0.5f, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Height = 0.5f, + }, + }; + break; + + default: + InternalChild = new Box + { + RelativeSizeAxes = Axes.Both, + }; + break; + } + } + } } } From 83be5455d3529707cbac2192e538984fc351eea4 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 7 May 2020 08:52:36 +0200 Subject: [PATCH 634/655] Disable the display of HUD through DisplayHud property. --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 9 +++++++++ osu.Game/Screens/Play/Player.cs | 9 ++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 5062c92afe..ff1f67783e 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -487,6 +487,15 @@ namespace osu.Game.Rulesets.UI protected virtual ResumeOverlay CreateResumeOverlay() => null; + /// + /// Whether to display the HUD with this ruleset. + /// Override to false to completely disable the display of the HUD with this ruleset. + /// + /// + /// HUD refers here to in player as well as . + /// + public virtual bool DisplayHud => true; + /// /// Sets a replay to be used, overriding local input. /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index af724d97a2..375976ea6c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -89,7 +89,7 @@ namespace osu.Game.Screens.Play protected DrawableRuleset DrawableRuleset { get; private set; } - public HUDOverlay HUDOverlay { get; private set; } + protected HUDOverlay HUDOverlay { get; private set; } public bool LoadedBeatmapSuccessfully => DrawableRuleset?.Objects.Any() == true; @@ -184,6 +184,13 @@ namespace osu.Game.Screens.Play addGameplayComponents(GameplayClockContainer, Beatmap.Value, playableBeatmap); addOverlayComponents(GameplayClockContainer, Beatmap.Value); + if (!DrawableRuleset.DisplayHud) + { + HUDOverlay.ShowHud.Value = false; + HUDOverlay.ShowHud.Disabled = true; + BreakOverlay.Hide(); + } + DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); // bind clock into components that require it From 83998d5ba5a661a284bb19b54d711f8bc583f6ad Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 7 May 2020 09:39:14 +0200 Subject: [PATCH 635/655] Trim whitespace. --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index ff1f67783e..57fbb7f1a5 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -489,7 +489,7 @@ namespace osu.Game.Rulesets.UI /// /// Whether to display the HUD with this ruleset. - /// Override to false to completely disable the display of the HUD with this ruleset. + /// Override to false to completely disable the display of the HUD with this ruleset. /// /// /// HUD refers here to in player as well as . From b6f232e39428ce2e056c821cf229c15717c1d134 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 May 2020 10:27:48 +0900 Subject: [PATCH 636/655] 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 8214fa2f2c..af699af1ba 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 9db5fe562c..47804ed06e 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 82253a0418..86cffa9fba 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From d6840d880a0df25e310c7ee32f7427f0d4baf675 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 May 2020 10:38:31 +0900 Subject: [PATCH 637/655] Update StableStorage implementation in line with framework changes --- osu.Desktop/OsuGameDesktop.cs | 78 +++++++++++------------ osu.Game.Tournament/IPC/FileBasedIPC.cs | 84 +++++++++++-------------- 2 files changed, 72 insertions(+), 90 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index f05ee48914..9351e17419 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -6,15 +6,14 @@ using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; +using Microsoft.Win32; using osu.Desktop.Overlays; using osu.Framework.Platform; using osu.Game; using osuTK.Input; -using Microsoft.Win32; using osu.Desktop.Updater; using osu.Framework; using osu.Framework.Logging; -using osu.Framework.Platform.Windows; using osu.Framework.Screens; using osu.Game.Screens.Menu; using osu.Game.Updater; @@ -37,7 +36,11 @@ namespace osu.Desktop try { if (Host is DesktopGameHost desktopHost) - return new StableStorage(desktopHost); + { + string stablePath = getStableInstallPath(); + if (!string.IsNullOrEmpty(stablePath)) + return new DesktopStorage(stablePath, desktopHost); + } } catch (Exception) { @@ -47,6 +50,35 @@ namespace osu.Desktop return null; } + private string getStableInstallPath() + { + static bool checkExists(string p) => Directory.Exists(Path.Combine(p, "Songs")); + + string stableInstallPath; + + try + { + using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu")) + stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString().Split('"')[1].Replace("osu!.exe", ""); + + if (checkExists(stableInstallPath)) + return stableInstallPath; + } + catch + { + } + + stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!"); + if (checkExists(stableInstallPath)) + return stableInstallPath; + + stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".osu"); + if (checkExists(stableInstallPath)) + return stableInstallPath; + + return null; + } + protected override UpdateManager CreateUpdateManager() { switch (RuntimeInfo.OS) @@ -111,45 +143,5 @@ namespace osu.Desktop Task.Factory.StartNew(() => Import(filePaths), TaskCreationOptions.LongRunning); } - - /// - /// A method of accessing an osu-stable install in a controlled fashion. - /// - private class StableStorage : WindowsStorage - { - protected override string LocateBasePath() - { - static bool checkExists(string p) => Directory.Exists(Path.Combine(p, "Songs")); - - string stableInstallPath; - - try - { - using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu")) - stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString().Split('"')[1].Replace("osu!.exe", ""); - - if (checkExists(stableInstallPath)) - return stableInstallPath; - } - catch - { - } - - stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!"); - if (checkExists(stableInstallPath)) - return stableInstallPath; - - stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".osu"); - if (checkExists(stableInstallPath)) - return stableInstallPath; - - return null; - } - - public StableStorage(DesktopGameHost host) - : base(string.Empty, host) - { - } - } } } diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index eefa9fcfe6..53ba597a7e 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -8,7 +8,6 @@ using Microsoft.Win32; using osu.Framework.Allocation; using osu.Framework.Logging; using osu.Framework.Platform; -using osu.Framework.Platform.Windows; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Legacy; @@ -52,7 +51,12 @@ namespace osu.Game.Tournament.IPC try { - Storage = new StableStorage(host as DesktopGameHost); + var path = findStablePath(); + + if (string.IsNullOrEmpty(path)) + return null; + + Storage = new DesktopStorage(path, host as DesktopGameHost); const string file_ipc_filename = "ipc.txt"; const string file_ipc_state_filename = "ipc-state.txt"; @@ -145,64 +149,50 @@ namespace osu.Game.Tournament.IPC return Storage; } - /// - /// A method of accessing an osu-stable install in a controlled fashion. - /// - private class StableStorage : WindowsStorage + private string findStablePath() { - protected override string LocateBasePath() - { - static bool checkExists(string p) - { - return File.Exists(Path.Combine(p, "ipc.txt")); - } + static bool checkExists(string p) => File.Exists(Path.Combine(p, "ipc.txt")); - string stableInstallPath = string.Empty; + string stableInstallPath = string.Empty; + + try + { + try + { + stableInstallPath = Environment.GetEnvironmentVariable("OSU_STABLE_PATH"); + + if (checkExists(stableInstallPath)) + return stableInstallPath; + } + catch + { + } try { - try - { - stableInstallPath = Environment.GetEnvironmentVariable("OSU_STABLE_PATH"); + using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu")) + stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString().Split('"')[1].Replace("osu!.exe", ""); - if (checkExists(stableInstallPath)) - return stableInstallPath; - } - catch - { - } - - try - { - using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu")) - stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString().Split('"')[1].Replace("osu!.exe", ""); - - if (checkExists(stableInstallPath)) - return stableInstallPath; - } - catch - { - } - - stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!"); if (checkExists(stableInstallPath)) return stableInstallPath; - - stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".osu"); - if (checkExists(stableInstallPath)) - return stableInstallPath; - - return null; } - finally + catch { - Logger.Log($"Stable path for tourney usage: {stableInstallPath}"); } - } - public StableStorage(DesktopGameHost host) - : base(string.Empty, host) + stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!"); + if (checkExists(stableInstallPath)) + return stableInstallPath; + + stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".osu"); + if (checkExists(stableInstallPath)) + return stableInstallPath; + + return null; + } + finally { + Logger.Log($"Stable path for tourney usage: {stableInstallPath}"); } } } From af5e1f8298ad47fdbd783e3dedcb5ba0038aa898 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 May 2020 14:22:07 +0900 Subject: [PATCH 638/655] Commit autogenerated rider VCS settings update --- .idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml b/.idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml index 4bb9f4d2a0..7515e76054 100644 --- a/.idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml +++ b/.idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml @@ -1,6 +1,6 @@ - \ No newline at end of file From 9f64882f371aeae723e646871cbdfe5bb250f168 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 May 2020 14:48:38 +0900 Subject: [PATCH 639/655] 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 af699af1ba..a406cdf08a 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 47804ed06e..5ccfaaac9e 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 86cffa9fba..dc83d937f7 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 8b5de7403f317cd87be9315976632d08873b021a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 May 2020 15:12:29 +0900 Subject: [PATCH 640/655] Fix android usage of obsoleted VersionCode --- osu.Android/OsuGameAndroid.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index 84f215f930..19ed7ffcf5 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -18,7 +18,8 @@ namespace osu.Android try { - string versionName = packageInfo.VersionCode.ToString(); + // todo: needs checking before play store redeploy. + string versionName = packageInfo.VersionName; // undo play store version garbling return new Version(int.Parse(versionName.Substring(0, 4)), int.Parse(versionName.Substring(4, 4)), int.Parse(versionName.Substring(8, 1))); } From c5589d278b17f30196272e26ac506dbfb2a3e46d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 May 2020 15:35:25 +0900 Subject: [PATCH 641/655] Revert "Commit autogenerated rider VCS settings update" This reverts commit af5e1f8298ad47fdbd783e3dedcb5ba0038aa898. --- .idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml b/.idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml index 7515e76054..4bb9f4d2a0 100644 --- a/.idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml +++ b/.idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml @@ -1,6 +1,6 @@ - \ No newline at end of file From 30dd158c33ef1c3968dd7432ffa3fc78f2aff460 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 8 May 2020 09:37:50 +0200 Subject: [PATCH 642/655] Rename property to AllowGameplayOverlays and update XMLDoc accordingly. --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 8 ++++---- osu.Game/Screens/Play/Player.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 57fbb7f1a5..e1c21209c2 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -488,13 +488,13 @@ namespace osu.Game.Rulesets.UI protected virtual ResumeOverlay CreateResumeOverlay() => null; /// - /// Whether to display the HUD with this ruleset. - /// Override to false to completely disable the display of the HUD with this ruleset. + /// Whether to display gameplay overlays with this ruleset. + /// Override to false to completely disable the display of gameplay overlays. /// /// - /// HUD refers here to in player as well as . + /// Gameplay overlays refer here to in player as well as . /// - public virtual bool DisplayHud => true; + public virtual bool AllowGameplayOverlays => true; /// /// Sets a replay to be used, overriding local input. diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 375976ea6c..acc8dc9c7c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -184,7 +184,7 @@ namespace osu.Game.Screens.Play addGameplayComponents(GameplayClockContainer, Beatmap.Value, playableBeatmap); addOverlayComponents(GameplayClockContainer, Beatmap.Value); - if (!DrawableRuleset.DisplayHud) + if (!DrawableRuleset.AllowGameplayOverlays) { HUDOverlay.ShowHud.Value = false; HUDOverlay.ShowHud.Disabled = true; From a59db976d66fab80cb3035dd779c59db6070d68c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 May 2020 18:05:06 +0900 Subject: [PATCH 643/655] Fix loading a ruleset with a new version specification causing a crash --- osu.Game/Rulesets/RulesetInfo.cs | 16 +++++++++++++++- osu.Game/Rulesets/RulesetStore.cs | 12 ++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index afd499cb9e..2e32b96084 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Linq; using Newtonsoft.Json; namespace osu.Game.Rulesets @@ -15,7 +16,20 @@ namespace osu.Game.Rulesets public string ShortName { get; set; } - public string InstantiationInfo { get; set; } + private string instantiationInfo; + + public string InstantiationInfo + { + get => instantiationInfo; + set => instantiationInfo = abbreviateInstantiationInfo(value); + } + + private string abbreviateInstantiationInfo(string value) + { + // exclude version onwards, matching only on namespace and type. + // this is mainly to allow for new versions of already loaded rulesets to "upgrade" from old. + return string.Join(',', value.Split(',').Take(2)); + } [JsonIgnore] public bool Available { get; set; } diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index f302f8700f..b8f2abd766 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -93,7 +93,9 @@ namespace osu.Game.Rulesets // add any other modes foreach (var r in instances.Where(r => !(r is ILegacyRuleset))) { - if (context.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo == r.RulesetInfo.InstantiationInfo) == null) + // todo: StartsWith can be changed to Equals on 2020-11-08 + // This is to give users enough time to have their database use new abbreviated info). + if (context.RulesetInfo.FirstOrDefault(ri => r.RulesetInfo.InstantiationInfo.StartsWith(ri.InstantiationInfo)) == null) context.RulesetInfo.Add(r.RulesetInfo); } @@ -104,13 +106,7 @@ namespace osu.Game.Rulesets { try { - var instanceInfo = ((Ruleset)Activator.CreateInstance(Type.GetType(r.InstantiationInfo, asm => - { - // for the time being, let's ignore the version being loaded. - // this allows for debug builds to successfully load rulesets (even though debug rulesets have a 0.0.0 version). - asm.Version = null; - return Assembly.Load(asm); - }, null))).RulesetInfo; + var instanceInfo = ((Ruleset)Activator.CreateInstance(Type.GetType(r.InstantiationInfo))).RulesetInfo; r.Name = instanceInfo.Name; r.ShortName = instanceInfo.ShortName; From dcfef6b44383c48246ab2a648b13855d13e8834c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 May 2020 18:46:37 +0900 Subject: [PATCH 644/655] Add clear method to EditorBeatmap --- osu.Game/Screens/Edit/EditorBeatmap.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 2e8e03bc73..23c8c9f605 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -201,6 +202,25 @@ namespace osu.Game.Screens.Edit updateHitObject(null, true); } + /// + /// Clears all from this . + /// + public void Clear() + { + var removed = HitObjects.ToList(); + + mutableHitObjects.Clear(); + + foreach (var b in startTimeBindables) + b.Value.UnbindAll(); + startTimeBindables.Clear(); + + foreach (var h in removed) + HitObjectRemoved?.Invoke(h); + + updateHitObject(null, true); + } + private void trackStartTime(HitObject hitObject) { startTimeBindables[hitObject] = hitObject.StartTimeBindable.GetBoundCopy(); From efff2bf15df6d588bcb6335c6bcac54c572fe23a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 May 2020 18:49:19 +0900 Subject: [PATCH 645/655] Add HitObject to DefaultsApplied event --- osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs | 2 +- .../Objects/Drawables/Connections/FollowPointConnection.cs | 2 +- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 7 +++++-- osu.Game/Rulesets/Objects/HitObject.cs | 4 ++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs index a6c3be7e5a..c3b4d2625e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs @@ -399,7 +399,7 @@ namespace osu.Game.Rulesets.Osu.Tests { public TestSlider() { - DefaultsApplied += () => + DefaultsApplied += _ => { HeadCircle.HitWindows = new TestHitWindows(); TailCircle.HitWindows = new TestHitWindows(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 6f09bbcd57..8a0ef22c4a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections private void bindEvents(DrawableOsuHitObject drawableObject) { drawableObject.HitObject.PositionBindable.BindValueChanged(_ => scheduleRefresh()); - drawableObject.HitObject.DefaultsApplied += scheduleRefresh; + drawableObject.HitObject.DefaultsApplied += _ => scheduleRefresh(); } private void scheduleRefresh() diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 0047142cbd..3838e52f9b 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Objects.Drawables samplesBindable.CollectionChanged += (_, __) => loadSamples(); updateState(ArmedState.Idle, true); - onDefaultsApplied(); + apply(HitObject); } private void loadSamples() @@ -175,7 +175,10 @@ namespace osu.Game.Rulesets.Objects.Drawables AddInternal(Samples); } - private void onDefaultsApplied() => apply(HitObject); + private void onDefaultsApplied(HitObject hitObject) + { + apply(hitObject); + } private void apply(HitObject hitObject) { diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 9a8efdde84..cffbdbae08 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Objects /// /// Invoked after has completed on this . /// - public event Action DefaultsApplied; + public event Action DefaultsApplied; public readonly Bindable StartTimeBindable = new BindableDouble(); @@ -124,7 +124,7 @@ namespace osu.Game.Rulesets.Objects foreach (var h in nestedHitObjects) h.ApplyDefaults(controlPointInfo, difficulty); - DefaultsApplied?.Invoke(); + DefaultsApplied?.Invoke(this); } protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) From 22dda3fe0231dc01c66591724097aef72e3c7f1a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 May 2020 18:49:58 +0900 Subject: [PATCH 646/655] Make ScrollingHitObjectContainer respond to defaults applied events --- .../Objects/Drawables/DrawableHitObject.cs | 3 + .../Scrolling/ScrollingHitObjectContainer.cs | 69 +++++++++++++------ 2 files changed, 51 insertions(+), 21 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 3838e52f9b..ba6571fe1a 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Objects.Drawables [Cached(typeof(DrawableHitObject))] public abstract class DrawableHitObject : SkinReloadableDrawable { + public event Action DefaultsApplied; + public readonly HitObject HitObject; /// @@ -178,6 +180,7 @@ namespace osu.Game.Rulesets.Objects.Drawables private void onDefaultsApplied(HitObject hitObject) { apply(hitObject); + DefaultsApplied?.Invoke(this); } private void apply(HitObject hitObject) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 57f58be55a..3e01bb1d31 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -16,17 +16,23 @@ namespace osu.Game.Rulesets.UI.Scrolling { private readonly IBindable timeRange = new BindableDouble(); private readonly IBindable direction = new Bindable(); + private readonly Dictionary hitObjectInitialStateCache = new Dictionary(); [Resolved] private IScrollingInfo scrollingInfo { get; set; } - private readonly LayoutValue initialStateCache = new LayoutValue(Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo); + // Responds to changes in the layout. When the layout is changes, all hit object states must be recomputed. + private readonly LayoutValue layoutCache = new LayoutValue(Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo); + + // A combined cache across all hit object states to reduce per-update iterations. + // When invalidated, one or more (but not necessarily all) hitobject states must be re-validated. + private readonly Cached combinedObjCache = new Cached(); public ScrollingHitObjectContainer() { RelativeSizeAxes = Axes.Both; - AddLayout(initialStateCache); + AddLayout(layoutCache); } [BackgroundDependencyLoader] @@ -35,13 +41,14 @@ namespace osu.Game.Rulesets.UI.Scrolling direction.BindTo(scrollingInfo.Direction); timeRange.BindTo(scrollingInfo.TimeRange); - direction.ValueChanged += _ => initialStateCache.Invalidate(); - timeRange.ValueChanged += _ => initialStateCache.Invalidate(); + direction.ValueChanged += _ => layoutCache.Invalidate(); + timeRange.ValueChanged += _ => layoutCache.Invalidate(); } public override void Add(DrawableHitObject hitObject) { - initialStateCache.Invalidate(); + combinedObjCache.Invalidate(); + hitObject.DefaultsApplied += onDefaultsApplied; base.Add(hitObject); } @@ -51,8 +58,10 @@ namespace osu.Game.Rulesets.UI.Scrolling if (result) { - initialStateCache.Invalidate(); + combinedObjCache.Invalidate(); hitObjectInitialStateCache.Remove(hitObject); + + hitObject.DefaultsApplied -= onDefaultsApplied; } return result; @@ -60,23 +69,45 @@ namespace osu.Game.Rulesets.UI.Scrolling public override void Clear(bool disposeChildren = true) { + foreach (var h in Objects) + h.DefaultsApplied -= onDefaultsApplied; + base.Clear(disposeChildren); - initialStateCache.Invalidate(); + combinedObjCache.Invalidate(); hitObjectInitialStateCache.Clear(); } + private void onDefaultsApplied(DrawableHitObject drawableObject) + { + // The cache may not exist if the hitobject state hasn't been computed yet (e.g. if the hitobject was added + defaults applied in the same frame). + // In such a case, combinedObjCache will take care of updating the hitobject. + if (hitObjectInitialStateCache.TryGetValue(drawableObject, out var objCache)) + { + combinedObjCache.Invalidate(); + objCache.Invalidate(); + } + } + private float scrollLength; protected override void Update() { base.Update(); - if (!initialStateCache.IsValid) + if (!layoutCache.IsValid) { foreach (var cached in hitObjectInitialStateCache.Values) cached.Invalidate(); + combinedObjCache.Invalidate(); + scrollingInfo.Algorithm.Reset(); + + layoutCache.Validate(); + } + + if (!combinedObjCache.IsValid) + { switch (direction.Value) { case ScrollingDirection.Up: @@ -89,15 +120,21 @@ namespace osu.Game.Rulesets.UI.Scrolling break; } - scrollingInfo.Algorithm.Reset(); - foreach (var obj in Objects) { + if (!hitObjectInitialStateCache.TryGetValue(obj, out var objCache)) + objCache = hitObjectInitialStateCache[obj] = new Cached(); + + if (objCache.IsValid) + return; + computeLifetimeStartRecursive(obj); computeInitialStateRecursive(obj); + + objCache.Validate(); } - initialStateCache.Validate(); + combinedObjCache.Validate(); } } @@ -109,8 +146,6 @@ namespace osu.Game.Rulesets.UI.Scrolling computeLifetimeStartRecursive(obj); } - private readonly Dictionary hitObjectInitialStateCache = new Dictionary(); - private double computeOriginAdjustedLifetimeStart(DrawableHitObject hitObject) { float originAdjustment = 0.0f; @@ -142,12 +177,6 @@ namespace osu.Game.Rulesets.UI.Scrolling // Cant use AddOnce() since the delegate is re-constructed every invocation private void computeInitialStateRecursive(DrawableHitObject hitObject) => hitObject.Schedule(() => { - if (!hitObjectInitialStateCache.TryGetValue(hitObject, out var cached)) - cached = hitObjectInitialStateCache[hitObject] = new Cached(); - - if (cached.IsValid) - return; - if (hitObject.HitObject is IHasEndTime e) { switch (direction.Value) @@ -171,8 +200,6 @@ namespace osu.Game.Rulesets.UI.Scrolling // Nested hitobjects don't need to scroll, but they do need accurate positions updatePosition(obj, hitObject.HitObject.StartTime); } - - cached.Validate(); }); protected override void UpdateAfterChildrenLife() From d67facf8e4c28443840af1519ec7be669ef552d3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 May 2020 18:50:06 +0900 Subject: [PATCH 647/655] Add test scene --- .../TestSceneManiaHitObjectComposer.cs | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs index 286e3f6e50..1554a956a2 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs @@ -10,10 +10,13 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Edit; +using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; @@ -48,6 +51,8 @@ namespace osu.Game.Rulesets.Mania.Tests DrawableHitObject lastObject = null; Vector2 originalPosition = Vector2.Zero; + setScrollStep(ScrollingDirection.Up); + AddStep("seek to last object", () => { lastObject = this.ChildrenOfType().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last()); @@ -81,7 +86,7 @@ namespace osu.Game.Rulesets.Mania.Tests DrawableHitObject lastObject = null; Vector2 originalPosition = Vector2.Zero; - AddStep("set down scroll", () => ((Bindable)composer.Composer.ScrollingInfo.Direction).Value = ScrollingDirection.Down); + setScrollStep(ScrollingDirection.Down); AddStep("seek to last object", () => { @@ -116,6 +121,8 @@ namespace osu.Game.Rulesets.Mania.Tests DrawableHitObject lastObject = null; Vector2 originalPosition = Vector2.Zero; + setScrollStep(ScrollingDirection.Down); + AddStep("seek to last object", () => { lastObject = this.ChildrenOfType().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last()); @@ -147,6 +154,46 @@ namespace osu.Game.Rulesets.Mania.Tests AddAssert("hitobjects not moved vertically", () => lastObject.DrawPosition.Y - originalPosition.Y <= DefaultNotePiece.NOTE_HEIGHT); } + [Test] + public void TestDragHoldNoteSelectionVertically() + { + setScrollStep(ScrollingDirection.Down); + + AddStep("setup beatmap", () => + { + composer.EditorBeatmap.Clear(); + composer.EditorBeatmap.Add(new HoldNote + { + Column = 1, + EndTime = 200 + }); + }); + + DrawableHoldNote holdNote = null; + + AddStep("grab hold note", () => + { + holdNote = this.ChildrenOfType().FirstOrDefault(); + InputManager.MoveMouseTo(holdNote); + InputManager.PressButton(MouseButton.Left); + }); + + AddStep("move drag upwards", () => + { + InputManager.MoveMouseTo(holdNote, new Vector2(0, -100)); + InputManager.ReleaseButton(MouseButton.Left); + }); + + AddAssert("head note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.BottomLeft, holdNote.Head.ScreenSpaceDrawQuad.BottomLeft)); + AddAssert("tail note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.TopLeft, holdNote.Tail.ScreenSpaceDrawQuad.BottomLeft)); + + AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType().ElementAt(0).DrawPosition == holdNote.Head.DrawPosition); + AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition); + } + + private void setScrollStep(ScrollingDirection direction) + => AddStep($"set scroll direction = {direction}", () => ((Bindable)composer.Composer.ScrollingInfo.Direction).Value = direction); + private class TestComposer : CompositeDrawable { [Cached(typeof(EditorBeatmap))] From be3b77cf25708ceb77d14d830a473f09d0403bfc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 May 2020 20:09:59 +0900 Subject: [PATCH 648/655] Fix potentially skipping hitobject updates --- osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 3e01bb1d31..ea72061216 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.UI.Scrolling objCache = hitObjectInitialStateCache[obj] = new Cached(); if (objCache.IsValid) - return; + continue; computeLifetimeStartRecursive(obj); computeInitialStateRecursive(obj); From 5c2778d5f08288211a1cd57b14beb85e456a513b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 May 2020 20:33:02 +0900 Subject: [PATCH 649/655] Change comparison direction --- osu.Game/Rulesets/RulesetStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index b8f2abd766..b3026bf2b7 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets { // todo: StartsWith can be changed to Equals on 2020-11-08 // This is to give users enough time to have their database use new abbreviated info). - if (context.RulesetInfo.FirstOrDefault(ri => r.RulesetInfo.InstantiationInfo.StartsWith(ri.InstantiationInfo)) == null) + if (context.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo.StartsWith(r.RulesetInfo.InstantiationInfo)) == null) context.RulesetInfo.Add(r.RulesetInfo); } From d1976b194d9bd2314775eb56dcbb1125738d0075 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 9 May 2020 10:42:56 +0300 Subject: [PATCH 650/655] Check local availability before disabling buttons --- .../BeatmapListing/Panels/BeatmapPanelDownloadButton.cs | 2 +- osu.Game/Overlays/BeatmapSet/Header.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs index 589f2d5072..6d5862ee2b 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs @@ -50,7 +50,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels [BackgroundDependencyLoader(true)] private void load(OsuGame game, BeatmapManager beatmaps, OsuConfigManager osuConfig) { - if (BeatmapSet.Value?.OnlineInfo?.Availability?.DownloadDisabled ?? false) + if ((BeatmapSet.Value?.OnlineInfo?.Availability?.DownloadDisabled ?? false) && State.Value != DownloadState.LocallyAvailable) { button.Enabled.Value = false; button.TooltipText = "this beatmap is currently not available for download."; diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index 1ff08aab2c..06e31277dd 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -264,7 +264,7 @@ namespace osu.Game.Overlays.BeatmapSet { if (BeatmapSet.Value == null) return; - if (BeatmapSet.Value.OnlineInfo.Availability?.DownloadDisabled ?? false) + if ((BeatmapSet.Value.OnlineInfo.Availability?.DownloadDisabled ?? false) && State.Value != DownloadState.LocallyAvailable) { downloadButtonsContainer.Clear(); return; From 55e0d91f37591061869917968a0897ff10ef726d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 9 May 2020 18:09:17 +0900 Subject: [PATCH 651/655] Fix download button being disabled after importing a download disabled beatmap --- .../Panels/BeatmapPanelDownloadButton.cs | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs index 6d5862ee2b..67782dfe3f 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs @@ -50,13 +50,6 @@ namespace osu.Game.Overlays.BeatmapListing.Panels [BackgroundDependencyLoader(true)] private void load(OsuGame game, BeatmapManager beatmaps, OsuConfigManager osuConfig) { - if ((BeatmapSet.Value?.OnlineInfo?.Availability?.DownloadDisabled ?? false) && State.Value != DownloadState.LocallyAvailable) - { - button.Enabled.Value = false; - button.TooltipText = "this beatmap is currently not available for download."; - return; - } - noVideoSetting = osuConfig.GetBindable(OsuSetting.PreferNoVideo); button.Action = () => @@ -81,6 +74,26 @@ namespace osu.Game.Overlays.BeatmapListing.Panels break; } }; + + State.BindValueChanged(state => + { + switch (state.NewValue) + { + case DownloadState.LocallyAvailable: + button.Enabled.Value = true; + button.TooltipText = string.Empty; + break; + + default: + if (BeatmapSet.Value?.OnlineInfo?.Availability?.DownloadDisabled ?? false) + { + button.Enabled.Value = false; + button.TooltipText = "this beatmap is currently not available for download."; + } + + break; + } + }, true); } } } From fa711a6456a4ca25eb74a3fda8da3977e13a8a63 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 9 May 2020 19:11:51 +0900 Subject: [PATCH 652/655] Fix null reference causing hard freeze if game is forcefully closed during disclaimer --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index fdc8d94352..b86be9858f 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -914,7 +914,7 @@ namespace osu.Game if (ScreenStack.CurrentScreen is Loader) return false; - if (introScreen.DidLoadMenu && !(ScreenStack.CurrentScreen is IntroScreen)) + if (introScreen?.DidLoadMenu == true && !(ScreenStack.CurrentScreen is IntroScreen)) { Scheduler.Add(introScreen.MakeCurrent); return true; From 3f78ddf482b7b74c399aa4adf0791156813860e2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 9 May 2020 19:13:18 +0900 Subject: [PATCH 653/655] Add CanBeNull hinting --- osu.Game/OsuGame.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b86be9858f..3caffb6db5 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -18,6 +18,7 @@ using osu.Game.Screens.Menu; using System.Linq; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Development; @@ -97,6 +98,7 @@ namespace osu.Game private MainMenu menuScreen; + [CanBeNull] private IntroScreen introScreen; private Bindable configRuleset; From 2f12c4126a10676982cc301c58e98ab376d2f493 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 10 May 2020 13:49:08 +0900 Subject: [PATCH 654/655] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../TestSceneManiaHitObjectComposer.cs | 2 +- osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs index 1554a956a2..48159c817d 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs @@ -173,7 +173,7 @@ namespace osu.Game.Rulesets.Mania.Tests AddStep("grab hold note", () => { - holdNote = this.ChildrenOfType().FirstOrDefault(); + holdNote = this.ChildrenOfType().Single(); InputManager.MoveMouseTo(holdNote); InputManager.PressButton(MouseButton.Left); }); diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index ea72061216..15e625872d 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.UI.Scrolling [Resolved] private IScrollingInfo scrollingInfo { get; set; } - // Responds to changes in the layout. When the layout is changes, all hit object states must be recomputed. + // Responds to changes in the layout. When the layout changes, all hit object states must be recomputed. private readonly LayoutValue layoutCache = new LayoutValue(Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo); // A combined cache across all hit object states to reduce per-update iterations. From 44fdf1e6b05b033977287647e126e4ed99b898e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 10 May 2020 20:09:41 +0900 Subject: [PATCH 655/655] Reword xmldoc --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 5f0a4b0975..9a10b7d1b2 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -488,12 +488,8 @@ namespace osu.Game.Rulesets.UI protected virtual ResumeOverlay CreateResumeOverlay() => null; /// - /// Whether to display gameplay overlays with this ruleset. - /// Override to false to completely disable the display of gameplay overlays. + /// Whether to display gameplay overlays, such as and . /// - /// - /// Gameplay overlays refer here to in player as well as . - /// public virtual bool AllowGameplayOverlays => true; ///