diff --git a/appveyor.yml b/appveyor.yml index 1f485485da..4dcaa7b45e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ clone_depth: 1 version: '{branch}-{build}' -image: Visual Studio 2017 +image: Previous Visual Studio 2017 test: off install: - cmd: git submodule update --init --recursive --depth=5 diff --git a/build/build.cake b/build/build.cake index de94eb7ab3..1d2588de49 100644 --- a/build/build.cake +++ b/build/build.cake @@ -1,5 +1,5 @@ #addin "nuget:?package=CodeFileSanity&version=0.0.21" -#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2018.3.4" +#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.1.1" #tool "nuget:?package=NVika.MSBuild&version=1.0.1" var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First(); diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 3f64bcdf19..48c16caf0f 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -35,7 +35,7 @@ platform :ios do changelog.gsub!('$BUILD_ID', options[:build]) pilot( - wait_processing_interval: 900, + wait_processing_interval: 1800, changelog: changelog, ipa: './osu.iOS/bin/iPhone/Release/osu.iOS.ipa' ) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index e7e0af7eea..00cabbadf7 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -77,6 +77,7 @@ namespace osu.Desktop if (versionManager != null) versionManager.State = Visibility.Visible; break; + default: if (versionManager != null) versionManager.State = Visibility.Hidden; @@ -87,6 +88,7 @@ namespace osu.Desktop public override void SetHost(GameHost host) { base.SetHost(host); + if (host.Window is DesktopGameWindow desktopWindow) { desktopWindow.CursorState |= CursorState.Hidden; diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs index 711ffa7d9e..d2aad99f41 100644 --- a/osu.Desktop/Overlays/VersionManager.cs +++ b/osu.Desktop/Overlays/VersionManager.cs @@ -1,13 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Platform; using osu.Game; using osu.Game.Configuration; using osu.Game.Graphics; @@ -25,15 +23,13 @@ namespace osu.Desktop.Overlays private OsuConfigManager config; private OsuGameBase game; private NotificationOverlay notificationOverlay; - private GameHost host; [BackgroundDependencyLoader] - private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config, GameHost host) + private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config) { notificationOverlay = notification; this.config = config; this.game = game; - this.host = host; AutoSizeAxes = Axes.Both; Anchor = Anchor.BottomCentre; @@ -95,33 +91,38 @@ namespace osu.Desktop.Overlays var version = game.Version; var lastVersion = config.Get(OsuSetting.Version); + if (game.IsDeployedBuild && version != lastVersion) { config.Set(OsuSetting.Version, version); // only show a notification if we've previously saved a version to the config file (ie. not the first run). if (!string.IsNullOrEmpty(lastVersion)) - notificationOverlay.Post(new UpdateCompleteNotification(version, host.OpenUrlExternally)); + notificationOverlay.Post(new UpdateCompleteNotification(version)); } } private class UpdateCompleteNotification : SimpleNotification { - public UpdateCompleteNotification(string version, Action openUrl = null) + private readonly string version; + + public UpdateCompleteNotification(string version) { + this.version = version; Text = $"You are now running osu!lazer {version}.\nClick to see what's new!"; - Icon = FontAwesome.Solid.CheckSquare; - Activated = delegate - { - openUrl?.Invoke($"https://osu.ppy.sh/home/changelog/lazer/{version}"); - return true; - }; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, ChangelogOverlay changelog) { + Icon = FontAwesome.Solid.CheckSquare; IconBackgound.Colour = colours.BlueDark; + + Activated = delegate + { + changelog.ShowBuild("lazer", version); + return true; + }; } } diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index ff9972ac48..29554df64c 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -31,6 +31,7 @@ namespace osu.Desktop var importer = new ArchiveImportIPCChannel(host); // Restore the cwd so relative paths given at the command line work correctly Directory.SetCurrentDirectory(cwd); + foreach (var file in args) { Console.WriteLine(@"Importing {0}", file); diff --git a/osu.Desktop/Updater/SimpleUpdateManager.cs b/osu.Desktop/Updater/SimpleUpdateManager.cs index 0600804339..5184791de1 100644 --- a/osu.Desktop/Updater/SimpleUpdateManager.cs +++ b/osu.Desktop/Updater/SimpleUpdateManager.cs @@ -78,6 +78,7 @@ namespace osu.Desktop.Updater case RuntimeInfo.Platform.Windows: bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".exe")); break; + case RuntimeInfo.Platform.MacOsx: bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".app.zip")); break; diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 5fed2a63e1..e2c7a5e892 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.IO; -using System.Reflection; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -169,23 +167,19 @@ namespace osu.Desktop.Updater private class SquirrelLogger : Splat.ILogger, IDisposable { - private readonly string path; - private readonly object locker = new object(); public LogLevel Level { get; set; } = LogLevel.Info; - public SquirrelLogger() - { - var file = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "SquirrelSetupUpdater.log"); - if (File.Exists(file)) File.Delete(file); - path = file; - } + private Logger logger; public void Write(string message, LogLevel logLevel) { if (logLevel < Level) return; - lock (locker) File.AppendAllText(path, message + "\r\n"); + if (logger == null) + logger = Logger.GetLogger("updater"); + + logger.Add(message); } public void Dispose() diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 66db439c82..aa8848c55f 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -26,9 +26,9 @@ - - - + + + diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index 7f85d4ccce..e45ed8c6f4 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -36,11 +36,13 @@ namespace osu.Game.Rulesets.Catch.Tests yield return new ConvertValue((CatchHitObject)nested); break; + case BananaShower shower: foreach (var nested in shower.NestedHitObjects) yield return new ConvertValue((CatchHitObject)nested); break; + default: yield return new ConvertValue((CatchHitObject)hitObject); diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs similarity index 88% rename from osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs rename to osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs index fbb2db33b0..9cec0d280d 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs @@ -13,9 +13,9 @@ using osuTK; namespace osu.Game.Rulesets.Catch.Tests { - public class TestCaseAutoJuiceStream : PlayerTestCase + public class TestSceneAutoJuiceStream : PlayerTestScene { - public TestCaseAutoJuiceStream() + public TestSceneAutoJuiceStream() : base(new CatchRuleset()) { } @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.Tests protected override Player CreatePlayer(Ruleset ruleset) { - Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); + Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); return base.CreatePlayer(ruleset); } } diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs similarity index 93% rename from osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs rename to osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs index d413b53d17..035bbe4b4e 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs @@ -13,7 +13,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseBananaShower : PlayerTestCase + public class TestSceneBananaShower : PlayerTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Tests typeof(DrawableCatchRuleset), }; - public TestCaseBananaShower() + public TestSceneBananaShower() : base(new CatchRuleset()) { } diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs similarity index 78% rename from osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs rename to osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs index 5b242d05d7..9836a7811a 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs @@ -7,9 +7,9 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseCatchPlayer : PlayerTestCase + public class TestSceneCatchPlayer : PlayerTestScene { - public TestCaseCatchPlayer() + public TestSceneCatchPlayer() : base(new CatchRuleset()) { } diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs similarity index 91% rename from osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs rename to osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs index 5a16a23a4e..7d7528372a 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs @@ -9,9 +9,9 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseCatchStacker : PlayerTestCase + public class TestSceneCatchStacker : PlayerTestScene { - public TestCaseCatchStacker() + public TestSceneCatchStacker() : base(new CatchRuleset()) { } diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs similarity index 95% rename from osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs rename to osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs index 5e3fcd239f..3ae6886c31 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs @@ -13,7 +13,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseCatcherArea : OsuTestCase + public class TestSceneCatcherArea : OsuTestScene { private RulesetInfo catchRuleset; private TestCatcherArea catcherArea; @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Catch.Tests typeof(CatcherArea), }; - public TestCaseCatcherArea() + public TestSceneCatcherArea() { AddSliderStep("CircleSize", 0, 8, 5, createCatcher); AddToggleStep("Hyperdash", t => catcherArea.ToggleHyperDash(t)); diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs similarity index 96% rename from osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs rename to osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs index a59f4ce150..44517382f7 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseFruitObjects : OsuTestCase + public class TestSceneFruitObjects : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Tests typeof(Pulp), }; - public TestCaseFruitObjects() + public TestSceneFruitObjects() { Add(new GridContainer { diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs similarity index 94% rename from osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs rename to osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs index a7e7f0ab14..7393f75e5a 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs @@ -10,9 +10,9 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseHyperDash : PlayerTestCase + public class TestSceneHyperDash : PlayerTestScene { - public TestCaseHyperDash() + public TestSceneHyperDash() : base(new CatchRuleset()) { } 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 3f8b3bf086..265ecb7688 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,8 +2,8 @@ - - + + diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index 78b5a510b2..645cb5701a 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps initialiseHyperDash((List)Beatmap.HitObjects); int index = 0; + foreach (var obj in Beatmap.HitObjects.OfType()) { obj.IndexInBeatmap = index++; @@ -58,6 +59,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps } break; + case JuiceStream juiceStream: foreach (var nested in juiceStream.NestedHitObjects) { @@ -103,6 +105,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps double timeToNext = nextObject.StartTime - currentObject.StartTime - 1000f / 60f / 4; // 1/4th of a frame of grace time, taken from osu-stable double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth); float distanceToHyper = (float)(timeToNext * CatcherArea.Catcher.BASE_SPEED - distanceToNext); + if (distanceToHyper < 0) { currentObject.HyperDashTarget = nextObject; diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index aa00e182a9..ea9f225cc1 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch { public class CatchRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableCatchRuleset(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableCatchRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap); @@ -87,6 +87,7 @@ namespace osu.Game.Rulesets.Catch new CatchModNoFail(), new MultiMod(new CatchModHalfTime(), new CatchModDaycore()) }; + case ModType.DifficultyIncrease: return new Mod[] { @@ -96,17 +97,20 @@ namespace osu.Game.Rulesets.Catch new CatchModHidden(), new CatchModFlashlight(), }; + case ModType.Automation: return new Mod[] { new MultiMod(new CatchModAutoplay(), new ModCinema()), new CatchModRelax(), }; + case ModType.Fun: return new Mod[] { new MultiMod(new ModWindUp(), new ModWindDown()) }; + default: return new Mod[] { }; } diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index b4998347f4..d6a1ed632b 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -57,32 +57,20 @@ namespace osu.Game.Rulesets.Catch.Difficulty CatchHitObject lastObject = null; - foreach (var hitObject in beatmap.HitObjects.OfType()) + // In 2B beatmaps, it is possible that a normal Fruit is placed in the middle of a JuiceStream. + foreach (var hitObject in beatmap.HitObjects + .SelectMany(obj => obj is JuiceStream stream ? stream.NestedHitObjects : new[] { obj }) + .Cast() + .OrderBy(x => x.StartTime)) { - if (lastObject == null) - { - lastObject = hitObject; + // We want to only consider fruits that contribute to the combo. + if (hitObject is BananaShower || hitObject is TinyDroplet) continue; - } - switch (hitObject) - { - // We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations. - case Fruit fruit: - yield return new CatchDifficultyHitObject(fruit, lastObject, clockRate, halfCatchWidth); + if (lastObject != null) + yield return new CatchDifficultyHitObject(hitObject, lastObject, clockRate, halfCatchWidth); - lastObject = hitObject; - break; - case JuiceStream _: - foreach (var nested in hitObject.NestedHitObjects.OfType().Where(o => !(o is TinyDroplet))) - { - yield return new CatchDifficultyHitObject(nested, lastObject, clockRate, halfCatchWidth); - - lastObject = nested; - } - - break; - } + lastObject = hitObject; } } diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs index 4e64753a65..374dd50c11 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs @@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Catch.Judgements { default: return 0; + case HitResult.Perfect: return 1100; } @@ -27,8 +28,9 @@ namespace osu.Game.Rulesets.Catch.Judgements { default: return 0; + case HitResult.Perfect: - return 8; + return 0.008; } } diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs index 2598dee156..f1399bb5c0 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs @@ -13,6 +13,7 @@ namespace osu.Game.Rulesets.Catch.Judgements { default: return 0; + case HitResult.Perfect: return 30; } @@ -23,9 +24,10 @@ namespace osu.Game.Rulesets.Catch.Judgements switch (result) { default: - return 0; + return base.HealthIncreaseFor(result); + case HitResult.Perfect: - return 7; + return 0.007; } } } diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs index 5d7ef04dd2..8fd9ac92ba 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs @@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Catch.Judgements { default: return 0; + case HitResult.Perfect: return 300; } @@ -27,9 +28,10 @@ namespace osu.Game.Rulesets.Catch.Judgements switch (result) { default: - return 0; + return -0.02; + case HitResult.Perfect: - return 10.2; + return 0.01; } } diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs index b8c51b7b60..3829b5e94f 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs @@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Catch.Judgements { default: return 0; + case HitResult.Perfect: return 10; } @@ -26,8 +27,9 @@ namespace osu.Game.Rulesets.Catch.Judgements { default: return 0; + case HitResult.Perfect: - return 4; + return 0.004; } } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs index 294fd97d59..2f8ccec48b 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs @@ -84,6 +84,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable case ArmedState.Miss: this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out).Expire(); break; + case ArmedState.Hit: this.FadeOut().Expire(); break; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs index 0dc3f73404..77407def54 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs @@ -6,6 +6,7 @@ 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.MathUtils; using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; @@ -105,6 +106,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { default: return new Container(); + case FruitVisualRepresentation.Raspberry: return new Container { @@ -143,6 +145,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable }, } }; + case FruitVisualRepresentation.Pineapple: return new Container { @@ -181,6 +184,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable }, } }; + case FruitVisualRepresentation.Pear: return new Container { @@ -213,6 +217,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable }, } }; + case FruitVisualRepresentation.Grape: return new Container { @@ -245,6 +250,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable }, } }; + case FruitVisualRepresentation.Banana: return new Container { @@ -282,19 +288,25 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable default: case FruitVisualRepresentation.Pear: return new Color4(17, 136, 170, 255); + case FruitVisualRepresentation.Grape: return new Color4(204, 102, 0, 255); + case FruitVisualRepresentation.Raspberry: return new Color4(121, 9, 13, 255); + case FruitVisualRepresentation.Pineapple: return new Color4(102, 136, 0, 255); + case FruitVisualRepresentation.Banana: switch (RNG.Next(0, 3)) { default: return new Color4(255, 240, 0, 255); + case 1: return new Color4(255, 192, 0, 255); + case 2: return new Color4(214, 221, 28, 255); } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs index 2e18c5f2ad..b9b6d5b924 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs @@ -3,7 +3,7 @@ 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; diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 2adc156efd..a9fd34455a 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -95,6 +95,7 @@ namespace osu.Game.Rulesets.Catch.Objects X = X + Path.PositionAt(e.PathProgress).X / CatchPlayfield.BASE_WIDTH, }); break; + case SliderEventType.Head: case SliderEventType.Tail: case SliderEventType.Repeat: diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index af614f95d0..99b22b2d56 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.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.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Judgements; @@ -27,20 +26,16 @@ namespace osu.Game.Rulesets.Catch.Scoring hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate; } - private const double harshness = 0.01; - - protected override void ApplyResult(JudgementResult result) + protected override double HealthAdjustmentFactorFor(JudgementResult result) { - base.ApplyResult(result); - - if (result.Type == HitResult.Miss) + switch (result.Type) { - if (!result.Judgement.IsBonus) - Health.Value -= hpDrainRate * (harshness * 2); - return; - } + case HitResult.Miss: + return hpDrainRate; - Health.Value += Math.Max(result.Judgement.HealthIncreaseFor(result) - hpDrainRate, 0) * harshness; + default: + return 10.2 - hpDrainRate; // Award less HP as drain rate is increased + } } public override HitWindows CreateHitWindows() => new CatchHitWindows(); diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 83f791690a..e7c7fd77df 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -292,6 +292,7 @@ namespace osu.Game.Rulesets.Catch.UI const float hyper_dash_transition_length = 180; bool previouslyHyperDashing = HyperDashing; + if (modifier <= 1 || X == targetPosition) { hyperDashModifier = 1; @@ -325,9 +326,11 @@ namespace osu.Game.Rulesets.Catch.UI case CatchAction.MoveLeft: currentDirection--; return true; + case CatchAction.MoveRight: currentDirection++; return true; + case CatchAction.Dash: Dashing = true; return true; @@ -343,9 +346,11 @@ namespace osu.Game.Rulesets.Catch.UI case CatchAction.MoveLeft: currentDirection++; return true; + case CatchAction.MoveRight: currentDirection--; return true; + case CatchAction.Dash: Dashing = false; return true; diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index ba0f5b90ba..f48b84e344 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.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 osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -10,6 +11,7 @@ using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Catch.Scoring; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; @@ -23,8 +25,8 @@ namespace osu.Game.Rulesets.Catch.UI protected override bool UserScrollSpeedAdjustment => false; - public DrawableCatchRuleset(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + public DrawableCatchRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + : base(ruleset, beatmap, mods) { Direction.Value = ScrollingDirection.Down; TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450); @@ -46,14 +48,19 @@ namespace osu.Game.Rulesets.Catch.UI { case Banana banana: return new DrawableBanana(banana); + case Fruit fruit: return new DrawableFruit(fruit); + case JuiceStream stream: return new DrawableJuiceStream(stream, CreateDrawableRepresentation); + case BananaShower shower: return new DrawableBananaShower(shower, CreateDrawableRepresentation); + case TinyDroplet tiny: return new DrawableTinyDroplet(tiny); + case Droplet droplet: return new DrawableDroplet(droplet); } diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestCase.cs b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs similarity index 93% rename from osu.Game.Rulesets.Mania.Tests/ManiaInputTestCase.cs rename to osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs index f281883e0c..909d0d45c6 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestCase.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs @@ -8,12 +8,12 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests { - public abstract class ManiaInputTestCase : OsuTestCase + public abstract class ManiaInputTestScene : OsuTestScene { private readonly Container content; protected override Container Content => content ?? base.Content; - protected ManiaInputTestCase(int keys) + protected ManiaInputTestScene(int keys) { base.Content.Add(content = new LocalInputManager(keys)); } diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestCase.cs b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs similarity index 82% rename from osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestCase.cs rename to osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs index 13bbe87513..4b3786c30a 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestCase.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -8,6 +10,7 @@ using osu.Framework.Timing; using osu.Game.Rulesets.Mania.Edit; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; @@ -17,11 +20,14 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Tests { [Cached(Type = typeof(IManiaHitObjectComposer))] - public abstract class ManiaPlacementBlueprintTestCase : PlacementBlueprintTestCase, IManiaHitObjectComposer + public abstract class ManiaPlacementBlueprintTestScene : PlacementBlueprintTestScene, IManiaHitObjectComposer { private readonly Column column; - protected ManiaPlacementBlueprintTestCase() + [Cached(typeof(IReadOnlyList))] + private IReadOnlyList mods { get; set; } = Array.Empty(); + + protected ManiaPlacementBlueprintTestScene() { Add(column = new Column(0) { diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestCase.cs b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs similarity index 86% rename from osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestCase.cs rename to osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs index a22e599681..b598893e8c 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestCase.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs @@ -13,14 +13,14 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Tests { [Cached(Type = typeof(IManiaHitObjectComposer))] - public abstract class ManiaSelectionBlueprintTestCase : SelectionBlueprintTestCase, IManiaHitObjectComposer + public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene, IManiaHitObjectComposer { [Cached(Type = typeof(IAdjustableClock))] private readonly IAdjustableClock clock = new StopwatchClock(); private readonly Column column; - protected ManiaSelectionBlueprintTestCase() + protected ManiaSelectionBlueprintTestScene() { Add(column = new Column(0) { diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseAutoGeneration.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs similarity index 99% rename from osu.Game.Rulesets.Mania.Tests/TestCaseAutoGeneration.cs rename to osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs index e8a056bbff..20ac5eaa39 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseAutoGeneration.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs @@ -12,7 +12,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests { [TestFixture] - public class TestCaseAutoGeneration : OsuTestCase + public class TestSceneAutoGeneration : OsuTestScene { [Test] public void TestSingleNote() diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs similarity index 93% rename from osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs rename to osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs index b14f999f61..d94a986dae 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI.Components; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; using osuTK; @@ -21,7 +22,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Tests { [TestFixture] - public class TestCaseColumn : ManiaInputTestCase + public class TestSceneColumn : ManiaInputTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -31,9 +32,12 @@ namespace osu.Game.Rulesets.Mania.Tests typeof(ColumnHitObjectArea) }; + [Cached(typeof(IReadOnlyList))] + private IReadOnlyList mods { get; set; } = Array.Empty(); + private readonly List columns = new List(); - public TestCaseColumn() + public TestSceneColumn() : base(2) { } diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseEditor.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneEditor.cs similarity index 92% rename from osu.Game.Rulesets.Mania.Tests/TestCaseEditor.cs rename to osu.Game.Rulesets.Mania.Tests/TestSceneEditor.cs index e721eb6fd9..7ed886be49 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseEditor.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneEditor.cs @@ -11,11 +11,11 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests { [TestFixture] - public class TestCaseEditor : EditorTestCase + public class TestSceneEditor : EditorTestScene { private readonly Bindable direction = new Bindable(); - public TestCaseEditor() + public TestSceneEditor() : base(new ManiaRuleset()) { AddStep("upwards scroll", () => direction.Value = ManiaScrollingDirection.Up); diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNotePlacementBlueprint.cs similarity index 88% rename from osu.Game.Rulesets.Mania.Tests/TestCaseHoldNotePlacementBlueprint.cs rename to osu.Game.Rulesets.Mania.Tests/TestSceneHoldNotePlacementBlueprint.cs index 411412e127..b4332264b9 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNotePlacementBlueprint.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Mania.Tests { - public class TestCaseHoldNotePlacementBlueprint : ManiaPlacementBlueprintTestCase + public class TestSceneHoldNotePlacementBlueprint : ManiaPlacementBlueprintTestScene { protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHoldNote((HoldNote)hitObject); protected override PlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint(); diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs similarity index 93% rename from osu.Game.Rulesets.Mania.Tests/TestCaseHoldNoteSelectionBlueprint.cs rename to osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs index ae614ae4b8..04c5724f93 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs @@ -15,14 +15,14 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests { - public class TestCaseHoldNoteSelectionBlueprint : ManiaSelectionBlueprintTestCase + public class TestSceneHoldNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene { private readonly DrawableHoldNote drawableObject; protected override Container Content => content ?? base.Content; private readonly Container content; - public TestCaseHoldNoteSelectionBlueprint() + public TestSceneHoldNoteSelectionBlueprint() { var holdNote = new HoldNote { Column = 0, Duration = 1000 }; holdNote.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotePlacementBlueprint.cs similarity index 88% rename from osu.Game.Rulesets.Mania.Tests/TestCaseNotePlacementBlueprint.cs rename to osu.Game.Rulesets.Mania.Tests/TestSceneNotePlacementBlueprint.cs index 12cbeb81f3..d7b539a2a0 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotePlacementBlueprint.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Mania.Tests { - public class TestCaseNotePlacementBlueprint : ManiaPlacementBlueprintTestCase + public class TestSceneNotePlacementBlueprint : ManiaPlacementBlueprintTestScene { protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableNote((Note)hitObject); protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint(); diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNoteSelectionBlueprint.cs similarity index 90% rename from osu.Game.Rulesets.Mania.Tests/TestCaseNoteSelectionBlueprint.cs rename to osu.Game.Rulesets.Mania.Tests/TestSceneNoteSelectionBlueprint.cs index 99fe464cfd..6bb344f977 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNoteSelectionBlueprint.cs @@ -15,14 +15,14 @@ using osuTK; namespace osu.Game.Rulesets.Mania.Tests { - public class TestCaseNoteSelectionBlueprint : ManiaSelectionBlueprintTestCase + public class TestSceneNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene { private readonly DrawableNote drawableObject; protected override Container Content => content ?? base.Content; private readonly Container content; - public TestCaseNoteSelectionBlueprint() + public TestSceneNoteSelectionBlueprint() { var note = new Note { Column = 0 }; note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs similarity index 98% rename from osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs rename to osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index 0bc2c3ea28..b2613a59d5 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs @@ -11,10 +11,10 @@ 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.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables; @@ -27,7 +27,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Tests { [TestFixture] - public class TestCaseNotes : OsuTestCase + public class TestSceneNotes : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Mania.Tests content = new Container { RelativeSizeAxes = Axes.Both } } }, - new SpriteText + new OsuSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -168,11 +168,13 @@ namespace osu.Game.Rulesets.Mania.Tests foreach (var nested in obj.NestedHitObjects) { double finalPosition = (nested.HitObject.StartTime - obj.HitObject.StartTime) / endTime.Duration; + switch (direction) { case ScrollingDirection.Up: nested.Y = (float)(finalPosition * content.DrawHeight); break; + case ScrollingDirection.Down: nested.Y = (float)(-finalPosition * content.DrawHeight); break; diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs similarity index 94% rename from osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs rename to osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs index ac430037e4..395e6daf0a 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.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; @@ -13,6 +14,7 @@ using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; using osuTK; @@ -20,15 +22,18 @@ using osuTK; namespace osu.Game.Rulesets.Mania.Tests { [TestFixture] - public class TestCaseStage : ManiaInputTestCase + public class TestSceneStage : ManiaInputTestScene { private const int columns = 4; + [Cached(typeof(IReadOnlyList))] + private IReadOnlyList mods { get; set; } = Array.Empty(); + private readonly List stages = new List(); private FillFlowContainer fill; - public TestCaseStage() + public TestSceneStage() : base(columns) { } 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 fd17285a38..dbade6ff8d 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,8 +2,8 @@ - - + + diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 71df68c087..704deba78b 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -48,6 +48,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps if (IsForCurrentRuleset) { TargetColumns = (int)Math.Max(1, roundedCircleSize); + if (TargetColumns >= 10) { TargetColumns = TargetColumns / 2; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index ed52bdd23f..1b6ff16388 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -179,6 +179,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy int usableColumns = TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects; int nextColumn = GetRandomColumn(); + for (int i = 0; i < Math.Min(usableColumns, noteCount); i++) { // Find available column @@ -217,6 +218,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy nextColumn = FindAvailableColumn(nextColumn, PreviousPattern); int lastColumn = nextColumn; + for (int i = 0; i < noteCount; i++) { addToPattern(pattern, nextColumn, startTime, startTime); @@ -299,6 +301,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy int interval = Random.Next(1, TotalColumns - (legacy ? 1 : 0)); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); + for (int i = 0; i <= spanCount; i++) { addToPattern(pattern, nextColumn, startTime, startTime); @@ -341,16 +344,19 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy p3 = 0; p4 = 0; break; + case 3: p2 = Math.Min(p2, 0.1); p3 = 0; p4 = 0; break; + case 4: p2 = Math.Min(p2, 0.3); p3 = Math.Min(p3, 0.04); p4 = 0; break; + case 5: p2 = Math.Min(p2, 0.34); p3 = Math.Min(p3, 0.1); @@ -440,6 +446,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy bool ignoreHead = !sampleInfoListAt(startTime).Any(s => s.Name == SampleInfo.HIT_WHISTLE || s.Name == SampleInfo.HIT_FINISH || s.Name == SampleInfo.HIT_CLAP); var rowPattern = new Pattern(); + for (int i = 0; i <= spanCount; i++) { if (!(ignoreHead && startTime == HitObject.StartTime)) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs index 0bf6c055ac..9e95be35fa 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs @@ -38,9 +38,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy case 8 when HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && endTime - HitObject.StartTime < 1000: addToPattern(pattern, 0, generateHold); break; + case 8: addToPattern(pattern, FindAvailableColumn(GetRandomColumn(), PreviousPattern), generateHold); break; + default: if (TotalColumns > 0) addToPattern(pattern, GetRandomColumn(), generateHold); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index 34f5f5c415..d13b21183b 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -233,6 +233,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy noteCount = Math.Min(noteCount, TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); + for (int i = 0; i < noteCount; i++) { nextColumn = allowStacking @@ -303,6 +304,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy int columnLimit = (TotalColumns % 2 == 0 ? TotalColumns : TotalColumns - 1) / 2; int nextColumn = GetRandomColumn(upperBound: columnLimit); + for (int i = 0; i < noteCount; i++) { nextColumn = FindAvailableColumn(nextColumn, upperBound: columnLimit, patterns: pattern); @@ -340,18 +342,21 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy p4 = 0; p5 = 0; break; + case 3: p2 = Math.Min(p2, 0.1); p3 = 0; p4 = 0; p5 = 0; break; + case 4: p2 = Math.Min(p2, 0.23); p3 = Math.Min(p3, 0.04); p4 = 0; p5 = 0; break; + case 5: p3 = Math.Min(p3, 0.15); p4 = Math.Min(p4, 0.03); @@ -384,20 +389,24 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy p2 = 0; p3 = 0; break; + case 3: centreProbability = Math.Min(centreProbability, 0.03); p2 = 0; p3 = 0; break; + case 4: centreProbability = 0; p2 = Math.Min(p2 * 2, 0.2); p3 = 0; break; + case 5: centreProbability = Math.Min(centreProbability, 0.03); p3 = 0; break; + case 6: centreProbability = 0; p2 = Math.Min(p2 * 2, 0.5); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index b702291c5d..fba52dfc32 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -158,6 +158,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy // Ensure that we have at least one free column, so that an endless loop is avoided bool hasValidColumns = false; + for (int i = lowerBound.Value; i < upperBound.Value; i++) { hasValidColumns = isValid(i); diff --git a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs index acafaffee6..e5f379f608 100644 --- a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs +++ b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Graphics; using osuTK; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; @@ -14,8 +16,8 @@ namespace osu.Game.Rulesets.Mania.Edit { public new IScrollingInfo ScrollingInfo => base.ScrollingInfo; - public DrawableManiaEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + public DrawableManiaEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + : base(ruleset, beatmap, mods) { } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 56c9471462..2729621ab3 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -41,9 +42,9 @@ namespace osu.Game.Rulesets.Mania.Edit public int TotalColumns => ((ManiaPlayfield)DrawableRuleset.Playfield).TotalColumns; - protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap) + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) { - DrawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap); + DrawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods); // This is the earliest we can cache the scrolling info to ourselves, before masks are added to the hierarchy and inject it dependencies.CacheAs(DrawableRuleset.ScrollingInfo); @@ -65,6 +66,7 @@ namespace osu.Game.Rulesets.Mania.Edit { case DrawableNote note: return new NoteSelectionBlueprint(note); + case DrawableHoldNote holdNote: return new HoldNoteSelectionBlueprint(holdNote); } diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs index 015eb1310e..b9c6e3a7f7 100644 --- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs @@ -10,5 +10,17 @@ namespace osu.Game.Rulesets.Mania.Judgements public override bool AffectsCombo => false; protected override int NumericResultFor(HitResult result) => 20; + + protected override double HealthIncreaseFor(HitResult result) + { + switch (result) + { + case HitResult.Miss: + return 0; + + default: + return 0.040; + } + } } } diff --git a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs index b6fb37f054..0e4c811945 100644 --- a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs @@ -14,16 +14,47 @@ namespace osu.Game.Rulesets.Mania.Judgements { default: return 0; + case HitResult.Meh: return 50; + case HitResult.Ok: return 100; + case HitResult.Good: return 200; + case HitResult.Great: case HitResult.Perfect: return 300; } } + + protected override double HealthIncreaseFor(HitResult result) + { + switch (result) + { + case HitResult.Miss: + return -0.125; + + case HitResult.Meh: + return 0.005; + + case HitResult.Ok: + return 0.010; + + case HitResult.Good: + return 0.035; + + case HitResult.Great: + return 0.055; + + case HitResult.Perfect: + return 0.065; + + default: + return 0; + } + } } } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 0ff79d2836..d83033f9c6 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Mania { public class ManiaRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableManiaRuleset(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableManiaRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score); @@ -117,6 +117,7 @@ namespace osu.Game.Rulesets.Mania new ManiaModNoFail(), new MultiMod(new ManiaModHalfTime(), new ManiaModDaycore()), }; + case ModType.DifficultyIncrease: return new Mod[] { @@ -126,6 +127,7 @@ namespace osu.Game.Rulesets.Mania new MultiMod(new ManiaModFadeIn(), new ManiaModHidden()), new ManiaModFlashlight(), }; + case ModType.Conversion: return new Mod[] { @@ -142,16 +144,19 @@ namespace osu.Game.Rulesets.Mania new ManiaModDualStages(), new ManiaModMirror(), }; + case ModType.Automation: return new Mod[] { new MultiMod(new ManiaModAutoplay(), new ModCinema()), }; + case ModType.Fun: return new Mod[] { new MultiMod(new ModWindUp(), new ModWindDown()) }; + default: return new Mod[] { }; } @@ -214,6 +219,7 @@ namespace osu.Game.Rulesets.Mania SpecialAction = ManiaAction.Special1, NormalActionStart = ManiaAction.Key1, }.GenerateKeyBindingsFor(variant, out _); + case PlayfieldType.Dual: int keys = getDualStageKeyCount(variant); @@ -271,6 +277,7 @@ namespace osu.Game.Rulesets.Mania { default: return $"{variant}K"; + case PlayfieldType.Dual: { var keys = getDualStageKeyCount(variant); diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs index f2be8d614c..9a29273282 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -7,6 +7,7 @@ using osuTK.Graphics; 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.Rulesets.Scoring; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index a78524011f..0873f753be 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -65,6 +65,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables case ArmedState.Miss: this.FadeOut(150, Easing.In).Expire(); break; + case ArmedState.Hit: this.FadeOut(150, Easing.OutQuint).Expire(); break; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 82a34224f4..afd7777861 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -5,7 +5,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osuTK.Graphics; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Scoring; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs index 2baf1ad520..8102718edf 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs @@ -7,6 +7,7 @@ using osuTK.Graphics; 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; @@ -144,6 +145,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces const float animation_length = 50; Foreground.ClearTransforms(false, nameof(Foreground.Colour)); + if (hitting) { // wait for the next sync point diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs index b146a33fd3..1d25a0c966 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs @@ -4,6 +4,7 @@ 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; diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 65b7d54cd2..e5669816fa 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -28,6 +28,7 @@ namespace osu.Game.Rulesets.Mania.Replays var normalAction = ManiaAction.Key1; var specialAction = ManiaAction.Special1; int totalCounter = 0; + foreach (var stage in Beatmap.Stages) { for (int i = 0; i < stage.Columns; i++) @@ -51,6 +52,7 @@ namespace osu.Game.Rulesets.Mania.Replays var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time); var actions = new List(); + foreach (var group in pointGroups) { foreach (var point in group) @@ -60,6 +62,7 @@ namespace osu.Game.Rulesets.Mania.Replays case HitPoint _: actions.Add(columnActions[point.Column]); break; + case ReleasePoint _: actions.Remove(columnActions[point.Column]); break; diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs index 81a76c93e6..f7277d3669 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs @@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.Mania.Replays int activeColumns = (int)(legacyFrame.MouseX ?? 0); int counter = 0; + while (activeColumns > 0) { var isSpecial = stage.IsSpecialColumn(counter); diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 5c914d8eac..5caf08fb1e 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -3,7 +3,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; @@ -28,36 +27,6 @@ namespace osu.Game.Rulesets.Mania.Scoring /// private const double hp_multiplier_max = 1; - /// - /// The default BAD hit HP increase. - /// - private const double hp_increase_bad = 0.005; - - /// - /// The default OK hit HP increase. - /// - private const double hp_increase_ok = 0.010; - - /// - /// The default GOOD hit HP increase. - /// - private const double hp_increase_good = 0.035; - - /// - /// The default tick hit HP increase. - /// - private const double hp_increase_tick = 0.040; - - /// - /// The default GREAT hit HP increase. - /// - private const double hp_increase_great = 0.055; - - /// - /// The default PERFECT hit HP increase. - /// - private const double hp_increase_perfect = 0.065; - /// /// The MISS HP multiplier at OD = 0. /// @@ -73,11 +42,6 @@ namespace osu.Game.Rulesets.Mania.Scoring /// private const double hp_multiplier_miss_max = 1; - /// - /// The default MISS HP increase. - /// - private const double hp_increase_miss = -0.125; - /// /// The MISS HP multiplier. This is multiplied to the miss hp increase. /// @@ -88,10 +52,6 @@ namespace osu.Game.Rulesets.Mania.Scoring /// private double hpMultiplier = 1; - public ManiaScoreProcessor() - { - } - public ManiaScoreProcessor(DrawableRuleset drawableRuleset) : base(drawableRuleset) { @@ -122,42 +82,8 @@ namespace osu.Game.Rulesets.Mania.Scoring } } - protected override void ApplyResult(JudgementResult result) - { - base.ApplyResult(result); - - bool isTick = result.Judgement is HoldNoteTickJudgement; - - if (isTick) - { - if (result.IsHit) - Health.Value += hpMultiplier * hp_increase_tick; - } - else - { - switch (result.Type) - { - case HitResult.Miss: - Health.Value += hpMissMultiplier * hp_increase_miss; - break; - case HitResult.Meh: - Health.Value += hpMultiplier * hp_increase_bad; - break; - case HitResult.Ok: - Health.Value += hpMultiplier * hp_increase_ok; - break; - case HitResult.Good: - Health.Value += hpMultiplier * hp_increase_good; - break; - case HitResult.Great: - Health.Value += hpMultiplier * hp_increase_great; - break; - case HitResult.Perfect: - Health.Value += hpMultiplier * hp_increase_perfect; - break; - } - } - } + protected override double HealthAdjustmentFactorFor(JudgementResult result) + => result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier; public override HitWindows CreateHitWindows() => new ManiaHitWindows(); } diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs index 89e8cd9b5a..a0d713067d 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs @@ -6,6 +6,7 @@ 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.Game.Graphics; using osu.Game.Rulesets.UI; diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs index 03b55cbead..85880222d7 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs @@ -7,6 +7,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Game.Graphics; diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 1c1ec604f6..c8aeda8fe4 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -18,6 +18,7 @@ using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Scoring; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; @@ -39,8 +40,8 @@ namespace osu.Game.Rulesets.Mania.UI private readonly Bindable configDirection = new Bindable(); - public DrawableManiaRuleset(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + public DrawableManiaRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + : base(ruleset, beatmap, mods) { // Generate the bar lines double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; @@ -56,6 +57,7 @@ namespace osu.Game.Rulesets.Mania.UI double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time - point.BeatLength : lastObjectTime + point.BeatLength * (int)point.TimeSignature; int index = 0; + for (double t = timingPoints[i].Time; Precision.DefinitelyBigger(endTime, t); t += point.BeatLength, index++) { barLines.Add(new BarLine @@ -104,8 +106,10 @@ namespace osu.Game.Rulesets.Mania.UI { case HoldNote holdNote: return new DrawableHoldNote(holdNote); + case Note note: return new DrawableNote(note); + default: return null; } diff --git a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs index f5a9978f77..0ec1fc38d2 100644 --- a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs @@ -4,6 +4,7 @@ using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.MathUtils; using osu.Game.Rulesets.Mania.Objects.Drawables; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index cbabfcc8b4..5ab07416a6 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -38,6 +38,7 @@ namespace osu.Game.Rulesets.Mania.UI var normalColumnAction = ManiaAction.Key1; var specialColumnAction = ManiaAction.Special1; int firstColumnIndex = 0; + for (int i = 0; i < stageDefinitions.Count; i++) { var newStage = new ManiaStage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction); @@ -92,6 +93,7 @@ namespace osu.Game.Rulesets.Mania.UI private ManiaStage getStageByColumn(int column) { int sum = 0; + foreach (var stage in stages) { sum = sum + stage.Columns.Count; diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs index f7d1ff4db1..f98d63e6c7 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs @@ -35,6 +35,7 @@ namespace osu.Game.Rulesets.Osu.Tests yield return createConvertValue(nested); break; + default: yield return createConvertValue(hitObject); diff --git a/osu.Game.Rulesets.Osu.Tests/StackingTest.cs b/osu.Game.Rulesets.Osu.Tests/StackingTest.cs index e2f6b2164c..e8b99e86f9 100644 --- a/osu.Game.Rulesets.Osu.Tests/StackingTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/StackingTest.cs @@ -1,11 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.IO; using System.Linq; using System.Text; using NUnit.Framework; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Beatmaps; using Decoder = osu.Game.Beatmaps.Formats.Decoder; @@ -22,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Tests using (var reader = new StreamReader(stream)) { var beatmap = Decoder.GetDecoder(reader).Decode(reader); - var converted = new TestWorkingBeatmap(beatmap).GetPlayableBeatmap(new OsuRuleset().RulesetInfo); + var converted = new TestWorkingBeatmap(beatmap).GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty()); var objects = converted.HitObjects.ToList(); diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseEditor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneEditor.cs similarity index 79% rename from osu.Game.Rulesets.Osu.Tests/TestCaseEditor.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneEditor.cs index 83626e7043..4aca34bf64 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseEditor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneEditor.cs @@ -7,9 +7,9 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestCaseEditor : EditorTestCase + public class TestSceneEditor : EditorTestScene { - public TestCaseEditor() + public TestSceneEditor() : base(new OsuRuleset()) { } diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs similarity index 93% rename from osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index 1e2a936002..1b1cfa89c0 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -15,7 +15,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestCaseGameplayCursor : OsuTestCase, IProvideCursor + public class TestSceneGameplayCursor : OsuTestScene, IProvideCursor { private GameplayCursorContainer cursorContainer; diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs similarity index 94% rename from osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index e1e854e8dc..d44a0cd841 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -19,7 +19,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestCaseHitCircle : OsuTestCase + public class TestSceneHitCircle : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -30,9 +30,8 @@ namespace osu.Game.Rulesets.Osu.Tests protected override Container Content => content; private int depthIndex; - protected readonly List Mods = new List(); - public TestCaseHitCircle() + public TestSceneHitCircle() { base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); @@ -68,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Tests Depth = depthIndex++ }; - foreach (var mod in Mods.OfType()) + foreach (var mod in Mods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); Add(drawable); diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs similarity index 75% rename from osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleHidden.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs index 26d9b5ae91..55c6b22146 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs @@ -10,13 +10,13 @@ using osu.Game.Rulesets.Osu.Mods; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestCaseHitCircleHidden : TestCaseHitCircle + public class TestSceneHitCircleHidden : TestSceneHitCircle { public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList(); - public TestCaseHitCircleHidden() + public TestSceneHitCircleHidden() { - Mods.Add(new OsuModHidden()); + Mods.Value = new[] { new OsuModHidden() }; } } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs similarity index 89% rename from osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs index 8d097ff1c1..921246751c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs @@ -10,9 +10,9 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestCaseHitCircleLongCombo : PlayerTestCase + public class TestSceneHitCircleLongCombo : PlayerTestScene { - public TestCaseHitCircleLongCombo() + public TestSceneHitCircleLongCombo() : base(new OsuRuleset()) { } diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCirclePlacementBlueprint.cs similarity index 89% rename from osu.Game.Rulesets.Osu.Tests/TestCaseHitCirclePlacementBlueprint.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneHitCirclePlacementBlueprint.cs index d536e39eef..4c6abc45f7 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCirclePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCirclePlacementBlueprint.cs @@ -11,7 +11,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { - public class TestCaseHitCirclePlacementBlueprint : PlacementBlueprintTestCase + public class TestSceneHitCirclePlacementBlueprint : PlacementBlueprintTestScene { protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHitCircle((HitCircle)hitObject); protected override PlacementBlueprint CreateBlueprint() => new HitCirclePlacementBlueprint(); diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs similarity index 87% rename from osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleSelectionBlueprint.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs index e9284e453e..32043bf5d7 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs @@ -12,11 +12,11 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Tests { - public class TestCaseHitCircleSelectionBlueprint : SelectionBlueprintTestCase + public class TestSceneHitCircleSelectionBlueprint : SelectionBlueprintTestScene { private readonly DrawableHitCircle drawableObject; - public TestCaseHitCircleSelectionBlueprint() + public TestSceneHitCircleSelectionBlueprint() { var hitCircle = new HitCircle { Position = new Vector2(256, 192) }; hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs new file mode 100644 index 0000000000..64e7632b1b --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.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.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneOsuFlashlight : TestSceneOsuPlayer + { + protected override Player CreatePlayer(Ruleset ruleset) + { + Mods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), }; + + return base.CreatePlayer(ruleset); + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs similarity index 78% rename from osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs index 720c3c66fe..0a33b09ba8 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs @@ -7,9 +7,9 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestCaseOsuPlayer : PlayerTestCase + public class TestSceneOsuPlayer : PlayerTestScene { - public TestCaseOsuPlayer() + public TestSceneOsuPlayer() : base(new OsuRuleset()) { } diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseResumeOverlay.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs similarity index 95% rename from osu.Game.Rulesets.Osu.Tests/TestCaseResumeOverlay.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs index 5956f12146..12a3a8d27e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs @@ -12,14 +12,14 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { - public class TestCaseResumeOverlay : ManualInputManagerTestCase + public class TestSceneResumeOverlay : ManualInputManagerTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(OsuResumeOverlay), }; - public TestCaseResumeOverlay() + public TestSceneResumeOverlay() { ManualOsuInputManager osuInputManager; CursorContainer cursor; diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseShaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs similarity index 92% rename from osu.Game.Rulesets.Osu.Tests/TestCaseShaking.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs index 5dc0dc1024..3d8afd66f4 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseShaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs @@ -7,7 +7,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Tests { - public class TestCaseShaking : TestCaseHitCircle + public class TestSceneShaking : TestSceneHitCircle { public override void Add(Drawable drawable) { diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs similarity index 98% rename from osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index 35e8f3e17e..1ba6d107be 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -27,7 +27,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestCaseSlider : OsuTestCase + public class TestSceneSlider : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -44,9 +44,8 @@ namespace osu.Game.Rulesets.Osu.Tests protected override Container Content => content; private int depthIndex; - protected readonly List Mods = new List(); - public TestCaseSlider() + public TestSceneSlider() { base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); @@ -292,7 +291,7 @@ namespace osu.Game.Rulesets.Osu.Tests Depth = depthIndex++ }; - foreach (var mod in Mods.OfType()) + foreach (var mod in Mods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); drawable.OnNewResult += onNewResult; diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs similarity index 76% rename from osu.Game.Rulesets.Osu.Tests/TestCaseSliderHidden.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs index ba5bd48c51..2a9c1d167b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs @@ -10,13 +10,13 @@ using osu.Game.Rulesets.Osu.Mods; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestCaseSliderHidden : TestCaseSlider + public class TestSceneSliderHidden : TestSceneSlider { public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList(); - public TestCaseSliderHidden() + public TestSceneSliderHidden() { - Mods.Add(new OsuModHidden()); + Mods.Value = new[] { new OsuModHidden() }; } } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs similarity index 99% rename from osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index 76bd9ef758..193cfe9c94 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -26,7 +26,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Tests { - public class TestCaseSliderInput : RateAdjustedBeatmapTestCase + public class TestSceneSliderInput : RateAdjustedBeatmapTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -353,6 +353,8 @@ namespace osu.Game.Rulesets.Osu.Tests { public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + protected override bool PauseOnFocusLost => false; + public ScoreAccessibleReplayPlayer(Score score) : base(score, false, false) { diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderPlacementBlueprint.cs similarity index 89% rename from osu.Game.Rulesets.Osu.Tests/TestCaseSliderPlacementBlueprint.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneSliderPlacementBlueprint.cs index f11d98613a..0522260150 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderPlacementBlueprint.cs @@ -11,7 +11,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { - public class TestCaseSliderPlacementBlueprint : PlacementBlueprintTestCase + public class TestSceneSliderPlacementBlueprint : PlacementBlueprintTestScene { protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSlider((Slider)hitObject); protected override PlacementBlueprint CreateBlueprint() => new SliderPlacementBlueprint(); diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs similarity index 92% rename from osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionBlueprint.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs index a7386ba48b..8cf5a2f33e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs @@ -17,7 +17,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Tests { - public class TestCaseSliderSelectionBlueprint : SelectionBlueprintTestCase + public class TestSceneSliderSelectionBlueprint : SelectionBlueprintTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Tests private readonly DrawableSlider drawableObject; - public TestCaseSliderSelectionBlueprint() + public TestSceneSliderSelectionBlueprint() { var slider = new Slider { diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs similarity index 92% rename from osu.Game.Rulesets.Osu.Tests/TestCaseSpinner.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs index e8b534bba9..3ed3f3e981 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinner.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs @@ -18,7 +18,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestCaseSpinner : OsuTestCase + public class TestSceneSpinner : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -31,9 +31,8 @@ namespace osu.Game.Rulesets.Osu.Tests protected override Container Content => content; private int depthIndex; - protected readonly List Mods = new List(); - public TestCaseSpinner() + public TestSceneSpinner() { base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); @@ -57,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Tests Depth = depthIndex++ }; - foreach (var mod in Mods.OfType()) + foreach (var mod in Mods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); Add(drawable); diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs similarity index 76% rename from osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerHidden.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs index 6136ce1639..a0ab1908d6 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs @@ -10,13 +10,13 @@ using osu.Game.Rulesets.Osu.Mods; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestCaseSpinnerHidden : TestCaseSpinner + public class TestSceneSpinnerHidden : TestSceneSpinner { public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList(); - public TestCaseSpinnerHidden() + public TestSceneSpinnerHidden() { - Mods.Add(new OsuModHidden()); + Mods.Value = new[] { new OsuModHidden() }; } } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerPlacementBlueprint.cs similarity index 89% rename from osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerPlacementBlueprint.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerPlacementBlueprint.cs index 9001ad3596..d74d072857 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerPlacementBlueprint.cs @@ -11,7 +11,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { - public class TestCaseSpinnerPlacementBlueprint : PlacementBlueprintTestCase + public class TestSceneSpinnerPlacementBlueprint : PlacementBlueprintTestScene { protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSpinner((Spinner)hitObject); diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs similarity index 92% rename from osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerSelectionBlueprint.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs index 11f5ddf8b5..c5cea76b14 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs @@ -17,7 +17,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Tests { - public class TestCaseSpinnerSelectionBlueprint : SelectionBlueprintTestCase + public class TestSceneSpinnerSelectionBlueprint : SelectionBlueprintTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Tests private readonly DrawableSpinner drawableSpinner; - public TestCaseSpinnerSelectionBlueprint() + public TestSceneSpinnerSelectionBlueprint() { var spinner = new Spinner { 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 8c31db9a7d..a99a93c3e9 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,8 +2,8 @@ - - + + diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index 59a5f90fd0..b2beda18f4 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs @@ -44,12 +44,14 @@ namespace osu.Game.Rulesets.Osu.Beatmaps if (endIndex < 0) throw new ArgumentOutOfRangeException(nameof(endIndex), $"{nameof(endIndex)} cannot be less than 0."); int extendedEndIndex = endIndex; + if (endIndex < beatmap.HitObjects.Count - 1) { // Extend the end index to include objects they are stacked on for (int i = endIndex; i >= startIndex; i--) { int stackBaseIndex = i; + for (int n = stackBaseIndex + 1; n < beatmap.HitObjects.Count; n++) { OsuHitObject stackBaseObject = beatmap.HitObjects[stackBaseIndex]; @@ -87,6 +89,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps //Reverse pass for stack calculation. int extendedStartIndex = startIndex; + for (int i = extendedEndIndex; i > startIndex; i--) { int n = i; @@ -138,6 +141,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps if (objectN is Slider && Vector2Extensions.Distance(objectN.EndPosition, objectI.Position) < stack_distance) { int offset = objectI.StackHeight - objectN.StackHeight + 1; + 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). diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 0dce5208dd..093081b6a1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -109,6 +109,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f); double approachRateFactor = 1.0f; + if (Attributes.ApproachRate > 10.33f) approachRateFactor += 0.3f * (Attributes.ApproachRate - 10.33f); else if (Attributes.ApproachRate < 8.0f) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 37276a3432..eacac7ae6a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -56,6 +56,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing { // We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps. float scalingFactor = normalized_radius / (float)BaseObject.Radius; + if (BaseObject.Radius < 30) { float smallCircleBonus = Math.Min(30 - (float)BaseObject.Radius, 5) / 50; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 46a81a9480..01f2fb8dc8 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -42,9 +42,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills speedBonus = 1 + Math.Pow((min_speed_bonus - deltaTime) / speed_balancing_factor, 2); double angleBonus = 1.0; + if (osuCurrent.Angle != null && osuCurrent.Angle.Value < angle_bonus_begin) { angleBonus = 1 + Math.Pow(Math.Sin(1.5 * (angle_bonus_begin - osuCurrent.Angle.Value)), 2) / 3.57; + if (osuCurrent.Angle.Value < pi_over_2) { angleBonus = 1.28; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs index 9d164ebe0b..2ecfea2e3e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs @@ -37,6 +37,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components case SliderPosition.Start: Position = slider.StackedPosition + slider.Path.PositionAt(0); break; + case SliderPosition.End: Position = slider.StackedPosition + slider.Path.PositionAt(1); break; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 989a53db1f..55de626d7d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -62,6 +62,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders case PlacementState.Initial: HitObject.Position = e.MousePosition; return true; + case PlacementState.Body: cursor = e.MousePosition - HitObject.Position; return true; @@ -77,6 +78,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders case PlacementState.Initial: beginCurve(); break; + case PlacementState.Body: switch (e.Button) { diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index d9cb203bdf..bcb6099cfb 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -1,7 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osuTK; @@ -10,8 +12,8 @@ namespace osu.Game.Rulesets.Osu.Edit { public class DrawableOsuEditRuleset : DrawableOsuRuleset { - public DrawableOsuEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + public DrawableOsuEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + : base(ruleset, beatmap, mods) { } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 039ec5585e..c5452ae0aa 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; @@ -23,8 +24,8 @@ namespace osu.Game.Rulesets.Osu.Edit { } - protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap) - => new DrawableOsuEditRuleset(ruleset, beatmap); + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + => new DrawableOsuEditRuleset(ruleset, beatmap, mods); protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { @@ -41,8 +42,10 @@ namespace osu.Game.Rulesets.Osu.Edit { case DrawableHitCircle circle: return new HitCircleSelectionBlueprint(circle); + case DrawableSlider slider: return new SliderSelectionBlueprint(slider); + case DrawableSpinner spinner: return new SpinnerSelectionBlueprint(spinner); } diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs index 81fedf9f4a..7a5b98864c 100644 --- a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs @@ -16,13 +16,33 @@ namespace osu.Game.Rulesets.Osu.Judgements { default: return 0; + case HitResult.Meh: return 50; + case HitResult.Good: return 100; + case HitResult.Great: return 300; } } + + protected override double HealthIncreaseFor(HitResult result) + { + switch (result) + { + case HitResult.Miss: + return -0.02; + + case HitResult.Meh: + case HitResult.Good: + case HitResult.Great: + return 0.01; + + default: + return 0; + } + } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index f3c7939a94..445f81c6d4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osuTK; using osuTK.Graphics; @@ -41,6 +42,8 @@ namespace osu.Game.Rulesets.Osu.Mods scoreProcessor.Health.ValueChanged += health => { blinds.AnimateClosedness((float)health.NewValue); }; } + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; + /// /// Element for the Blinds mod drawing 2 black boxes covering the whole screen which resize inside a restricted area with some leniency. /// diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 2c40d18f1b..7fa3dbe07e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -1,23 +1,38 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; +using osu.Framework.MathUtils; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModFlashlight : ModFlashlight + public class OsuModFlashlight : ModFlashlight, IApplicableToDrawableHitObjects { public override double ScoreMultiplier => 1.12; private const float default_flashlight_size = 180; - public override Flashlight CreateFlashlight() => new OsuFlashlight(); + private OsuFlashlight flashlight; + + public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(); + + public void ApplyToDrawableHitObjects(IEnumerable drawables) + { + foreach (var s in drawables.OfType()) + { + s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange; + } + } private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition { @@ -26,9 +41,22 @@ namespace osu.Game.Rulesets.Osu.Mods FlashlightSize = new Vector2(0, getSizeFor(0)); } + public void OnSliderTrackingChange(ValueChangedEvent e) + { + // If a slider is in a tracking state, a further dim should be applied to the (remaining) visible portion of the playfield over a brief duration. + this.TransformTo(nameof(FlashlightDim), e.NewValue ? 0.8f : 0.0f, 50); + } + protected override bool OnMouseMove(MouseMoveEvent e) { - FlashlightPosition = e.MousePosition; + const double follow_delay = 120; + + var position = FlashlightPosition; + var destination = e.MousePosition; + + FlashlightPosition = Interpolation.ValueAt( + MathHelper.Clamp(Clock.ElapsedFrameTime, 0, follow_delay), position, destination, 0, follow_delay, Easing.Out); + return base.OnMouseMove(e); } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs index 35a5992e25..a2da2bbf53 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs @@ -33,6 +33,7 @@ namespace osu.Game.Rulesets.Osu.Mods { case DrawableSpinner _: continue; + default: drawable.ApplyCustomUpdateState += ApplyCustomState; break; @@ -51,6 +52,7 @@ namespace osu.Game.Rulesets.Osu.Mods case DrawableSliderTail _: // special cases we should *not* be scaling. break; + case DrawableSlider _: case DrawableHitCircle _: { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 1a30b2c944..ddf708d0f1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -59,11 +59,13 @@ namespace osu.Game.Rulesets.Osu.Mods circle.FadeOut(fadeOutDuration); break; + case DrawableSlider slider: using (slider.BeginAbsoluteSequence(fadeOutStartTime, true)) slider.Body.FadeOut(longFadeDuration, Easing.Out); break; + case DrawableSliderTick sliderTick: // slider ticks fade out over up to one second var tickFadeOutDuration = Math.Min(sliderTick.HitObject.TimePreempt - DrawableSliderTick.ANIM_DURATION, 1000); @@ -72,6 +74,7 @@ namespace osu.Game.Rulesets.Osu.Mods sliderTick.FadeOut(tickFadeOutDuration); break; + case DrawableSpinner spinner: // hide elements we don't care about. spinner.Disc.Hide(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index 3c64fe57d4..aacf3ee08d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -6,6 +6,7 @@ using osuTK.Graphics; 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.Skinning; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 8f9d487d49..7569626230 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -67,6 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections return; OsuHitObject prevHitObject = null; + foreach (var currHitObject in hitObjects) { if (prevHitObject != null && !currHitObject.NewCombo && !(prevHitObject is Spinner) && !(currHitObject is Spinner)) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index decd0ce073..fef0bfdc2c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -124,6 +124,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } var result = HitObject.HitWindows.ResultFor(timeOffset); + if (result == HitResult.None) { Shake(Math.Abs(timeOffset) - HitObject.HitWindows.HalfWindowFor(HitResult.Miss)); @@ -158,11 +159,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // override lifetime end as FadeIn may have been changed externally, causing out expiration to be too early. LifetimeEnd = HitObject.StartTime + HitObject.HitWindows.HalfWindowFor(HitResult.Miss); break; + case ArmedState.Miss: ApproachCircle.FadeOut(50); this.FadeOut(100); Expire(); break; + case ArmedState.Hit: ApproachCircle.FadeOut(50); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index edf2d90c08..cce6dfe106 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void CheckForResult(bool userTriggered, double timeOffset) { if (repeatPoint.StartTime <= Time.Current) - ApplyResult(r => r.Type = drawableSlider.Tracking ? HitResult.Great : HitResult.Miss); + ApplyResult(r => r.Type = drawableSlider.Tracking.Value ? HitResult.Great : HitResult.Miss); } protected override void UpdatePreemptState() @@ -64,9 +64,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Idle: this.Delay(HitObject.TimePreempt).FadeOut(); break; + case ArmedState.Miss: this.FadeOut(animDuration); break; + case ArmedState.Hit: this.FadeOut(animDuration, Easing.OutQuint) .ScaleTo(Scale * 1.5f, animDuration, Easing.Out); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 57ea0abdd8..05cb42d853 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -130,13 +130,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - public bool Tracking; + public readonly Bindable Tracking = new Bindable(); protected override void Update() { base.Update(); - Tracking = Ball.Tracking; + Tracking.Value = Ball.Tracking; double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); @@ -160,9 +160,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.SkinChanged(skin, allowFallback); - Body.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderTrackOverride") ? s.CustomColours["SliderTrackOverride"] : (Color4?)null) ?? Body.AccentColour; - Body.BorderColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBorder") ? s.CustomColours["SliderBorder"] : (Color4?)null) ?? Body.BorderColour; - Ball.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? Ball.AccentColour; + Body.BorderSize = skin.GetValue(s => s.SliderBorderSize) ?? SliderBody.DEFAULT_BORDER_SIZE; + Body.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderTrackOverride") ? s.CustomColours["SliderTrackOverride"] : (Color4?)null) ?? AccentColour; + Body.BorderColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBorder") ? s.CustomColours["SliderBorder"] : (Color4?)null) ?? Color4.White; + Ball.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? AccentColour; } protected override void CheckForResult(bool userTriggered, double timeOffset) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index b5ce36f889..72b648bfd0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -67,10 +67,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Idle: this.Delay(HitObject.TimePreempt).FadeOut(); break; + case ArmedState.Miss: this.FadeOut(ANIM_DURATION); this.FadeColour(Color4.Red, ANIM_DURATION / 2); break; + case ArmedState.Hit: this.FadeOut(ANIM_DURATION, Easing.OutQuint); this.ScaleTo(Scale * 1.5f, ANIM_DURATION, Easing.Out); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index ab4935e350..1794da54b7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -222,9 +222,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Idle: Expire(true); break; + case ArmedState.Hit: sequence.ScaleTo(Scale * 1.2f, 320, Easing.Out); break; + case ArmedState.Miss: sequence.ScaleTo(Scale * 0.8f, 320, Easing.In); break; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs index 93ac8748dd..84034d3ee9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs @@ -4,6 +4,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Game.Graphics.Sprites; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index 2f5c326bda..25e1aebd18 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -14,6 +14,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public abstract class SliderBody : CompositeDrawable { + public const float DEFAULT_BORDER_SIZE = 1; + private readonly SliderPath path; protected Path Path => path; @@ -64,6 +66,23 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } } + /// + /// Used to size the path border. + /// + public float BorderSize + { + get => path.BorderSize; + set + { + if (path.BorderSize == value) + return; + + path.BorderSize = value; + + container.ForceRedraw(); + } + } + public Quad PathDrawQuad => container.ScreenSpaceDrawQuad; protected SliderBody() @@ -92,6 +111,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private class SliderPath : SmoothPath { + private const float border_max_size = 8f; + private const float border_min_size = 0f; + private const float border_portion = 0.128f; private const float gradient_portion = 1 - border_portion; @@ -130,12 +152,33 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } } + private float borderSize = DEFAULT_BORDER_SIZE; + + public float BorderSize + { + get => borderSize; + set + { + if (borderSize == value) + return; + + if (value < border_min_size || value > border_max_size) + return; + + borderSize = value; + + InvalidateTexture(); + } + } + + private float calculatedBorderPortion => BorderSize * border_portion; + protected override Color4 ColourAt(float position) { - if (position <= border_portion) + if (calculatedBorderPortion != 0f && position <= calculatedBorderPortion) return BorderColour; - position -= border_portion; + position -= calculatedBorderPortion; return new Color4(AccentColour.R, AccentColour.G, AccentColour.B, (opacity_at_edge - (opacity_at_edge - opacity_at_centre) * position / gradient_portion) * AccentColour.A); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs index c982f53c2b..77228e28af 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs @@ -4,6 +4,7 @@ using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs index f47617bcf6..9219fab830 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 1afbacc01e..a8aec005d1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -183,6 +183,7 @@ namespace osu.Game.Rulesets.Osu.Objects Samples = sampleList }); break; + case SliderEventType.Head: AddNested(HeadCircle = new SliderCircle { @@ -194,6 +195,7 @@ namespace osu.Game.Rulesets.Osu.Objects ComboIndex = ComboIndex, }); break; + case SliderEventType.LegacyLastTick: // we need to use the LegacyLastTick here for compatibility reasons (difficulty). // it is *okay* to use this because the TailCircle is not used for any meaningful purpose in gameplay. @@ -206,6 +208,7 @@ namespace osu.Game.Rulesets.Osu.Objects ComboIndex = ComboIndex, }); break; + case SliderEventType.Repeat: AddNested(new RepeatPoint { diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 44bce5bed8..83d29c156d 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu { public class OsuRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableOsuRuleset(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableOsuRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap); @@ -104,6 +104,7 @@ namespace osu.Game.Rulesets.Osu new MultiMod(new OsuModHalfTime(), new OsuModDaycore()), new OsuModSpunOut(), }; + case ModType.DifficultyIncrease: return new Mod[] { @@ -113,11 +114,13 @@ namespace osu.Game.Rulesets.Osu new OsuModHidden(), new MultiMod(new OsuModFlashlight(), new OsuModBlinds()), }; + case ModType.Conversion: return new Mod[] { new OsuModTarget(), }; + case ModType.Automation: return new Mod[] { @@ -125,6 +128,7 @@ namespace osu.Game.Rulesets.Osu new OsuModRelax(), new OsuModAutopilot(), }; + case ModType.Fun: return new Mod[] { @@ -133,6 +137,7 @@ namespace osu.Game.Rulesets.Osu new OsuModGrow(), new MultiMod(new ModWindUp(), new ModWindDown()), }; + default: return new Mod[] { }; } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 41bb740e46..690263c6a0 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -199,6 +199,7 @@ namespace osu.Game.Rulesets.Osu.Replays // Wait until Auto could "see and react" to the next note. double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - reactionTime); + if (waitTime > lastFrame.Time) { lastFrame = new OsuReplayFrame(waitTime, lastFrame.Position) { Actions = lastFrame.Actions }; @@ -314,6 +315,7 @@ namespace osu.Game.Rulesets.Osu.Replays endFrame.Position = endPosition; break; + case Slider slider: for (double j = FrameDelay; j < slider.Duration; j += FrameDelay) { diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 2c8bf11016..cf0565c6da 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -37,8 +37,6 @@ namespace osu.Game.Rulesets.Osu.Scoring comboResultCounts.Clear(); } - private const double harshness = 0.01; - protected override void ApplyResult(JudgementResult result) { base.ApplyResult(result); @@ -47,28 +45,29 @@ namespace osu.Game.Rulesets.Osu.Scoring if (result.Type != HitResult.None) comboResultCounts[osuResult.ComboType] = comboResultCounts.GetOrDefault(osuResult.ComboType) + 1; + } + protected override double HealthAdjustmentFactorFor(JudgementResult result) + { switch (result.Type) { case HitResult.Great: - Health.Value += (10.2 - hpDrainRate) * harshness; - break; + return 10.2 - hpDrainRate; case HitResult.Good: - Health.Value += (8 - hpDrainRate) * harshness; - break; + return 8 - hpDrainRate; case HitResult.Meh: - Health.Value += (4 - hpDrainRate) * harshness; - break; + return 4 - hpDrainRate; - /*case HitResult.SliderTick: - Health.Value += Math.Max(7 - hpDrainRate, 0) * 0.01; - break;*/ + // case HitResult.SliderTick: + // return Math.Max(7 - hpDrainRate, 0) * 0.01; case HitResult.Miss: - Health.Value -= hpDrainRate * (harshness * 2); - break; + return hpDrainRate; + + default: + return 0; } } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 03dbf7ac63..1b8fa0de01 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -43,22 +43,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private readonly InputResampler resampler = new InputResampler(); - protected override DrawNode CreateDrawNode() => new TrailDrawNode(); - - protected override void ApplyDrawNode(DrawNode node) - { - base.ApplyDrawNode(node); - - TrailDrawNode tNode = (TrailDrawNode)node; - tNode.Shader = shader; - tNode.Texture = texture; - tNode.Size = size; - tNode.Time = time; - - for (int i = 0; i < parts.Length; ++i) - if (parts[i].InvalidationID > tNode.Parts[i].InvalidationID) - tNode.Parts[i] = parts[i]; - } + protected override DrawNode CreateDrawNode() => new TrailDrawNode(this); public CursorTrail() { @@ -167,33 +152,53 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private class TrailDrawNode : DrawNode { - public IShader Shader; - public Texture Texture; + protected new CursorTrail Source => (CursorTrail)base.Source; - public float Time; + private IShader shader; + private Texture texture; - public readonly TrailPart[] Parts = new TrailPart[max_sprites]; - public Vector2 Size; + private float time; + + private readonly TrailPart[] parts = new TrailPart[max_sprites]; + private Vector2 size; private readonly VertexBuffer vertexBuffer = new QuadVertexBuffer(max_sprites, BufferUsageHint.DynamicDraw); - public TrailDrawNode() + public TrailDrawNode(CursorTrail source) + : base(source) { for (int i = 0; i < max_sprites; i++) { - Parts[i].InvalidationID = 0; - Parts[i].WasUpdated = false; + parts[i].InvalidationID = 0; + parts[i].WasUpdated = false; + } + } + + public override void ApplyState() + { + base.ApplyState(); + + shader = Source.shader; + texture = Source.texture; + size = Source.size; + time = Source.time; + + for (int i = 0; i < Source.parts.Length; ++i) + { + if (Source.parts[i].InvalidationID > parts[i].InvalidationID) + parts[i] = Source.parts[i]; } } public override void Draw(Action vertexAction) { - Shader.GetUniform("g_FadeClock").UpdateValue(ref Time); + shader.GetUniform("g_FadeClock").UpdateValue(ref time); int updateStart = -1, updateEnd = 0; - for (int i = 0; i < Parts.Length; ++i) + + for (int i = 0; i < parts.Length; ++i) { - if (Parts[i].WasUpdated) + if (parts[i].WasUpdated) { if (updateStart == -1) updateStart = i; @@ -202,22 +207,22 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor int start = i * 4; int end = start; - Vector2 pos = Parts[i].Position; - float time = Parts[i].Time; + Vector2 pos = parts[i].Position; + float localTime = parts[i].Time; - Texture.DrawQuad( - new Quad(pos.X - Size.X / 2, pos.Y - Size.Y / 2, Size.X, Size.Y), + texture.DrawQuad( + new Quad(pos.X - size.X / 2, pos.Y - size.Y / 2, size.X, size.Y), DrawColourInfo.Colour, null, v => vertexBuffer.Vertices[end++] = new TexturedTrailVertex { Position = v.Position, TexturePosition = v.TexturePosition, - Time = time + 1, + Time = localTime + 1, Colour = v.Colour, }); - Parts[i].WasUpdated = false; + parts[i].WasUpdated = false; } else if (updateStart != -1) { @@ -232,12 +237,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor base.Draw(vertexAction); - Shader.Bind(); + shader.Bind(); - Texture.TextureGL.Bind(); + texture.TextureGL.Bind(); vertexBuffer.Draw(); - Shader.Unbind(); + shader.Unbind(); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index ecdafb0fa2..27546fa424 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -6,6 +6,7 @@ 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.Game.Beatmaps; using osu.Game.Configuration; diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index f6a3be40b0..d185d7d4c9 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -1,11 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Input.Handlers; using osu.Game.Replays; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Objects; @@ -22,8 +25,8 @@ namespace osu.Game.Rulesets.Osu.UI { protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config; - public DrawableOsuRuleset(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + public DrawableOsuRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + : base(ruleset, beatmap, mods) { } @@ -43,8 +46,10 @@ namespace osu.Game.Rulesets.Osu.UI { case HitCircle circle: return new DrawableHitCircle(circle); + case Slider slider: return new DrawableSlider(slider); + case Spinner spinner: return new DrawableSpinner(spinner); } @@ -58,8 +63,10 @@ namespace osu.Game.Rulesets.Osu.UI { get { - var first = (OsuHitObject)Objects.First(); - return first.StartTime - first.TimePreempt; + if (Objects.FirstOrDefault() is OsuHitObject first) + return first.StartTime - Math.Max(2000, first.TimePreempt); + + return 0; } } } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestCaseInputDrum.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs similarity index 93% rename from osu.Game.Rulesets.Taiko.Tests/TestCaseInputDrum.cs rename to osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs index 136d9067d5..02300a5dde 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestCaseInputDrum.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs @@ -16,7 +16,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests { [TestFixture] - public class TestCaseInputDrum : OsuTestCase + public class TestSceneInputDrum : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Tests typeof(SampleControlPoint) }; - public TestCaseInputDrum() + public TestSceneInputDrum() { Add(new TaikoInputManager(new RulesetInfo { ID = 1 }) { diff --git a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs similarity index 98% rename from osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs rename to osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs index 369cdd49d2..3634ec7d4a 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs @@ -11,6 +11,7 @@ using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Judgements; @@ -25,7 +26,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Taiko.Tests { [TestFixture] - public class TestCaseTaikoPlayfield : OsuTestCase + public class TestSceneTaikoPlayfield : OsuTestScene { private const double default_duration = 1000; private const float scroll_time = 1000; @@ -86,7 +87,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, Height = 768, - Children = new[] { drawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap) } + Children = new[] { drawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap, Array.Empty()) } }); } @@ -100,15 +101,19 @@ namespace osu.Game.Rulesets.Taiko.Tests case 1: addCentreHit(false); break; + case 2: addCentreHit(true); break; + case 3: addDrumRoll(false); break; + case 4: addDrumRoll(true); break; + case 5: addSwell(); delay = scroll_time - 100; @@ -121,6 +126,7 @@ namespace osu.Game.Rulesets.Taiko.Tests default: playfieldContainer.Delay(delay).ResizeTo(new Vector2(1, rng.Next(25, 400)), 500); break; + case 6: playfieldContainer.Delay(delay).ResizeTo(new Vector2(1, TaikoPlayfield.DEFAULT_HEIGHT), 500); break; 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 72ce6c947b..216cc0222f 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,8 +2,8 @@ - - + + diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 3e0e2624bf..f8672037cd 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -120,6 +120,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps List> allSamples = curveData != null ? curveData.NodeSamples : new List>(new[] { samples }); int i = 0; + for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing) { List currentSamples = allSamples[i]; diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs index e5ebd5c647..604daa929f 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs @@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Taiko.Judgements { case HitResult.Miss: return 0; + default: return base.HealthIncreaseFor(result); } diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs index 32d4b77ca4..a617028f1c 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs @@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Judgements { case HitResult.Great: return 200; + default: return 0; } @@ -26,6 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Judgements { case HitResult.Great: return 0.15; + default: return 0; } diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs index 427d38aaa7..eb5f443365 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs @@ -16,8 +16,10 @@ namespace osu.Game.Rulesets.Taiko.Judgements { case HitResult.Good: return 100; + case HitResult.Great: return 300; + default: return 0; } @@ -29,10 +31,13 @@ namespace osu.Game.Rulesets.Taiko.Judgements { case HitResult.Miss: return -1.0; + case HitResult.Good: return 1.1; + case HitResult.Great: return 3.0; + default: return 0; } diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs index f0f621d12b..29be5e0eac 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs @@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Judgements { case HitResult.Miss: return -0.65; + default: return 0; } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index d3837946c9..4c8d5d5204 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -98,6 +98,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables circlePiece?.FlashBox.FinishTransforms(); var offset = !AllJudged ? 0 : Time.Current - HitObject.StartTime; + using (BeginDelayedSequence(HitObject.StartTime - Time.Current + offset, true)) { switch (State.Value) @@ -108,15 +109,18 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables UnproxyContent(); this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire(); break; + case ArmedState.Miss: this.FadeOut(100) .Expire(); break; + case ArmedState.Hit: // If we're far enough away from the left stage, we should bring outselves in front of it ProxyContent(); var flash = circlePiece?.FlashBox; + if (flash != null) { flash.FadeTo(0.9f); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 9211eccc40..5ec9dc61e2 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -192,6 +192,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables using (BeginAbsoluteSequence(HitObject.StartTime - preempt, true)) targetRing.ScaleTo(target_ring_scale, preempt * 4, Easing.OutQuint); break; + case ArmedState.Miss: case ArmedState.Hit: this.FadeOut(out_transition_time, Easing.Out); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 8dfe89eea7..119940536e 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -111,6 +111,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables MainPiece.KiaiMode = HitObject.Kiai; var strongObject = HitObject.NestedHitObjects.OfType().FirstOrDefault(); + if (strongObject != null) { var strongHit = CreateStrongHit(strongObject); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs index 53dbe5d08e..b7db819717 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs @@ -9,6 +9,7 @@ using osu.Game.Graphics.Backgrounds; using osuTK.Graphics; using osu.Game.Beatmaps.ControlPoints; using osu.Framework.Audio.Track; +using osu.Framework.Graphics.Effects; namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces { diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 6f3bdca6fb..1d25735fe3 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -70,6 +70,7 @@ namespace osu.Game.Rulesets.Taiko.Objects return; bool first = true; + for (double t = StartTime; t < EndTime + tickSpacing / 2; t += tickSpacing) { AddNested(new DrumRollTick diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs index ce841fff80..f232919cbf 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs @@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Objects case HitResult.Good: case HitResult.Miss: return true; + default: return false; } diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index 01ba53e07b..422ba748e3 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -52,6 +52,7 @@ namespace osu.Game.Rulesets.Taiko.Replays int count = 0; int req = swell.RequiredHits; double hitRate = Math.Min(swell_hit_speed, swell.Duration / req); + for (double j = h.StartTime; j < endTime; j += hitRate) { TaikoAction action; @@ -62,12 +63,15 @@ namespace osu.Game.Rulesets.Taiko.Replays case 0: action = TaikoAction.LeftCentre; break; + case 1: action = TaikoAction.LeftRim; break; + case 2: action = TaikoAction.RightCentre; break; + case 3: action = TaikoAction.RightRim; break; diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index 442cca49f8..68ddf2db19 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -46,19 +46,8 @@ namespace osu.Game.Rulesets.Taiko.Scoring hpMissMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120); } - protected override void ApplyResult(JudgementResult result) - { - base.ApplyResult(result); - - double hpIncrease = result.Judgement.HealthIncreaseFor(result); - - if (result.Type == HitResult.Miss) - hpIncrease *= hpMissMultiplier; - else - hpIncrease *= hpMultiplier; - - Health.Value += hpIncrease; - } + protected override double HealthAdjustmentFactorFor(JudgementResult result) + => result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier; protected override void Reset(bool storeResults) { diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 448b1b42bb..a67004e9c7 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Taiko { public class TaikoRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableTaikoRuleset(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableTaikoRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap); public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] @@ -86,6 +86,7 @@ namespace osu.Game.Rulesets.Taiko new TaikoModNoFail(), new MultiMod(new TaikoModHalfTime(), new TaikoModDaycore()), }; + case ModType.DifficultyIncrease: return new Mod[] { @@ -95,17 +96,20 @@ namespace osu.Game.Rulesets.Taiko new TaikoModHidden(), new TaikoModFlashlight(), }; + case ModType.Automation: return new Mod[] { new MultiMod(new TaikoModAutoplay(), new ModCinema()), new TaikoModRelax(), }; + case ModType.Fun: return new Mod[] { new MultiMod(new ModWindUp(), new ModWindDown()) }; + default: return new Mod[] { }; } diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs index 943adaed4b..f91bbb14e8 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs @@ -33,6 +33,7 @@ namespace osu.Game.Rulesets.Taiko.UI case HitResult.Good: JudgementBody.Colour = colours.GreenLight; break; + case HitResult.Great: JudgementBody.Colour = colours.BlueLight; break; diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index f4b9c46dfc..ec3a56e9c7 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.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 osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Drawables; @@ -16,6 +17,7 @@ using osu.Framework.Input; using osu.Game.Configuration; using osu.Game.Input.Handlers; using osu.Game.Replays; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Taiko.UI @@ -26,8 +28,8 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool UserScrollSpeedAdjustment => false; - public DrawableTaikoRuleset(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + public DrawableTaikoRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + : base(ruleset, beatmap, mods) { Direction.Value = ScrollingDirection.Left; TimeRange.Value = 7000; @@ -52,9 +54,11 @@ namespace osu.Game.Rulesets.Taiko.UI int currentIndex = 0; int currentBeat = 0; double time = timingPoints[currentIndex].Time; + while (time <= lastHitTime) { int nextIndex = currentIndex + 1; + if (nextIndex < timingPoints.Count && time > timingPoints[nextIndex].Time) { currentIndex = nextIndex; @@ -93,10 +97,13 @@ namespace osu.Game.Rulesets.Taiko.UI { case CentreHit centreHit: return new DrawableCentreHit(centreHit); + case RimHit rimHit: return new DrawableRimHit(rimHit); + case DrumRoll drumRoll: return new DrawableDrumRoll(drumRoll); + case Swell swell: return new DrawableSwell(swell); } diff --git a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs index bed2c554ec..e80b463481 100644 --- a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs @@ -5,6 +5,7 @@ using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index dbff5270d2..7427a3235d 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -6,6 +6,7 @@ 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; @@ -211,6 +212,7 @@ namespace osu.Game.Rulesets.Taiko.UI case DrawableBarLine barline: barlineContainer.Add(barline.CreateProxy()); break; + case DrawableTaikoHitObject taikoObject: topLevelHitContainer.Add(taikoObject.CreateProxiedContent()); break; @@ -231,6 +233,7 @@ namespace osu.Game.Rulesets.Taiko.UI if (result.IsHit) hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == ((DrawableStrongNestedHit)judgedObject).MainObject)?.VisualiseSecondHit(); break; + default: judgementContainer.Add(new DrawableTaikoJudgement(result, judgedObject) { diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 02dff6993d..5fd5fe342d 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.IO; using NUnit.Framework; using osuTK; @@ -13,6 +14,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Catch.Beatmaps; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Osu; @@ -39,7 +41,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(6, working.BeatmapInfo.BeatmapVersion); Assert.AreEqual(6, working.Beatmap.BeatmapInfo.BeatmapVersion); - Assert.AreEqual(6, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapVersion); + Assert.AreEqual(6, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty()).BeatmapInfo.BeatmapVersion); } } @@ -47,6 +49,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapGeneral() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -70,6 +73,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapEditor() { var decoder = new LegacyBeatmapDecoder(); + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -95,6 +99,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapMetadata() { var decoder = new LegacyBeatmapDecoder(); + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -119,6 +124,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapDifficulty() { var decoder = new LegacyBeatmapDecoder(); + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -137,6 +143,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapEvents() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -155,6 +162,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapTimingPoints() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -162,27 +170,98 @@ namespace osu.Game.Tests.Beatmaps.Formats var controlPoints = beatmap.ControlPointInfo; Assert.AreEqual(4, controlPoints.TimingPoints.Count); - var timingPoint = controlPoints.TimingPoints[0]; + Assert.AreEqual(42, controlPoints.DifficultyPoints.Count); + Assert.AreEqual(42, controlPoints.SamplePoints.Count); + Assert.AreEqual(42, controlPoints.EffectPoints.Count); + + var timingPoint = controlPoints.TimingPointAt(0); + Assert.AreEqual(956, timingPoint.Time); + Assert.AreEqual(329.67032967033, timingPoint.BeatLength); + Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature); + + timingPoint = controlPoints.TimingPointAt(48428); Assert.AreEqual(956, timingPoint.Time); Assert.AreEqual(329.67032967033d, timingPoint.BeatLength); Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature); - Assert.AreEqual(5, controlPoints.DifficultyPoints.Count); - var difficultyPoint = controlPoints.DifficultyPoints[0]; - Assert.AreEqual(116999, difficultyPoint.Time); - Assert.AreEqual(0.75000000000000189d, difficultyPoint.SpeedMultiplier); + timingPoint = controlPoints.TimingPointAt(119637); + Assert.AreEqual(119637, timingPoint.Time); + Assert.AreEqual(659.340659340659, timingPoint.BeatLength); + Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature); - Assert.AreEqual(34, controlPoints.SamplePoints.Count); - var soundPoint = controlPoints.SamplePoints[0]; + var difficultyPoint = controlPoints.DifficultyPointAt(0); + Assert.AreEqual(0, difficultyPoint.Time); + Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); + + difficultyPoint = controlPoints.DifficultyPointAt(48428); + Assert.AreEqual(48428, difficultyPoint.Time); + Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); + + difficultyPoint = controlPoints.DifficultyPointAt(116999); + Assert.AreEqual(116999, difficultyPoint.Time); + Assert.AreEqual(0.75, difficultyPoint.SpeedMultiplier, 0.1); + + var soundPoint = controlPoints.SamplePointAt(0); Assert.AreEqual(956, soundPoint.Time); Assert.AreEqual("soft", soundPoint.SampleBank); Assert.AreEqual(60, soundPoint.SampleVolume); - Assert.AreEqual(8, controlPoints.EffectPoints.Count); - var effectPoint = controlPoints.EffectPoints[0]; + soundPoint = controlPoints.SamplePointAt(53373); + Assert.AreEqual(53373, soundPoint.Time); + Assert.AreEqual("soft", soundPoint.SampleBank); + Assert.AreEqual(60, soundPoint.SampleVolume); + + soundPoint = controlPoints.SamplePointAt(119637); + Assert.AreEqual(119637, soundPoint.Time); + Assert.AreEqual("soft", soundPoint.SampleBank); + Assert.AreEqual(80, soundPoint.SampleVolume); + + var effectPoint = controlPoints.EffectPointAt(0); + Assert.AreEqual(0, effectPoint.Time); + Assert.IsFalse(effectPoint.KiaiMode); + Assert.IsFalse(effectPoint.OmitFirstBarLine); + + effectPoint = controlPoints.EffectPointAt(53703); Assert.AreEqual(53703, effectPoint.Time); Assert.IsTrue(effectPoint.KiaiMode); Assert.IsFalse(effectPoint.OmitFirstBarLine); + + effectPoint = controlPoints.EffectPointAt(119637); + Assert.AreEqual(119637, effectPoint.Time); + Assert.IsFalse(effectPoint.KiaiMode); + Assert.IsFalse(effectPoint.OmitFirstBarLine); + } + } + + [Test] + public void TestDecodeOverlappingTimingPoints() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("overlapping-control-points.osu")) + using (var stream = new StreamReader(resStream)) + { + var controlPoints = decoder.Decode(stream).ControlPointInfo; + + 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)); + Assert.That(controlPoints.DifficultyPointAt(3500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1)); + + Assert.That(controlPoints.EffectPointAt(500).KiaiMode, Is.True); + Assert.That(controlPoints.EffectPointAt(1500).KiaiMode, Is.True); + Assert.That(controlPoints.EffectPointAt(2500).KiaiMode, Is.False); + Assert.That(controlPoints.EffectPointAt(3500).KiaiMode, Is.True); + + Assert.That(controlPoints.SamplePointAt(500).SampleBank, Is.EqualTo("drum")); + Assert.That(controlPoints.SamplePointAt(1500).SampleBank, Is.EqualTo("drum")); + Assert.That(controlPoints.SamplePointAt(2500).SampleBank, Is.EqualTo("normal")); + Assert.That(controlPoints.SamplePointAt(3500).SampleBank, Is.EqualTo("drum")); + + Assert.That(controlPoints.TimingPointAt(500).BeatLength, Is.EqualTo(500).Within(0.1)); + Assert.That(controlPoints.TimingPointAt(1500).BeatLength, Is.EqualTo(500).Within(0.1)); + Assert.That(controlPoints.TimingPointAt(2500).BeatLength, Is.EqualTo(250).Within(0.1)); + Assert.That(controlPoints.TimingPointAt(3500).BeatLength, Is.EqualTo(500).Within(0.1)); } } @@ -190,6 +269,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapColours() { var decoder = new LegacySkinDecoder(); + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -215,6 +295,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapComboOffsetsOsu() { var decoder = new LegacyBeatmapDecoder(); + using (var resStream = TestResources.OpenResource("hitobject-combo-offset.osu")) using (var stream = new StreamReader(resStream)) { @@ -237,6 +318,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapComboOffsetsCatch() { var decoder = new LegacyBeatmapDecoder(); + using (var resStream = TestResources.OpenResource("hitobject-combo-offset.osu")) using (var stream = new StreamReader(resStream)) { @@ -259,6 +341,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapHitObjects() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -286,6 +369,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeControlPointCustomSampleBank() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("controlpoint-custom-samplebank.osu")) using (var stream = new StreamReader(resStream)) { @@ -307,6 +391,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeHitObjectCustomSampleBank() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("hitobject-custom-samplebank.osu")) using (var stream = new StreamReader(resStream)) { @@ -324,6 +409,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeHitObjectFileSamples() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("hitobject-file-samples.osu")) using (var stream = new StreamReader(resStream)) { @@ -343,6 +429,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeSliderSamples() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("slider-samples.osu")) using (var stream = new StreamReader(resStream)) { @@ -386,6 +473,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeHitObjectNullAdditionBank() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("hitobject-no-addition-bank.osu")) using (var stream = new StreamReader(resStream)) { diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyDecoderTest.cs new file mode 100644 index 0000000000..b4d219456c --- /dev/null +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyDecoderTest.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 System.Collections.Generic; +using System.IO; +using NUnit.Framework; +using osu.Game.Beatmaps.Formats; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Beatmaps.Formats +{ + [TestFixture] + public class LegacyDecoderTest + { + [Test] + public void TestDecodeComments() + { + var decoder = new LineLoggingDecoder(14); + + using (var resStream = TestResources.OpenResource("comments.osu")) + using (var stream = new StreamReader(resStream)) + { + decoder.Decode(stream); + + Assert.That(decoder.ParsedLines, Has.None.EqualTo("// Combo1: 0, 0, 0")); + Assert.That(decoder.ParsedLines, Has.None.EqualTo("//Combo2: 0, 0, 0")); + Assert.That(decoder.ParsedLines, Has.None.EqualTo(" // Combo3: 0, 0, 0")); + Assert.That(decoder.ParsedLines, Has.One.EqualTo("Combo1: 100, 100, 100 // Comment at end of line")); + } + } + + private class LineLoggingDecoder : LegacyDecoder + { + public readonly List ParsedLines = new List(); + + public LineLoggingDecoder(int version) + : base(version) + { + } + + protected override bool ShouldSkipLine(string line) + { + var result = base.ShouldSkipLine(line); + + if (!result) + ParsedLines.Add(line); + + return result; + } + } + + private class TestModel + { + } + } +} diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index 2288d04493..971518909d 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -19,6 +19,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeStoryboardEvents() { var decoder = new LegacyStoryboardDecoder(); + using (var resStream = TestResources.OpenResource("Himeringo - Yotsuya-san ni Yoroshiku (RLC) [Winber1's Extreme].osu")) using (var stream = new StreamReader(resStream)) { @@ -91,6 +92,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeVariableWithSuffix() { var decoder = new LegacyStoryboardDecoder(); + using (var resStream = TestResources.OpenResource("variable-with-suffix.osb")) using (var stream = new StreamReader(resStream)) { diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index a867ddebae..39b7735a55 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -151,6 +151,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var sr = new StreamReader(stream)) { var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr); + using (var ms = new MemoryStream()) using (var sw = new StreamWriter(ms)) using (var sr2 = new StreamReader(ms)) diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index 17197aff27..37e0565df0 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -75,6 +75,7 @@ namespace osu.Game.Tests.Beatmaps.IO using (var osz = TestResources.GetTestBeatmapStream()) { var reader = new ZipArchiveReader(osz); + using (var stream = new StreamReader( reader.GetStream("Soleily - Renatus (Deif) [Platter].osu"))) { diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index 720d93cb10..0d6ed67767 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Chat } [Test] - public void TestCaseInsensitiveLinks() + public void TestInsensitiveLinks() { Message result = MessageFormatter.FormatMessage(new Message { Content = "look: http://puu.sh/7Ggh8xcC6/asf0asd9876.NEF" }); diff --git a/osu.Game.Tests/Resources/comments.osu b/osu.Game.Tests/Resources/comments.osu new file mode 100644 index 0000000000..78b48dd804 --- /dev/null +++ b/osu.Game.Tests/Resources/comments.osu @@ -0,0 +1,9 @@ +osu file format v14 + +[Colours] + +// Combo1: 0, 0, 0 +//Combo2: 0, 0, 0 + // Combo3: 0, 0, 0 + +Combo1: 100, 100, 100 // Comment at end of line \ No newline at end of file diff --git a/osu.Game.Tests/Resources/overlapping-control-points.osu b/osu.Game.Tests/Resources/overlapping-control-points.osu new file mode 100644 index 0000000000..31d38a3d01 --- /dev/null +++ b/osu.Game.Tests/Resources/overlapping-control-points.osu @@ -0,0 +1,19 @@ +osu file format v14 + +[TimingPoints] + +// Timing then inherited +0,500,4,2,0,100,1,0 +0,-66.6666666666667,4,3,0,100,0,1 + +// Inherited then timing (equivalent to previous) +1000,-66.6666666666667,4,3,0,100,0,1 +1000,500,4,2,0,100,1,0 + +// Inherited then timing (different to previous) +2000,-133.333333333333,4,1,0,100,0,0 +2000,250,4,2,0,100,1,0 + +// Timing then inherited (different to previous) +3000,500,4,2,0,100,1,0 +3000,-66.6666666666667,4,3,0,100,0,1 diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs index 2a97519e21..24ef9e4535 100644 --- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs +++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs @@ -18,6 +18,7 @@ namespace osu.Game.Tests.Skins public void TestDecodeSkinColours(bool hasColours) { var decoder = new LegacySkinDecoder(); + using (var resStream = TestResources.OpenResource(hasColours ? "skin.ini" : "skin-empty.ini")) using (var stream = new StreamReader(resStream)) { diff --git a/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs similarity index 98% rename from osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs rename to osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs index 891b89e72d..c9bdcf928f 100644 --- a/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs @@ -9,7 +9,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Framework.Platform; @@ -19,6 +18,7 @@ using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Scoring; @@ -35,7 +35,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Background { [TestFixture] - public class TestCaseBackgroundScreenBeatmap : ManualInputManagerTestCase + public class TestSceneBackgroundScreenBeatmap : ManualInputManagerTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -240,7 +240,7 @@ namespace osu.Game.Tests.Visual.Background { player.StoryboardEnabled.Value = false; player.ReplacesBackground.Value = false; - player.CurrentStoryboardContainer.Add(new SpriteText + player.CurrentStoryboardContainer.Add(new OsuSpriteText { Size = new Vector2(250, 50), Alpha = 1, @@ -267,7 +267,7 @@ namespace osu.Game.Tests.Visual.Background AddUntilStep("Song select has selection", () => songSelect.Carousel.SelectedBeatmap != null); AddStep("Set default user settings", () => { - Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { new OsuModNoFail() }); + Mods.Value = Mods.Value.Concat(new[] { new OsuModNoFail() }).ToArray(); songSelect.DimLevel.Value = 0.7f; songSelect.BlurLevel.Value = 0.4f; }); @@ -328,7 +328,7 @@ namespace osu.Game.Tests.Visual.Background public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR); } - private class TestPlayer : Player + private class TestPlayer : Visual.TestPlayer { protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value); diff --git a/osu.Game.Tests/Visual/Components/TestCaseIdleTracker.cs b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs similarity index 97% rename from osu.Game.Tests/Visual/Components/TestCaseIdleTracker.cs rename to osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs index bf59c116bb..e97983dd8b 100644 --- a/osu.Game.Tests/Visual/Components/TestCaseIdleTracker.cs +++ b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs @@ -12,14 +12,14 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Components { [TestFixture] - public class TestCaseIdleTracker : ManualInputManagerTestCase + public class TestSceneIdleTracker : ManualInputManagerTestScene { private readonly IdleTrackingBox box1; private readonly IdleTrackingBox box2; private readonly IdleTrackingBox box3; private readonly IdleTrackingBox box4; - public TestCaseIdleTracker() + public TestSceneIdleTracker() { Children = new Drawable[] { diff --git a/osu.Game.Tests/Visual/Components/TestCasePollingComponent.cs b/osu.Game.Tests/Visual/Components/TestScenePollingComponent.cs similarity index 98% rename from osu.Game.Tests/Visual/Components/TestCasePollingComponent.cs rename to osu.Game.Tests/Visual/Components/TestScenePollingComponent.cs index 6582145f6e..fb10015ef4 100644 --- a/osu.Game.Tests/Visual/Components/TestCasePollingComponent.cs +++ b/osu.Game.Tests/Visual/Components/TestScenePollingComponent.cs @@ -15,7 +15,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Components { - public class TestCasePollingComponent : OsuTestCase + public class TestScenePollingComponent : OsuTestScene { private Container pollBox; private TestPoller poller; diff --git a/osu.Game.Tests/Visual/Components/TestCasePreviewTrackManager.cs b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs similarity index 98% rename from osu.Game.Tests/Visual/Components/TestCasePreviewTrackManager.cs rename to osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs index 4b6ae696fe..94412455a0 100644 --- a/osu.Game.Tests/Visual/Components/TestCasePreviewTrackManager.cs +++ b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs @@ -10,7 +10,7 @@ using osu.Game.Beatmaps; namespace osu.Game.Tests.Visual.Components { - public class TestCasePreviewTrackManager : OsuTestCase, IPreviewTrackOwner + public class TestScenePreviewTrackManager : OsuTestScene, IPreviewTrackOwner { private readonly PreviewTrackManager trackManager = new TestPreviewTrackManager(); diff --git a/osu.Game.Tests/Visual/Editor/TestCaseWaveform.cs b/osu.Game.Tests/Visual/Editor/TestCaseWaveform.cs deleted file mode 100644 index c35e8741c1..0000000000 --- a/osu.Game.Tests/Visual/Editor/TestCaseWaveform.cs +++ /dev/null @@ -1,74 +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 NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Audio; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Sprites; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Tests.Visual.Editor -{ - [TestFixture] - public class TestCaseWaveform : OsuTestCase - { - [BackgroundDependencyLoader] - private void load() - { - Beatmap.Value = new WaveformTestBeatmap(); - - FillFlowContainer flow; - Child = flow = new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), - }; - - for (int i = 1; i <= 16; i *= 2) - { - var newDisplay = new WaveformGraph - { - RelativeSizeAxes = Axes.Both, - Resolution = 1f / i, - Waveform = Beatmap.Value.Waveform, - }; - - flow.Add(new Container - { - RelativeSizeAxes = Axes.X, - Height = 100, - Children = new Drawable[] - { - newDisplay, - new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.75f - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = $"Resolution: {1f / i:0.00}" - } - } - } - } - }); - } - } - } -} diff --git a/osu.Game.Tests/Visual/Editor/TestCaseBeatDivisorControl.cs b/osu.Game.Tests/Visual/Editor/TestSceneBeatDivisorControl.cs similarity index 93% rename from osu.Game.Tests/Visual/Editor/TestCaseBeatDivisorControl.cs rename to osu.Game.Tests/Visual/Editor/TestSceneBeatDivisorControl.cs index e822e01110..7531a7be2c 100644 --- a/osu.Game.Tests/Visual/Editor/TestCaseBeatDivisorControl.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneBeatDivisorControl.cs @@ -11,7 +11,7 @@ using osuTK; namespace osu.Game.Tests.Visual.Editor { - public class TestCaseBeatDivisorControl : OsuTestCase + public class TestSceneBeatDivisorControl : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(BindableBeatDivisor) }; diff --git a/osu.Game.Tests/Visual/Editor/TestCaseEditorCompose.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorCompose.cs similarity index 92% rename from osu.Game.Tests/Visual/Editor/TestCaseEditorCompose.cs rename to osu.Game.Tests/Visual/Editor/TestSceneEditorCompose.cs index aa7c7f5cb3..b537cb0beb 100644 --- a/osu.Game.Tests/Visual/Editor/TestCaseEditorCompose.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorCompose.cs @@ -12,7 +12,7 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual.Editor { [TestFixture] - public class TestCaseEditorCompose : EditorClockTestCase + public class TestSceneEditorCompose : EditorClockTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(ComposeScreen) }; diff --git a/osu.Game.Tests/Visual/Editor/TestCaseEditorComposeRadioButtons.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeRadioButtons.cs similarity index 92% rename from osu.Game.Tests/Visual/Editor/TestCaseEditorComposeRadioButtons.cs rename to osu.Game.Tests/Visual/Editor/TestSceneEditorComposeRadioButtons.cs index 499db1b69f..1709067d5d 100644 --- a/osu.Game.Tests/Visual/Editor/TestCaseEditorComposeRadioButtons.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeRadioButtons.cs @@ -10,11 +10,11 @@ using osu.Game.Screens.Edit.Components.RadioButtons; namespace osu.Game.Tests.Visual.Editor { [TestFixture] - public class TestCaseEditorComposeRadioButtons : OsuTestCase + public class TestSceneEditorComposeRadioButtons : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableRadioButton) }; - public TestCaseEditorComposeRadioButtons() + public TestSceneEditorComposeRadioButtons() { RadioButtonCollection collection; Add(collection = new RadioButtonCollection diff --git a/osu.Game.Tests/Visual/Editor/TestCaseEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs similarity index 98% rename from osu.Game.Tests/Visual/Editor/TestCaseEditorComposeTimeline.cs rename to osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs index d7712293c3..154c58dd99 100644 --- a/osu.Game.Tests/Visual/Editor/TestCaseEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs @@ -19,7 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Editor { [TestFixture] - public class TestCaseEditorComposeTimeline : EditorClockTestCase + public class TestSceneEditorComposeTimeline : EditorClockTestScene { public override IReadOnlyList RequiredTypes => new[] { diff --git a/osu.Game.Tests/Visual/Editor/TestCaseEditorMenuBar.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorMenuBar.cs similarity index 98% rename from osu.Game.Tests/Visual/Editor/TestCaseEditorMenuBar.cs rename to osu.Game.Tests/Visual/Editor/TestSceneEditorMenuBar.cs index b012d4b52d..53c2d62067 100644 --- a/osu.Game.Tests/Visual/Editor/TestCaseEditorMenuBar.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorMenuBar.cs @@ -13,11 +13,11 @@ using osu.Game.Screens.Edit.Components.Menus; namespace osu.Game.Tests.Visual.Editor { [TestFixture] - public class TestCaseEditorMenuBar : OsuTestCase + public class TestSceneEditorMenuBar : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(EditorMenuBar), typeof(ScreenSelectionTabControl) }; - public TestCaseEditorMenuBar() + public TestSceneEditorMenuBar() { Add(new Container { diff --git a/osu.Game.Tests/Visual/Editor/TestCaseEditorSeekSnapping.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorSeekSnapping.cs similarity index 99% rename from osu.Game.Tests/Visual/Editor/TestCaseEditorSeekSnapping.cs rename to osu.Game.Tests/Visual/Editor/TestSceneEditorSeekSnapping.cs index 9daba54b58..590fa59107 100644 --- a/osu.Game.Tests/Visual/Editor/TestCaseEditorSeekSnapping.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorSeekSnapping.cs @@ -17,9 +17,9 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Editor { [TestFixture] - public class TestCaseEditorSeekSnapping : EditorClockTestCase + public class TestSceneEditorSeekSnapping : EditorClockTestScene { - public TestCaseEditorSeekSnapping() + public TestSceneEditorSeekSnapping() { BeatDivisor.Value = 4; } diff --git a/osu.Game.Tests/Visual/Editor/TestCaseEditorSummaryTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorSummaryTimeline.cs similarity index 93% rename from osu.Game.Tests/Visual/Editor/TestCaseEditorSummaryTimeline.cs rename to osu.Game.Tests/Visual/Editor/TestSceneEditorSummaryTimeline.cs index 99d6385804..f20c921ff2 100644 --- a/osu.Game.Tests/Visual/Editor/TestCaseEditorSummaryTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorSummaryTimeline.cs @@ -14,7 +14,7 @@ using osuTK; namespace osu.Game.Tests.Visual.Editor { [TestFixture] - public class TestCaseEditorSummaryTimeline : EditorClockTestCase + public class TestSceneEditorSummaryTimeline : EditorClockTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(SummaryTimeline) }; diff --git a/osu.Game.Tests/Visual/Editor/TestCaseHitObjectComposer.cs b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs similarity index 97% rename from osu.Game.Tests/Visual/Editor/TestCaseHitObjectComposer.cs rename to osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs index be335fb71f..47aa059b62 100644 --- a/osu.Game.Tests/Visual/Editor/TestCaseHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Editor { [TestFixture] [Cached(Type = typeof(IPlacementHandler))] - public class TestCaseHitObjectComposer : OsuTestCase, IPlacementHandler + public class TestSceneHitObjectComposer : OsuTestScene, IPlacementHandler { public override IReadOnlyList RequiredTypes => new[] { diff --git a/osu.Game.Tests/Visual/Editor/TestCasePlaybackControl.cs b/osu.Game.Tests/Visual/Editor/TestScenePlaybackControl.cs similarity index 94% rename from osu.Game.Tests/Visual/Editor/TestCasePlaybackControl.cs rename to osu.Game.Tests/Visual/Editor/TestScenePlaybackControl.cs index 7d9b43251e..126ab98291 100644 --- a/osu.Game.Tests/Visual/Editor/TestCasePlaybackControl.cs +++ b/osu.Game.Tests/Visual/Editor/TestScenePlaybackControl.cs @@ -13,7 +13,7 @@ using osuTK; namespace osu.Game.Tests.Visual.Editor { [TestFixture] - public class TestCasePlaybackControl : OsuTestCase + public class TestScenePlaybackControl : OsuTestScene { [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Tests/Visual/Editor/TestCaseWaveContainer.cs b/osu.Game.Tests/Visual/Editor/TestSceneWaveContainer.cs similarity index 96% rename from osu.Game.Tests/Visual/Editor/TestCaseWaveContainer.cs rename to osu.Game.Tests/Visual/Editor/TestSceneWaveContainer.cs index e87304ded6..de19727251 100644 --- a/osu.Game.Tests/Visual/Editor/TestCaseWaveContainer.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneWaveContainer.cs @@ -15,7 +15,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Editor { [TestFixture] - public class TestCaseWaveContainer : OsuTestCase + public class TestSceneWaveContainer : OsuTestScene { [BackgroundDependencyLoader] private void load(OsuColour colours) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneWaveform.cs b/osu.Game.Tests/Visual/Editor/TestSceneWaveform.cs new file mode 100644 index 0000000000..e93789b1d3 --- /dev/null +++ b/osu.Game.Tests/Visual/Editor/TestSceneWaveform.cs @@ -0,0 +1,107 @@ +// 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.Track; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Sprites; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Editor +{ + [TestFixture] + public class TestSceneWaveform : OsuTestScene + { + private WorkingBeatmap waveformBeatmap; + + [BackgroundDependencyLoader] + private void load() + { + waveformBeatmap = new WaveformTestBeatmap(); + } + + [TestCase(1f)] + [TestCase(1f / 2)] + [TestCase(1f / 4)] + [TestCase(1f / 8)] + [TestCase(1f / 16)] + [TestCase(0f)] + public void TestResolution(float resolution) + { + TestWaveformGraph graph = null; + + AddStep("add graph", () => + { + Child = new Container + { + RelativeSizeAxes = Axes.X, + Height = 100, + Children = new Drawable[] + { + graph = new TestWaveformGraph + { + RelativeSizeAxes = Axes.Both, + Resolution = resolution, + Waveform = waveformBeatmap.Waveform, + }, + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.75f + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = $"Resolution: {resolution:0.00}" + } + } + } + } + }; + }); + + AddUntilStep("wait for load", () => graph.ResampledWaveform != null); + } + + [Test] + public void TestDefaultBeatmap() + { + TestWaveformGraph graph = null; + + AddStep("add graph", () => + { + Child = new Container + { + RelativeSizeAxes = Axes.X, + Height = 100, + Child = graph = new TestWaveformGraph + { + RelativeSizeAxes = Axes.Both, + Waveform = new DummyWorkingBeatmap().Waveform, + }, + }; + }); + + AddUntilStep("wait for load", () => graph.ResampledWaveform != null); + } + + public class TestWaveformGraph : WaveformGraph + { + public new Waveform ResampledWaveform => base.ResampledWaveform; + } + } +} diff --git a/osu.Game.Tests/Visual/Editor/TestCaseZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editor/TestSceneZoomableScrollContainer.cs similarity index 73% rename from osu.Game.Tests/Visual/Editor/TestCaseZoomableScrollContainer.cs rename to osu.Game.Tests/Visual/Editor/TestSceneZoomableScrollContainer.cs index e2cf1ef28a..da8702209c 100644 --- a/osu.Game.Tests/Visual/Editor/TestCaseZoomableScrollContainer.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneZoomableScrollContainer.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.MathUtils; +using osu.Framework.Testing; using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -16,40 +17,51 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Editor { - public class TestCaseZoomableScrollContainer : ManualInputManagerTestCase + public class TestSceneZoomableScrollContainer : ManualInputManagerTestScene { - private readonly ZoomableScrollContainer scrollContainer; - private readonly Drawable innerBox; + private ZoomableScrollContainer scrollContainer; + private Drawable innerBox; - public TestCaseZoomableScrollContainer() + [SetUpSteps] + public void SetUpSteps() { - Children = new Drawable[] + AddStep("Add new scroll container", () => { - new Container + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = 250, - Width = 0.75f, - Children = new Drawable[] + new Container { - new Box + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = 250, + Width = 0.75f, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(30) - }, - scrollContainer = new ZoomableScrollContainer { RelativeSizeAxes = Axes.Both } - } - }, - new MenuCursor() - }; + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(30) + }, + scrollContainer = new ZoomableScrollContainer { RelativeSizeAxes = Axes.Both } + } + }, + new MenuCursor() + }; - scrollContainer.Add(innerBox = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(new Color4(0.8f, 0.6f, 0.4f, 1f), new Color4(0.4f, 0.6f, 0.8f, 1f)) + scrollContainer.Add(innerBox = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientHorizontal(new Color4(0.8f, 0.6f, 0.4f, 1f), new Color4(0.4f, 0.6f, 0.8f, 1f)) + }); }); + AddUntilStep("Scroll container is loaded", () => scrollContainer.LoadState >= LoadState.Loaded); + } + + [Test] + public void TestWidthInitialization() + { + AddAssert("Inner container width was initialized", () => innerBox.DrawWidth > 0); } [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs deleted file mode 100644 index 41d484e21f..0000000000 --- a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs +++ /dev/null @@ -1,70 +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.Threading; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Screens; -using osu.Game.Beatmaps; -using osu.Game.Screens; -using osu.Game.Screens.Play; - -namespace osu.Game.Tests.Visual.Gameplay -{ - public class TestCasePlayerLoader : ManualInputManagerTestCase - { - private PlayerLoader loader; - private readonly OsuScreenStack stack; - - public TestCasePlayerLoader() - { - InputManager.Add(stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }); - } - - [BackgroundDependencyLoader] - private void load(OsuGameBase game) - { - Beatmap.Value = new DummyWorkingBeatmap(game); - - AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => new Player(false, false)))); - - AddUntilStep("wait for current", () => loader.IsCurrentScreen()); - - AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); - - AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen()); - - AddStep("exit loader", () => loader.Exit()); - - AddUntilStep("wait for no longer alive", () => !loader.IsAlive); - - AddStep("load slow dummy beatmap", () => - { - SlowLoadPlayer slow = null; - - stack.Push(loader = new PlayerLoader(() => slow = new SlowLoadPlayer(false, false))); - - Scheduler.AddDelayed(() => slow.Ready = true, 5000); - }); - - AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen()); - } - - protected class SlowLoadPlayer : Player - { - public bool Ready; - - public SlowLoadPlayer(bool allowPause = true, bool showResults = true) - : base(allowPause, showResults) - { - } - - [BackgroundDependencyLoader] - private void load() - { - while (!Ready) - Thread.Sleep(1); - } - } - } -} diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs similarity index 84% rename from osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index a2d92b7861..452ac859de 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -10,11 +10,11 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual.Gameplay { [Description("Player instantiated with an autoplay mod.")] - public class TestCaseAutoplay : AllPlayersTestCase + public class TestSceneAutoplay : AllPlayersTestScene { protected override Player CreatePlayer(Ruleset ruleset) { - Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); + Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); return new ScoreAccessiblePlayer(); } @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0)); } - private class ScoreAccessiblePlayer : Player + private class ScoreAccessiblePlayer : TestPlayer { public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; public new HUDOverlay HUDOverlay => base.HUDOverlay; diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseBreakOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs similarity index 96% rename from osu.Game.Tests/Visual/Gameplay/TestCaseBreakOverlay.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs index dda8005f70..3cd1b8307a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseBreakOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs @@ -9,11 +9,11 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] - public class TestCaseBreakOverlay : OsuTestCase + public class TestSceneBreakOverlay : OsuTestScene { private readonly BreakOverlay breakOverlay; - public TestCaseBreakOverlay() + public TestSceneBreakOverlay() { Child = breakOverlay = new BreakOverlay(true); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs new file mode 100644 index 0000000000..5eb71e92c2 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs @@ -0,0 +1,174 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneFrameStabilityContainer : OsuTestScene + { + private readonly ManualClock manualClock; + + private readonly Container mainContainer; + + private ClockConsumingChild consumer; + + public TestSceneFrameStabilityContainer() + { + Child = mainContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Clock = new FramedClock(manualClock = new ManualClock()), + }; + } + + [Test] + public void TestLargeJumps() + { + seekManualTo(0); + createStabilityContainer(); + seekManualTo(100000); + + confirmSeek(100000); + checkFrameCount(6000); + + seekManualTo(0); + + confirmSeek(0); + checkFrameCount(12000); + } + + [Test] + public void TestSmallJumps() + { + seekManualTo(0); + createStabilityContainer(); + seekManualTo(40); + + confirmSeek(40); + checkFrameCount(3); + + seekManualTo(0); + + confirmSeek(0); + checkFrameCount(6); + } + + [Test] + public void TestSingleFrameJump() + { + seekManualTo(0); + createStabilityContainer(); + seekManualTo(8); + confirmSeek(8); + checkFrameCount(1); + + seekManualTo(16); + confirmSeek(16); + checkFrameCount(2); + } + + [Test] + public void TestInitialSeekWithGameplayStart() + { + seekManualTo(1000); + createStabilityContainer(30000); + + confirmSeek(1000); + checkFrameCount(0); + + seekManualTo(10000); + confirmSeek(10000); + + checkFrameCount(1); + + seekManualTo(130000); + confirmSeek(130000); + + checkFrameCount(6002); + } + + [Test] + public void TestInitialSeek() + { + seekManualTo(100000); + createStabilityContainer(); + + confirmSeek(100000); + checkFrameCount(0); + } + + private const int max_frames_catchup = 50; + + private void createStabilityContainer(double gameplayStartTime = double.MinValue) => AddStep("create container", () => + mainContainer.Child = new FrameStabilityContainer(gameplayStartTime) { MaxCatchUpFrames = max_frames_catchup } + .WithChild(consumer = new ClockConsumingChild())); + + private void seekManualTo(double time) => AddStep($"seek manual clock to {time}", () => manualClock.CurrentTime = time); + + private void confirmSeek(double time) => AddUntilStep($"wait for seek to {time}", () => consumer.Clock.CurrentTime == time); + + private void checkFrameCount(int frames) => + AddAssert($"elapsed frames is {frames}", () => consumer.ElapsedFrames == frames); + + public class ClockConsumingChild : CompositeDrawable + { + private readonly OsuSpriteText text; + private readonly OsuSpriteText text2; + private readonly OsuSpriteText text3; + + public ClockConsumingChild() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + text2 = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + text3 = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + }, + }; + } + + public int ElapsedFrames; + + protected override void Update() + { + base.Update(); + + if (Clock.ElapsedFrameTime != 0) + ElapsedFrames++; + + text.Text = $"current time: {Clock.CurrentTime:F0}"; + if (Clock.ElapsedFrameTime != 0) + text2.Text = $"last elapsed frame time: {Clock.ElapsedFrameTime:F0}"; + text3.Text = $"total frames: {ElapsedFrames:F0}"; + } + } + } +} diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs similarity index 99% rename from osu.Game.Tests/Visual/Gameplay/TestCaseGameplayMenuOverlay.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs index 8e43bf6d3a..ba9c583b08 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs @@ -17,7 +17,7 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { [Description("player pause/fail screens")] - public class TestCaseGameplayMenuOverlay : ManualInputManagerTestCase + public class TestSceneGameplayMenuOverlay : ManualInputManagerTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseOverlay) }; diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseHoldForMenuButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs similarity index 96% rename from osu.Game.Tests/Visual/Gameplay/TestCaseHoldForMenuButton.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs index 14e9c7cdb6..d42b61ea55 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseHoldForMenuButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs @@ -13,7 +13,7 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { [Description("'Hold to Quit' UI element")] - public class TestCaseHoldForMenuButton : ManualInputManagerTestCase + public class TestSceneHoldForMenuButton : ManualInputManagerTestScene { private bool exitAction; diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs similarity index 97% rename from osu.Game.Tests/Visual/Gameplay/TestCaseKeyCounter.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs index 4b55879224..18088a9a5b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseKeyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs @@ -14,7 +14,7 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] - public class TestCaseKeyCounter : ManualInputManagerTestCase + public class TestSceneKeyCounter : ManualInputManagerTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Gameplay typeof(KeyCounterDisplay) }; - public TestCaseKeyCounter() + public TestSceneKeyCounter() { KeyCounterKeyboard rewindTestKeyCounterKeyboard; KeyCounterDisplay kc = new KeyCounterDisplay diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseMedalOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs similarity index 90% rename from osu.Game.Tests/Visual/Gameplay/TestCaseMedalOverlay.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs index dd686c36e6..41722b430e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseMedalOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs @@ -11,7 +11,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] - public class TestCaseMedalOverlay : OsuTestCase + public class TestSceneMedalOverlay : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Gameplay typeof(DrawableMedal), }; - public TestCaseMedalOverlay() + public TestSceneMedalOverlay() { AddStep(@"display", () => { diff --git a/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs similarity index 89% rename from osu.Game.Tests/Visual/Gameplay/TestCasePause.cs rename to osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index a52e84ed62..b6f8638f4a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; +using osu.Framework.Testing; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Rulesets; @@ -17,7 +18,7 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { - public class TestCasePause : PlayerTestCase + public class TestScenePause : PlayerTestScene { protected new PausePlayer Player => (PausePlayer)base.Player; @@ -25,12 +26,20 @@ namespace osu.Game.Tests.Visual.Gameplay protected override Container Content => content; - public TestCasePause() + public TestScenePause() : base(new OsuRuleset()) { base.Content.Add(content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }); } + [SetUpSteps] + public override void SetUpSteps() + { + base.SetUpSteps(); + AddStep("resume player", () => Player.GameplayClockContainer.Start()); + confirmClockRunning(true); + } + [Test] public void TestPauseResume() { @@ -77,8 +86,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10))); pauseAndConfirm(); - resumeAndConfirm(); + resume(); pause(); confirmClockRunning(true); @@ -157,6 +166,8 @@ namespace osu.Game.Tests.Visual.Gameplay private void confirmPaused() { confirmClockRunning(false); + AddAssert("player not exited", () => Player.IsCurrentScreen()); + AddAssert("player not failed", () => !Player.HasFailed); AddAssert("pause overlay shown", () => Player.PauseOverlayVisible); } @@ -184,7 +195,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override Player CreatePlayer(Ruleset ruleset) => new PausePlayer(); - protected class PausePlayer : Player + protected class PausePlayer : TestPlayer { public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; @@ -196,9 +207,10 @@ namespace osu.Game.Tests.Visual.Gameplay public bool PauseOverlayVisible => PauseOverlay.State == Visibility.Visible; - public PausePlayer() + public override void OnEntering(IScreen last) { - PauseOnFocusLost = false; + base.OnEntering(last); + GameplayClockContainer.Stop(); } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs new file mode 100644 index 0000000000..5c26f733ab --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -0,0 +1,132 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Screens; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens; +using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestScenePlayerLoader : ManualInputManagerTestScene + { + private PlayerLoader loader; + private OsuScreenStack stack; + + [SetUp] + public void Setup() => Schedule(() => + { + InputManager.Child = stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }; + Beatmap.Value = new TestWorkingBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo), Clock); + }); + + [Test] + public void TestLoadContinuation() + { + Player player = null; + SlowLoadPlayer slowPlayer = null; + + AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => player = new TestPlayer(false, false)))); + AddUntilStep("wait for current", () => loader.IsCurrentScreen()); + AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); + AddUntilStep("wait for player to be current", () => player.IsCurrentScreen()); + AddStep("load slow dummy beatmap", () => + { + stack.Push(loader = new PlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false))); + Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000); + }); + + AddUntilStep("wait for player to be current", () => slowPlayer.IsCurrentScreen()); + } + + [Test] + public void TestModReinstantiation() + { + TestPlayer player = null; + TestMod gameMod = null; + TestMod playerMod1 = null; + TestMod playerMod2 = null; + + AddStep("load player", () => + { + Mods.Value = new[] { gameMod = new TestMod() }; + stack.Push(loader = new PlayerLoader(() => player = new TestPlayer())); + }); + + AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen()); + AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); + AddUntilStep("wait for player to be current", () => player.IsCurrentScreen()); + AddStep("retrieve mods", () => playerMod1 = (TestMod)player.Mods.Value.Single()); + AddAssert("game mods not applied", () => gameMod.Applied == false); + AddAssert("player mods applied", () => playerMod1.Applied); + + AddStep("restart player", () => + { + var lastPlayer = player; + player = null; + lastPlayer.Restart(); + }); + + AddUntilStep("wait for player to be current", () => player.IsCurrentScreen()); + AddStep("retrieve mods", () => playerMod2 = (TestMod)player.Mods.Value.Single()); + AddAssert("game mods not applied", () => gameMod.Applied == false); + AddAssert("player has different mods", () => playerMod1 != playerMod2); + AddAssert("player mods applied", () => playerMod2.Applied); + } + + private class TestMod : Mod, IApplicableToScoreProcessor + { + public override string Name => string.Empty; + public override string Acronym => string.Empty; + public override double ScoreMultiplier => 1; + + public bool Applied { get; private set; } + + public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) + { + Applied = true; + } + + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; + } + + private class TestPlayer : Visual.TestPlayer + { + public new Bindable> Mods => base.Mods; + + public TestPlayer(bool allowPause = true, bool showResults = true) + : base(allowPause, showResults) + { + } + } + + protected class SlowLoadPlayer : Visual.TestPlayer + { + public readonly ManualResetEventSlim AllowLoad = new ManualResetEventSlim(false); + + public SlowLoadPlayer(bool allowPause = true, bool showResults = true) + : base(allowPause, showResults) + { + } + + [BackgroundDependencyLoader] + private void load() + { + if (!AllowLoad.Wait(TimeSpan.FromSeconds(10))) + throw new TimeoutException(); + } + } + } +} diff --git a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerReferenceLeaking.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerReferenceLeaking.cs similarity index 86% rename from osu.Game.Tests/Visual/Gameplay/TestCasePlayerReferenceLeaking.cs rename to osu.Game.Tests/Visual/Gameplay/TestScenePlayerReferenceLeaking.cs index 5937d489f2..c75fb2567b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerReferenceLeaking.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerReferenceLeaking.cs @@ -10,7 +10,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual.Gameplay { - public class TestCasePlayerReferenceLeaking : AllPlayersTestCase + public class TestScenePlayerReferenceLeaking : AllPlayersTestScene { private readonly WeakList workingWeakReferences = new WeakList(); @@ -24,7 +24,9 @@ namespace osu.Game.Tests.Visual.Gameplay GC.WaitForPendingFinalizers(); int count = 0; - workingWeakReferences.ForEachAlive(_ => count++); + foreach (var unused in workingWeakReferences) + count++; + return count == 1; }); @@ -34,7 +36,9 @@ namespace osu.Game.Tests.Visual.Gameplay GC.WaitForPendingFinalizers(); int count = 0; - playerWeakReferences.ForEachAlive(_ => count++); + foreach (var unused in playerWeakReferences) + count++; + return count == 1; }); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs similarity index 87% rename from osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs index b98ce96fbb..3fbce9d43c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs @@ -1,9 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.ComponentModel; using System.Linq; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; @@ -11,11 +13,11 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual.Gameplay { [Description("Player instantiated with a replay.")] - public class TestCaseReplay : AllPlayersTestCase + public class TestSceneReplay : AllPlayersTestScene { protected override Player CreatePlayer(Ruleset ruleset) { - var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo); + var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty()); return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod().CreateReplayScore(beatmap)); } @@ -31,6 +33,8 @@ namespace osu.Game.Tests.Visual.Gameplay public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; public new HUDOverlay HUDOverlay => base.HUDOverlay; + protected override bool PauseOnFocusLost => false; + public ScoreAccessibleReplayPlayer(Score score) : base(score) { diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseReplaySettingsOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplaySettingsOverlay.cs similarity index 92% rename from osu.Game.Tests/Visual/Gameplay/TestCaseReplaySettingsOverlay.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneReplaySettingsOverlay.cs index 2fdfda0d80..944480243d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseReplaySettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplaySettingsOverlay.cs @@ -10,9 +10,9 @@ using osu.Game.Screens.Play.PlayerSettings; namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] - public class TestCaseReplaySettingsOverlay : OsuTestCase + public class TestSceneReplaySettingsOverlay : OsuTestScene { - public TestCaseReplaySettingsOverlay() + public TestSceneReplaySettingsOverlay() { ExampleContainer container; diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseResults.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs similarity index 97% rename from osu.Game.Tests/Visual/Gameplay/TestCaseResults.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs index d9da45f39a..f3c8f89db7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseResults.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs @@ -16,7 +16,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] - public class TestCaseResults : ScreenTestCase + public class TestSceneResults : ScreenTestScene { private BeatmapManager beatmaps; diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs similarity index 94% rename from osu.Game.Tests/Visual/Gameplay/TestCaseScoreCounter.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs index 3dd5c99e45..080a287b48 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseScoreCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.MathUtils; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Play.HUD; using osuTK; @@ -12,9 +13,9 @@ using osuTK; namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] - public class TestCaseScoreCounter : OsuTestCase + public class TestSceneScoreCounter : OsuTestScene { - public TestCaseScoreCounter() + public TestSceneScoreCounter() { int numerator = 0, denominator = 0; @@ -53,7 +54,7 @@ namespace osu.Game.Tests.Visual.Gameplay }; Add(stars); - SpriteText starsLabel = new SpriteText + SpriteText starsLabel = new OsuSpriteText { Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs similarity index 96% rename from osu.Game.Tests/Visual/Gameplay/TestCaseScrollingHitObjects.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs index c99a4bb89b..0a9cdc6a8e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseScrollingHitObjects.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs @@ -4,11 +4,13 @@ using System; using System.Collections.Generic; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Timing; @@ -19,14 +21,17 @@ using osuTK; namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] - public class TestCaseScrollingHitObjects : OsuTestCase + public class TestSceneScrollingHitObjects : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(Playfield) }; + [Cached(typeof(IReadOnlyList))] + private IReadOnlyList mods { get; set; } = Array.Empty(); + private readonly ScrollingTestContainer[] scrollContainers = new ScrollingTestContainer[4]; private readonly TestPlayfield[] playfields = new TestPlayfield[4]; - public TestCaseScrollingHitObjects() + public TestSceneScrollingHitObjects() { Add(new GridContainer { @@ -124,12 +129,15 @@ namespace osu.Game.Tests.Visual.Gameplay case ScrollingDirection.Up: obj.Anchor = Anchor.TopCentre; break; + case ScrollingDirection.Down: obj.Anchor = Anchor.BottomCentre; break; + case ScrollingDirection.Left: obj.Anchor = Anchor.CentreLeft; break; + case ScrollingDirection.Right: obj.Anchor = Anchor.CentreRight; break; @@ -184,6 +192,7 @@ namespace osu.Game.Tests.Visual.Gameplay RelativeSizeAxes = Axes.X; Height = 2; break; + case ScrollingDirection.Left: case ScrollingDirection.Right: RelativeSizeAxes = Axes.Y; diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseSkinReloadable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinReloadable.cs similarity index 90% rename from osu.Game.Tests/Visual/Gameplay/TestCaseSkinReloadable.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneSkinReloadable.cs index a9fbf35d37..c7a0df6e9f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseSkinReloadable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinReloadable.cs @@ -7,15 +7,15 @@ using osu.Framework.Audio.Sample; 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.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Skinning; using osuTK.Graphics; namespace osu.Game.Tests.Visual.Gameplay { - public class TestCaseSkinReloadable : OsuTestCase + public class TestSceneSkinReloadable : OsuTestScene { [Test] public void TestInitialLoad() @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Gameplay Colour = Color4.Black, RelativeSizeAxes = Axes.Both, }, - new SpriteText + new OsuSpriteText { Font = OsuFont.Default.With(size: 40), Anchor = Anchor.Centre, @@ -120,12 +120,8 @@ namespace osu.Game.Tests.Visual.Gameplay } } - private class SecondarySource : ISkinSource + private class SecondarySource : ISkin { - public event Action SourceChanged; - - public void TriggerSourceChanged() => SourceChanged?.Invoke(); - public Drawable GetDrawableComponent(string componentName) => new SecondarySourceBox(); public Texture GetTexture(string componentName) => throw new NotImplementedException(); @@ -135,12 +131,8 @@ namespace osu.Game.Tests.Visual.Gameplay public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException(); } - private class SkinSourceContainer : Container, ISkinSource + private class SkinSourceContainer : Container, ISkin { - public event Action SourceChanged; - - public void TriggerSourceChanged() => SourceChanged?.Invoke(); - public Drawable GetDrawableComponent(string componentName) => new BaseSourceBox(); public Texture GetTexture(string componentName) => throw new NotImplementedException(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs similarity index 89% rename from osu.Game.Tests/Visual/Gameplay/TestCaseSkipOverlay.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index b46d79ac04..0519660477 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -7,7 +7,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] - public class TestCaseSkipOverlay : OsuTestCase + public class TestSceneSkipOverlay : OsuTestScene { protected override void LoadComplete() { diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs similarity index 97% rename from osu.Game.Tests/Visual/Gameplay/TestCaseSongProgress.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs index e17dcef19c..af21007efe 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseSongProgress.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs @@ -13,7 +13,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] - public class TestCaseSongProgress : OsuTestCase + public class TestSceneSongProgress : OsuTestScene { private readonly SongProgress progress; private readonly TestSongProgressGraph graph; @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Gameplay private readonly FramedClock framedClock; - public TestCaseSongProgress() + public TestSceneSongProgress() { clock = new StopwatchClock(true); diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs similarity index 96% rename from osu.Game.Tests/Visual/Gameplay/TestCaseStoryboard.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs index 651683a671..213cdf5e48 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs @@ -16,12 +16,12 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] - public class TestCaseStoryboard : OsuTestCase + public class TestSceneStoryboard : OsuTestScene { private readonly Container storyboardContainer; private DrawableStoryboard storyboard; - public TestCaseStoryboard() + public TestSceneStoryboard() { Clock = new FramedClock(); diff --git a/osu.Game.Tests/Visual/Menus/TestCaseDisclaimer.cs b/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs similarity index 94% rename from osu.Game.Tests/Visual/Menus/TestCaseDisclaimer.cs rename to osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs index 68a1ceec16..f2718b8e80 100644 --- a/osu.Game.Tests/Visual/Menus/TestCaseDisclaimer.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs @@ -8,7 +8,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Menus { - public class TestCaseDisclaimer : ScreenTestCase + public class TestSceneDisclaimer : ScreenTestScene { [Cached(typeof(IAPIProvider))] private readonly DummyAPIAccess api = new DummyAPIAccess(); diff --git a/osu.Game.Tests/Visual/Menus/TestCaseIntroSequence.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroSequence.cs similarity index 93% rename from osu.Game.Tests/Visual/Menus/TestCaseIntroSequence.cs rename to osu.Game.Tests/Visual/Menus/TestSceneIntroSequence.cs index 0b924e56f5..b59fb18428 100644 --- a/osu.Game.Tests/Visual/Menus/TestCaseIntroSequence.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroSequence.cs @@ -14,14 +14,14 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Menus { [TestFixture] - public class TestCaseIntroSequence : OsuTestCase + public class TestSceneIntroSequence : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(OsuLogo), }; - public TestCaseIntroSequence() + public TestSceneIntroSequence() { OsuLogo logo; diff --git a/osu.Game.Tests/Visual/Menus/TestCaseLoaderAnimation.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoaderAnimation.cs similarity index 72% rename from osu.Game.Tests/Visual/Menus/TestCaseLoaderAnimation.cs rename to osu.Game.Tests/Visual/Menus/TestSceneLoaderAnimation.cs index df12e14891..000832b784 100644 --- a/osu.Game.Tests/Visual/Menus/TestCaseLoaderAnimation.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneLoaderAnimation.cs @@ -14,16 +14,20 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Menus { [TestFixture] - public class TestCaseLoaderAnimation : ScreenTestCase + public class TestSceneLoaderAnimation : ScreenTestScene { private TestLoader loader; [Cached] private OsuLogo logo; - public TestCaseLoaderAnimation() + public TestSceneLoaderAnimation() { - Child = logo = new OsuLogo { Depth = float.MinValue }; + Child = logo = new OsuLogo + { + Alpha = 0, + Depth = float.MinValue + }; } [Test] @@ -39,7 +43,7 @@ namespace osu.Game.Tests.Visual.Menus LoadScreen(loader); }); - AddAssert("loaded", () => + AddUntilStep("loaded", () => { logoVisible = loader.Logo?.Alpha > 0; return loader.Logo != null && loader.ScreenLoaded; @@ -49,38 +53,12 @@ namespace osu.Game.Tests.Visual.Menus } [Test] - public void TestShortLoad() + public void TestDelayedLoad() { - bool logoVisible = false; - AddStep("begin loading", () => LoadScreen(loader = new TestLoader())); - AddWaitStep("wait", 2); - AddStep("finish loading", () => - { - logoVisible = loader.Logo?.Alpha > 0; - loader.AllowLoad.Set(); - }); - + AddUntilStep("wait for logo visible", () => loader.Logo?.Alpha > 0); + AddStep("finish loading", () => loader.AllowLoad.Set()); AddAssert("loaded", () => loader.Logo != null && loader.ScreenLoaded); - AddAssert("logo was visible", () => logoVisible); - AddUntilStep("logo gone", () => loader.Logo?.Alpha == 0); - } - - [Test] - public void TestLongLoad() - { - bool logoVisible = false; - - AddStep("begin loading", () => LoadScreen(loader = new TestLoader())); - AddWaitStep("wait", 10); - AddStep("finish loading", () => - { - logoVisible = loader.Logo?.Alpha > 0; - loader.AllowLoad.Set(); - }); - - AddAssert("loaded", () => loader.Logo != null && loader.ScreenLoaded); - AddAssert("logo was visible", () => logoVisible); AddUntilStep("logo gone", () => loader.Logo?.Alpha == 0); } diff --git a/osu.Game.Tests/Visual/Menus/TestCaseToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs similarity index 94% rename from osu.Game.Tests/Visual/Menus/TestCaseToolbar.cs rename to osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs index 4da17f9944..0c789d8cb7 100644 --- a/osu.Game.Tests/Visual/Menus/TestCaseToolbar.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs @@ -11,7 +11,7 @@ using osu.Game.Overlays.Toolbar; namespace osu.Game.Tests.Visual.Menus { [TestFixture] - public class TestCaseToolbar : OsuTestCase + public class TestSceneToolbar : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Menus typeof(ToolbarNotificationButton), }; - public TestCaseToolbar() + public TestSceneToolbar() { var toolbar = new Toolbar { State = Visibility.Visible }; ToolbarNotificationButton notificationButton = null; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestCaseLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs similarity index 98% rename from osu.Game.Tests/Visual/Multiplayer/TestCaseLoungeRoomsContainer.cs rename to osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 497da33a05..eb4dc909df 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestCaseLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -16,7 +16,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestCaseLoungeRoomsContainer : MultiplayerTestCase + public class TestSceneLoungeRoomsContainer : MultiplayerTestScene { public override IReadOnlyList RequiredTypes => new[] { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs similarity index 93% rename from osu.Game.Tests/Visual/Multiplayer/TestCaseMatchHeader.cs rename to osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs index 81cb90c7cd..e42042f2ea 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchHeader.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs @@ -12,14 +12,14 @@ using osu.Game.Screens.Multi.Match.Components; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestCaseMatchHeader : MultiplayerTestCase + public class TestSceneMatchHeader : MultiplayerTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(Header) }; - public TestCaseMatchHeader() + public TestSceneMatchHeader() { Room.Playlist.Add(new PlaylistItem { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchHostInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHostInfo.cs similarity index 90% rename from osu.Game.Tests/Visual/Multiplayer/TestCaseMatchHostInfo.cs rename to osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHostInfo.cs index d2dc417100..808a45cdf0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchHostInfo.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHostInfo.cs @@ -10,7 +10,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestCaseMatchHostInfo : OsuTestCase + public class TestSceneMatchHostInfo : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private readonly Bindable host = new Bindable(new User { Username = "SomeHost" }); - public TestCaseMatchHostInfo() + public TestSceneMatchHostInfo() { HostInfo hostInfo; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchInfo.cs similarity index 97% rename from osu.Game.Tests/Visual/Multiplayer/TestCaseMatchInfo.cs rename to osu.Game.Tests/Visual/Multiplayer/TestSceneMatchInfo.cs index 6b04b71da4..3f0c0b07b7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchInfo.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchInfo.cs @@ -14,7 +14,7 @@ using osu.Game.Screens.Multi.Match.Components; namespace osu.Game.Tests.Visual.Multiplayer { [TestFixture] - public class TestCaseMatchInfo : MultiplayerTestCase + public class TestSceneMatchInfo : MultiplayerTestScene { public override IReadOnlyList RequiredTypes => new[] { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs similarity index 94% rename from osu.Game.Tests/Visual/Multiplayer/TestCaseMatchLeaderboard.cs rename to osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs index 8ec323dbc3..fa3c392b2e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs @@ -12,9 +12,9 @@ using osuTK; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestCaseMatchLeaderboard : MultiplayerTestCase + public class TestSceneMatchLeaderboard : MultiplayerTestScene { - public TestCaseMatchLeaderboard() + public TestSceneMatchLeaderboard() { Room.RoomID.Value = 3; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchParticipants.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs similarity index 94% rename from osu.Game.Tests/Visual/Multiplayer/TestCaseMatchParticipants.cs rename to osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs index 5382726516..50df4022dc 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchParticipants.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs @@ -9,9 +9,9 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { [TestFixture] - public class TestCaseMatchParticipants : MultiplayerTestCase + public class TestSceneMatchParticipants : MultiplayerTestScene { - public TestCaseMatchParticipants() + public TestSceneMatchParticipants() { Add(new Participants { RelativeSizeAxes = Axes.Both }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchResults.cs similarity index 98% rename from osu.Game.Tests/Visual/Multiplayer/TestCaseMatchResults.cs rename to osu.Game.Tests/Visual/Multiplayer/TestSceneMatchResults.cs index 69606c9ba7..7915a981dd 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchResults.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchResults.cs @@ -18,7 +18,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestCaseMatchResults : MultiplayerTestCase + public class TestSceneMatchResults : MultiplayerTestScene { public override IReadOnlyList RequiredTypes => new[] { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs similarity index 98% rename from osu.Game.Tests/Visual/Multiplayer/TestCaseMatchSettingsOverlay.cs rename to osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs index 51854800e3..21b97fe73b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs @@ -17,7 +17,7 @@ using osu.Game.Screens.Multi.Match.Components; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestCaseMatchSettingsOverlay : MultiplayerTestCase + public class TestSceneMatchSettingsOverlay : MultiplayerTestScene { public override IReadOnlyList RequiredTypes => new[] { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestCaseMultiHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiHeader.cs similarity index 92% rename from osu.Game.Tests/Visual/Multiplayer/TestCaseMultiHeader.cs rename to osu.Game.Tests/Visual/Multiplayer/TestSceneMultiHeader.cs index b49bb7fd84..3f89f636b1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestCaseMultiHeader.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiHeader.cs @@ -10,9 +10,9 @@ using osu.Game.Screens.Multi; namespace osu.Game.Tests.Visual.Multiplayer { [TestFixture] - public class TestCaseMultiHeader : OsuTestCase + public class TestSceneMultiHeader : OsuTestScene { - public TestCaseMultiHeader() + public TestSceneMultiHeader() { int index = 0; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestCaseMultiScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs similarity index 88% rename from osu.Game.Tests/Visual/Multiplayer/TestCaseMultiScreen.cs rename to osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs index ef381efd67..069e133c2b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestCaseMultiScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs @@ -10,7 +10,7 @@ using osu.Game.Screens.Multi.Lounge.Components; namespace osu.Game.Tests.Visual.Multiplayer { [TestFixture] - public class TestCaseMultiScreen : ScreenTestCase + public class TestSceneMultiScreen : ScreenTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Multiplayer typeof(FilterControl) }; - public TestCaseMultiScreen() + public TestSceneMultiScreen() { Screens.Multi.Multiplayer multi = new Screens.Multi.Multiplayer(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestCaseRoomStatus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneRoomStatus.cs similarity index 94% rename from osu.Game.Tests/Visual/Multiplayer/TestCaseRoomStatus.cs rename to osu.Game.Tests/Visual/Multiplayer/TestSceneRoomStatus.cs index a7c7d41ed4..74d1645f6d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestCaseRoomStatus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneRoomStatus.cs @@ -11,7 +11,7 @@ using osu.Game.Screens.Multi.Lounge.Components; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestCaseRoomStatus : OsuTestCase + public class TestSceneRoomStatus : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Multiplayer typeof(RoomStatusPlaying) }; - public TestCaseRoomStatus() + public TestSceneRoomStatus() { Child = new FillFlowContainer { diff --git a/osu.Game.Tests/Visual/Online/TestCaseBadgeContainer.cs b/osu.Game.Tests/Visual/Online/TestCaseBadgeContainer.cs deleted file mode 100644 index 631cb190d2..0000000000 --- a/osu.Game.Tests/Visual/Online/TestCaseBadgeContainer.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Game.Overlays.Profile.Header; -using osu.Game.Users; - -namespace osu.Game.Tests.Visual.Online -{ - [TestFixture] - public class TestCaseBadgeContainer : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] { typeof(BadgeContainer) }; - - public TestCaseBadgeContainer() - { - BadgeContainer badgeContainer; - - Child = badgeContainer = new BadgeContainer - { - RelativeSizeAxes = Axes.Both - }; - - AddStep("Show 1 badge", () => badgeContainer.ShowBadges(new[] - { - new Badge - { - AwardedAt = DateTimeOffset.Now, - Description = "Appreciates compasses", - ImageUrl = "https://assets.ppy.sh/profile-badges/mg2018-1star.png", - } - })); - - AddStep("Show 2 badges", () => badgeContainer.ShowBadges(new[] - { - new Badge - { - AwardedAt = DateTimeOffset.Now, - Description = "Contributed to osu!lazer testing", - ImageUrl = "https://assets.ppy.sh/profile-badges/contributor.png", - }, - new Badge - { - AwardedAt = DateTimeOffset.Now, - Description = "Appreciates compasses", - ImageUrl = "https://assets.ppy.sh/profile-badges/mg2018-1star.png", - } - })); - - AddStep("Show many badges", () => badgeContainer.ShowBadges(Enumerable.Range(1, 20).Select(i => new Badge - { - AwardedAt = DateTimeOffset.Now, - Description = $"Contributed to osu!lazer testing {i} times", - ImageUrl = "https://assets.ppy.sh/profile-badges/contributor.jpg", - }).ToArray())); - } - } -} diff --git a/osu.Game.Tests/Visual/Online/TestCaseAccountCreationOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs similarity index 93% rename from osu.Game.Tests/Visual/Online/TestCaseAccountCreationOverlay.cs rename to osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs index 5cdb90b61f..a7e725ec3f 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseAccountCreationOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs @@ -13,7 +13,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Online { - public class TestCaseAccountCreationOverlay : OsuTestCase + public class TestSceneAccountCreationOverlay : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.Online [Cached(typeof(IAPIProvider))] private DummyAPIAccess api = new DummyAPIAccess(); - public TestCaseAccountCreationOverlay() + public TestSceneAccountCreationOverlay() { Container userPanelArea; AccountCreationOverlay accountCreation; diff --git a/osu.Game.Tests/Visual/Online/TestCaseBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs similarity index 99% rename from osu.Game.Tests/Visual/Online/TestCaseBeatmapSetOverlay.cs rename to osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index 8363f8be04..5910da7b88 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -17,7 +17,7 @@ using System.Linq; namespace osu.Game.Tests.Visual.Online { [TestFixture] - public class TestCaseBeatmapSetOverlay : OsuTestCase + public class TestSceneBeatmapSetOverlay : OsuTestScene { private readonly BeatmapSetOverlay overlay; @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.Online typeof(SuccessRate), }; - public TestCaseBeatmapSetOverlay() + public TestSceneBeatmapSetOverlay() { Add(overlay = new BeatmapSetOverlay()); } diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs new file mode 100644 index 0000000000..d1a7730bee --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs @@ -0,0 +1,70 @@ +// 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.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Overlays.Changelog; + +namespace osu.Game.Tests.Visual.Online +{ + [TestFixture] + public class TestSceneChangelogOverlay : OsuTestScene + { + private ChangelogOverlay changelog; + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(UpdateStreamBadgeArea), + typeof(UpdateStreamBadge), + typeof(ChangelogHeader), + typeof(ChangelogContent), + typeof(ChangelogListing), + typeof(ChangelogSingleBuild), + typeof(ChangelogBuild), + }; + + protected override void LoadComplete() + { + base.LoadComplete(); + + Add(changelog = new ChangelogOverlay()); + AddStep(@"Show", changelog.Show); + AddStep(@"Hide", changelog.Hide); + + AddWaitStep("wait for hide", 3); + + AddStep(@"Show with Lazer 2018.712.0", () => + { + changelog.ShowBuild(new APIChangelogBuild + { + Version = "2018.712.0", + DisplayVersion = "2018.712.0", + UpdateStream = new APIUpdateStream { Name = "lazer" }, + ChangelogEntries = new List + { + new APIChangelogEntry + { + Category = "Test", + Title = "Title", + MessageHtml = "Message", + } + } + }); + changelog.Show(); + }); + + AddWaitStep("wait for show", 3); + AddStep(@"Hide", changelog.Hide); + AddWaitStep("wait for hide", 3); + + AddStep(@"Show with listing", () => + { + changelog.ShowListing(); + changelog.Show(); + }); + } + } +} diff --git a/osu.Game.Tests/Visual/Online/TestCaseChannelTabControl.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelTabControl.cs similarity index 94% rename from osu.Game.Tests/Visual/Online/TestCaseChannelTabControl.cs rename to osu.Game.Tests/Visual/Online/TestSceneChannelTabControl.cs index fdc3d5394f..364c986723 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseChannelTabControl.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelTabControl.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.MathUtils; +using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; using osu.Game.Overlays.Chat.Tabs; using osu.Game.Users; @@ -17,7 +18,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Online { - public class TestCaseChannelTabControl : OsuTestCase + public class TestSceneChannelTabControl : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -26,7 +27,7 @@ namespace osu.Game.Tests.Visual.Online private readonly ChannelTabControl channelTabControl; - public TestCaseChannelTabControl() + public TestSceneChannelTabControl() { SpriteText currentText; Add(new Container @@ -61,7 +62,7 @@ namespace osu.Game.Tests.Visual.Online Anchor = Anchor.TopLeft, Children = new Drawable[] { - currentText = new SpriteText + currentText = new OsuSpriteText { Text = "Currently selected channel:" } @@ -93,7 +94,7 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("remove all channels", () => { var first = channelTabControl.Items.First(); - if (first.Name == "+") + if (first is ChannelSelectorTabItem.ChannelSelectorTabChannel) return true; channelTabControl.RemoveChannel(first); diff --git a/osu.Game.Tests/Visual/Online/TestCaseChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatDisplay.cs similarity index 96% rename from osu.Game.Tests/Visual/Online/TestCaseChatDisplay.cs rename to osu.Game.Tests/Visual/Online/TestSceneChatDisplay.cs index 6e20165c1b..634176e65f 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatDisplay.cs @@ -15,7 +15,7 @@ using osu.Game.Overlays.Chat.Tabs; namespace osu.Game.Tests.Visual.Online { [Description("Testing chat api and overlay")] - public class TestCaseChatDisplay : OsuTestCase + public class TestSceneChatDisplay : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { diff --git a/osu.Game.Tests/Visual/Online/TestCaseChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs similarity index 99% rename from osu.Game.Tests/Visual/Online/TestCaseChatLink.cs rename to osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index 8843f136a1..c18e0e3064 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -21,7 +21,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Online { [TestFixture] - public class TestCaseChatLink : OsuTestCase + public class TestSceneChatLink : OsuTestScene { private readonly TestChatLineContainer textContainer; private readonly DialogOverlay dialogOverlay; @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Online typeof(MessageFormatter) }; - public TestCaseChatLink() + public TestSceneChatLink() { Add(dialogOverlay = new DialogOverlay { Depth = float.MinValue }); Add(textContainer = new TestChatLineContainer diff --git a/osu.Game.Tests/Visual/Online/TestCaseDirect.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs similarity index 99% rename from osu.Game.Tests/Visual/Online/TestCaseDirect.cs rename to osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs index ff57104d8a..efc12c5fdd 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseDirect.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets; namespace osu.Game.Tests.Visual.Online { [TestFixture] - public class TestCaseDirect : OsuTestCase + public class TestSceneDirectOverlay : OsuTestScene { private DirectOverlay direct; private RulesetStore rulesets; diff --git a/osu.Game.Tests/Visual/Online/TestCaseDirectPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs similarity index 96% rename from osu.Game.Tests/Visual/Online/TestCaseDirectPanel.cs rename to osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs index fbda531792..a3d932a383 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseDirectPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs @@ -13,7 +13,7 @@ using osuTK; namespace osu.Game.Tests.Visual.Online { - public class TestCaseDirectPanel : OsuTestCase + public class TestSceneDirectPanel : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { diff --git a/osu.Game.Tests/Visual/Online/TestCaseExternalLinkButton.cs b/osu.Game.Tests/Visual/Online/TestSceneExternalLinkButton.cs similarity index 84% rename from osu.Game.Tests/Visual/Online/TestCaseExternalLinkButton.cs rename to osu.Game.Tests/Visual/Online/TestSceneExternalLinkButton.cs index a73cbd86d0..637b577021 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseExternalLinkButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneExternalLinkButton.cs @@ -8,11 +8,11 @@ using osuTK; namespace osu.Game.Tests.Visual.Online { - public class TestCaseExternalLinkButton : OsuTestCase + public class TestSceneExternalLinkButton : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(ExternalLinkButton) }; - public TestCaseExternalLinkButton() + public TestSceneExternalLinkButton() { Child = new ExternalLinkButton("https://osu.ppy.sh/home") { diff --git a/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs new file mode 100644 index 0000000000..6dc3428bff --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs @@ -0,0 +1,40 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Overlays; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Online +{ + [TestFixture] + public class TestSceneFullscreenOverlay : OsuTestScene + { + private FullscreenOverlay overlay; + + protected override void LoadComplete() + { + base.LoadComplete(); + + Add(overlay = new TestFullscreenOverlay()); + AddStep(@"toggle", overlay.ToggleVisibility); + } + + private class TestFullscreenOverlay : FullscreenOverlay + { + public TestFullscreenOverlay() + { + Children = new Drawable[] + { + new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }, + }; + } + } + } +} diff --git a/osu.Game.Tests/Visual/Online/TestCaseGraph.cs b/osu.Game.Tests/Visual/Online/TestSceneGraph.cs similarity index 94% rename from osu.Game.Tests/Visual/Online/TestCaseGraph.cs rename to osu.Game.Tests/Visual/Online/TestSceneGraph.cs index 77e850fc92..fa433571cf 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneGraph.cs @@ -10,9 +10,9 @@ using osuTK; namespace osu.Game.Tests.Visual.Online { [TestFixture] - public class TestCaseGraph : OsuTestCase + public class TestSceneGraph : OsuTestScene { - public TestCaseGraph() + public TestSceneGraph() { BarGraph graph; diff --git a/osu.Game.Tests/Visual/Online/TestCaseHistoricalSection.cs b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs similarity index 93% rename from osu.Game.Tests/Visual/Online/TestCaseHistoricalSection.cs rename to osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs index 92aa9320c8..455807649a 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseHistoricalSection.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs @@ -15,7 +15,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Online { [TestFixture] - public class TestCaseHistoricalSection : OsuTestCase + public class TestSceneHistoricalSection : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Online typeof(DrawableProfileRow) }; - public TestCaseHistoricalSection() + public TestSceneHistoricalSection() { HistoricalSection section; diff --git a/osu.Game.Tests/Visual/Online/TestCaseRankGraph.cs b/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs similarity index 80% rename from osu.Game.Tests/Visual/Online/TestCaseRankGraph.cs rename to osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs index dff018bf91..709e75ab13 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseRankGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs @@ -9,14 +9,14 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Profile.Header; +using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Users; using osuTK; namespace osu.Game.Tests.Visual.Online { [TestFixture] - public class TestCaseRankGraph : OsuTestCase + public class TestSceneRankGraph : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -24,13 +24,14 @@ namespace osu.Game.Tests.Visual.Online typeof(LineGraph) }; - public TestCaseRankGraph() + public TestSceneRankGraph() { RankGraph graph; var data = new int[89]; var dataWithZeros = new int[89]; var smallData = new int[89]; + var edgyData = new int[89]; for (int i = 0; i < 89; i++) data[i] = dataWithZeros[i] = (i + 1) * 1000; @@ -41,6 +42,14 @@ namespace osu.Game.Tests.Visual.Online for (int i = 79; i < 89; i++) smallData[i] = 100000 - i * 1000; + bool edge = true; + + for (int i = 0; i < 20; i++) + { + edgyData[i] = 100000 + (edge ? 1000 : -1000) * (i + 1); + edge = !edge; + } + Add(new Container { Anchor = Anchor.Centre, @@ -120,6 +129,22 @@ namespace osu.Game.Tests.Visual.Online } }; }); + + AddStep("graph with edges", () => + { + graph.User.Value = new User + { + Statistics = new UserStatistics + { + Ranks = new UserStatistics.UserRanks { Global = 12000 }, + PP = 12345, + }, + RankHistory = new User.RankHistoryData + { + Data = edgyData, + } + }; + }); } } } diff --git a/osu.Game.Tests/Visual/Online/TestCaseScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs similarity index 98% rename from osu.Game.Tests/Visual/Online/TestCaseScoresContainer.cs rename to osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 1ef4558691..6815018be6 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -18,7 +18,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Online { - public class TestCaseScoresContainer : OsuTestCase + public class TestSceneScoresContainer : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Online private readonly Box background; - public TestCaseScoresContainer() + public TestSceneScoresContainer() { ScoresContainer scoresContainer; diff --git a/osu.Game.Tests/Visual/Online/TestCaseSocial.cs b/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs similarity index 96% rename from osu.Game.Tests/Visual/Online/TestCaseSocial.cs rename to osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs index 48325713df..5cb96c7ed2 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseSocial.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs @@ -11,19 +11,18 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Online { [TestFixture] - public class TestCaseSocial : OsuTestCase + public class TestSceneSocialOverlay : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(UserPanel), typeof(SocialPanel), typeof(FilterControl), - typeof(SocialOverlay), typeof(SocialGridPanel), typeof(SocialListPanel) }; - public TestCaseSocial() + public TestSceneSocialOverlay() { SocialOverlay s = new SocialOverlay { diff --git a/osu.Game.Tests/Visual/Online/TestCaseStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs similarity index 96% rename from osu.Game.Tests/Visual/Online/TestCaseStandAloneChatDisplay.cs rename to osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 4c4b3b2612..91006bc0d9 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -9,7 +9,7 @@ using osuTK; namespace osu.Game.Tests.Visual.Online { - public class TestCaseStandAloneChatDisplay : OsuTestCase + public class TestSceneStandAloneChatDisplay : OsuTestScene { private readonly Channel testChannel = new Channel(); @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Online private readonly StandAloneChatDisplay chatDisplay; private readonly StandAloneChatDisplay chatDisplay2; - public TestCaseStandAloneChatDisplay() + public TestSceneStandAloneChatDisplay() { Add(channelManager); diff --git a/osu.Game.Tests/Visual/Online/TestCaseUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs similarity index 94% rename from osu.Game.Tests/Visual/Online/TestCaseUserPanel.cs rename to osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index b2877f7bd7..fca18a9263 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -10,9 +10,9 @@ using osuTK; namespace osu.Game.Tests.Visual.Online { [TestFixture] - public class TestCaseUserPanel : OsuTestCase + public class TestSceneUserPanel : OsuTestScene { - public TestCaseUserPanel() + public TestSceneUserPanel() { UserPanel flyte; UserPanel peppy; @@ -38,6 +38,7 @@ namespace osu.Game.Tests.Visual.Online Country = new Country { FlagName = @"AU" }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", IsSupporter = true, + SupportLevel = 3, }) { Width = 300 }, }, }); diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs new file mode 100644 index 0000000000..d9230090fc --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs @@ -0,0 +1,82 @@ +// 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.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays; +using osu.Game.Overlays.Profile; +using osu.Game.Overlays.Profile.Header; +using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneUserProfileHeader : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ProfileHeader), + typeof(RankGraph), + typeof(LineGraph), + typeof(OverlayHeaderTabControl), + typeof(CentreHeaderContainer), + typeof(BottomHeaderContainer), + typeof(DetailHeaderContainer), + typeof(ProfileHeaderButton) + }; + + [Resolved] + private IAPIProvider api { get; set; } + + private readonly ProfileHeader header; + + public TestSceneUserProfileHeader() + { + header = new ProfileHeader(); + Add(header); + + AddStep("Show offline dummy", () => header.User.Value = TestSceneUserProfileOverlay.TEST_USER); + + AddStep("Show null dummy", () => header.User.Value = new User + { + Username = "Null" + }); + + addOnlineStep("Show ppy", new User + { + Username = @"peppy", + Id = 2, + IsSupporter = true, + Country = new Country { FullName = @"Australia", FlagName = @"AU" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg" + }); + + addOnlineStep("Show flyte", new User + { + Username = @"flyte", + Id = 3103765, + Country = new Country { FullName = @"Japan", FlagName = @"JP" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" + }); + } + + private void addOnlineStep(string name, User fallback) + { + AddStep(name, () => + { + if (api.IsLoggedIn) + { + var request = new GetUserRequest(fallback.Id); + request.Success += user => header.User.Value = user; + api.Queue(request); + } + else + header.User.Value = fallback; + }); + } + } +} diff --git a/osu.Game.Tests/Visual/Online/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs similarity index 52% rename from osu.Game.Tests/Visual/Online/TestCaseUserProfile.cs rename to osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index 5b86de28f9..c2376aa153 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseUserProfile.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -6,76 +6,81 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Profile; -using osu.Game.Overlays.Profile.Header; +using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Users; namespace osu.Game.Tests.Visual.Online { [TestFixture] - public class TestCaseUserProfile : OsuTestCase + public class TestSceneUserProfileOverlay : OsuTestScene { private readonly TestUserProfileOverlay profile; - private IAPIProvider api; + + [Resolved] + private IAPIProvider api { get; set; } public override IReadOnlyList RequiredTypes => new[] { typeof(ProfileHeader), - typeof(UserProfileOverlay), typeof(RankGraph), typeof(LineGraph), - typeof(BadgeContainer) + typeof(SectionsContainer<>), + typeof(SupporterIcon) }; - public TestCaseUserProfile() + public static readonly User TEST_USER = new User + { + Username = @"Somebody", + Id = 1, + Country = new Country { FullName = @"Alien" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", + JoinDate = DateTimeOffset.Now.AddDays(-1), + LastVisit = DateTimeOffset.Now, + ProfileOrder = new[] { "me" }, + Statistics = new UserStatistics + { + Ranks = new UserStatistics.UserRanks { Global = 2148, Country = 1 }, + PP = 4567.89m, + Level = new UserStatistics.LevelInfo + { + Current = 727, + Progress = 69, + } + }, + RankHistory = new User.RankHistoryData + { + Mode = @"osu", + Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray() + }, + Badges = new[] + { + new Badge + { + AwardedAt = DateTimeOffset.FromUnixTimeSeconds(1505741569), + Description = "Outstanding help by being a voluntary test subject.", + ImageUrl = "https://assets.ppy.sh/profile-badges/contributor.jpg" + } + }, + Title = "osu!volunteer", + Colour = "ff0000", + Achievements = new User.UserAchievement[0], + }; + + public TestSceneUserProfileOverlay() { Add(profile = new TestUserProfileOverlay()); } - [BackgroundDependencyLoader] - private void load(IAPIProvider api) - { - this.api = api; - } - protected override void LoadComplete() { base.LoadComplete(); - AddStep("Show offline dummy", () => profile.ShowUser(new User - { - Username = @"Somebody", - Id = 1, - Country = new Country { FullName = @"Alien" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", - JoinDate = DateTimeOffset.Now.AddDays(-1), - LastVisit = DateTimeOffset.Now, - ProfileOrder = new[] { "me" }, - Statistics = new UserStatistics - { - Ranks = new UserStatistics.UserRanks { Global = 2148, Country = 1 }, - PP = 4567.89m, - }, - RankHistory = new User.RankHistoryData - { - Mode = @"osu", - Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray() - }, - Badges = new[] - { - new Badge - { - AwardedAt = DateTimeOffset.FromUnixTimeSeconds(1505741569), - Description = "Outstanding help by being a voluntary test subject.", - ImageUrl = "https://assets.ppy.sh/profile-badges/contributor.jpg" - } - } - }, false)); - - checkSupporterTag(false); + AddStep("Show offline dummy", () => profile.ShowUser(TEST_USER, false)); AddStep("Show null dummy", () => profile.ShowUser(new User { @@ -92,8 +97,6 @@ namespace osu.Game.Tests.Visual.Online CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg" }, api.IsLoggedIn)); - checkSupporterTag(true); - AddStep("Show flyte", () => profile.ShowUser(new User { Username = @"flyte", @@ -106,15 +109,6 @@ namespace osu.Game.Tests.Visual.Online AddStep("Show without reload", profile.Show); } - private void checkSupporterTag(bool isSupporter) - { - AddUntilStep("wait for load", () => profile.Header.User != null); - if (isSupporter) - AddAssert("is supporter", () => profile.Header.SupporterTag.Alpha == 1); - else - AddAssert("no supporter", () => profile.Header.SupporterTag.Alpha == 0); - } - private class TestUserProfileOverlay : UserProfileOverlay { public new ProfileHeader Header => base.Header; diff --git a/osu.Game.Tests/Visual/Online/TestCaseUserProfileRecentSection.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileRecentSection.cs similarity index 97% rename from osu.Game.Tests/Visual/Online/TestCaseUserProfileRecentSection.cs rename to osu.Game.Tests/Visual/Online/TestSceneUserProfileRecentSection.cs index 6b29ed1e85..d60e723102 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseUserProfileRecentSection.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileRecentSection.cs @@ -17,7 +17,7 @@ using osu.Game.Overlays.Profile.Sections.Recent; namespace osu.Game.Tests.Visual.Online { [TestFixture] - public class TestCaseUserProfileRecentSection : OsuTestCase + public class TestSceneUserProfileRecentSection : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Online typeof(MedalIcon) }; - public TestCaseUserProfileRecentSection() + public TestSceneUserProfileRecentSection() { Children = new Drawable[] { diff --git a/osu.Game.Tests/Visual/Online/TestCaseUserRanks.cs b/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs similarity index 94% rename from osu.Game.Tests/Visual/Online/TestCaseUserRanks.cs rename to osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs index 64257f8877..70118b5ebd 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseUserRanks.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs @@ -15,11 +15,11 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Online { [TestFixture] - public class TestCaseUserRanks : OsuTestCase + public class TestSceneUserRanks : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableProfileScore), typeof(RanksSection) }; - public TestCaseUserRanks() + public TestSceneUserRanks() { RanksSection ranks; diff --git a/osu.Game.Tests/Visual/Settings/TestCaseKeyConfiguration.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyConfiguration.cs similarity index 63% rename from osu.Game.Tests/Visual/Settings/TestCaseKeyConfiguration.cs rename to osu.Game.Tests/Visual/Settings/TestSceneKeyConfiguration.cs index ce179c21ba..d06d82ddb5 100644 --- a/osu.Game.Tests/Visual/Settings/TestCaseKeyConfiguration.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyConfiguration.cs @@ -7,19 +7,19 @@ using osu.Game.Overlays; namespace osu.Game.Tests.Visual.Settings { [TestFixture] - public class TestCaseKeyConfiguration : OsuTestCase + public class TestSceneKeyConfiguration : OsuTestScene { - private readonly KeyBindingOverlay overlay; + private readonly KeyBindingPanel panel; - public TestCaseKeyConfiguration() + public TestSceneKeyConfiguration() { - Child = overlay = new KeyBindingOverlay(); + Child = panel = new KeyBindingPanel(); } protected override void LoadComplete() { base.LoadComplete(); - overlay.Show(); + panel.Show(); } } } diff --git a/osu.Game.Tests/Visual/Settings/TestCaseSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettings.cs similarity index 81% rename from osu.Game.Tests/Visual/Settings/TestCaseSettings.cs rename to osu.Game.Tests/Visual/Settings/TestSceneSettings.cs index e846d5c020..964754f8d0 100644 --- a/osu.Game.Tests/Visual/Settings/TestCaseSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettings.cs @@ -9,14 +9,14 @@ using osu.Game.Overlays; namespace osu.Game.Tests.Visual.Settings { [TestFixture] - public class TestCaseSettings : OsuTestCase + public class TestSceneSettings : OsuTestScene { - private readonly SettingsOverlay settings; + private readonly SettingsPanel settings; private readonly DialogOverlay dialogOverlay; - public TestCaseSettings() + public TestSceneSettings() { - settings = new MainSettings + settings = new SettingsOverlay { State = Visibility.Visible }; diff --git a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs similarity index 99% rename from osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapCarousel.cs rename to osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 1500605896..7c9b7c7815 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -20,7 +20,7 @@ using osu.Game.Screens.Select.Filter; namespace osu.Game.Tests.Visual.SongSelect { [TestFixture] - public class TestCaseBeatmapCarousel : OsuTestCase + public class TestSceneBeatmapCarousel : OsuTestScene { private TestBeatmapCarousel carousel; private RulesetStore rulesets; @@ -508,6 +508,7 @@ namespace osu.Game.Tests.Visual.SongSelect }, Beatmaps = new List(), }; + for (int b = 1; b < 101; b++) { toReturn.Beatmaps.Add(new BeatmapInfo diff --git a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapDetailArea.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs similarity index 98% rename from osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapDetailArea.cs rename to osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs index 722a63f2b0..cf4362ba28 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs @@ -14,11 +14,11 @@ namespace osu.Game.Tests.Visual.SongSelect { [TestFixture] [System.ComponentModel.Description("PlaySongSelect leaderboard/details area")] - public class TestCaseBeatmapDetailArea : OsuTestCase + public class TestSceneBeatmapDetailArea : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(BeatmapDetails) }; - public TestCaseBeatmapDetailArea() + public TestSceneBeatmapDetailArea() { BeatmapDetailArea detailsArea; Add(detailsArea = new BeatmapDetailArea diff --git a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapDetails.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs similarity index 98% rename from osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapDetails.cs rename to osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs index 37987b8884..acbbd4e18b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapDetails.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs @@ -10,9 +10,9 @@ using osu.Game.Screens.Select; namespace osu.Game.Tests.Visual.SongSelect { [Description("PlaySongSelect beatmap details")] - public class TestCaseBeatmapDetails : OsuTestCase + public class TestSceneBeatmapDetails : OsuTestScene { - public TestCaseBeatmapDetails() + public TestSceneBeatmapDetails() { BeatmapDetails details; Add(details = new BeatmapDetails diff --git a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs similarity index 99% rename from osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapInfoWedge.cs rename to osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index f3e44bd808..b1ed5c46c2 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -24,7 +24,7 @@ using osuTK; namespace osu.Game.Tests.Visual.SongSelect { [TestFixture] - public class TestCaseBeatmapInfoWedge : OsuTestCase + public class TestSceneBeatmapInfoWedge : OsuTestScene { private RulesetStore rulesets; private TestBeatmapInfoWedge infoWedge; @@ -83,15 +83,19 @@ namespace osu.Game.Tests.Visual.SongSelect case OsuRuleset _: testInfoLabels(5); break; + case TaikoRuleset _: testInfoLabels(5); break; + case CatchRuleset _: testInfoLabels(5); break; + case ManiaRuleset _: testInfoLabels(4); break; + default: testInfoLabels(2); break; diff --git a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapOptionsOverlay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs similarity index 90% rename from osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapOptionsOverlay.cs rename to osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs index 7d09debbd6..ecdc484887 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapOptionsOverlay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs @@ -10,9 +10,9 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.SongSelect { [Description("bottom beatmap details")] - public class TestCaseBeatmapOptionsOverlay : OsuTestCase + public class TestSceneBeatmapOptionsOverlay : OsuTestScene { - public TestCaseBeatmapOptionsOverlay() + public TestSceneBeatmapOptionsOverlay() { var overlay = new BeatmapOptionsOverlay(); diff --git a/osu.Game.Tests/Visual/SongSelect/TestCaseLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs similarity index 99% rename from osu.Game.Tests/Visual/SongSelect/TestCaseLeaderboard.cs rename to osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs index 13ae6f228a..3d75470328 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestCaseLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs @@ -18,7 +18,7 @@ using osuTK; namespace osu.Game.Tests.Visual.SongSelect { [Description("PlaySongSelect leaderboard")] - public class TestCaseLeaderboard : OsuTestCase + public class TestSceneLeaderboard : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.SongSelect private readonly FailableLeaderboard leaderboard; - public TestCaseLeaderboard() + public TestSceneLeaderboard() { Add(leaderboard = new FailableLeaderboard { diff --git a/osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs similarity index 93% rename from osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs rename to osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index d5bc452d75..7e962dbc06 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -26,7 +26,7 @@ using osu.Game.Screens.Select.Filter; namespace osu.Game.Tests.Visual.SongSelect { [TestFixture] - public class TestCasePlaySongSelect : ScreenTestCase + public class TestScenePlaySongSelect : ScreenTestScene { private BeatmapManager manager; @@ -35,10 +35,6 @@ namespace osu.Game.Tests.Visual.SongSelect private WorkingBeatmap defaultBeatmap; private DatabaseContextFactory factory; - [Cached] - [Cached(Type = typeof(IBindable>))] - private readonly Bindable> selectedMods = new Bindable>(new Mod[] { }); - public override IReadOnlyList RequiredTypes => new[] { typeof(Screens.Select.SongSelect), @@ -175,19 +171,19 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("change ruleset", () => { - songSelect.CurrentBeatmap.Mods.ValueChanged += onModChange; + Mods.ValueChanged += onModChange; songSelect.Ruleset.ValueChanged += onRulesetChange; Ruleset.Value = new TaikoRuleset().RulesetInfo; - songSelect.CurrentBeatmap.Mods.ValueChanged -= onModChange; + Mods.ValueChanged -= onModChange; songSelect.Ruleset.ValueChanged -= onRulesetChange; }); AddAssert("mods changed before ruleset", () => modChangeIndex < rulesetChangeIndex); - AddAssert("empty mods", () => !selectedMods.Value.Any()); + AddAssert("empty mods", () => !Mods.Value.Any()); - void onModChange(ValueChangedEvent> e) => modChangeIndex = actionIndex++; + void onModChange(ValueChangedEvent> e) => modChangeIndex = actionIndex++; void onRulesetChange(ValueChangedEvent e) => rulesetChangeIndex = actionIndex--; } @@ -218,7 +214,7 @@ namespace osu.Game.Tests.Visual.SongSelect private static int importId; private int getImportId() => ++importId; - private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => selectedMods.Value = mods); + private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => Mods.Value = mods); private void changeRuleset(int id) => AddStep($"change ruleset to {id}", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == id)); diff --git a/osu.Game.Tests/Visual/TestCaseOsuGame.cs b/osu.Game.Tests/Visual/TestCaseOsuGame.cs deleted file mode 100644 index 9e649b92e4..0000000000 --- a/osu.Game.Tests/Visual/TestCaseOsuGame.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Platform; -using osu.Game.Screens.Menu; -using osuTK.Graphics; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseOsuGame : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] - { - typeof(OsuLogo), - }; - - [BackgroundDependencyLoader] - private void load(GameHost host) - { - OsuGame game = new OsuGame(); - game.SetHost(host); - - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - game - }; - } - } -} diff --git a/osu.Game.Tests/Visual/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/TestSceneOsuGame.cs new file mode 100644 index 0000000000..fcc3a3596f --- /dev/null +++ b/osu.Game.Tests/Visual/TestSceneOsuGame.cs @@ -0,0 +1,127 @@ +// 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.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Textures; +using osu.Framework.Platform; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Database; +using osu.Game.Graphics; +using osu.Game.Input; +using osu.Game.Input.Bindings; +using osu.Game.IO; +using osu.Game.Online.API; +using osu.Game.Online.Chat; +using osu.Game.Overlays; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Scoring; +using osu.Game.Screens.Menu; +using osu.Game.Skinning; +using osu.Game.Utils; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestSceneOsuGame : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OsuLogo), + }; + + private IReadOnlyList requiredGameDependencies => new[] + { + typeof(OsuGame), + typeof(RavenLogger), + typeof(OsuLogo), + typeof(IdleTracker), + typeof(OnScreenDisplay), + typeof(NotificationOverlay), + typeof(DirectOverlay), + typeof(SocialOverlay), + typeof(ChannelManager), + typeof(ChatOverlay), + typeof(SettingsOverlay), + typeof(UserProfileOverlay), + typeof(BeatmapSetOverlay), + typeof(LoginOverlay), + typeof(MusicController), + typeof(AccountCreationOverlay), + typeof(DialogOverlay), + typeof(ScreenshotManager) + }; + + private IReadOnlyList requiredGameBaseDependencies => new[] + { + typeof(OsuGameBase), + typeof(DatabaseContextFactory), + typeof(Bindable), + typeof(IBindable), + typeof(Bindable>), + typeof(IBindable>), + typeof(LargeTextureStore), + typeof(OsuConfigManager), + typeof(SkinManager), + typeof(ISkinSource), + typeof(IAPIProvider), + typeof(RulesetStore), + typeof(FileStore), + typeof(ScoreManager), + typeof(BeatmapManager), + typeof(KeyBindingStore), + typeof(SettingsStore), + typeof(RulesetConfigCache), + typeof(OsuColour), + typeof(IBindable), + typeof(Bindable), + typeof(GlobalActionContainer), + typeof(PreviewTrackManager), + }; + + [BackgroundDependencyLoader] + private void load(GameHost host, OsuGameBase gameBase) + { + OsuGame game = new OsuGame(); + game.SetHost(host); + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + game + }; + + AddUntilStep("wait for load", () => game.IsLoaded); + + AddAssert("check OsuGame DI members", () => + { + foreach (var type in requiredGameDependencies) + if (game.Dependencies.Get(type) == null) + throw new Exception($"{type} has not been cached"); + + return true; + }); + AddAssert("check OsuGameBase DI members", () => + { + foreach (var type in requiredGameBaseDependencies) + if (gameBase.Dependencies.Get(type) == null) + throw new Exception($"{type} has not been cached"); + + return true; + }); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs b/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs similarity index 94% rename from osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs rename to osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs index 0831228681..a68fd0ef40 100644 --- a/osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs +++ b/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs @@ -4,9 +4,9 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Game.Graphics.Sprites; using osu.Game.Screens; using osu.Game.Screens.Play; using osuTK.Graphics; @@ -14,7 +14,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual { [TestFixture] - public class TestCaseOsuScreenStack : OsuTestCase + public class TestSceneOsuScreenStack : OsuTestScene { private TestOsuScreenStack stack; @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load() { - AddInternal(new SpriteText + AddInternal(new OsuSpriteText { Text = screenText, Colour = Color4.White, diff --git a/osu.Game.Tests/Visual/Tournament/TestCaseDrawings.cs b/osu.Game.Tests/Visual/Tournament/TestSceneDrawings.cs similarity index 97% rename from osu.Game.Tests/Visual/Tournament/TestCaseDrawings.cs rename to osu.Game.Tests/Visual/Tournament/TestSceneDrawings.cs index 53fb60bcb6..995819f7ae 100644 --- a/osu.Game.Tests/Visual/Tournament/TestCaseDrawings.cs +++ b/osu.Game.Tests/Visual/Tournament/TestSceneDrawings.cs @@ -10,7 +10,7 @@ using osu.Game.Screens.Tournament.Teams; namespace osu.Game.Tests.Visual.Tournament { [Description("for tournament use")] - public class TestCaseDrawings : ScreenTestCase + public class TestSceneDrawings : ScreenTestScene { [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs deleted file mode 100644 index 74114b2e53..0000000000 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Drawables; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; -using osu.Game.Rulesets; -using osu.Game.Tests.Beatmaps.IO; - -namespace osu.Game.Tests.Visual.UserInterface -{ - public class TestCaseUpdateableBeatmapBackgroundSprite : OsuTestCase - { - private TestUpdateableBeatmapBackgroundSprite backgroundSprite; - - [Resolved] - private BeatmapManager beatmaps { get; set; } - - [BackgroundDependencyLoader] - private void load(OsuGameBase osu, IAPIProvider api, RulesetStore rulesets) - { - Bindable beatmapBindable = new Bindable(); - - var imported = ImportBeatmapTest.LoadOszIntoOsu(osu); - - Child = backgroundSprite = new TestUpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both }; - - backgroundSprite.Beatmap.BindTo(beatmapBindable); - - var req = new GetBeatmapSetRequest(1); - api.Queue(req); - - AddStep("load null beatmap", () => beatmapBindable.Value = null); - AddUntilStep("wait for cleanup...", () => backgroundSprite.ChildCount == 1); - AddStep("load imported beatmap", () => beatmapBindable.Value = imported.Beatmaps.First()); - AddUntilStep("wait for cleanup...", () => backgroundSprite.ChildCount == 1); - - if (api.IsLoggedIn) - { - AddUntilStep("wait for api response", () => req.Result != null); - AddStep("load online beatmap", () => beatmapBindable.Value = new BeatmapInfo - { - BeatmapSet = req.Result?.ToBeatmapSet(rulesets) - }); - AddUntilStep("wait for cleanup...", () => backgroundSprite.ChildCount == 1); - } - else - { - AddStep("online (login first)", () => { }); - } - } - - private class TestUpdateableBeatmapBackgroundSprite : UpdateableBeatmapBackgroundSprite - { - public int ChildCount => InternalChildren.Count; - } - } -} diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs similarity index 98% rename from osu.Game.Tests/Visual/UserInterface/TestCaseBeatSyncedContainer.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index dcd194e050..28f0cc027e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -20,11 +20,11 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestCaseBeatSyncedContainer : OsuTestCase + public class TestSceneBeatSyncedContainer : OsuTestScene { private readonly MusicController mc; - public TestCaseBeatSyncedContainer() + public TestSceneBeatSyncedContainer() { Clock = new FramedClock(); Clock.ProcessFrame(); diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseBreadcrumbs.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbs.cs similarity index 93% rename from osu.Game.Tests/Visual/UserInterface/TestCaseBreadcrumbs.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbs.cs index 5e09e0a5b9..554696765e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseBreadcrumbs.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbs.cs @@ -10,11 +10,11 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestCaseBreadcrumbs : OsuTestCase + public class TestSceneBreadcrumbs : OsuTestScene { private readonly BreadcrumbControl breadcrumbs; - public TestCaseBreadcrumbs() + public TestSceneBreadcrumbs() { Add(breadcrumbs = new BreadcrumbControl { diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseButtonSystem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs similarity index 88% rename from osu.Game.Tests/Visual/UserInterface/TestCaseButtonSystem.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs index 261e87ff07..c8cc864089 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseButtonSystem.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs @@ -14,7 +14,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestCaseButtonSystem : OsuTestCase + public class TestSceneButtonSystem : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.UserInterface typeof(Button) }; - public TestCaseButtonSystem() + public TestSceneButtonSystem() { OsuLogo logo; ButtonSystem buttons; @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.UserInterface RelativeSizeAxes = Axes.Both, }, buttons = new ButtonSystem(), - logo = new OsuLogo() + logo = new OsuLogo { RelativePositionAxes = Axes.Both } }; buttons.SetOsuLogo(logo); diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseContextMenu.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneContextMenu.cs similarity index 97% rename from osu.Game.Tests/Visual/UserInterface/TestCaseContextMenu.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneContextMenu.cs index 71cde787f9..53693d1b70 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseContextMenu.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneContextMenu.cs @@ -15,14 +15,14 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestCaseContextMenu : OsuTestCase + public class TestSceneContextMenu : OsuTestScene { private const int start_time = 0; private const int duration = 1000; private readonly Container container; - public TestCaseContextMenu() + public TestSceneContextMenu() { Add(new OsuContextMenuContainer { diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseCursors.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs similarity index 99% rename from osu.Game.Tests/Visual/UserInterface/TestCaseCursors.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs index 5f45d9ba4d..590ee4e720 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseCursors.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs @@ -17,12 +17,12 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestCaseCursors : ManualInputManagerTestCase + public class TestSceneCursors : ManualInputManagerTestScene { private readonly MenuCursorContainer menuCursorContainer; private readonly CustomCursorBox[] cursorBoxes = new CustomCursorBox[6]; - public TestCaseCursors() + public TestSceneCursors() { Child = menuCursorContainer = new MenuCursorContainer { diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseDialogOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs similarity index 96% rename from osu.Game.Tests/Visual/UserInterface/TestCaseDialogOverlay.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs index 8964d20564..a6ff3462d4 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseDialogOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs @@ -9,9 +9,9 @@ using osu.Game.Overlays.Dialog; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestCaseDialogOverlay : OsuTestCase + public class TestSceneDialogOverlay : OsuTestScene { - public TestCaseDialogOverlay() + public TestSceneDialogOverlay() { DialogOverlay overlay; diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseDrawableDate.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDrawableDate.cs similarity index 96% rename from osu.Game.Tests/Visual/UserInterface/TestCaseDrawableDate.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneDrawableDate.cs index e8662ce965..19097f33bb 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseDrawableDate.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDrawableDate.cs @@ -11,9 +11,9 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.UserInterface { - public class TestCaseDrawableDate : OsuTestCase + public class TestSceneDrawableDate : OsuTestScene { - public TestCaseDrawableDate() + public TestSceneDrawableDate() { Child = new FillFlowContainer { diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingBar.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingBar.cs new file mode 100644 index 0000000000..f92aae43d2 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingBar.cs @@ -0,0 +1,50 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +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.Tests.Visual.UserInterface +{ + public class TestSceneExpandingBar : OsuTestScene + { + public TestSceneExpandingBar() + { + Container container; + ExpandingBar expandingBar; + + Add(container = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Gray, + Alpha = 0.5f, + RelativeSizeAxes = Axes.Both, + }, + expandingBar = new ExpandingBar + { + Anchor = Anchor.Centre, + ExpandedSize = 10, + CollapsedSize = 2, + Colour = Color4.DeepSkyBlue, + } + } + }); + + AddStep(@"Collapse", () => expandingBar.Collapse()); + AddStep(@"Expand", () => expandingBar.Expand()); + AddSliderStep(@"Resize container", 1, 300, 150, value => container.ResizeTo(value)); + AddStep(@"Horizontal", () => expandingBar.RelativeSizeAxes = Axes.X); + AddStep(@"Anchor top", () => expandingBar.Anchor = Anchor.TopCentre); + AddStep(@"Vertical", () => expandingBar.RelativeSizeAxes = Axes.Y); + AddStep(@"Anchor left", () => expandingBar.Anchor = Anchor.CentreLeft); + } + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseHoldToConfirmOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs similarity index 94% rename from osu.Game.Tests/Visual/UserInterface/TestCaseHoldToConfirmOverlay.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs index 38dc4a11dc..7e6cf1285e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseHoldToConfirmOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs @@ -10,11 +10,11 @@ using osu.Game.Screens.Menu; namespace osu.Game.Tests.Visual.UserInterface { - public class TestCaseHoldToConfirmOverlay : OsuTestCase + public class TestSceneHoldToConfirmOverlay : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(ExitConfirmOverlay) }; - public TestCaseHoldToConfirmOverlay() + public TestSceneHoldToConfirmOverlay() { bool fired = false; diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseIconButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneIconButton.cs similarity index 97% rename from osu.Game.Tests/Visual/UserInterface/TestCaseIconButton.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneIconButton.cs index 6bb1347608..0c9ce50288 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseIconButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneIconButton.cs @@ -14,9 +14,9 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestCaseIconButton : OsuTestCase + public class TestSceneIconButton : OsuTestScene { - public TestCaseIconButton() + public TestSceneIconButton() { Child = new FillFlowContainer { diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseLabelledTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs similarity index 95% rename from osu.Game.Tests/Visual/UserInterface/TestCaseLabelledTextBox.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs index 781dfbdcc1..395905a30d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseLabelledTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs @@ -12,7 +12,7 @@ using osu.Game.Screens.Edit.Setup.Components.LabelledComponents; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestCaseLabelledTextBox : OsuTestCase + public class TestSceneLabelledTextBox : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseLoadingAnimation.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLoadingAnimation.cs similarity index 91% rename from osu.Game.Tests/Visual/UserInterface/TestCaseLoadingAnimation.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneLoadingAnimation.cs index 43f6f0e4db..b9a6d74f19 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseLoadingAnimation.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLoadingAnimation.cs @@ -9,9 +9,9 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.UserInterface { - public class TestCaseLoadingAnimation : GridTestCase + public class TestSceneLoadingAnimation : GridTestScene //todo: this should be an OsuTestScene { - public TestCaseLoadingAnimation() + public TestSceneLoadingAnimation() : base(2, 2) { LoadingAnimation loading; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs new file mode 100644 index 0000000000..54876dbbda --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs @@ -0,0 +1,299 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.MathUtils; +using osu.Framework.Testing; +using osu.Game.Graphics.Containers; +using osu.Game.Screens.Menu; +using osu.Game.Screens.Play; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneLogoTrackingContainer : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(PlayerLoader), + typeof(Player), + typeof(LogoTrackingContainer), + typeof(ButtonSystem), + typeof(ButtonSystemState), + typeof(Menu), + typeof(MainMenu) + }; + + private OsuLogo logo; + private TestLogoTrackingContainer trackingContainer; + private Container transferContainer; + private Box visualBox; + private Box transferContainerBox; + private Drawable logoFacade; + private bool randomPositions; + + private const float visual_box_size = 72; + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Clear facades", () => + { + Clear(); + Add(logo = new OsuLogo { Scale = new Vector2(0.15f), RelativePositionAxes = Axes.Both }); + trackingContainer = null; + transferContainer = null; + }); + } + + /// + /// Move the facade to 0,0, then move it to a random new location while the logo is still transforming to it. + /// Check if the logo is still tracking the facade. + /// + [Test] + public void TestMoveFacade() + { + AddToggleStep("Toggle move continuously", b => randomPositions = b); + AddStep("Add tracking containers", addFacadeContainers); + AddStep("Move facade to random position", moveLogoFacade); + waitForMove(); + AddAssert("Logo is tracking", () => trackingContainer.IsLogoTracking); + } + + /// + /// Check if the facade is removed from the container, the logo stops tracking. + /// + [Test] + public void TestRemoveFacade() + { + AddStep("Add tracking containers", addFacadeContainers); + AddStep("Move facade to random position", moveLogoFacade); + AddStep("Remove facade from FacadeContainer", removeFacade); + waitForMove(); + AddAssert("Logo is not tracking", () => !trackingContainer.IsLogoTracking); + } + + /// + /// Check if the facade gets added to a new container, tracking starts on the new facade. + /// + [Test] + public void TestTransferFacade() + { + AddStep("Add tracking containers", addFacadeContainers); + AddStep("Move facade to random position", moveLogoFacade); + AddStep("Remove facade from FacadeContainer", removeFacade); + AddStep("Transfer facade to a new container", () => + { + transferContainer.Add(logoFacade); + transferContainerBox.Colour = Color4.Tomato; + moveLogoFacade(); + }); + + waitForMove(); + AddAssert("Logo is tracking", () => trackingContainer.IsLogoTracking); + } + + /// + /// Add a facade to a flow container, move the logo to the center of the screen, then start tracking on the facade. + /// + [Test] + public void TestFlowContainer() + { + FillFlowContainer flowContainer; + + AddStep("Create new flow container with facade", () => + { + Add(trackingContainer = new TestLogoTrackingContainer + { + AutoSizeAxes = Axes.Both, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Child = flowContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Direction = FillDirection.Vertical, + } + }); + flowContainer.Children = new Drawable[] + { + new Box + { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Colour = Color4.Azure, + Size = new Vector2(visual_box_size) + }, + new Container + { + Alpha = 0.35f, + RelativeSizeAxes = Axes.None, + Size = new Vector2(visual_box_size), + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Children = new Drawable[] + { + visualBox = new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + }, + trackingContainer.LogoFacade, + } + }, + new Box + { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Colour = Color4.Azure, + Size = new Vector2(visual_box_size) + }, + }; + }); + + AddStep("Perform logo movements", () => + { + trackingContainer.StopTracking(); + logo.MoveTo(new Vector2(0.5f), 500, Easing.InOutExpo); + + visualBox.Colour = Color4.White; + + Scheduler.AddDelayed(() => + { + trackingContainer.StartTracking(logo, 1000, Easing.InOutExpo); + visualBox.Colour = Color4.Tomato; + }, 700); + }); + + waitForMove(8); + AddAssert("Logo is tracking", () => trackingContainer.IsLogoTracking); + } + + [Test] + public void TestSetFacadeSize() + { + bool failed = false; + + AddStep("Set up scenario", () => + { + failed = false; + addFacadeContainers(); + }); + + AddStep("Try setting facade size", () => + { + try + { + logoFacade.Size = new Vector2(0, 0); + } + catch (Exception e) + { + if (e is InvalidOperationException) + failed = true; + } + }); + + AddAssert("Exception thrown", () => failed); + } + + [Test] + public void TestSetMultipleContainers() + { + bool failed = false; + LogoTrackingContainer newContainer = new LogoTrackingContainer(); + + AddStep("Set up scenario", () => + { + failed = false; + newContainer = new LogoTrackingContainer(); + addFacadeContainers(); + moveLogoFacade(); + }); + + AddStep("Try tracking new container", () => + { + try + { + newContainer.StartTracking(logo); + } + catch (Exception e) + { + if (e is InvalidOperationException) + failed = true; + } + }); + + AddAssert("Exception thrown", () => failed); + } + + private void addFacadeContainers() + { + AddRange(new Drawable[] + { + trackingContainer = new TestLogoTrackingContainer + { + Alpha = 0.35f, + RelativeSizeAxes = Axes.None, + Size = new Vector2(visual_box_size), + Child = visualBox = new Box + { + Colour = Color4.Tomato, + RelativeSizeAxes = Axes.Both, + } + }, + transferContainer = new Container + { + Alpha = 0.35f, + RelativeSizeAxes = Axes.None, + Size = new Vector2(visual_box_size), + Child = transferContainerBox = new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + } + }, + }); + + trackingContainer.Add(logoFacade = trackingContainer.LogoFacade); + trackingContainer.StartTracking(logo, 1000); + } + + private void waitForMove(int count = 5) => AddWaitStep("Wait for transforms to finish", count); + + private void removeFacade() + { + trackingContainer.Remove(logoFacade); + visualBox.Colour = Color4.White; + moveLogoFacade(); + } + + private void moveLogoFacade() + { + if (logoFacade?.Transforms.Count == 0 && transferContainer?.Transforms.Count == 0) + { + Random random = new Random(); + trackingContainer.Delay(500).MoveTo(new Vector2(random.Next(0, (int)logo.Parent.DrawWidth), random.Next(0, (int)logo.Parent.DrawHeight)), 300); + transferContainer.Delay(500).MoveTo(new Vector2(random.Next(0, (int)logo.Parent.DrawWidth), random.Next(0, (int)logo.Parent.DrawHeight)), 300); + } + + if (randomPositions) + Schedule(moveLogoFacade); + } + + private class TestLogoTrackingContainer : LogoTrackingContainer + { + /// + /// Check that the logo is tracking the position of the facade, with an acceptable precision lenience. + /// + public bool IsLogoTracking => Precision.AlmostEquals(Logo.Position, ComputeLogoTrackingPosition()); + } + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMods.cs similarity index 98% rename from osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneMods.cs index aab44f7d92..2e36ba39ed 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMods.cs @@ -24,7 +24,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.UserInterface { [Description("mod select and icon display")] - public class TestCaseMods : OsuTestCase + public class TestSceneMods : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -253,7 +253,7 @@ namespace osu.Game.Tests.Visual.UserInterface private class TestModSelectOverlay : ModSelectOverlay { - public new Bindable> SelectedMods => base.SelectedMods; + public new Bindable> SelectedMods => base.SelectedMods; public ModButton GetModButton(Mod mod) { diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseMusicController.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMusicController.cs similarity index 89% rename from osu.Game.Tests/Visual/UserInterface/TestCaseMusicController.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneMusicController.cs index 644c7eb4fc..a62fd6467b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseMusicController.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMusicController.cs @@ -10,9 +10,9 @@ using osu.Game.Overlays; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestCaseMusicController : OsuTestCase + public class TestSceneMusicController : OsuTestScene { - public TestCaseMusicController() + public TestSceneMusicController() { Clock = new FramedClock(); diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs similarity index 96% rename from osu.Game.Tests/Visual/UserInterface/TestCaseNotificationOverlay.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index 4819597d22..71033fcd2f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -9,13 +9,14 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.MathUtils; +using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestCaseNotificationOverlay : OsuTestCase + public class TestSceneNotificationOverlay : OsuTestScene { private readonly NotificationOverlay manager; private readonly List progressingNotifications = new List(); @@ -30,7 +31,7 @@ namespace osu.Game.Tests.Visual.UserInterface typeof(Notification) }; - public TestCaseNotificationOverlay() + public TestSceneNotificationOverlay() { progressingNotifications.Clear(); @@ -40,7 +41,7 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.TopRight }); - SpriteText displayedCount = new SpriteText(); + SpriteText displayedCount = new OsuSpriteText(); Content.Add(displayedCount); @@ -86,12 +87,15 @@ namespace osu.Game.Tests.Visual.UserInterface case 0: sendHelloNotification(); break; + case 1: sendAmazingNotification(); break; + case 2: sendUploadProgress(); break; + case 3: sendDownloadProgress(); break; diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseOnScreenDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs similarity index 98% rename from osu.Game.Tests/Visual/UserInterface/TestCaseOnScreenDisplay.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs index 7ad42cb926..d900526c07 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseOnScreenDisplay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs @@ -11,7 +11,7 @@ using osu.Game.Overlays; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestCaseOnScreenDisplay : OsuTestCase + public class TestSceneOnScreenDisplay : OsuTestScene { [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseOsuIcon.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs similarity index 95% rename from osu.Game.Tests/Visual/UserInterface/TestCaseOsuIcon.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs index a57e11cb0c..2c2a28394c 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseOsuIcon.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Testing; using osu.Game.Graphics; using osuTK; using osuTK.Graphics; @@ -17,9 +16,9 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestCaseOsuIcon : TestCase + public class TestSceneOsuIcon : OsuTestScene { - public TestCaseOsuIcon() + public TestSceneOsuIcon() { FillFlowContainer flow; diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseParallaxContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneParallaxContainer.cs similarity index 91% rename from osu.Game.Tests/Visual/UserInterface/TestCaseParallaxContainer.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneParallaxContainer.cs index 5de4c3f41f..588b25c02d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseParallaxContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneParallaxContainer.cs @@ -8,9 +8,9 @@ using osu.Game.Screens.Backgrounds; namespace osu.Game.Tests.Visual.UserInterface { - public class TestCaseParallaxContainer : OsuTestCase + public class TestSceneParallaxContainer : OsuTestScene { - public TestCaseParallaxContainer() + public TestSceneParallaxContainer() { ParallaxContainer parallax; diff --git a/osu.Game.Tests/Visual/UserInterface/TestCasePopupDialog.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs similarity index 92% rename from osu.Game.Tests/Visual/UserInterface/TestCasePopupDialog.cs rename to osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs index 2f01f593c7..24140125e0 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCasePopupDialog.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs @@ -9,9 +9,9 @@ using osu.Game.Overlays.Dialog; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestCasePopupDialog : OsuTestCase + public class TestScenePopupDialog : OsuTestScene { - public TestCasePopupDialog() + public TestScenePopupDialog() { var popup = new PopupDialog { diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseScreenBreadcrumbControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenBreadcrumbControl.cs similarity index 97% rename from osu.Game.Tests/Visual/UserInterface/TestCaseScreenBreadcrumbControl.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneScreenBreadcrumbControl.cs index c92072eb71..9c83fdf96c 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseScreenBreadcrumbControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenBreadcrumbControl.cs @@ -16,12 +16,12 @@ using osuTK; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestCaseScreenBreadcrumbControl : OsuTestCase + public class TestSceneScreenBreadcrumbControl : OsuTestScene { private readonly ScreenBreadcrumbControl breadcrumbs; private readonly OsuScreenStack screenStack; - public TestCaseScreenBreadcrumbControl() + public TestSceneScreenBreadcrumbControl() { OsuSpriteText titleText; diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseTabControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneTabControl.cs similarity index 93% rename from osu.Game.Tests/Visual/UserInterface/TestCaseTabControl.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneTabControl.cs index 480dc73dde..a884741ff8 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseTabControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneTabControl.cs @@ -11,9 +11,9 @@ using osuTK; namespace osu.Game.Tests.Visual.UserInterface { [Description("SongSelect filter control")] - public class TestCaseTabControl : OsuTestCase + public class TestSceneTabControl : OsuTestScene { - public TestCaseTabControl() + public TestSceneTabControl() { OsuSpriteText text; OsuTabControl filter; diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseTwoLayerButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneTwoLayerButton.cs similarity index 79% rename from osu.Game.Tests/Visual/UserInterface/TestCaseTwoLayerButton.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneTwoLayerButton.cs index 8d3cc7a0f2..b9ed1a71cc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseTwoLayerButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneTwoLayerButton.cs @@ -7,9 +7,9 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Tests.Visual.UserInterface { [Description("mostly back button")] - public class TestCaseTwoLayerButton : OsuTestCase + public class TestSceneTwoLayerButton : OsuTestScene { - public TestCaseTwoLayerButton() + public TestSceneTwoLayerButton() { Add(new BackButton()); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs new file mode 100644 index 0000000000..23065629a6 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs @@ -0,0 +1,147 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Rulesets; +using osu.Game.Tests.Beatmaps.IO; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneUpdateableBeatmapBackgroundSprite : OsuTestScene + { + private BeatmapSetInfo testBeatmap; + private IAPIProvider api; + private RulesetStore rulesets; + + [Resolved] + private BeatmapManager beatmaps { get; set; } + + [BackgroundDependencyLoader] + private void load(OsuGameBase osu, IAPIProvider api, RulesetStore rulesets) + { + this.api = api; + this.rulesets = rulesets; + + testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu); + } + + [Test] + public void TestNullBeatmap() + { + TestUpdateableBeatmapBackgroundSprite background = null; + + AddStep("load null beatmap", () => Child = background = new TestUpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both }); + AddUntilStep("wait for load", () => background.ContentLoaded); + } + + [Test] + public void TestLocalBeatmap() + { + TestUpdateableBeatmapBackgroundSprite background = null; + + AddStep("load local beatmap", () => + { + Child = background = new TestUpdateableBeatmapBackgroundSprite + { + RelativeSizeAxes = Axes.Both, + Beatmap = { Value = testBeatmap.Beatmaps.First() } + }; + }); + + AddUntilStep("wait for load", () => background.ContentLoaded); + } + + [Test] + public void TestOnlineBeatmap() + { + if (api.IsLoggedIn) + { + var req = new GetBeatmapSetRequest(1); + api.Queue(req); + + AddUntilStep("wait for api response", () => req.Result != null); + + TestUpdateableBeatmapBackgroundSprite background = null; + + AddStep("load online beatmap", () => + { + Child = background = new TestUpdateableBeatmapBackgroundSprite + { + RelativeSizeAxes = Axes.Both, + Beatmap = { Value = new BeatmapInfo { BeatmapSet = req.Result?.ToBeatmapSet(rulesets) } } + }; + }); + + AddUntilStep("wait for load", () => background.ContentLoaded); + } + else + AddStep("online (login first)", () => { }); + } + + [Test] + public void TestUnloadAndReload() + { + var backgrounds = new List(); + ScrollContainer scrollContainer = null; + + AddStep("create backgrounds hierarchy", () => + { + FillFlowContainer backgroundFlow; + + Child = scrollContainer = new ScrollContainer + { + Size = new Vector2(500), + Child = backgroundFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + Padding = new MarginPadding { Bottom = 550 } + } + }; + + for (int i = 0; i < 25; i++) + { + var background = new TestUpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both }; + + if (i % 2 == 0) + background.Beatmap.Value = testBeatmap.Beatmaps.First(); + + backgroundFlow.Add(new Container + { + RelativeSizeAxes = Axes.X, + Height = 100, + Masking = true, + Child = background + }); + + backgrounds.Add(background); + } + }); + + var loadedBackgrounds = backgrounds.Where(b => b.ContentLoaded); + + AddUntilStep("some loaded", () => loadedBackgrounds.Any()); + AddStep("scroll to bottom", () => scrollContainer.ScrollToEnd()); + AddUntilStep("all unloaded", () => !loadedBackgrounds.Any()); + } + + private class TestUpdateableBeatmapBackgroundSprite : UpdateableBeatmapBackgroundSprite + { + protected override double UnloadDelay => 2000; + + public bool ContentLoaded => ((DelayedLoadUnloadWrapper)InternalChildren.LastOrDefault())?.Content?.IsLoaded ?? false; + } + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseVolumePieces.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneVolumePieces.cs similarity index 95% rename from osu.Game.Tests/Visual/UserInterface/TestCaseVolumePieces.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneVolumePieces.cs index 3ad1c922e4..2fe6240b22 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseVolumePieces.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneVolumePieces.cs @@ -10,7 +10,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.UserInterface { - public class TestCaseVolumePieces : OsuTestCase + public class TestSceneVolumePieces : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(VolumeMeter), typeof(MuteButton) }; diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs index 2028671b0e..f66b374cd7 100644 --- a/osu.Game.Tests/WaveformTestBeatmap.cs +++ b/osu.Game.Tests/WaveformTestBeatmap.cs @@ -13,7 +13,7 @@ using osu.Game.Tests.Resources; namespace osu.Game.Tests { /// - /// A that is used for testcases that include waveforms. + /// A that is used for test scenes that include waveforms. /// public class WaveformTestBeatmap : WorkingBeatmap { diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 938e1ae0f8..11d70ee7be 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -3,8 +3,8 @@ - - + + diff --git a/osu.Game/Audio/IPreviewTrackOwner.cs b/osu.Game/Audio/IPreviewTrackOwner.cs index fdcae65e3c..8ab93257a5 100644 --- a/osu.Game/Audio/IPreviewTrackOwner.cs +++ b/osu.Game/Audio/IPreviewTrackOwner.cs @@ -4,7 +4,7 @@ namespace osu.Game.Audio { /// - /// Interface for objects that can own s. + /// Interface for objects that can own s. /// /// /// s can cancel the currently playing through the diff --git a/osu.Game/Audio/PreviewTrack.cs b/osu.Game/Audio/PreviewTrack.cs index 3b21bdefc4..22ce7d4711 100644 --- a/osu.Game/Audio/PreviewTrack.cs +++ b/osu.Game/Audio/PreviewTrack.cs @@ -28,7 +28,8 @@ namespace osu.Game.Audio private void load() { track = GetTrack(); - track.Completed += () => Schedule(Stop); + if (track != null) + track.Completed += () => Schedule(Stop); } /// @@ -56,19 +57,25 @@ namespace osu.Game.Audio /// /// Starts playing this . /// - public void Start() => startDelegate = Schedule(() => + /// Whether the track is started or already playing. + public bool Start() { if (track == null) - return; + return false; - if (hasStarted) - return; + startDelegate = Schedule(() => + { + if (hasStarted) + return; - hasStarted = true; + hasStarted = true; - track.Restart(); - Started?.Invoke(); - }); + track.Restart(); + Started?.Invoke(); + }); + + return true; + } /// /// Stops playing this . diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index b6fa6674f6..7922843626 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -98,7 +98,7 @@ namespace osu.Game.Beatmaps protected abstract IEnumerable ValidConversionTypes { get; } /// - /// Creates the that will be returned by this . + /// Creates the that will be returned by this . /// protected virtual Beatmap CreateBeatmap() => new Beatmap(); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 9caa64ec96..798bca3ada 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -117,6 +117,7 @@ namespace osu.Game.Beatmaps if (beatmapSet.OnlineBeatmapSetID != null) { var existingOnlineId = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineBeatmapSetID == beatmapSet.OnlineBeatmapSetID); + if (existingOnlineId != null) { Delete(existingOnlineId); @@ -217,7 +218,7 @@ namespace osu.Game.Beatmaps { request.Perform(api); } - catch (Exception e) + catch { // no need to handle here as exceptions will filter down to request.Failure above. } @@ -325,6 +326,7 @@ namespace osu.Game.Beatmaps { // let's make sure there are actually .osu files to import. string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu")); + if (string.IsNullOrEmpty(mapName)) { Logger.Log($"No beatmap files found in the beatmap archive ({reader.Name}).", LoggingTarget.Database); @@ -382,7 +384,6 @@ namespace osu.Game.Beatmaps /// Query the API to populate missing values like OnlineBeatmapID / OnlineBeatmapSetID or (Rank-)Status. /// /// The beatmap to populate. - /// The other beatmaps contained within this set. /// Whether to re-query if the provided beatmap already has populated values. /// True if population was successful. private bool fetchAndPopulateOnlineValues(BeatmapInfo beatmap, bool force = false) diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index a2e43e5a97..0bdab22dd2 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -101,6 +101,7 @@ namespace osu.Game.Beatmaps protected override Storyboard GetStoryboard() { Storyboard storyboard; + try { using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) @@ -131,6 +132,7 @@ namespace osu.Game.Beatmaps protected override Skin GetSkin() { Skin skin; + try { skin = new LegacyBeatmapSkin(BeatmapInfo, store, audioManager); diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index f4b7b1d74f..a2279fdb14 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -65,7 +65,6 @@ namespace osu.Game.Beatmaps protected override IQueryable AddIncludesForDeletion(IQueryable query) => base.AddIncludesForDeletion(query) .Include(s => s.Metadata) - .Include(s => s.Beatmaps).ThenInclude(b => b.Scores) .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata); diff --git a/osu.Game/Beatmaps/BindableBeatmap.cs b/osu.Game/Beatmaps/BindableBeatmap.cs index 657dc06297..27bad65062 100644 --- a/osu.Game/Beatmaps/BindableBeatmap.cs +++ b/osu.Game/Beatmaps/BindableBeatmap.cs @@ -12,7 +12,7 @@ namespace osu.Game.Beatmaps { /// /// A for the beatmap. - /// This should be used sparingly in-favour of . + /// This should be used sparingly in-favour of . /// public abstract class BindableBeatmap : NonNullableBindable { @@ -67,6 +67,6 @@ namespace osu.Game.Beatmaps /// If you are further binding to events of the retrieved , ensure a local reference is held. /// [NotNull] - public abstract BindableBeatmap GetBoundCopy(); + public new abstract BindableBeatmap GetBoundCopy(); } } diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index 825b60ae5f..abe7e5e803 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -12,17 +12,14 @@ namespace osu.Game.Beatmaps.ControlPoints /// public double Time; + /// + /// Whether this timing point was generated internally, as opposed to parsed from the underlying beatmap. + /// + internal bool AutoGenerated; + public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time); - /// - /// Whether this provides the same parametric changes as another . - /// Basically an equality check without considering the . - /// - /// The to compare to. - /// Whether this is equivalent to . - public virtual bool EquivalentTo(ControlPoint other) => true; - public bool Equals(ControlPoint other) - => EquivalentTo(other) && Time.Equals(other?.Time); + => Time.Equals(other?.Time); } } diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 013271d597..a3e3121575 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -1,11 +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 osuTK; namespace osu.Game.Beatmaps.ControlPoints { - public class DifficultyControlPoint : ControlPoint + public class DifficultyControlPoint : ControlPoint, IEquatable { /// /// The speed multiplier at this control point. @@ -18,9 +19,8 @@ namespace osu.Game.Beatmaps.ControlPoints private double speedMultiplier = 1; - public override bool EquivalentTo(ControlPoint other) - => base.EquivalentTo(other) - && other is DifficultyControlPoint difficulty - && SpeedMultiplier.Equals(difficulty.SpeedMultiplier); + public bool Equals(DifficultyControlPoint other) + => base.Equals(other) + && SpeedMultiplier.Equals(other?.SpeedMultiplier); } } diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index 3978b7b4b0..354d86dc13 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -1,9 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; + namespace osu.Game.Beatmaps.ControlPoints { - public class EffectControlPoint : ControlPoint + public class EffectControlPoint : ControlPoint, IEquatable { /// /// Whether this control point enables Kiai mode. @@ -15,10 +17,8 @@ namespace osu.Game.Beatmaps.ControlPoints /// public bool OmitFirstBarLine; - public override bool EquivalentTo(ControlPoint other) - => base.EquivalentTo(other) - && other is EffectControlPoint effect - && KiaiMode.Equals(effect.KiaiMode) - && OmitFirstBarLine.Equals(effect.OmitFirstBarLine); + public bool Equals(EffectControlPoint other) + => base.Equals(other) + && KiaiMode == other?.KiaiMode && OmitFirstBarLine == other.OmitFirstBarLine; } } diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index 241ce90740..4c45bef862 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -1,11 +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 osu.Game.Audio; namespace osu.Game.Beatmaps.ControlPoints { - public class SampleControlPoint : ControlPoint + public class SampleControlPoint : ControlPoint, IEquatable { public const string DEFAULT_BANK = "normal"; @@ -44,10 +45,8 @@ namespace osu.Game.Beatmaps.ControlPoints return newSampleInfo; } - public override bool EquivalentTo(ControlPoint other) - => base.EquivalentTo(other) - && other is SampleControlPoint sample - && SampleBank.Equals(sample.SampleBank) - && SampleVolume.Equals(sample.SampleVolume); + public bool Equals(SampleControlPoint other) + => base.Equals(other) + && string.Equals(SampleBank, other?.SampleBank) && SampleVolume == other?.SampleVolume; } } diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 9ec27bdfdf..e5815a3f3b 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -1,12 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osuTK; using osu.Game.Beatmaps.Timing; namespace osu.Game.Beatmaps.ControlPoints { - public class TimingControlPoint : ControlPoint + public class TimingControlPoint : ControlPoint, IEquatable { /// /// The time signature at this control point. @@ -24,10 +25,8 @@ namespace osu.Game.Beatmaps.ControlPoints private double beatLength = 1000; - public override bool EquivalentTo(ControlPoint other) - => base.EquivalentTo(other) - && other is TimingControlPoint timing - && TimeSignature.Equals(timing.TimeSignature) - && BeatLength.Equals(timing.BeatLength); + public bool Equals(TimingControlPoint other) + => base.Equals(other) + && TimeSignature == other?.TimeSignature && beatLength.Equals(other.beatLength); } } diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs index 3adfcb85ea..d0db7765c2 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs @@ -32,9 +32,11 @@ namespace osu.Game.Beatmaps.Drawables case BeatmapSetCoverType.Cover: resource = set.OnlineInfo.Covers.Cover; break; + case BeatmapSetCoverType.Card: resource = set.OnlineInfo.Covers.Card; break; + case BeatmapSetCoverType.List: resource = set.OnlineInfo.Covers.List; break; diff --git a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs index f1607ad749..26ffcca1ec 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs @@ -63,15 +63,20 @@ namespace osu.Game.Beatmaps.Drawables { case DifficultyRating.Easy: return palette.Green; + default: case DifficultyRating.Normal: return palette.Blue; + case DifficultyRating.Hard: return palette.Yellow; + case DifficultyRating.Insane: return palette.Pink; + case DifficultyRating.Expert: return palette.Purple; + case DifficultyRating.ExpertPlus: return palette.Gray0; } diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 1be7411bec..0a0ad28fdf 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -6,6 +6,7 @@ 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.Game.Graphics.Containers; diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs index ce7811fc0d..96786f5f49 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs @@ -26,7 +26,13 @@ namespace osu.Game.Beatmaps.Drawables this.beatmapSetCoverType = beatmapSetCoverType; } + /// + /// Delay before the background is unloaded while off-screen. + /// + protected virtual double UnloadDelay => 10000; + private BeatmapInfo lastModel; + private bool firstLoad = true; protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Drawable content, double timeBeforeLoad) { @@ -34,13 +40,14 @@ namespace osu.Game.Beatmaps.Drawables { // If DelayedLoadUnloadWrapper is attempting to RELOAD the same content (Beatmap), that means that it was // previously UNLOADED and thus its children have been disposed of, so we need to recreate them here. - if (lastModel == Beatmap.Value && Beatmap.Value != null) + if (!firstLoad && lastModel == Beatmap.Value) return CreateDrawable(Beatmap.Value); // If the model has changed since the previous unload (or if there was no load), then we can safely use the given content lastModel = Beatmap.Value; + firstLoad = false; return content; - }, timeBeforeLoad, 10000); + }, timeBeforeLoad, UnloadDelay); } protected override Drawable CreateDrawable(BeatmapInfo model) diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 73aa12a3db..7d25ca3ede 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using osu.Framework.Audio.Track; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Textures; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; @@ -52,7 +53,7 @@ namespace osu.Game.Beatmaps { public override IEnumerable GetModsFor(ModType type) => new Mod[] { }; - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) { throw new NotImplementedException(); } @@ -73,9 +74,18 @@ namespace osu.Game.Beatmaps private class DummyBeatmapConverter : IBeatmapConverter { public event Action> ObjectConverted; + public IBeatmap Beatmap { get; set; } + public bool CanConvert => true; - public IBeatmap Convert() => Beatmap; + + public IBeatmap Convert() + { + foreach (var obj in Beatmap.HitObjects) + ObjectConverted?.Invoke(obj, obj.Yield()); + + return Beatmap; + } } } } diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs index a895ba3d63..953e50eadc 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -49,6 +49,7 @@ namespace osu.Game.Beatmaps.Formats throw new IOException(@"Unknown decoder type"); string line; + do { line = stream.ReadLine()?.Trim(); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index a27126ad9c..3cd425ea44 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -70,21 +70,27 @@ namespace osu.Game.Beatmaps.Formats case Section.General: handleGeneral(strippedLine); return; + case Section.Editor: handleEditor(strippedLine); return; + case Section.Metadata: handleMetadata(line); return; + case Section.Difficulty: handleDifficulty(strippedLine); return; + case Section.Events: handleEvent(strippedLine); return; + case Section.TimingPoints: handleTimingPoint(strippedLine); return; + case Section.HitObjects: handleHitObject(strippedLine); return; @@ -98,29 +104,37 @@ namespace osu.Game.Beatmaps.Formats var pair = SplitKeyVal(line); var metadata = beatmap.BeatmapInfo.Metadata; + switch (pair.Key) { case @"AudioFilename": metadata.AudioFile = FileSafety.PathStandardise(pair.Value); break; + case @"AudioLeadIn": beatmap.BeatmapInfo.AudioLeadIn = Parsing.ParseInt(pair.Value); break; + case @"PreviewTime": metadata.PreviewTime = getOffsetTime(Parsing.ParseInt(pair.Value)); break; + case @"Countdown": beatmap.BeatmapInfo.Countdown = Parsing.ParseInt(pair.Value) == 1; break; + case @"SampleSet": defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), pair.Value); break; + case @"SampleVolume": defaultSampleVolume = Parsing.ParseInt(pair.Value); break; + case @"StackLeniency": beatmap.BeatmapInfo.StackLeniency = Parsing.ParseFloat(pair.Value); break; + case @"Mode": beatmap.BeatmapInfo.RulesetID = Parsing.ParseInt(pair.Value); @@ -129,24 +143,30 @@ namespace osu.Game.Beatmaps.Formats case 0: parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion); break; + case 1: parser = new Rulesets.Objects.Legacy.Taiko.ConvertHitObjectParser(getOffsetTime(), FormatVersion); break; + case 2: parser = new Rulesets.Objects.Legacy.Catch.ConvertHitObjectParser(getOffsetTime(), FormatVersion); break; + case 3: parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser(getOffsetTime(), FormatVersion); break; } break; + case @"LetterboxInBreaks": beatmap.BeatmapInfo.LetterboxInBreaks = Parsing.ParseInt(pair.Value) == 1; break; + case @"SpecialStyle": beatmap.BeatmapInfo.SpecialStyle = Parsing.ParseInt(pair.Value) == 1; break; + case @"WidescreenStoryboard": beatmap.BeatmapInfo.WidescreenStoryboard = Parsing.ParseInt(pair.Value) == 1; break; @@ -162,15 +182,19 @@ namespace osu.Game.Beatmaps.Formats case @"Bookmarks": beatmap.BeatmapInfo.StoredBookmarks = pair.Value; break; + case @"DistanceSpacing": beatmap.BeatmapInfo.DistanceSpacing = Math.Max(0, Parsing.ParseDouble(pair.Value)); break; + case @"BeatDivisor": beatmap.BeatmapInfo.BeatDivisor = Parsing.ParseInt(pair.Value); break; + case @"GridSize": beatmap.BeatmapInfo.GridSize = Parsing.ParseInt(pair.Value); break; + case @"TimelineZoom": beatmap.BeatmapInfo.TimelineZoom = Math.Max(0, Parsing.ParseDouble(pair.Value)); break; @@ -182,35 +206,45 @@ namespace osu.Game.Beatmaps.Formats var pair = SplitKeyVal(line); var metadata = beatmap.BeatmapInfo.Metadata; + switch (pair.Key) { case @"Title": metadata.Title = pair.Value; break; + case @"TitleUnicode": metadata.TitleUnicode = pair.Value; break; + case @"Artist": metadata.Artist = pair.Value; break; + case @"ArtistUnicode": metadata.ArtistUnicode = pair.Value; break; + case @"Creator": metadata.AuthorString = pair.Value; break; + case @"Version": beatmap.BeatmapInfo.Version = pair.Value; break; + case @"Source": beatmap.BeatmapInfo.Metadata.Source = pair.Value; break; + case @"Tags": beatmap.BeatmapInfo.Metadata.Tags = pair.Value; break; + case @"BeatmapID": beatmap.BeatmapInfo.OnlineBeatmapID = Parsing.ParseInt(pair.Value); break; + case @"BeatmapSetID": beatmap.BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = Parsing.ParseInt(pair.Value) }; break; @@ -222,23 +256,29 @@ namespace osu.Game.Beatmaps.Formats var pair = SplitKeyVal(line); var difficulty = beatmap.BeatmapInfo.BaseDifficulty; + switch (pair.Key) { case @"HPDrainRate": difficulty.DrainRate = Parsing.ParseFloat(pair.Value); break; + case @"CircleSize": difficulty.CircleSize = Parsing.ParseFloat(pair.Value); break; + case @"OverallDifficulty": difficulty.OverallDifficulty = Parsing.ParseFloat(pair.Value); break; + case @"ApproachRate": difficulty.ApproachRate = Parsing.ParseFloat(pair.Value); break; + case @"SliderMultiplier": difficulty.SliderMultiplier = Parsing.ParseDouble(pair.Value); break; + case @"SliderTickRate": difficulty.SliderTickRate = Parsing.ParseDouble(pair.Value); break; @@ -259,6 +299,7 @@ namespace osu.Game.Beatmaps.Formats string filename = split[2].Trim('"'); beatmap.BeatmapInfo.Metadata.BackgroundFile = FileSafety.PathStandardise(filename); break; + case EventType.Break: double start = getOffsetTime(Parsing.ParseDouble(split[1])); @@ -308,6 +349,7 @@ namespace osu.Game.Beatmaps.Formats bool kiaiMode = false; bool omitFirstBarSignature = false; + if (split.Length >= 8) { EffectFlags effectFlags = (EffectFlags)Parsing.ParseInt(split[7]); @@ -332,14 +374,16 @@ namespace osu.Game.Beatmaps.Formats handleDifficultyControlPoint(new DifficultyControlPoint { Time = time, - SpeedMultiplier = speedMultiplier + SpeedMultiplier = speedMultiplier, + AutoGenerated = timingChange }); handleEffectControlPoint(new EffectControlPoint { Time = time, KiaiMode = kiaiMode, - OmitFirstBarLine = omitFirstBarSignature + OmitFirstBarLine = omitFirstBarSignature, + AutoGenerated = timingChange }); handleSampleControlPoint(new LegacySampleControlPoint @@ -347,7 +391,8 @@ namespace osu.Game.Beatmaps.Formats Time = time, SampleBank = stringSampleSet, SampleVolume = sampleVolume, - CustomSampleBank = customSampleBank + CustomSampleBank = customSampleBank, + AutoGenerated = timingChange }); } catch (FormatException) @@ -365,7 +410,14 @@ namespace osu.Game.Beatmaps.Formats var existing = beatmap.ControlPointInfo.TimingPointAt(newPoint.Time); if (existing.Time == newPoint.Time) + { + // autogenerated points should not replace non-autogenerated. + // this allows for incorrectly ordered timing points to still be correctly handled. + if (newPoint.AutoGenerated && !existing.AutoGenerated) + return; + beatmap.ControlPointInfo.TimingPoints.Remove(existing); + } beatmap.ControlPointInfo.TimingPoints.Add(newPoint); } @@ -374,11 +426,15 @@ namespace osu.Game.Beatmaps.Formats { var existing = beatmap.ControlPointInfo.DifficultyPointAt(newPoint.Time); - if (newPoint.EquivalentTo(existing)) - return; - if (existing.Time == newPoint.Time) + { + // autogenerated points should not replace non-autogenerated. + // this allows for incorrectly ordered timing points to still be correctly handled. + if (newPoint.AutoGenerated && !existing.AutoGenerated) + return; + beatmap.ControlPointInfo.DifficultyPoints.Remove(existing); + } beatmap.ControlPointInfo.DifficultyPoints.Add(newPoint); } @@ -387,11 +443,15 @@ namespace osu.Game.Beatmaps.Formats { var existing = beatmap.ControlPointInfo.EffectPointAt(newPoint.Time); - if (newPoint.EquivalentTo(existing)) - return; - if (existing.Time == newPoint.Time) + { + // autogenerated points should not replace non-autogenerated. + // this allows for incorrectly ordered timing points to still be correctly handled. + if (newPoint.AutoGenerated && !existing.AutoGenerated) + return; + beatmap.ControlPointInfo.EffectPoints.Remove(existing); + } beatmap.ControlPointInfo.EffectPoints.Add(newPoint); } @@ -400,11 +460,15 @@ namespace osu.Game.Beatmaps.Formats { var existing = beatmap.ControlPointInfo.SamplePointAt(newPoint.Time); - if (newPoint.EquivalentTo(existing)) - return; - if (existing.Time == newPoint.Time) + { + // autogenerated points should not replace non-autogenerated. + // this allows for incorrectly ordered timing points to still be correctly handled. + if (newPoint.AutoGenerated && !existing.AutoGenerated) + return; + beatmap.ControlPointInfo.SamplePoints.Remove(existing); + } beatmap.ControlPointInfo.SamplePoints.Add(newPoint); } diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 040f582e3b..7b7e0e7101 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -26,6 +26,7 @@ namespace osu.Game.Beatmaps.Formats Section section = Section.None; string line; + while ((line = stream.ReadLine()) != null) { if (ShouldSkipLine(line)) @@ -53,7 +54,7 @@ namespace osu.Game.Beatmaps.Formats } } - protected virtual bool ShouldSkipLine(string line) => string.IsNullOrWhiteSpace(line) || line.StartsWith("//", StringComparison.Ordinal); + protected virtual bool ShouldSkipLine(string line) => string.IsNullOrWhiteSpace(line) || line.AsSpan().TrimStart().StartsWith("//".AsSpan(), StringComparison.Ordinal); protected virtual void ParseLine(T output, Section section, string line) { @@ -95,7 +96,7 @@ namespace osu.Game.Beatmaps.Formats { colour = new Color4(byte.Parse(split[0]), byte.Parse(split[1]), byte.Parse(split[2]), split.Length == 4 ? byte.Parse(split[3]) : (byte)255); } - catch (Exception e) + catch { throw new InvalidOperationException(@"Color must be specified with 8-bit integer components"); } @@ -188,7 +189,7 @@ namespace osu.Game.Beatmaps.Formats Foreground = 3 } - internal class LegacySampleControlPoint : SampleControlPoint + internal class LegacySampleControlPoint : SampleControlPoint, IEquatable { public int CustomSampleBank; @@ -202,10 +203,9 @@ namespace osu.Game.Beatmaps.Formats return baseInfo; } - public override bool EquivalentTo(ControlPoint other) - => base.EquivalentTo(other) - && other is LegacySampleControlPoint legacy - && CustomSampleBank == legacy.CustomSampleBank; + public bool Equals(LegacySampleControlPoint other) + => base.Equals(other) + && CustomSampleBank == other?.CustomSampleBank; } } } diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 0f83edf034..f6e2bf6966 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -54,6 +54,7 @@ namespace osu.Game.Beatmaps.Formats case Section.Events: handleEvents(line); return; + case Section.Variables: handleVariables(line); return; @@ -65,6 +66,7 @@ namespace osu.Game.Beatmaps.Formats private void handleEvents(string line) { var depth = 0; + while (line.StartsWith(" ", StringComparison.Ordinal) || line.StartsWith("_", StringComparison.Ordinal)) { ++depth; @@ -94,8 +96,9 @@ namespace osu.Game.Beatmaps.Formats var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo); storyboardSprite = new StoryboardSprite(path, origin, new Vector2(x, y)); storyboard.GetLayer(layer).Add(storyboardSprite); - } break; + } + case EventType.Animation: { var layer = parseLayer(split[1]); @@ -108,8 +111,9 @@ namespace osu.Game.Beatmaps.Formats var loopType = split.Length > 8 ? (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]) : AnimationLoopType.LoopForever; storyboardSprite = new StoryboardAnimation(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType); storyboard.GetLayer(layer).Add(storyboardSprite); - } break; + } + case EventType.Sample: { var time = double.Parse(split[1], CultureInfo.InvariantCulture); @@ -117,8 +121,8 @@ namespace osu.Game.Beatmaps.Formats var path = cleanFilename(split[3]); var volume = split.Length > 4 ? float.Parse(split[4], CultureInfo.InvariantCulture) : 100; storyboard.GetLayer(layer).Add(new StoryboardSample(path, time, volume)); - } break; + } } } else @@ -127,6 +131,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup = storyboardSprite?.TimelineGroup; var commandType = split[0]; + switch (commandType) { case "T": @@ -138,6 +143,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber); } break; + case "L": { var startTime = double.Parse(split[1], CultureInfo.InvariantCulture); @@ -145,6 +151,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup = storyboardSprite?.AddLoop(startTime, loopCount); } break; + default: { if (string.IsNullOrEmpty(split[3])) @@ -163,6 +170,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue); } break; + case "S": { var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -170,6 +178,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue)); } break; + case "V": { var startX = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -179,6 +188,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY)); } break; + case "R": { var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -186,6 +196,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.Rotation.Add(easing, startTime, endTime, MathHelper.RadiansToDegrees(startValue), MathHelper.RadiansToDegrees(endValue)); } break; + case "M": { var startX = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -196,6 +207,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY); } break; + case "MX": { var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -203,6 +215,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue); } break; + case "MY": { var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -210,6 +223,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue); } break; + case "C": { var startRed = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -223,23 +237,28 @@ namespace osu.Game.Beatmaps.Formats new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1)); } break; + case "P": { var type = split[4]; + switch (type) { case "A": timelineGroup?.BlendingMode.Add(easing, startTime, endTime, BlendingMode.Additive, startTime == endTime ? BlendingMode.Additive : BlendingMode.Inherit); break; + case "H": timelineGroup?.FlipH.Add(easing, startTime, endTime, true, startTime == endTime); break; + case "V": timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime); break; } } break; + default: throw new InvalidDataException($@"Unknown command type: {commandType}"); } @@ -254,26 +273,36 @@ namespace osu.Game.Beatmaps.Formats private Anchor parseOrigin(string value) { var origin = (LegacyOrigins)Enum.Parse(typeof(LegacyOrigins), value); + switch (origin) { case LegacyOrigins.TopLeft: return Anchor.TopLeft; + case LegacyOrigins.TopCentre: return Anchor.TopCentre; + case LegacyOrigins.TopRight: return Anchor.TopRight; + case LegacyOrigins.CentreLeft: return Anchor.CentreLeft; + case LegacyOrigins.Centre: return Anchor.Centre; + case LegacyOrigins.CentreRight: return Anchor.CentreRight; + case LegacyOrigins.BottomLeft: return Anchor.BottomLeft; + case LegacyOrigins.BottomCentre: return Anchor.BottomCentre; + case LegacyOrigins.BottomRight: return Anchor.BottomRight; + default: return Anchor.TopLeft; } diff --git a/osu.Game/Beatmaps/Timing/BreakPeriod.cs b/osu.Game/Beatmaps/Timing/BreakPeriod.cs index 7cff54a058..856a5fefd4 100644 --- a/osu.Game/Beatmaps/Timing/BreakPeriod.cs +++ b/osu.Game/Beatmaps/Timing/BreakPeriod.cs @@ -29,5 +29,12 @@ namespace osu.Game.Beatmaps.Timing /// Whether the break has any effect. Breaks that are too short are culled before they are added to the beatmap. /// public bool HasEffect => Duration >= MIN_BREAK_DURATION; + + /// + /// Whether this break contains a specified time. + /// + /// The time to check in milliseconds. + /// Whether the time falls within this . + public bool Contains(double time) => time >= StartTime && time <= EndTime; } } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 2e36d87024..4b0720d867 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -11,7 +11,6 @@ using osu.Framework.IO.File; using System.IO; using System.Linq; using System.Threading; -using osu.Framework.Bindables; using osu.Game.IO.Serialization; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; @@ -28,16 +27,12 @@ namespace osu.Game.Beatmaps public readonly BeatmapMetadata Metadata; - public readonly Bindable> Mods = new Bindable>(new Mod[] { }); - protected WorkingBeatmap(BeatmapInfo beatmapInfo) { BeatmapInfo = beatmapInfo; BeatmapSetInfo = beatmapInfo.BeatmapSet; Metadata = beatmapInfo.Metadata ?? BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); - Mods.ValueChanged += _ => applyRateAdjustments(); - beatmap = new RecyclableLazy(() => { var b = GetBeatmap() ?? new Beatmap(); @@ -51,14 +46,7 @@ namespace osu.Game.Beatmaps return b; }); - track = new RecyclableLazy(() => - { - // we want to ensure that we always have a track, even if it's a fake one. - var t = GetTrack() ?? new VirtualBeatmapTrack(Beatmap); - applyRateAdjustments(t); - return t; - }); - + track = new RecyclableLazy(() => GetTrack() ?? new VirtualBeatmapTrack(Beatmap)); background = new RecyclableLazy(GetBackground, BackgroundStillValid); waveform = new RecyclableLazy(GetWaveform); storyboard = new RecyclableLazy(GetStoryboard); @@ -85,9 +73,10 @@ namespace osu.Game.Beatmaps /// /// /// The to create a playable for. + /// The s to apply to the . /// The converted . /// If could not be converted to . - public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset) + public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods) { var rulesetInstance = ruleset.CreateInstance(); @@ -98,19 +87,19 @@ namespace osu.Game.Beatmaps throw new BeatmapInvalidForRulesetException($"{nameof(Beatmaps.Beatmap)} can not be converted for the ruleset (ruleset: {ruleset.InstantiationInfo}, converter: {converter})."); // Apply conversion mods - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in mods.OfType()) mod.ApplyToBeatmapConverter(converter); // Convert IBeatmap converted = converter.Convert(); // Apply difficulty mods - if (Mods.Value.Any(m => m is IApplicableToDifficulty)) + if (mods.Any(m => m is IApplicableToDifficulty)) { converted.BeatmapInfo = converted.BeatmapInfo.Clone(); converted.BeatmapInfo.BaseDifficulty = converted.BeatmapInfo.BaseDifficulty.Clone(); - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in mods.OfType()) mod.ApplyToDifficulty(converted.BeatmapInfo.BaseDifficulty); } @@ -122,7 +111,7 @@ namespace osu.Game.Beatmaps foreach (var obj in converted.HitObjects) obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty); - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in mods.OfType()) foreach (var obj in converted.HitObjects) mod.ApplyToHitObject(obj); @@ -188,16 +177,6 @@ namespace osu.Game.Beatmaps /// public void RecycleTrack() => track.Recycle(); - private void applyRateAdjustments(Track t = null) - { - if (t == null && track.IsResultAvailable) t = Track; - if (t == null) return; - - t.ResetSpeedAdjustments(); - foreach (var mod in Mods.Value.OfType()) - mod.ApplyToClock(t); - } - public class RecyclableLazy { private Lazy lazy; diff --git a/osu.Game/Beatmaps/WorkingBeatmap_VirtualBeatmapTrack.cs b/osu.Game/Beatmaps/WorkingBeatmap_VirtualBeatmapTrack.cs index cb1be82c69..1e237a2b53 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap_VirtualBeatmapTrack.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap_VirtualBeatmapTrack.cs @@ -26,9 +26,11 @@ namespace osu.Game.Beatmaps case null: Length = excess_length; break; + case IHasEndTime endTime: Length = endTime.EndTime + excess_length; break; + default: Length = lastObject.StartTime + excess_length; break; diff --git a/osu.Game/Configuration/DatabasedConfigManager.cs b/osu.Game/Configuration/DatabasedConfigManager.cs index f547a7d3e1..d5cdd7e4bc 100644 --- a/osu.Game/Configuration/DatabasedConfigManager.cs +++ b/osu.Game/Configuration/DatabasedConfigManager.cs @@ -2,6 +2,7 @@ // 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.Configuration; using osu.Game.Rulesets; @@ -19,6 +20,8 @@ namespace osu.Game.Configuration private readonly RulesetInfo ruleset; + private readonly bool legacySettingsExist; + protected DatabasedConfigManager(SettingsStore settings, RulesetInfo ruleset = null, int? variant = null) { this.settings = settings; @@ -26,6 +29,7 @@ namespace osu.Game.Configuration this.variant = variant; databasedSettings = settings.Query(ruleset?.ID, variant); + legacySettingsExist = databasedSettings.Any(s => int.TryParse(s.Key, out var _)); InitialiseDefaults(); } @@ -43,7 +47,19 @@ namespace osu.Game.Configuration { base.AddBindable(lookup, bindable); - var setting = databasedSettings.Find(s => (int)s.Key == (int)(object)lookup); + if (legacySettingsExist) + { + var legacySetting = databasedSettings.Find(s => s.Key == ((int)(object)lookup).ToString()); + + if (legacySetting != null) + { + bindable.Parse(legacySetting.Value); + settings.Delete(legacySetting); + } + } + + var setting = databasedSettings.Find(s => s.Key == lookup.ToString()); + if (setting != null) { bindable.Parse(setting.Value); @@ -52,7 +68,7 @@ namespace osu.Game.Configuration { settings.Update(setting = new DatabasedSetting { - Key = lookup, + Key = lookup.ToString(), Value = bindable.Value, RulesetID = ruleset?.ID, Variant = variant, diff --git a/osu.Game/Configuration/DatabasedSetting.cs b/osu.Game/Configuration/DatabasedSetting.cs index d56ac49358..3e0a9ecd28 100644 --- a/osu.Game/Configuration/DatabasedSetting.cs +++ b/osu.Game/Configuration/DatabasedSetting.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.ComponentModel.DataAnnotations.Schema; @@ -16,11 +16,7 @@ namespace osu.Game.Configuration public int? Variant { get; set; } [Column("Key")] - public int IntKey - { - get => (int)Key; - private set => Key = value; - } + public string Key { get; set; } [Column("Value")] public string StringValue @@ -29,10 +25,9 @@ namespace osu.Game.Configuration set => Value = value; } - public object Key; public object Value; - public DatabasedSetting(object key, object value) + public DatabasedSetting(string key, object value) { Key = key; Value = value; diff --git a/osu.Game/Configuration/SettingsStore.cs b/osu.Game/Configuration/SettingsStore.cs index f15fd1f17b..f8c9bdeaf8 100644 --- a/osu.Game/Configuration/SettingsStore.cs +++ b/osu.Game/Configuration/SettingsStore.cs @@ -37,5 +37,11 @@ namespace osu.Game.Configuration SettingChanged?.Invoke(); } + + public void Delete(DatabasedSetting setting) + { + using (var usage = ContextFactory.GetForWrite()) + usage.Context.Remove(setting); + } } } diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 3805921ac2..54dbae9ddc 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -84,6 +84,7 @@ namespace osu.Game.Database private void flushEvents(bool perform) { Action[] events; + lock (queuedEvents) { events = queuedEvents.ToArray(); @@ -147,6 +148,7 @@ namespace osu.Game.Database List imported = new List(); int current = 0; + foreach (string path in paths) { if (notification.State == ProgressNotificationState.Cancelled) @@ -383,7 +385,7 @@ namespace osu.Game.Database /// Delete multiple items. /// This will post notifications tracking progress. /// - public void Delete(List items) + public void Delete(List items, bool silent = false) { if (items.Count == 0) return; @@ -394,7 +396,8 @@ namespace osu.Game.Database State = ProgressNotificationState.Active, }; - PostNotification?.Invoke(notification); + if (!silent) + PostNotification?.Invoke(notification); int i = 0; @@ -421,7 +424,7 @@ namespace osu.Game.Database /// Restore multiple items that were previously deleted. /// This will post notifications tracking progress. /// - public void Undelete(List items) + public void Undelete(List items, bool silent = false) { if (!items.Any()) return; @@ -432,7 +435,8 @@ namespace osu.Game.Database State = ProgressNotificationState.Active, }; - PostNotification?.Invoke(notification); + if (!silent) + PostNotification?.Invoke(notification); int i = 0; @@ -563,7 +567,7 @@ namespace osu.Game.Database /// /// Check whether an existing model already exists for a new import item. /// - /// The new model proposed for import. + /// The new model proposed for import. /// An existing model which matches the criteria to skip importing, else null. protected TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash); diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index f6250732d9..554337c477 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; using System.Threading; using Microsoft.EntityFrameworkCore.Storage; @@ -67,7 +66,7 @@ namespace osu.Game.Database context = threadContexts.Value; } } - catch (Exception e) + catch { // retrieval of a context could trigger a fatal error. Monitor.Exit(writeLock); diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 17efe2c839..d31d7cbff7 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -60,6 +60,7 @@ namespace osu.Game.Database this.connectionString = connectionString; var connection = Database.GetDbConnection(); + try { connection.Open(); @@ -70,7 +71,7 @@ namespace osu.Game.Database cmd.ExecuteNonQuery(); } } - catch (Exception e) + catch { connection.Close(); throw; @@ -170,9 +171,11 @@ namespace osu.Game.Database default: frameworkLogLevel = Framework.Logging.LogLevel.Debug; break; + case LogLevel.Warning: frameworkLogLevel = Framework.Logging.LogLevel.Important; break; + case LogLevel.Error: case LogLevel.Critical: frameworkLogLevel = Framework.Logging.LogLevel.Error; diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index c67d779c37..e2c7693700 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -178,64 +178,68 @@ namespace osu.Game.Graphics.Backgrounds /// The colour. protected virtual Color4 CreateTriangleShade() => Interpolation.ValueAt(RNG.NextSingle(), ColourDark, ColourLight, 0, 1); - protected override DrawNode CreateDrawNode() => new TrianglesDrawNode(); - - protected override void ApplyDrawNode(DrawNode node) - { - base.ApplyDrawNode(node); - - var trianglesNode = (TrianglesDrawNode)node; - - trianglesNode.Shader = shader; - trianglesNode.Texture = texture; - trianglesNode.Size = DrawSize; - - trianglesNode.Parts.Clear(); - trianglesNode.Parts.AddRange(parts); - } + protected override DrawNode CreateDrawNode() => new TrianglesDrawNode(this); private class TrianglesDrawNode : DrawNode { - public IShader Shader; - public Texture Texture; + protected new Triangles Source => (Triangles)base.Source; - public readonly List Parts = new List(); - public Vector2 Size; + private IShader shader; + private Texture texture; + + private readonly List parts = new List(); + private Vector2 size; private readonly LinearBatch vertexBatch = new LinearBatch(100 * 3, 10, PrimitiveType.Triangles); + public TrianglesDrawNode(Triangles source) + : base(source) + { + } + + public override void ApplyState() + { + base.ApplyState(); + + shader = Source.shader; + texture = Source.texture; + size = Source.DrawSize; + + parts.Clear(); + parts.AddRange(Source.parts); + } + public override void Draw(Action vertexAction) { base.Draw(vertexAction); - Shader.Bind(); - Texture.TextureGL.Bind(); + shader.Bind(); + texture.TextureGL.Bind(); Vector2 localInflationAmount = edge_smoothness * DrawInfo.MatrixInverse.ExtractScale().Xy; - foreach (TriangleParticle particle in Parts) + foreach (TriangleParticle particle in parts) { var offset = triangle_size * new Vector2(particle.Scale * 0.5f, particle.Scale * 0.866f); - var size = new Vector2(2 * offset.X, offset.Y); var triangle = new Triangle( - Vector2Extensions.Transform(particle.Position * Size, DrawInfo.Matrix), - Vector2Extensions.Transform(particle.Position * Size + offset, DrawInfo.Matrix), - Vector2Extensions.Transform(particle.Position * Size + new Vector2(-offset.X, offset.Y), DrawInfo.Matrix) + Vector2Extensions.Transform(particle.Position * size, DrawInfo.Matrix), + Vector2Extensions.Transform(particle.Position * size + offset, DrawInfo.Matrix), + Vector2Extensions.Transform(particle.Position * size + new Vector2(-offset.X, offset.Y), DrawInfo.Matrix) ); ColourInfo colourInfo = DrawColourInfo.Colour; colourInfo.ApplyChild(particle.Colour); - Texture.DrawTriangle( + texture.DrawTriangle( triangle, colourInfo, null, vertexBatch.AddAction, - Vector2.Divide(localInflationAmount, size)); + Vector2.Divide(localInflationAmount, new Vector2(2 * offset.X, offset.Y))); } - Shader.Unbind(); + shader.Unbind(); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs b/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs index c1811f37d5..9d3f342a70 100644 --- a/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs +++ b/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osuTK; namespace osu.Game.Graphics.Containers @@ -34,6 +35,7 @@ namespace osu.Game.Graphics.Containers protected override void Update() { base.Update(); + if (InternalChildren.Count > 0 && InternalChild.DrawSize.X > 0) { // We're modifying scale here for a few reasons diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index eefbeea24c..15068d81c0 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -52,6 +52,7 @@ namespace osu.Game.Graphics.Containers } int previousLinkEnd = 0; + foreach (var link in links) { AddText(text.Substring(previousLinkEnd, link.Index - previousLinkEnd)); @@ -94,33 +95,39 @@ namespace osu.Game.Graphics.Containers if (linkArgument != null && int.TryParse(linkArgument.Contains('?') ? linkArgument.Split('?')[0] : linkArgument, out int beatmapId)) game?.ShowBeatmap(beatmapId); break; + case LinkAction.OpenBeatmapSet: if (int.TryParse(linkArgument, out int setId)) game?.ShowBeatmapSet(setId); break; + case LinkAction.OpenChannel: try { channelManager?.OpenChannel(linkArgument); } - catch (ChannelNotFoundException e) + catch (ChannelNotFoundException) { Logger.Log($"The requested channel \"{linkArgument}\" does not exist"); } break; + case LinkAction.OpenEditorTimestamp: case LinkAction.JoinMultiplayerMatch: case LinkAction.Spectate: showNotImplementedError?.Invoke(); break; + case LinkAction.External: game?.OpenUrlExternally(url); break; + case LinkAction.OpenUserProfile: if (long.TryParse(linkArgument, out long userId)) game?.ShowUser(userId); break; + default: throw new NotImplementedException($"This {nameof(LinkAction)} ({linkType.ToString()}) is missing an associated action."); } diff --git a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs new file mode 100644 index 0000000000..23015e8bf5 --- /dev/null +++ b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs @@ -0,0 +1,157 @@ +// 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.MathUtils; +using osu.Game.Screens.Menu; +using osuTK; + +namespace osu.Game.Graphics.Containers +{ + /// + /// A container that handles tracking of an through different layout scenarios. + /// + public class LogoTrackingContainer : Container + { + public Facade LogoFacade => facade; + + protected OsuLogo Logo { get; private set; } + + private readonly InternalFacade facade = new InternalFacade(); + + private Easing easing; + private Vector2? startPosition; + private double? startTime; + private double duration; + + /// + /// Assign the logo that should track the facade's position, as well as how it should transform to its initial position. + /// + /// The instance of the logo to be used for tracking. + /// The duration of the initial transform. Default is instant. + /// The easing type of the initial transform. + public void StartTracking(OsuLogo logo, double duration = 0, Easing easing = Easing.None) + { + if (logo == null) + throw new ArgumentNullException(nameof(logo)); + + if (logo.IsTracking && Logo == null) + throw new InvalidOperationException($"Cannot track an instance of {typeof(OsuLogo)} to multiple {typeof(LogoTrackingContainer)}s"); + + if (Logo != logo && Logo != null) + { + // If we're replacing the logo to be tracked, the old one no longer has a tracking container + Logo.IsTracking = false; + } + + Logo = logo; + Logo.IsTracking = true; + + this.duration = duration; + this.easing = easing; + + startTime = null; + startPosition = null; + } + + /// + /// Stops the logo assigned in from tracking the facade's position. + /// + public void StopTracking() + { + if (Logo != null) + { + Logo.IsTracking = false; + Logo = null; + } + } + + /// + /// Gets the position that the logo should move to with respect to the . + /// Manually performs a conversion of the Facade's position to the Logo's parent's relative space. + /// + /// Will only be correct if the logo's are set to Axes.Both + protected Vector2 ComputeLogoTrackingPosition() + { + var absolutePos = Logo.Parent.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre); + + return new Vector2(absolutePos.X / Logo.Parent.RelativeToAbsoluteFactor.X, + absolutePos.Y / Logo.Parent.RelativeToAbsoluteFactor.Y); + } + + protected override void Update() + { + base.Update(); + + if (Logo == null) + return; + + if (Logo.RelativePositionAxes != Axes.Both) + throw new InvalidOperationException($"Tracking logo must have {nameof(RelativePositionAxes)} = Axes.Both"); + + // Account for the scale of the actual OsuLogo, as SizeForFlow only accounts for the sprite scale. + facade.SetSize(new Vector2(Logo.SizeForFlow * Logo.Scale.X)); + + var localPos = ComputeLogoTrackingPosition(); + + if (LogoFacade.Parent != null && Logo.Position != localPos) + { + // If this is our first update since tracking has started, initialize our starting values for interpolation + if (startTime == null || startPosition == null) + { + startTime = Time.Current; + startPosition = Logo.Position; + } + + if (duration != 0) + { + double elapsedDuration = (double)(Time.Current - startTime); + + var amount = (float)Interpolation.ApplyEasing(easing, Math.Min(elapsedDuration / duration, 1)); + + // Interpolate the position of the logo, where amount 0 is where the logo was when it first began interpolating, and amount 1 is the target location. + Logo.Position = Vector2.Lerp(startPosition.Value, localPos, amount); + } + else + { + Logo.Position = localPos; + } + } + } + + protected override void Dispose(bool isDisposing) + { + if (Logo != null) + Logo.IsTracking = false; + + base.Dispose(isDisposing); + } + + private class InternalFacade : Facade + { + public new void SetSize(Vector2 size) + { + base.SetSize(size); + } + } + + /// + /// A dummy object used to denote another object's location. + /// + public abstract class Facade : Drawable + { + public override Vector2 Size + { + get => base.Size; + set => throw new InvalidOperationException($"Cannot set the Size of a {typeof(Facade)} outside of a {typeof(LogoTrackingContainer)}"); + } + + protected void SetSize(Vector2 size) + { + base.Size = size; + } + } + } +} diff --git a/osu.Game/Graphics/Containers/OsuClickableContainer.cs b/osu.Game/Graphics/Containers/OsuClickableContainer.cs index e4d30cebb7..6dbe340efb 100644 --- a/osu.Game/Graphics/Containers/OsuClickableContainer.cs +++ b/osu.Game/Graphics/Containers/OsuClickableContainer.cs @@ -4,11 +4,12 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Game.Graphics.UserInterface; namespace osu.Game.Graphics.Containers { - public class OsuClickableContainer : ClickableContainer + public class OsuClickableContainer : ClickableContainer, IHasTooltip { private readonly HoverSampleSet sampleSet; @@ -23,6 +24,8 @@ namespace osu.Game.Graphics.Containers this.sampleSet = sampleSet; } + public virtual string TooltipText { get; set; } + [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index c6ee91f961..3f84f77081 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -84,6 +84,7 @@ namespace osu.Game.Graphics.Containers case GlobalAction.Back: State = Visibility.Hidden; return true; + case GlobalAction.Select: return true; } @@ -107,6 +108,7 @@ namespace osu.Game.Graphics.Containers State = Visibility.Hidden; break; + case Visibility.Hidden: if (PlaySamplesOnStateChange) samplePopOut?.Play(); if (BlockScreenWideMouse) osuGame?.RemoveBlockingOverlay(this); diff --git a/osu.Game/Graphics/Containers/OsuHoverContainer.cs b/osu.Game/Graphics/Containers/OsuHoverContainer.cs index 880807c8b4..c4f85926ee 100644 --- a/osu.Game/Graphics/Containers/OsuHoverContainer.cs +++ b/osu.Game/Graphics/Containers/OsuHoverContainer.cs @@ -20,22 +20,46 @@ namespace osu.Game.Graphics.Containers protected virtual IEnumerable EffectTargets => new[] { Content }; + public OsuHoverContainer() + { + Enabled.ValueChanged += e => + { + if (!e.NewValue) unhover(); + }; + } + + private bool isHovered; + protected override bool OnHover(HoverEvent e) { + if (!Enabled.Value) + return false; + EffectTargets.ForEach(d => d.FadeColour(HoverColour, FADE_DURATION, Easing.OutQuint)); + isHovered = true; + return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - EffectTargets.ForEach(d => d.FadeColour(IdleColour, FADE_DURATION, Easing.OutQuint)); + unhover(); base.OnHoverLost(e); } + private void unhover() + { + if (!isHovered) return; + + isHovered = false; + EffectTargets.ForEach(d => d.FadeColour(IdleColour, FADE_DURATION, Easing.OutQuint)); + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { - HoverColour = colours.Yellow; + if (HoverColour == default) + HoverColour = colours.Yellow; } protected override void LoadComplete() diff --git a/osu.Game/Graphics/Containers/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs index 6bbab4766d..a8d0e03c01 100644 --- a/osu.Game/Graphics/Containers/SectionsContainer.cs +++ b/osu.Game/Graphics/Containers/SectionsContainer.cs @@ -142,6 +142,17 @@ namespace osu.Game.Graphics.Containers public void ScrollToTop() => scrollContainer.ScrollTo(0); + public override void InvalidateFromChild(Invalidation invalidation, Drawable source = null) + { + base.InvalidateFromChild(invalidation, source); + + if ((invalidation & Invalidation.DrawSize) != 0) + { + if (source == ExpandableHeader) //We need to recalculate the positions if the ExpandableHeader changed its size + lastKnownScroll = -1; + } + } + private float lastKnownScroll; protected override void UpdateAfterChildren() @@ -150,6 +161,7 @@ namespace osu.Game.Graphics.Containers float headerH = (ExpandableHeader?.LayoutSize.Y ?? 0) + (FixedHeader?.LayoutSize.Y ?? 0); float footerH = Footer?.LayoutSize.Y ?? 0; + if (headerH != headerHeight || footerH != footerHeight) { headerHeight = headerH; @@ -181,6 +193,7 @@ namespace osu.Game.Graphics.Containers foreach (var section in Children) { float diff = Math.Abs(scrollContainer.GetChildPosInContent(section) - currentScroll - scrollOffset); + if (diff < minDiff) { minDiff = diff; diff --git a/osu.Game/Graphics/Containers/WaveContainer.cs b/osu.Game/Graphics/Containers/WaveContainer.cs index 48131d7e86..464682a0ad 100644 --- a/osu.Game/Graphics/Containers/WaveContainer.cs +++ b/osu.Game/Graphics/Containers/WaveContainer.cs @@ -5,6 +5,7 @@ using System; 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 osuTK.Graphics; diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 059beeca4d..092a23e787 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -45,10 +45,12 @@ namespace osu.Game.Graphics.Cursor { var position = e.MousePosition; var distance = Vector2Extensions.Distance(position, positionMouseDown); + // don't start rotating until we're moved a minimum distance away from the mouse down location, // else it can have an annoying effect. if (dragRotationState == DragRotationState.DragStarted && distance > 30) dragRotationState = DragRotationState.Rotating; + // don't rotate when distance is zero to avoid NaN if (dragRotationState == DragRotationState.Rotating && distance > 0) { diff --git a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs index 4e0ce4a3e1..cfcda892fd 100644 --- a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs @@ -6,8 +6,8 @@ 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.Cursor; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Sprites; @@ -37,6 +37,7 @@ namespace osu.Game.Graphics.Cursor if (value == text.Text) return; text.Text = value; + if (IsPresent) { AutoSizeDuration = 250; diff --git a/osu.Game/Graphics/DrawableDate.cs b/osu.Game/Graphics/DrawableDate.cs index 3ae1033f5d..125c994c92 100644 --- a/osu.Game/Graphics/DrawableDate.cs +++ b/osu.Game/Graphics/DrawableDate.cs @@ -54,9 +54,11 @@ namespace osu.Game.Graphics var diffToNow = DateTimeOffset.Now.Subtract(Date); double timeUntilNextUpdate = 1000; + if (Math.Abs(diffToNow.TotalSeconds) > 120) { timeUntilNextUpdate *= 60; + if (Math.Abs(diffToNow.TotalMinutes) > 120) { timeUntilNextUpdate *= 60; diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 712dc4c444..63ec24f84f 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -20,12 +20,14 @@ namespace osu.Game.Graphics { default: throw new ArgumentException(@"Invalid hex string length!"); + case 3: return new Color4( (byte)(Convert.ToByte(hex.Substring(0, 1), 16) * 17), (byte)(Convert.ToByte(hex.Substring(1, 1), 16) * 17), (byte)(Convert.ToByte(hex.Substring(2, 1), 16) * 17), 255); + case 6: return new Color4( Convert.ToByte(hex.Substring(0, 2), 16), @@ -38,8 +40,10 @@ namespace osu.Game.Graphics // See https://github.com/ppy/osu-web/blob/master/resources/assets/less/colors.less public readonly Color4 PurpleLighter = FromHex(@"eeeeff"); public readonly Color4 PurpleLight = FromHex(@"aa88ff"); + public readonly Color4 PurpleLightAlternative = FromHex(@"cba4da"); public readonly Color4 Purple = FromHex(@"8866ee"); public readonly Color4 PurpleDark = FromHex(@"6644cc"); + public readonly Color4 PurpleDarkAlternative = FromHex(@"312436"); public readonly Color4 PurpleDarker = FromHex(@"441188"); public readonly Color4 PinkLighter = FromHex(@"ffddee"); @@ -66,6 +70,48 @@ namespace osu.Game.Graphics public readonly Color4 GreenDark = FromHex(@"668800"); public readonly Color4 GreenDarker = FromHex(@"445500"); + public readonly Color4 Sky = FromHex(@"6bb5ff"); + public readonly Color4 GreySkyLighter = FromHex(@"c6e3f4"); + public readonly Color4 GreySkyLight = FromHex(@"8ab3cc"); + public readonly Color4 GreySky = FromHex(@"405461"); + public readonly Color4 GreySkyDark = FromHex(@"303d47"); + public readonly Color4 GreySkyDarker = FromHex(@"21272c"); + + public readonly Color4 Seafoam = FromHex(@"05ffa2"); + public readonly Color4 GreySeafoamLighter = FromHex(@"9ebab1"); + public readonly Color4 GreySeafoamLight = FromHex(@"4d7365"); + public readonly Color4 GreySeafoam = FromHex(@"33413c"); + public readonly Color4 GreySeafoamDark = FromHex(@"2c3532"); + public readonly Color4 GreySeafoamDarker = FromHex(@"1e2422"); + + public readonly Color4 Cyan = FromHex(@"05f4fd"); + public readonly Color4 GreyCyanLighter = FromHex(@"77b1b3"); + public readonly Color4 GreyCyanLight = FromHex(@"436d6f"); + public readonly Color4 GreyCyan = FromHex(@"293d3e"); + public readonly Color4 GreyCyanDark = FromHex(@"243536"); + public readonly Color4 GreyCyanDarker = FromHex(@"1e2929"); + + public readonly Color4 Lime = FromHex(@"82ff05"); + public readonly Color4 GreyLimeLighter = FromHex(@"deff87"); + public readonly Color4 GreyLimeLight = FromHex(@"657259"); + public readonly Color4 GreyLime = FromHex(@"3f443a"); + public readonly Color4 GreyLimeDark = FromHex(@"32352e"); + public readonly Color4 GreyLimeDarker = FromHex(@"2e302b"); + + public readonly Color4 Violet = FromHex(@"bf04ff"); + public readonly Color4 GreyVioletLighter = FromHex(@"ebb8fe"); + public readonly Color4 GreyVioletLight = FromHex(@"685370"); + public readonly Color4 GreyViolet = FromHex(@"46334d"); + public readonly Color4 GreyVioletDark = FromHex(@"2c2230"); + public readonly Color4 GreyVioletDarker = FromHex(@"201823"); + + public readonly Color4 Carmine = FromHex(@"ff0542"); + public readonly Color4 GreyCarmineLighter = FromHex(@"deaab4"); + public readonly Color4 GreyCarmineLight = FromHex(@"644f53"); + public readonly Color4 GreyCarmine = FromHex(@"342b2d"); + public readonly Color4 GreyCarmineDark = FromHex(@"302a2b"); + public readonly Color4 GreyCarmineDarker = FromHex(@"241d1e"); + public readonly Color4 Gray0 = FromHex(@"000"); public readonly Color4 Gray1 = FromHex(@"111"); public readonly Color4 Gray2 = FromHex(@"222"); diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs index c8a736f49a..22250d4a56 100644 --- a/osu.Game/Graphics/OsuFont.cs +++ b/osu.Game/Graphics/OsuFont.cs @@ -42,6 +42,7 @@ namespace osu.Game.Graphics { case Typeface.Exo: return "Exo2.0"; + case Typeface.Venera: return "Venera"; } @@ -61,9 +62,9 @@ namespace osu.Game.Graphics /// /// Retrieves the string representation of a . /// - /// The . + /// The family string. /// The . - /// The string representation of in the specified . + /// The string representation of in the specified . public static string GetWeightString(string family, FontWeight weight) { string weightString = weight.ToString(); @@ -81,6 +82,7 @@ namespace osu.Game.Graphics /// /// Creates a new by applying adjustments to this . /// + /// The base . /// The font typeface. If null, the value is copied from this . /// The text size. If null, the value is copied from this . /// The font weight. If null, the value is copied from this . diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index a2ac71de93..24a98e6dc9 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -104,9 +104,11 @@ namespace osu.Game.Graphics case ScreenshotFormat.Png: image.SaveAsPng(stream); break; + case ScreenshotFormat.Jpg: image.SaveAsJpeg(stream); break; + default: throw new ArgumentOutOfRangeException(nameof(screenshotFormat)); } diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index 58058c9d4c..953f3985f9 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -25,6 +25,7 @@ namespace osu.Game.Graphics.UserInterface { direction = value; base.Direction = direction.HasFlag(BarDirection.Horizontal) ? FillDirection.Vertical : FillDirection.Horizontal; + foreach (var bar in Children) { bar.Size = direction.HasFlag(BarDirection.Horizontal) ? new Vector2(1, 1.0f / Children.Count) : new Vector2(1.0f / Children.Count, 1); @@ -41,6 +42,7 @@ namespace osu.Game.Graphics.UserInterface set { List bars = Children.ToList(); + foreach (var bar in value.Select((length, index) => new { Value = length, Bar = bars.Count > index ? bars[index] : null })) { float length = MaxValue ?? value.Max(); diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index dbbe5b4258..b50bf14bab 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics.Effects; using osu.Game.Graphics.Containers; using osu.Framework.Input.Events; diff --git a/osu.Game/Graphics/UserInterface/ExpandingBar.cs b/osu.Game/Graphics/UserInterface/ExpandingBar.cs new file mode 100644 index 0000000000..439a6002d8 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/ExpandingBar.cs @@ -0,0 +1,101 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osuTK; + +namespace osu.Game.Graphics.UserInterface +{ + /// + /// A rounded bar which can be expanded or collapsed. + /// Generally used for tabs or breadcrumbs. + /// + public class ExpandingBar : Circle + { + private bool isCollapsed; + + public bool IsCollapsed + { + get => isCollapsed; + set + { + if (value == isCollapsed) + return; + + isCollapsed = value; + updateState(); + } + } + + private float expandedSize = 4; + + public float ExpandedSize + { + get => expandedSize; + set + { + if (value == expandedSize) + return; + + expandedSize = value; + updateState(); + } + } + + private float collapsedSize = 2; + + public float CollapsedSize + { + get => collapsedSize; + set + { + if (value == collapsedSize) + return; + + collapsedSize = value; + updateState(); + } + } + + public override Axes RelativeSizeAxes + { + get => base.RelativeSizeAxes; + set + { + base.RelativeSizeAxes = Axes.None; + Size = Vector2.Zero; + + base.RelativeSizeAxes = value; + updateState(); + } + } + + public ExpandingBar() + { + RelativeSizeAxes = Axes.X; + Origin = Anchor.Centre; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + updateState(); + } + + public void Collapse() => IsCollapsed = true; + + public void Expand() => IsCollapsed = false; + + private void updateState() + { + float newSize = IsCollapsed ? CollapsedSize : ExpandedSize; + Easing easingType = IsCollapsed ? Easing.Out : Easing.OutElastic; + + if (RelativeSizeAxes == Axes.X) + this.ResizeHeightTo(newSize, 400, easingType); + else + this.ResizeWidthTo(newSize, 400, easingType); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/LineGraph.cs b/osu.Game/Graphics/UserInterface/LineGraph.cs index 74025b71ff..757a9a349c 100644 --- a/osu.Game/Graphics/UserInterface/LineGraph.cs +++ b/osu.Game/Graphics/UserInterface/LineGraph.cs @@ -9,6 +9,7 @@ using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; +using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { @@ -63,6 +64,12 @@ namespace osu.Game.Graphics.UserInterface } } + public Color4 LineColour + { + get => maskingContainer.Colour; + set => maskingContainer.Colour = value; + } + public LineGraph() { Add(maskingContainer = new Container @@ -86,6 +93,7 @@ namespace osu.Game.Graphics.UserInterface protected override void Update() { base.Update(); + if (!pathCached.IsValid) { applyPath(); diff --git a/osu.Game/Graphics/UserInterface/Nub.cs b/osu.Game/Graphics/UserInterface/Nub.cs index 1f5195eaf1..82b09e0821 100644 --- a/osu.Game/Graphics/UserInterface/Nub.cs +++ b/osu.Game/Graphics/UserInterface/Nub.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; diff --git a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs index d64068f74c..a8041c79fc 100644 --- a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs @@ -5,6 +5,7 @@ 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.Input.Events; using osu.Game.Graphics.Containers; diff --git a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs index de3d93d845..2944fc87af 100644 --- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs @@ -78,7 +78,7 @@ namespace osu.Game.Graphics.UserInterface Current.DisabledChanged += disabled => { - Alpha = disabled ? 0.3f : 1; + labelSpriteText.Alpha = Nub.Alpha = disabled ? 0.3f : 1; }; } diff --git a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs index c72d11b57e..cea8427296 100644 --- a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs @@ -5,7 +5,7 @@ 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.Effects; namespace osu.Game.Graphics.UserInterface { diff --git a/osu.Game/Graphics/UserInterface/OsuMenu.cs b/osu.Game/Graphics/UserInterface/OsuMenu.cs index 9b5755ea8b..32994be78a 100644 --- a/osu.Game/Graphics/UserInterface/OsuMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuMenu.cs @@ -88,9 +88,11 @@ namespace osu.Game.Graphics.UserInterface case MenuItemType.Standard: text.Colour = Color4.White; break; + case MenuItemType.Destructive: text.Colour = Color4.Red; break; + case MenuItemType.Highlighted: text.Colour = OsuColour.FromHex(@"ffcc22"); break; diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index c558fd7c7b..c3c447ef83 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -203,6 +203,7 @@ namespace osu.Game.Graphics.UserInterface private int findPrecision(decimal d) { int precision = 0; + while (d != Math.Round(d)) { d *= 10; diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index ebe38db60a..89de91bc9b 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -30,6 +30,7 @@ namespace osu.Game.Graphics.UserInterface Height = 40; TextContainer.Height = 0.5f; CornerRadius = 5; + LengthLimit = 1000; Current.DisabledChanged += disabled => { Alpha = disabled ? 0.3f : 1; }; } diff --git a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs index f564a4b5a8..3e0a6c3265 100644 --- a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs +++ b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs @@ -8,7 +8,7 @@ using osu.Framework.Screens; namespace osu.Game.Graphics.UserInterface { /// - /// A which follows the active screen (and allows navigation) in a stack. + /// A which follows the active screen (and allows navigation) in a stack. /// public class ScreenBreadcrumbControl : BreadcrumbControl { diff --git a/osu.Game/Graphics/UserInterface/ScreenTitle.cs b/osu.Game/Graphics/UserInterface/ScreenTitle.cs index 1574023068..7b39238e5e 100644 --- a/osu.Game/Graphics/UserInterface/ScreenTitle.cs +++ b/osu.Game/Graphics/UserInterface/ScreenTitle.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.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -12,24 +13,33 @@ namespace osu.Game.Graphics.UserInterface { public abstract class ScreenTitle : CompositeDrawable, IHasAccentColour { - private readonly SpriteIcon iconSprite; + public const float ICON_WIDTH = ICON_SIZE + icon_spacing; + + protected const float ICON_SIZE = 25; + + private SpriteIcon iconSprite; private readonly OsuSpriteText titleText, pageText; + private const float icon_spacing = 10; + protected IconUsage Icon { - get => iconSprite.Icon; - set => iconSprite.Icon = value; + set + { + if (iconSprite == null) + throw new InvalidOperationException($"Cannot use {nameof(Icon)} with a custom {nameof(CreateIcon)} function."); + + iconSprite.Icon = value; + } } protected string Title { - get => titleText.Text; set => titleText.Text = value; } protected string Section { - get => pageText.Text; set => pageText.Text = value; } @@ -39,6 +49,11 @@ namespace osu.Game.Graphics.UserInterface set => pageText.Colour = value; } + protected virtual Drawable CreateIcon() => iconSprite = new SpriteIcon + { + Size = new Vector2(ICON_SIZE), + }; + protected ScreenTitle() { AutoSizeAxes = Axes.Both; @@ -48,13 +63,10 @@ namespace osu.Game.Graphics.UserInterface new FillFlowContainer { AutoSizeAxes = Axes.Both, - Spacing = new Vector2(10, 0), - Children = new Drawable[] + Spacing = new Vector2(icon_spacing, 0), + Children = new[] { - iconSprite = new SpriteIcon - { - Size = new Vector2(25), - }, + CreateIcon(), new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -64,11 +76,11 @@ namespace osu.Game.Graphics.UserInterface { titleText = new OsuSpriteText { - Font = OsuFont.GetFont(size: 25), + Font = OsuFont.GetFont(size: 30, weight: FontWeight.Light), }, pageText = new OsuSpriteText { - Font = OsuFont.GetFont(size: 25), + Font = OsuFont.GetFont(size: 30, weight: FontWeight.Light), } } } diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs index 8ccf3001e3..3ee572602b 100644 --- a/osu.Game/Graphics/UserInterface/StarCounter.cs +++ b/osu.Game/Graphics/UserInterface/StarCounter.cs @@ -100,6 +100,7 @@ namespace osu.Game.Graphics.UserInterface public void StopAnimation() { int i = 0; + foreach (var star in stars.Children) { star.ClearTransforms(true); @@ -120,6 +121,7 @@ namespace osu.Game.Graphics.UserInterface private void transformCount(float newValue) { int i = 0; + foreach (var star in stars.Children) { star.ClearTransforms(true); diff --git a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs index 9911a7c368..36a9aca412 100644 --- a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs +++ b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Beatmaps.ControlPoints; using osu.Framework.Audio.Track; using System; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; diff --git a/osu.Game/IO/Legacy/SerializationReader.cs b/osu.Game/IO/Legacy/SerializationReader.cs index 95ee5aea6b..7a84c11930 100644 --- a/osu.Game/IO/Legacy/SerializationReader.cs +++ b/osu.Game/IO/Legacy/SerializationReader.cs @@ -85,6 +85,7 @@ namespace osu.Game.IO.Legacy for (int i = 0; i < count; i++) { T obj = new T(); + try { obj.ReadFromStream(sr); @@ -129,44 +130,63 @@ namespace osu.Game.IO.Legacy public object ReadObject() { ObjType t = (ObjType)ReadByte(); + switch (t) { case ObjType.boolType: return ReadBoolean(); + case ObjType.byteType: return ReadByte(); + case ObjType.uint16Type: return ReadUInt16(); + case ObjType.uint32Type: return ReadUInt32(); + case ObjType.uint64Type: return ReadUInt64(); + case ObjType.sbyteType: return ReadSByte(); + case ObjType.int16Type: return ReadInt16(); + case ObjType.int32Type: return ReadInt32(); + case ObjType.int64Type: return ReadInt64(); + case ObjType.charType: return ReadChar(); + case ObjType.stringType: return base.ReadString(); + case ObjType.singleType: return ReadSingle(); + case ObjType.doubleType: return ReadDouble(); + case ObjType.decimalType: return ReadDecimal(); + case ObjType.dateTimeType: return ReadDateTime(); + case ObjType.byteArrayType: return ReadByteArray(); + case ObjType.charArrayType: return ReadCharArray(); + case ObjType.otherType: return DynamicDeserializer.Deserialize(BaseStream); + default: return null; } @@ -241,6 +261,7 @@ namespace osu.Game.IO.Legacy string toAssemblyName = assemblyName.Split(',')[0]; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); + foreach (Assembly a in assemblies) { if (a.FullName.Split(',')[0] == toAssemblyName) diff --git a/osu.Game/IO/Legacy/SerializationWriter.cs b/osu.Game/IO/Legacy/SerializationWriter.cs index 695767c822..f30e4492af 100644 --- a/osu.Game/IO/Legacy/SerializationWriter.cs +++ b/osu.Game/IO/Legacy/SerializationWriter.cs @@ -111,6 +111,7 @@ namespace osu.Game.IO.Legacy else { Write(d.Count); + foreach (KeyValuePair kvp in d) { WriteObject(kvp.Key); diff --git a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs index 13be4be0c6..6d244bff60 100644 --- a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs +++ b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs @@ -65,6 +65,7 @@ namespace osu.Game.IO.Serialization.Converters var lookupTable = new List(); var objects = new List(); + foreach (var item in list) { var type = item.GetType(); @@ -75,6 +76,7 @@ namespace osu.Game.IO.Serialization.Converters typeString += $", {assemblyName.Version}"; int typeId = lookupTable.IndexOf(typeString); + if (typeId == -1) { lookupTable.Add(typeString); diff --git a/osu.Game/Input/IdleTracker.cs b/osu.Game/Input/IdleTracker.cs index 91e2456ef7..cbc446a126 100644 --- a/osu.Game/Input/IdleTracker.cs +++ b/osu.Game/Input/IdleTracker.cs @@ -62,6 +62,7 @@ namespace osu.Game.Input case MouseUpEvent _: case MouseMoveEvent _: return updateLastInteractionTime(); + default: return base.Handle(e); } diff --git a/osu.Game/Migrations/20180125143340_Settings.cs b/osu.Game/Migrations/20180125143340_Settings.cs index 2e2768dc7c..166d3c086d 100644 --- a/osu.Game/Migrations/20180125143340_Settings.cs +++ b/osu.Game/Migrations/20180125143340_Settings.cs @@ -16,7 +16,7 @@ namespace osu.Game.Migrations { ID = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - Key = table.Column(type: "INTEGER", nullable: false), + Key = table.Column(type: "TEXT", nullable: false), RulesetID = table.Column(type: "INTEGER", nullable: true), Value = table.Column(type: "TEXT", nullable: true), Variant = table.Column(type: "INTEGER", nullable: true) diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index 8430e00e4f..f942d357e8 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -14,7 +14,7 @@ namespace osu.Game.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.2.1-servicing-10028"); + .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => { @@ -198,7 +198,7 @@ namespace osu.Game.Migrations b.Property("ID") .ValueGeneratedOnAdd(); - b.Property("IntKey") + b.Property("Key") .HasColumnName("Key"); b.Property("RulesetID"); diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index c5f6ef41c2..594bc1e3ca 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -77,13 +77,13 @@ namespace osu.Game.Online.API /// public void Register(IOnlineComponent component) { - Scheduler.Add(delegate { components.Add(component); }); + Schedule(() => components.Add(component)); component.APIStateChanged(this, state); } public void Unregister(IOnlineComponent component) { - Scheduler.Add(delegate { components.Remove(component); }); + Schedule(() => components.Remove(component)); } public string AccessToken => authentication.RequestAccessToken(); @@ -113,6 +113,7 @@ namespace osu.Game.Online.API } break; + case APIState.Offline: case APIState.Connecting: //work to restore a connection... @@ -253,7 +254,7 @@ namespace osu.Game.Online.API handleWebException(we); return false; } - catch (Exception e) + catch { return false; } @@ -273,7 +274,7 @@ namespace osu.Game.Online.API state = value; log.Add($@"We just went {state}!"); - Scheduler.Add(delegate + Schedule(() => { components.ForEach(c => c.APIStateChanged(this, state)); OnStateChange?.Invoke(oldState, state); @@ -300,6 +301,7 @@ namespace osu.Game.Online.API case HttpStatusCode.Unauthorized: Logout(); return true; + case HttpStatusCode.RequestTimeout: failureCount++; log.Add($@"API failure count is now {failureCount}"); @@ -350,9 +352,13 @@ namespace osu.Game.Online.API public void Logout() { flushQueue(); + password = null; authentication.Clear(); - LocalUser.Value = createGuestUser(); + + // Scheduled prior to state change such that the state changed event is invoked with the correct user present + Schedule(() => LocalUser.Value = createGuestUser()); + State = APIState.Offline; } diff --git a/osu.Game/Online/API/Requests/GetChangelogBuildRequest.cs b/osu.Game/Online/API/Requests/GetChangelogBuildRequest.cs new file mode 100644 index 0000000000..baa15c70c4 --- /dev/null +++ b/osu.Game/Online/API/Requests/GetChangelogBuildRequest.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 osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Online.API.Requests +{ + public class GetChangelogBuildRequest : APIRequest + { + private readonly string name; + private readonly string version; + + public GetChangelogBuildRequest(string streamName, string buildVersion) + { + name = streamName; + version = buildVersion; + } + + protected override string Target => $@"changelog/{name}/{version}"; + } +} diff --git a/osu.Game/Online/API/Requests/GetChangelogRequest.cs b/osu.Game/Online/API/Requests/GetChangelogRequest.cs new file mode 100644 index 0000000000..97799ff66a --- /dev/null +++ b/osu.Game/Online/API/Requests/GetChangelogRequest.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Online.API.Requests +{ + public class GetChangelogRequest : APIRequest + { + protected override string Target => @"changelog"; + } +} diff --git a/osu.Game/Online/API/Requests/GetRoomsRequest.cs b/osu.Game/Online/API/Requests/GetRoomsRequest.cs index d7c66707e4..8f1497ef33 100644 --- a/osu.Game/Online/API/Requests/GetRoomsRequest.cs +++ b/osu.Game/Online/API/Requests/GetRoomsRequest.cs @@ -26,12 +26,15 @@ namespace osu.Game.Online.API.Requests { case PrimaryFilter.Open: break; + case PrimaryFilter.Owned: target += "/owned"; break; + case PrimaryFilter.Participated: target += "/participated"; break; + case PrimaryFilter.RecentlyEnded: target += "/ended"; break; diff --git a/osu.Game/Online/API/Requests/Responses/APIChangelogBuild.cs b/osu.Game/Online/API/Requests/Responses/APIChangelogBuild.cs new file mode 100644 index 0000000000..40f1b791f9 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIChangelogBuild.cs @@ -0,0 +1,49 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using System; +using System.Collections.Generic; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIChangelogBuild : IEquatable + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("version")] + public string Version { get; set; } + + [JsonProperty("display_version")] + public string DisplayVersion { get; set; } + + [JsonProperty("users")] + public long Users { get; set; } + + [JsonProperty("created_at")] + public DateTimeOffset CreatedAt { get; set; } + + [JsonProperty("update_stream")] + public APIUpdateStream UpdateStream { get; set; } + + [JsonProperty("changelog_entries")] + public List ChangelogEntries { get; set; } + + [JsonProperty("versions")] + public VersionNatigation Versions { get; set; } + + public class VersionNatigation + { + [JsonProperty("next")] + public APIChangelogBuild Next { get; set; } + + [JsonProperty("previous")] + public APIChangelogBuild Previous { get; set; } + } + + public bool Equals(APIChangelogBuild other) => Id == other?.Id; + + public override string ToString() => $"{UpdateStream.DisplayName} {DisplayVersion}"; + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIChangelogEntry.cs b/osu.Game/Online/API/Requests/Responses/APIChangelogEntry.cs new file mode 100644 index 0000000000..abaff9b7ae --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIChangelogEntry.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 Newtonsoft.Json; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIChangelogEntry + { + [JsonProperty("id")] + public long? Id { get; set; } + + [JsonProperty("repository")] + public string Repository { get; set; } + + [JsonProperty("github_pull_request_id")] + public long? GithubPullRequestId { get; set; } + + [JsonProperty("github_url")] + public string GithubUrl { get; set; } + + [JsonProperty("url")] + public string Url { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("category")] + public string Category { get; set; } + + [JsonProperty("title")] + public string Title { get; set; } + + [JsonProperty("message_html")] + public string MessageHtml { get; set; } + + [JsonProperty("major")] + public bool? Major { get; set; } + + [JsonProperty("created_at")] + public DateTimeOffset? CreatedAt { get; set; } + + [JsonProperty("github_user")] + public APIChangelogUser GithubUser { get; set; } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIChangelogIndex.cs b/osu.Game/Online/API/Requests/Responses/APIChangelogIndex.cs new file mode 100644 index 0000000000..778e8754fe --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIChangelogIndex.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. + +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIChangelogIndex + { + [JsonProperty] + public List Builds; + + [JsonProperty] + public List Streams; + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIChangelogUser.cs b/osu.Game/Online/API/Requests/Responses/APIChangelogUser.cs new file mode 100644 index 0000000000..5891391e83 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIChangelogUser.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIChangelogUser + { + [JsonProperty("id")] + public long? Id { get; set; } + + [JsonProperty("display_name")] + public string DisplayName { get; set; } + + [JsonProperty("github_url")] + public string GithubUrl { get; set; } + + [JsonProperty("osu_username")] + public string OsuUsername { get; set; } + + [JsonProperty("user_id")] + public long? UserId { get; set; } + + [JsonProperty("user_url")] + public string UserUrl { get; set; } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs index 8ee71ce9ac..3060300077 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs @@ -7,7 +7,6 @@ using System.Linq; using Newtonsoft.Json; using osu.Game.Beatmaps; using osu.Game.Rulesets; -using osu.Game.Rulesets.Scoring; using osu.Game.Scoring.Legacy; using osu.Game.Users; @@ -71,27 +70,32 @@ namespace osu.Game.Online.API.Requests.Responses { foreach (var kvp in value) { - HitResult newKey; switch (kvp.Key) { case @"count_geki": CountGeki = kvp.Value; break; + case @"count_300": Count300 = kvp.Value; break; + case @"count_katu": CountKatu = kvp.Value; break; + case @"count_100": Count100 = kvp.Value; break; + case @"count_50": Count50 = kvp.Value; break; + case @"count_miss": CountMiss = kvp.Value; break; + default: continue; } diff --git a/osu.Game/Online/API/Requests/Responses/APIMod.cs b/osu.Game/Online/API/Requests/Responses/APIMod.cs index d7dda07b33..b9da4f49ee 100644 --- a/osu.Game/Online/API/Requests/Responses/APIMod.cs +++ b/osu.Game/Online/API/Requests/Responses/APIMod.cs @@ -8,5 +8,7 @@ namespace osu.Game.Online.API.Requests.Responses public class APIMod : IMod { public string Acronym { get; set; } + + public bool Equals(IMod other) => Acronym == other?.Acronym; } } diff --git a/osu.Game/Online/API/Requests/Responses/APIUpdateStream.cs b/osu.Game/Online/API/Requests/Responses/APIUpdateStream.cs new file mode 100644 index 0000000000..ef204c7687 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIUpdateStream.cs @@ -0,0 +1,60 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using Newtonsoft.Json; +using osu.Framework.Graphics.Colour; +using osuTK.Graphics; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIUpdateStream : IEquatable + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("is_featured")] + public bool IsFeatured { get; set; } + + [JsonProperty("display_name")] + public string DisplayName { get; set; } + + [JsonProperty("latest_build")] + public APIChangelogBuild LatestBuild { get; set; } + + public bool Equals(APIUpdateStream other) => Id == other?.Id; + + public ColourInfo Colour + { + get + { + switch (Name) + { + case "stable40": + return new Color4(102, 204, 255, 255); + + case "stable": + return new Color4(34, 153, 187, 255); + + case "beta40": + return new Color4(255, 221, 85, 255); + + case "cuttingedge": + return new Color4(238, 170, 0, 255); + + case "lazer": + return new Color4(237, 18, 33, 255); + + case "web": + return new Color4(136, 102, 238, 255); + + default: + return new Color4(0, 0, 0, 255); + } + } + } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs index 8177f99abe..4614fe29b7 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs @@ -10,16 +10,16 @@ namespace osu.Game.Online.API.Requests.Responses public class APIUserMostPlayedBeatmap { [JsonProperty("beatmap_id")] - public int BeatmapID; + public int BeatmapID { get; set; } [JsonProperty("count")] - public int PlayCount; + public int PlayCount { get; set; } [JsonProperty] - private BeatmapInfo beatmap; + private BeatmapInfo beatmap { get; set; } [JsonProperty] - private APIBeatmapSet beatmapSet; + private APIBeatmapSet beatmapSet { get; set; } public BeatmapInfo GetBeatmapInfo(RulesetStore rulesets) { diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 8c6422afe3..2efc9f4968 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Chat.Tabs; using osu.Game.Users; namespace osu.Game.Online.Chat @@ -84,7 +85,11 @@ namespace osu.Game.Online.Chat ?? new Channel(user); } - private void currentChannelChanged(ValueChangedEvent e) => JoinChannel(e.NewValue); + private void currentChannelChanged(ValueChangedEvent e) + { + if (!(e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel)) + JoinChannel(e.NewValue); + } /// /// Ensure we run post actions in sequence, once at a time. diff --git a/osu.Game/Online/Chat/DrawableLinkCompiler.cs b/osu.Game/Online/Chat/DrawableLinkCompiler.cs index d34ec8091c..d27a3fbffe 100644 --- a/osu.Game/Online/Chat/DrawableLinkCompiler.cs +++ b/osu.Game/Online/Chat/DrawableLinkCompiler.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.Cursor; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -16,7 +15,7 @@ namespace osu.Game.Online.Chat /// /// An invisible drawable that brings multiple pieces together to form a consumable clickable link. /// - public class DrawableLinkCompiler : OsuHoverContainer, IHasTooltip + public class DrawableLinkCompiler : OsuHoverContainer { /// /// Each word part of a chat link (split for word-wrap support). @@ -40,8 +39,6 @@ namespace osu.Game.Online.Chat protected override IEnumerable EffectTargets => Parts; - public string TooltipText { get; set; } - private class LinkHoverSounds : HoverClickSounds { private readonly List parts; diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index d35dc07368..e1fc65da6c 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -51,6 +51,7 @@ namespace osu.Game.Online.Chat private static void handleMatches(Regex regex, string display, string link, MessageFormatterResult result, int startIndex = 0, LinkAction? linkActionOverride = null) { int captureOffset = 0; + foreach (Match m in regex.Matches(result.Text, startIndex)) { var index = m.Index - captureOffset; @@ -114,51 +115,63 @@ namespace osu.Game.Online.Chat case "b": case "beatmaps": return new LinkDetails(LinkAction.OpenBeatmap, args[3]); + case "s": case "beatmapsets": case "d": return new LinkDetails(LinkAction.OpenBeatmapSet, args[3]); + case "u": return new LinkDetails(LinkAction.OpenUserProfile, args[3]); } } return new LinkDetails(LinkAction.External, null); + case "osu": // every internal link also needs some kind of argument if (args.Length < 3) return new LinkDetails(LinkAction.External, null); LinkAction linkType; + switch (args[1]) { case "chan": linkType = LinkAction.OpenChannel; break; + case "edit": linkType = LinkAction.OpenEditorTimestamp; break; + case "b": linkType = LinkAction.OpenBeatmap; break; + case "s": case "dl": linkType = LinkAction.OpenBeatmapSet; break; + case "spectate": linkType = LinkAction.Spectate; break; + case "u": linkType = LinkAction.OpenUserProfile; break; + default: linkType = LinkAction.External; break; } return new LinkDetails(linkType, args[2]); + case "osump": return new LinkDetails(LinkAction.JoinMultiplayerMatch, args[1]); + default: return new LinkDetails(LinkAction.External, null); } diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 438bf231c4..ae4a056033 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -27,8 +27,6 @@ namespace osu.Game.Online.Chat protected ChannelManager ChannelManager; - private ScrollContainer scroll; - private DrawableChannel drawableChannel; private readonly bool postingTextbox; diff --git a/osu.Game/Online/Leaderboards/DrawableRank.cs b/osu.Game/Online/Leaderboards/DrawableRank.cs index 9155df69b4..ce64395dde 100644 --- a/osu.Game/Online/Leaderboards/DrawableRank.cs +++ b/osu.Game/Online/Leaderboards/DrawableRank.cs @@ -43,7 +43,24 @@ namespace osu.Game.Online.Leaderboards private void updateTexture() { - rankSprite.Texture = textures.Get($@"Grades/{Rank.GetDescription()}"); + string textureName; + + switch (Rank) + { + default: + textureName = Rank.GetDescription(); + break; + + case ScoreRank.SH: + textureName = "SPlus"; + break; + + case ScoreRank.XH: + textureName = "SSPlus"; + break; + } + + rankSprite.Texture = textures.Get($@"Grades/{textureName}"); } public void UpdateRank(ScoreRank newRank) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index ac1666f8ed..3ce71cccba 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -74,6 +74,7 @@ namespace osu.Game.Online.Leaderboards scrollContainer.Add(scrollFlow); int i = 0; + foreach (var s in scrollFlow.Children) { using (s.BeginDelayedSequence(i++ * 50, true)) @@ -138,18 +139,23 @@ namespace osu.Game.Online.Leaderboards OnRetry = UpdateScores, }); break; + case PlaceholderState.Unavailable: replacePlaceholder(new MessagePlaceholder(@"Leaderboards are not available for this beatmap!")); break; + case PlaceholderState.NoScores: replacePlaceholder(new MessagePlaceholder(@"No records yet!")); break; + case PlaceholderState.NotLoggedIn: replacePlaceholder(new MessagePlaceholder(@"Please sign in to view online leaderboards!")); break; + case PlaceholderState.NotSupporter: replacePlaceholder(new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!")); break; + default: replacePlaceholder(null); break; diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 70edcc3fc8..c6db939f6b 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -8,6 +8,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 4f92ff831c..ba9abcdefc 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.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; @@ -31,11 +31,9 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Input; using osu.Game.Overlays.Notifications; -using osu.Game.Rulesets; using osu.Game.Screens.Play; using osu.Game.Input.Bindings; using osu.Game.Online.Chat; -using osu.Game.Rulesets.Mods; using osu.Game.Skinning; using osuTK.Graphics; using osu.Game.Overlays.Volume; @@ -58,16 +56,8 @@ namespace osu.Game private ChannelManager channelManager; - private MusicController musicController; - private NotificationOverlay notifications; - private LoginOverlay loginOverlay; - - private DialogOverlay dialogOverlay; - - private AccountCreationOverlay accountCreation; - private DirectOverlay direct; private SocialOverlay social; @@ -91,29 +81,24 @@ namespace osu.Game private OsuScreenStack screenStack; private VolumeOverlay volume; - private OnScreenDisplay onscreenDisplay; private OsuLogo osuLogo; private MainMenu menuScreen; private Intro introScreen; private Bindable configRuleset; - private readonly Bindable ruleset = new Bindable(); private Bindable configSkin; private readonly string[] args; - private SettingsOverlay settings; + private SettingsPanel settings; private readonly List overlays = new List(); - private readonly List visibleBlockingOverlays = new List(); + private readonly List toolbarElements = new List(); - // todo: move this to SongSelect once Screen has the ability to unsuspend. - [Cached] - [Cached(Type = typeof(IBindable>))] - private readonly Bindable> selectedMods = new Bindable>(new Mod[] { }); + private readonly List visibleBlockingOverlays = new List(); public OsuGame(string[] args = null) { @@ -124,10 +109,6 @@ namespace osu.Game RavenLogger = new RavenLogger(this); } - public void ToggleSettings() => settings.ToggleVisibility(); - - public void ToggleDirect() => direct.ToggleVisibility(); - private void updateBlockingOverlayFade() => screenContainer.FadeColour(visibleBlockingOverlays.Any() ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint); @@ -147,12 +128,17 @@ namespace osu.Game /// /// Close all game-wide overlays. /// - /// Whether the toolbar should also be hidden. - public void CloseAllOverlays(bool toolbar = true) + /// Whether the toolbar (and accompanying controls) should also be hidden. + public void CloseAllOverlays(bool hideToolbarElements = true) { foreach (var overlay in overlays) overlay.State = Visibility.Hidden; - if (toolbar) Toolbar.State = Visibility.Hidden; + + if (hideToolbarElements) + { + foreach (var overlay in toolbarElements) + overlay.State = Visibility.Hidden; + } } private DependencyContainer dependencies; @@ -182,15 +168,12 @@ namespace osu.Game dependencies.Cache(RavenLogger); - dependencies.CacheAs(ruleset); - dependencies.CacheAs>(ruleset); - dependencies.Cache(osuLogo = new OsuLogo { Alpha = 0 }); // bind config int to database RulesetInfo configRuleset = LocalConfig.GetBindable(OsuSetting.Ruleset); - ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First(); - ruleset.ValueChanged += r => configRuleset.Value = r.NewValue.ID ?? 0; + Ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First(); + Ruleset.ValueChanged += r => configRuleset.Value = r.NewValue.ID ?? 0; // bind config int to database SkinInfo configSkin = LocalConfig.GetBindable(OsuSetting.Skin); @@ -261,9 +244,9 @@ namespace osu.Game } // Use first beatmap available for current ruleset, else switch ruleset. - var first = databasedSet.Beatmaps.Find(b => b.Ruleset == ruleset.Value) ?? databasedSet.Beatmaps.First(); + var first = databasedSet.Beatmaps.Find(b => b.Ruleset == Ruleset.Value) ?? databasedSet.Beatmaps.First(); - ruleset.Value = first.Ruleset; + Ruleset.Value = first.Ruleset; Beatmap.Value = BeatmapManager.GetWorkingBeatmap(first); }, $"load {beatmap}", bypassScreenAllowChecks: true, targetScreen: typeof(PlaySongSelect)); } @@ -272,11 +255,11 @@ namespace osu.Game /// Present a score's replay immediately. /// The user should have already requested this interactively. /// - /// The beatmap to select. public void PresentScore(ScoreInfo score) { var databasedScore = ScoreManager.GetScore(score); var databasedScoreInfo = databasedScore.ScoreInfo; + if (databasedScore.Replay == null) { Logger.Log("The loaded score has no replay data.", LoggingTarget.Information); @@ -284,6 +267,7 @@ namespace osu.Game } var databasedBeatmap = BeatmapManager.QueryBeatmap(b => b.ID == databasedScoreInfo.Beatmap.ID); + if (databasedBeatmap == null) { Logger.Log("Tried to load a score for a beatmap we don't have!", LoggingTarget.Information); @@ -292,10 +276,9 @@ namespace osu.Game performFromMainMenu(() => { - ruleset.Value = databasedScoreInfo.Ruleset; - + Ruleset.Value = databasedScoreInfo.Ruleset; Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); - Beatmap.Value.Mods.Value = databasedScoreInfo.Mods; + Mods.Value = databasedScoreInfo.Mods; menuScreen.Push(new PlayerLoader(() => new ReplayPlayer(databasedScore))); }, $"watch {databasedScoreInfo}", bypassScreenAllowChecks: true); @@ -381,6 +364,8 @@ namespace osu.Game Container logoContainer; + dependencies.CacheAs(idleTracker = new GameIdleTracker(6000)); + AddRange(new Drawable[] { new VolumeControlReceptor @@ -402,7 +387,7 @@ namespace osu.Game rightFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, leftFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, topMostOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, - idleTracker = new GameIdleTracker(6000) + idleTracker }); screenStack.ScreenPushed += screenPushed; @@ -426,62 +411,56 @@ namespace osu.Game CloseAllOverlays(false); menuScreen?.MakeCurrent(); }, - }, topMostOverlayContent.Add); + }, d => + { + topMostOverlayContent.Add(d); + toolbarElements.Add(d); + }); loadComponentSingleFile(volume = new VolumeOverlay(), leftFloatingOverlayContent.Add); - loadComponentSingleFile(onscreenDisplay = new OnScreenDisplay(), Add); + loadComponentSingleFile(new OnScreenDisplay(), Add, true); loadComponentSingleFile(notifications = new NotificationOverlay { GetToolbarHeight = () => ToolbarOffset, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - }, rightFloatingOverlayContent.Add); + }, rightFloatingOverlayContent.Add, true); loadComponentSingleFile(screenshotManager, Add); //overlay elements - loadComponentSingleFile(direct = new DirectOverlay(), overlayContent.Add); - loadComponentSingleFile(social = new SocialOverlay(), overlayContent.Add); - loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal); - loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add); - loadComponentSingleFile(settings = new MainSettings { GetToolbarHeight = () => ToolbarOffset }, leftFloatingOverlayContent.Add); - loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add); - loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add); + loadComponentSingleFile(direct = new DirectOverlay(), overlayContent.Add, true); + loadComponentSingleFile(social = new SocialOverlay(), overlayContent.Add, true); + loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true); + loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true); + loadComponentSingleFile(settings = new SettingsOverlay { GetToolbarHeight = () => ToolbarOffset }, leftFloatingOverlayContent.Add, true); + var changelogOverlay = loadComponentSingleFile(new ChangelogOverlay(), overlayContent.Add, true); + loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true); + loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add, true); - loadComponentSingleFile(loginOverlay = new LoginOverlay + loadComponentSingleFile(new LoginOverlay { GetToolbarHeight = () => ToolbarOffset, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - }, rightFloatingOverlayContent.Add); + }, rightFloatingOverlayContent.Add, true); - loadComponentSingleFile(musicController = new MusicController + loadComponentSingleFile(new MusicController { GetToolbarHeight = () => ToolbarOffset, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - }, rightFloatingOverlayContent.Add); + }, d => + { + rightFloatingOverlayContent.Add(d); + toolbarElements.Add(d); + }, true); - loadComponentSingleFile(accountCreation = new AccountCreationOverlay(), topMostOverlayContent.Add); - loadComponentSingleFile(dialogOverlay = new DialogOverlay(), topMostOverlayContent.Add); + loadComponentSingleFile(new AccountCreationOverlay(), topMostOverlayContent.Add, true); + loadComponentSingleFile(new DialogOverlay(), topMostOverlayContent.Add, true); loadComponentSingleFile(externalLinkOpener = new ExternalLinkOpener(), topMostOverlayContent.Add); - dependencies.CacheAs(idleTracker); - dependencies.Cache(settings); - dependencies.Cache(onscreenDisplay); - dependencies.Cache(social); - dependencies.Cache(direct); - dependencies.Cache(chatOverlay); - dependencies.Cache(channelManager); - dependencies.Cache(userProfile); - dependencies.Cache(musicController); - dependencies.Cache(beatmapSetOverlay); - dependencies.Cache(notifications); - dependencies.Cache(loginOverlay); - dependencies.Cache(dialogOverlay); - dependencies.Cache(accountCreation); - chatOverlay.StateChanged += state => channelManager.HighPollRate.Value = state == Visibility.Visible; Add(externalLinkOpener = new ExternalLinkOpener()); @@ -514,7 +493,7 @@ namespace osu.Game } // ensure only one of these overlays are open at once. - var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, social, direct }; + var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, social, direct, changelogOverlay }; overlays.AddRange(singleDisplayOverlays); foreach (var overlay in singleDisplayOverlays) @@ -544,7 +523,7 @@ namespace osu.Game if (notifications.State == Visibility.Visible) offset -= ToolbarButton.WIDTH / 2; - screenContainer.MoveToX(offset, SettingsOverlay.TRANSITION_LENGTH, Easing.OutQuint); + screenContainer.MoveToX(offset, SettingsPanel.TRANSITION_LENGTH, Easing.OutQuint); } settings.StateChanged += _ => updateScreenOffset(); @@ -610,9 +589,12 @@ namespace osu.Game private Task asyncLoadStream; - private void loadComponentSingleFile(T d, Action add) + private T loadComponentSingleFile(T d, Action add, bool cache = false) where T : Drawable { + if (cache) + dependencies.Cache(d); + // schedule is here to ensure that all component loads are done after LoadComplete is run (and thus all dependencies are cached). // with some better organisation of LoadComplete to do construction and dependency caching in one step, followed by calls to loadComponentSingleFile, // we could avoid the need for scheduling altogether. @@ -655,6 +637,8 @@ namespace osu.Game } }); }); + + return d; } public bool OnPressed(GlobalAction action) @@ -666,9 +650,11 @@ namespace osu.Game case GlobalAction.ToggleChat: chatOverlay.ToggleVisibility(); return true; + case GlobalAction.ToggleSocial: social.ToggleVisibility(); return true; + case GlobalAction.ResetInputSettings: var sensitivity = frameworkConfig.GetBindable(FrameworkSetting.CursorSensitivity); @@ -679,15 +665,19 @@ namespace osu.Game frameworkConfig.Set(FrameworkSetting.IgnoredInputHandlers, string.Empty); frameworkConfig.GetBindable(FrameworkSetting.ConfineMouseMode).SetDefault(); return true; + case GlobalAction.ToggleToolbar: Toolbar.ToggleVisibility(); return true; + case GlobalAction.ToggleSettings: settings.ToggleVisibility(); return true; + case GlobalAction.ToggleDirect: direct.ToggleVisibility(); return true; + case GlobalAction.ToggleGameplayMouseButtons: LocalConfig.Set(OsuSetting.MouseDisableButtons, !LocalConfig.Get(OsuSetting.MouseDisableButtons)); return true; @@ -765,6 +755,7 @@ namespace osu.Game case Intro intro: introScreen = intro; break; + case MainMenu menu: menuScreen = menu; break; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 44776bb2a8..7b9aed8364 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -29,6 +29,7 @@ using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.IO; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Skinning; using osuTK.Input; @@ -69,7 +70,16 @@ namespace osu.Game protected override Container Content => content; - private Bindable beatmap; + private Bindable beatmap; // cached via load() method + + [Cached] + [Cached(typeof(IBindable))] + protected readonly Bindable Ruleset = new Bindable(); + + // todo: move this to SongSelect once Screen has the ability to unsuspend. + [Cached] + [Cached(Type = typeof(IBindable>))] + protected readonly Bindable> Mods = new Bindable>(Array.Empty()); protected Bindable Beatmap => beatmap; @@ -155,8 +165,23 @@ namespace osu.Game dependencies.Cache(RulesetStore = new RulesetStore(contextFactory)); dependencies.Cache(FileStore = new FileStore(contextFactory, Host.Storage)); + + // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Host.Storage, contextFactory, Host)); dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, API, Audio, Host, defaultBeatmap)); - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, BeatmapManager, Host.Storage, contextFactory, Host)); + + // this should likely be moved to ArchiveModelManager when another case appers where it is necessary + // to have inter-dependent model managers. this could be obtained with an IHasForeign interface to + // allow lookups to be done on the child (ScoreManager in this case) to perform the cascading delete. + List getBeatmapScores(BeatmapSetInfo set) + { + var beatmapIds = BeatmapManager.QueryBeatmaps(b => b.BeatmapSetInfoID == set.ID).Select(b => b.ID).ToList(); + return ScoreManager.QueryScores(s => beatmapIds.Contains(s.Beatmap.ID)).ToList(); + } + + BeatmapManager.ItemRemoved += i => ScoreManager.Delete(getBeatmapScores(i), true); + BeatmapManager.ItemAdded += (i, existing) => ScoreManager.Undelete(getBeatmapScores(i), true); + dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); @@ -205,8 +230,10 @@ namespace osu.Game // TODO: This is temporary until we reimplement the local FPS display. // It's just to allow end-users to access the framework FPS display without knowing the shortcut key. fpsDisplayVisible = LocalConfig.GetBindable(OsuSetting.ShowFpsDisplay); - fpsDisplayVisible.ValueChanged += visible => { FrameStatisticsMode = visible.NewValue ? FrameStatisticsMode.Minimal : FrameStatisticsMode.None; }; + fpsDisplayVisible.ValueChanged += visible => { FrameStatistics.Value = visible.NewValue ? FrameStatisticsMode.Minimal : FrameStatisticsMode.None; }; fpsDisplayVisible.TriggerChange(); + + FrameStatistics.ValueChanged += e => fpsDisplayVisible.Value = e.NewValue != FrameStatisticsMode.None; } private void runMigrations() diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs index 13d8df098f..e136fc1403 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -209,6 +209,7 @@ namespace osu.Game.Overlays.AccountCreation private bool focusNextTextbox() { var nextTextbox = nextUnfilledTextbox(); + if (nextTextbox != null) { Schedule(() => GetContainingInputManager().ChangeFocus(nextTextbox)); diff --git a/osu.Game/Overlays/AccountCreationOverlay.cs b/osu.Game/Overlays/AccountCreationOverlay.cs index e8e44c206e..52d2917677 100644 --- a/osu.Game/Overlays/AccountCreationOverlay.cs +++ b/osu.Game/Overlays/AccountCreationOverlay.cs @@ -5,6 +5,7 @@ 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.Screens; using osu.Game.Graphics; @@ -103,8 +104,10 @@ namespace osu.Game.Overlays case APIState.Offline: case APIState.Failing: break; + case APIState.Connecting: break; + case APIState.Online: State = Visibility.Hidden; break; diff --git a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs index 18de87e7b4..7331faa618 100644 --- a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs +++ b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs @@ -9,8 +9,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Users; using osuTK; using osuTK.Graphics; -using osu.Game.Graphics.Containers; -using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; @@ -128,10 +127,5 @@ namespace osu.Game.Overlays.BeatmapSet }; } } - - private class ClickableArea : OsuClickableContainer, IHasTooltip - { - public string TooltipText => @"View Profile"; - } } } diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs index 4a60b69a5a..0a159507fe 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs @@ -123,6 +123,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons }, }; break; + case DownloadState.Downloaded: textSprites.Children = new Drawable[] { @@ -133,9 +134,11 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons }, }; break; + case DownloadState.LocallyAvailable: this.FadeOut(200); break; + case DownloadState.NotDownloaded: textSprites.Children = new Drawable[] { diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index 95cf9e9d04..a0f71d05c0 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -6,6 +6,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; @@ -225,11 +226,13 @@ namespace osu.Game.Overlays.BeatmapSet RelativeSizeAxes = Axes.Y }; break; + case DownloadState.Downloading: case DownloadState.Downloaded: // temporary to avoid showing two buttons for maps with novideo. will be fixed in new beatmap overlay design. downloadButtonsContainer.Child = new DownloadButton(BeatmapSet.Value); break; + default: downloadButtonsContainer.Child = new DownloadButton(BeatmapSet.Value); if (BeatmapSet.Value.OnlineInfo.HasVideo) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 4d974a0b63..44827f0a0c 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -6,6 +6,7 @@ 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.Game.Beatmaps; using osu.Game.Graphics; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index d8bd15a4a9..8e806c6747 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -5,6 +5,7 @@ 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.Input.Events; using osu.Game.Graphics; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs index c0d9ecad3a..89da0fc254 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -6,6 +6,7 @@ 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.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -82,7 +83,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, }, - date = new SpriteText + date = new OsuSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index c49268bc16..3ed398d31a 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -4,7 +4,6 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -12,17 +11,15 @@ using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Rulesets; using osuTK; -using osuTK.Graphics; namespace osu.Game.Overlays { - public class BeatmapSetOverlay : WaveOverlayContainer + public class BeatmapSetOverlay : FullscreenOverlay { private const int fade_duration = 300; @@ -31,7 +28,6 @@ namespace osu.Game.Overlays private readonly Header header; - private IAPIProvider api; private RulesetStore rulesets; private readonly ScrollContainer scroll; @@ -45,24 +41,6 @@ namespace osu.Game.Overlays { Info info; ScoresContainer scores; - Waves.FirstWaveColour = OsuColour.Gray(0.4f); - Waves.SecondWaveColour = OsuColour.Gray(0.3f); - Waves.ThirdWaveColour = OsuColour.Gray(0.2f); - Waves.FourthWaveColour = OsuColour.Gray(0.1f); - - Anchor = Anchor.TopCentre; - Origin = Anchor.TopCentre; - RelativeSizeAxes = Axes.Both; - Width = 0.85f; - - Masking = true; - EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.Black.Opacity(0), - Type = EdgeEffectType.Shadow, - Radius = 3, - Offset = new Vector2(0f, 1f), - }; Children = new Drawable[] { @@ -101,22 +79,15 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(IAPIProvider api, RulesetStore rulesets) + private void load(RulesetStore rulesets) { - this.api = api; this.rulesets = rulesets; } - protected override void PopIn() + protected override void PopOutComplete() { - base.PopIn(); - FadeEdgeEffectTo(0.25f, WaveContainer.APPEAR_DURATION, Easing.In); - } - - protected override void PopOut() - { - base.PopOut(); - FadeEdgeEffectTo(0, WaveContainer.DISAPPEAR_DURATION, Easing.Out).OnComplete(_ => beatmapSet.Value = null); + base.PopOutComplete(); + beatmapSet.Value = null; } protected override bool OnClick(ClickEvent e) @@ -134,7 +105,7 @@ namespace osu.Game.Overlays beatmapSet.Value = res.ToBeatmapSet(rulesets); header.Picker.Beatmap.Value = header.BeatmapSet.Value.Beatmaps.First(b => b.OnlineBeatmapID == beatmapId); }; - api.Queue(req); + API.Queue(req); Show(); } @@ -143,7 +114,7 @@ namespace osu.Game.Overlays beatmapSet.Value = null; var req = new GetBeatmapSetRequest(beatmapSetId); req.Success += res => beatmapSet.Value = res.ToBeatmapSet(rulesets); - api.Queue(req); + API.Queue(req); Show(); } diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs new file mode 100644 index 0000000000..57615332da --- /dev/null +++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs @@ -0,0 +1,167 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; +using System; +using System.Linq; +using System.Text.RegularExpressions; +using osu.Game.Graphics.Sprites; +using osu.Game.Users; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Changelog +{ + public class ChangelogBuild : FillFlowContainer + { + public const float HORIZONTAL_PADDING = 70; + + public Action SelectBuild; + + protected readonly APIChangelogBuild Build; + + public readonly FillFlowContainer ChangelogEntries; + + public ChangelogBuild(APIChangelogBuild build) + { + Build = build; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + Padding = new MarginPadding { Horizontal = HORIZONTAL_PADDING }; + + Children = new Drawable[] + { + CreateHeader(), + ChangelogEntries = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }, + }; + + foreach (var categoryEntries in build.ChangelogEntries.GroupBy(b => b.Category).OrderBy(c => c.Key)) + { + ChangelogEntries.Add(new OsuSpriteText + { + Text = categoryEntries.Key, + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 24), + Margin = new MarginPadding { Top = 35, Bottom = 15 }, + }); + + var fontLarge = OsuFont.GetFont(size: 18); + var fontMedium = OsuFont.GetFont(size: 14); + var fontSmall = OsuFont.GetFont(size: 12); + + foreach (APIChangelogEntry entry in categoryEntries) + { + LinkFlowContainer title = new LinkFlowContainer + { + Direction = FillDirection.Full, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Vertical = 5 }, + }; + + title.AddIcon(FontAwesome.Solid.Check, t => + { + t.Font = fontSmall; + t.Padding = new MarginPadding { Left = -17, Right = 5 }; + }); + + title.AddText(entry.Title, t => { t.Font = fontLarge; }); + + if (!string.IsNullOrEmpty(entry.Repository)) + { + title.AddText(" (", t => t.Font = fontLarge); + title.AddLink($"{entry.Repository.Replace("ppy/", "")}#{entry.GithubPullRequestId}", entry.GithubUrl, Online.Chat.LinkAction.External, + creationParameters: t => { t.Font = fontLarge; }); + title.AddText(")", t => t.Font = fontLarge); + } + + title.AddText(" by ", t => t.Font = fontMedium); + + if (entry.GithubUser.UserId != null) + title.AddUserLink(new User + { + Username = entry.GithubUser.OsuUsername, + Id = entry.GithubUser.UserId.Value + }, t => t.Font = fontMedium); + else if (entry.GithubUser.GithubUrl != null) + title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, Online.Chat.LinkAction.External, null, null, t => t.Font = fontMedium); + else + title.AddText(entry.GithubUser.DisplayName, t => t.Font = fontSmall); + + ChangelogEntries.Add(title); + + if (!string.IsNullOrEmpty(entry.MessageHtml)) + { + TextFlowContainer message = new TextFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + }; + + // todo: use markdown parsing once API returns markdown + message.AddText(Regex.Replace(entry.MessageHtml, @"<(.|\n)*?>", string.Empty), t => + { + t.Font = fontSmall; + t.Colour = new Color4(235, 184, 254, 255); + }); + + ChangelogEntries.Add(message); + } + } + } + } + + protected virtual FillFlowContainer CreateHeader() => new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Top = 20 }, + Children = new Drawable[] + { + new OsuHoverContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Action = () => SelectBuild?.Invoke(Build), + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Horizontal = 40 }, + Children = new[] + { + new OsuSpriteText + { + Text = Build.UpdateStream.DisplayName, + Font = OsuFont.GetFont(weight: FontWeight.Medium, size: 19), + }, + new OsuSpriteText + { + Text = " ", + Font = OsuFont.GetFont(weight: FontWeight.Medium, size: 19), + }, + new OsuSpriteText + { + Text = Build.DisplayVersion, + Font = OsuFont.GetFont(weight: FontWeight.Light, size: 19), + Colour = Build.UpdateStream.Colour, + }, + } + } + }, + } + }; + } +} diff --git a/osu.Game/Overlays/Changelog/ChangelogContent.cs b/osu.Game/Overlays/Changelog/ChangelogContent.cs new file mode 100644 index 0000000000..f8d5bbd66c --- /dev/null +++ b/osu.Game/Overlays/Changelog/ChangelogContent.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 osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; +using System; + +namespace osu.Game.Overlays.Changelog +{ + public class ChangelogContent : FillFlowContainer + { + public Action BuildSelected; + + public void SelectBuild(APIChangelogBuild build) => BuildSelected?.Invoke(build); + + public ChangelogContent() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + Padding = new MarginPadding { Bottom = 100 }; + } + } +} diff --git a/osu.Game/Overlays/Changelog/ChangelogHeader.cs b/osu.Game/Overlays/Changelog/ChangelogHeader.cs new file mode 100644 index 0000000000..fca62fbb44 --- /dev/null +++ b/osu.Game/Overlays/Changelog/ChangelogHeader.cs @@ -0,0 +1,170 @@ +// 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.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API.Requests.Responses; +using osuTK; + +namespace osu.Game.Overlays.Changelog +{ + public class ChangelogHeader : OverlayHeader + { + public readonly Bindable Current = new Bindable(); + + public Action ListingSelected; + + public UpdateStreamBadgeArea Streams; + + private const string listing_string = "Listing"; + + public ChangelogHeader() + { + TabControl.AddItem(listing_string); + TabControl.Current.ValueChanged += e => + { + if (e.NewValue == listing_string) + ListingSelected?.Invoke(); + }; + + Current.ValueChanged += showBuild; + + Streams.Current.ValueChanged += e => + { + if (e.NewValue?.LatestBuild != null && e.NewValue != Current.Value?.UpdateStream) + Current.Value = e.NewValue.LatestBuild; + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + TabControl.AccentColour = colours.Violet; + } + + private ChangelogHeaderTitle title; + + private void showBuild(ValueChangedEvent e) + { + if (e.OldValue != null) + TabControl.RemoveItem(e.OldValue.ToString()); + + if (e.NewValue != null) + { + TabControl.AddItem(e.NewValue.ToString()); + TabControl.Current.Value = e.NewValue.ToString(); + + Streams.Current.Value = Streams.Items.FirstOrDefault(s => s.Name == e.NewValue.UpdateStream.Name); + + title.Version = e.NewValue.UpdateStream.DisplayName; + } + else + { + TabControl.Current.Value = listing_string; + Streams.Current.Value = null; + title.Version = null; + } + } + + protected override Drawable CreateBackground() => new HeaderBackground(); + + protected override Drawable CreateContent() => new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + Streams = new UpdateStreamBadgeArea(), + } + }; + + protected override ScreenTitle CreateTitle() => title = new ChangelogHeaderTitle(); + + public class HeaderBackground : Sprite + { + public HeaderBackground() + { + RelativeSizeAxes = Axes.Both; + FillMode = FillMode.Fill; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + Texture = textures.Get(@"Headers/changelog"); + } + } + + private class ChangelogHeaderTitle : ScreenTitle + { + public string Version + { + set => Section = value ?? listing_string; + } + + public ChangelogHeaderTitle() + { + Title = "Changelog"; + Version = null; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.Violet; + } + + protected override Drawable CreateIcon() => new ChangelogIcon(); + + internal class ChangelogIcon : CompositeDrawable + { + private const float circle_allowance = 0.8f; + + [BackgroundDependencyLoader] + private void load(TextureStore textures, OsuColour colours) + { + Size = new Vector2(ICON_SIZE / circle_allowance); + + InternalChildren = new Drawable[] + { + new CircularContainer + { + Masking = true, + BorderColour = colours.Violet, + BorderThickness = 3, + MaskingSmoothness = 1, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Sprite + { + RelativeSizeAxes = Axes.Both, + Texture = textures.Get(@"Icons/changelog"), + Size = new Vector2(circle_allowance), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Violet, + Alpha = 0, + AlwaysPresent = true, + }, + } + }, + }; + } + } + } + } +} diff --git a/osu.Game/Overlays/Changelog/ChangelogListing.cs b/osu.Game/Overlays/Changelog/ChangelogListing.cs new file mode 100644 index 0000000000..41d8228475 --- /dev/null +++ b/osu.Game/Overlays/Changelog/ChangelogListing.cs @@ -0,0 +1,80 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Changelog +{ + public class ChangelogListing : ChangelogContent + { + private readonly List entries; + + public ChangelogListing(List entries) + { + this.entries = entries; + } + + [BackgroundDependencyLoader] + private void load() + { + DateTime currentDate = DateTime.MinValue; + + if (entries == null) return; + + foreach (APIChangelogBuild build in entries) + { + if (build.CreatedAt.Date != currentDate) + { + if (Children.Count != 0) + { + Add(new Box + { + RelativeSizeAxes = Axes.X, + Height = 2, + Colour = new Color4(17, 17, 17, 255), + Margin = new MarginPadding { Top = 30 }, + }); + } + + Add(new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding { Top = 15 }, + Text = build.CreatedAt.Date.ToString("dd MMM yyyy"), + Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 24), + Colour = OsuColour.FromHex(@"FD5"), + }); + + currentDate = build.CreatedAt.Date; + } + else + { + Add(new Container + { + RelativeSizeAxes = Axes.X, + Height = 1, + Padding = new MarginPadding { Horizontal = ChangelogBuild.HORIZONTAL_PADDING }, + Margin = new MarginPadding { Top = 30 }, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new Color4(32, 24, 35, 255), + } + }); + } + + Add(new ChangelogBuild(build) { SelectBuild = SelectBuild }); + } + } + } +} diff --git a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs new file mode 100644 index 0000000000..36ae5a756c --- /dev/null +++ b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs @@ -0,0 +1,142 @@ +// 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 System.Threading; +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osuTK; + +namespace osu.Game.Overlays.Changelog +{ + public class ChangelogSingleBuild : ChangelogContent + { + private APIChangelogBuild build; + + public ChangelogSingleBuild(APIChangelogBuild build) + { + this.build = build; + } + + [BackgroundDependencyLoader] + private void load(CancellationToken? cancellation, IAPIProvider api) + { + bool complete = false; + + var req = new GetChangelogBuildRequest(build.UpdateStream.Name, build.Version); + req.Success += res => + { + build = res; + complete = true; + }; + req.Failure += _ => complete = true; + + // This is done on a separate thread to support cancellation below + Task.Run(() => req.Perform(api)); + + while (!complete) + { + if (cancellation?.IsCancellationRequested == true) + { + req.Cancel(); + return; + } + + Thread.Sleep(10); + } + + if (build != null) + Child = new ChangelogBuildWithNavigation(build) { SelectBuild = SelectBuild }; + } + + public class ChangelogBuildWithNavigation : ChangelogBuild + { + public ChangelogBuildWithNavigation(APIChangelogBuild build) + : base(build) + { + } + + protected override FillFlowContainer CreateHeader() + { + var fill = base.CreateHeader(); + + foreach (var existing in fill.Children.OfType()) + { + existing.Scale = new Vector2(1.25f); + existing.Action = null; + + existing.Add(new OsuSpriteText + { + Text = Build.CreatedAt.Date.ToString("dd MMM yyyy"), + Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 14), + Colour = OsuColour.FromHex(@"FD5"), + Anchor = Anchor.BottomCentre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding { Top = 5 }, + }); + } + + NavigationIconButton left, right; + + fill.AddRange(new[] + { + left = new NavigationIconButton(Build.Versions?.Previous) + { + Icon = FontAwesome.Solid.ChevronLeft, + SelectBuild = b => SelectBuild(b) + }, + right = new NavigationIconButton(Build.Versions?.Next) + { + Icon = FontAwesome.Solid.ChevronRight, + SelectBuild = b => SelectBuild(b) + }, + }); + + fill.SetLayoutPosition(left, -1); + fill.SetLayoutPosition(right, 1); + + return fill; + } + } + + private class NavigationIconButton : IconButton + { + public Action SelectBuild; + + public NavigationIconButton(APIChangelogBuild build) + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + if (build == null) return; + + TooltipText = build.DisplayVersion; + + Action = () => + { + SelectBuild?.Invoke(build); + Enabled.Value = false; + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + HoverColour = colours.GreyVioletLight.Opacity(0.6f); + FlashColour = colours.GreyVioletLighter; + } + } + } +} diff --git a/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs b/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs new file mode 100644 index 0000000000..514e75c31a --- /dev/null +++ b/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs @@ -0,0 +1,157 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Humanizer; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Online.API.Requests.Responses; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Changelog +{ + public class UpdateStreamBadge : TabItem + { + private const float badge_height = 66.5f; + private const float badge_width = 100; + private const float transition_duration = 100; + + private readonly ExpandingBar expandingBar; + private SampleChannel sampleClick; + private SampleChannel sampleHover; + + private readonly FillFlowContainer text; + + public readonly Bindable SelectedTab = new Bindable(); + + private readonly Container fadeContainer; + + public UpdateStreamBadge(APIUpdateStream stream) + : base(stream) + { + Size = new Vector2(stream.IsFeatured ? badge_width * 2 : badge_width, badge_height); + Padding = new MarginPadding(5); + + Child = fadeContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + text = new FillFlowContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new[] + { + new OsuSpriteText + { + Text = stream.DisplayName, + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 12), + Margin = new MarginPadding { Top = 6 }, + }, + new OsuSpriteText + { + Text = stream.LatestBuild.DisplayVersion, + Font = OsuFont.GetFont(weight: FontWeight.Light, size: 16), + }, + new OsuSpriteText + { + Text = stream.LatestBuild.Users > 0 ? $"{stream.LatestBuild.Users:N0} {"user".Pluralize(stream.LatestBuild.Users == 1)} online" : null, + Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 10), + Colour = new Color4(203, 164, 218, 255), + }, + } + }, + expandingBar = new ExpandingBar + { + Anchor = Anchor.TopCentre, + Colour = stream.Colour, + ExpandedSize = 4, + CollapsedSize = 2, + IsCollapsed = true + }, + } + }; + + SelectedTab.BindValueChanged(_ => updateState(), true); + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + sampleClick = audio.Sample.Get(@"UI/generic-select-soft"); + sampleHover = audio.Sample.Get(@"UI/generic-hover-soft"); + } + + protected override void OnActivated() => updateState(); + + protected override void OnDeactivated() => updateState(); + + protected override bool OnClick(ClickEvent e) + { + sampleClick?.Play(); + return base.OnClick(e); + } + + protected override bool OnHover(HoverEvent e) + { + sampleHover?.Play(); + updateState(); + + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateState(); + base.OnHoverLost(e); + } + + private void updateState() + { + // Expand based on the local state + bool shouldExpand = Active.Value || IsHovered; + + // Expand based on whether no build is selected and the badge area is hovered + shouldExpand |= SelectedTab.Value == null && !externalDimRequested; + + if (shouldExpand) + { + expandingBar.Expand(); + fadeContainer.FadeTo(1, transition_duration); + } + else + { + expandingBar.Collapse(); + fadeContainer.FadeTo(0.5f, transition_duration); + } + + text.FadeTo(externalDimRequested && !IsHovered ? 0.5f : 1, transition_duration); + } + + private bool externalDimRequested; + + public void EnableDim() + { + externalDimRequested = true; + updateState(); + } + + public void DisableDim() + { + externalDimRequested = false; + updateState(); + } + } +} diff --git a/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs new file mode 100644 index 0000000000..2b48811bd6 --- /dev/null +++ b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs @@ -0,0 +1,75 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Game.Online.API.Requests.Responses; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Changelog +{ + public class UpdateStreamBadgeArea : TabControl + { + public UpdateStreamBadgeArea() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + AddInternal(new Box + { + Colour = Color4.Black, + Alpha = 0.12f, + RelativeSizeAxes = Axes.Both, + }); + } + + public void Populate(List streams) + { + Current.Value = null; + + foreach (APIUpdateStream updateStream in streams) + AddItem(updateStream); + } + + protected override bool OnHover(HoverEvent e) + { + foreach (UpdateStreamBadge streamBadge in TabContainer.Children.OfType()) + streamBadge.EnableDim(); + + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + foreach (UpdateStreamBadge streamBadge in TabContainer.Children.OfType()) + streamBadge.DisableDim(); + + base.OnHoverLost(e); + } + + protected override TabFillFlowContainer CreateTabFlow() + { + var flow = base.CreateTabFlow(); + + flow.RelativeSizeAxes = Axes.X; + flow.AutoSizeAxes = Axes.Y; + flow.AllowMultiline = true; + flow.Padding = new MarginPadding + { + Vertical = 20, + Horizontal = 85, + }; + + return flow; + } + + protected override Dropdown CreateDropdown() => null; + + protected override TabItem CreateTabItem(APIUpdateStream value) => + new UpdateStreamBadge(value) { SelectedTab = { BindTarget = Current } }; + } +} diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs new file mode 100644 index 0000000000..7d791b2a88 --- /dev/null +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -0,0 +1,210 @@ +// 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; +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Input.Bindings; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays.Changelog; + +namespace osu.Game.Overlays +{ + public class ChangelogOverlay : FullscreenOverlay + { + public readonly Bindable Current = new Bindable(); + + private ChangelogHeader header; + + private Container content; + + private SampleChannel sampleBack; + + private List builds; + + private List streams; + + [BackgroundDependencyLoader] + private void load(AudioManager audio, OsuColour colour) + { + Waves.FirstWaveColour = colour.GreyVioletLight; + Waves.SecondWaveColour = colour.GreyViolet; + Waves.ThirdWaveColour = colour.GreyVioletDark; + Waves.FourthWaveColour = colour.GreyVioletDarker; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colour.PurpleDarkAlternative, + }, + new ScrollContainer + { + RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, + Child = new ReverseChildIDFillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + header = new ChangelogHeader + { + ListingSelected = ShowListing, + }, + content = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } + }, + }, + }, + }; + + sampleBack = audio.Sample.Get(@"UI/generic-select-soft"); + + header.Current.BindTo(Current); + + Current.BindValueChanged(e => + { + if (e.NewValue != null) + loadContent(new ChangelogSingleBuild(e.NewValue)); + else + loadContent(new ChangelogListing(builds)); + }); + } + + public void ShowListing() + { + Current.Value = null; + State = Visibility.Visible; + } + + /// + /// Fetches and shows a specific build from a specific update stream. + /// + /// Must contain at least and + /// . If and + /// are specified, the header will instantly display them. + public void ShowBuild([NotNull] APIChangelogBuild build) + { + if (build == null) throw new ArgumentNullException(nameof(build)); + + Current.Value = build; + State = Visibility.Visible; + } + + public void ShowBuild([NotNull] string updateStream, [NotNull] string version) + { + if (updateStream == null) throw new ArgumentNullException(nameof(updateStream)); + if (version == null) throw new ArgumentNullException(nameof(version)); + + performAfterFetch(() => + { + var build = builds.Find(b => b.Version == version && b.UpdateStream.Name == updateStream) + ?? streams.Find(s => s.Name == updateStream)?.LatestBuild; + + if (build != null) + ShowBuild(build); + }); + + State = Visibility.Visible; + } + + public override bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.Back: + if (Current.Value == null) + { + State = Visibility.Hidden; + } + else + { + Current.Value = null; + sampleBack?.Play(); + } + + return true; + } + + return false; + } + + protected override void PopIn() + { + base.PopIn(); + + if (initialFetchTask == null) + // fetch and refresh to show listing, if no other request was made via Show methods + performAfterFetch(() => Current.TriggerChange()); + } + + private Task initialFetchTask; + + private void performAfterFetch(Action action) => fetchListing()?.ContinueWith(_ => Schedule(action)); + + private Task fetchListing() + { + if (initialFetchTask != null) + return initialFetchTask; + + return initialFetchTask = Task.Run(async () => + { + var tcs = new TaskCompletionSource(); + + var req = new GetChangelogRequest(); + req.Success += res => + { + // remap streams to builds to ensure model equality + res.Builds.ForEach(b => b.UpdateStream = res.Streams.Find(s => s.Id == b.UpdateStream.Id)); + res.Streams.ForEach(s => s.LatestBuild.UpdateStream = res.Streams.Find(s2 => s2.Id == s.LatestBuild.UpdateStream.Id)); + + builds = res.Builds; + streams = res.Streams; + + header.Streams.Populate(res.Streams); + + tcs.SetResult(true); + }; + req.Failure += _ => initialFetchTask = null; + req.Perform(API); + + await tcs.Task; + }); + } + + private CancellationTokenSource loadContentCancellation; + + private void loadContent(ChangelogContent newContent) + { + content.FadeTo(0.2f, 300, Easing.OutQuint); + + loadContentCancellation?.Cancel(); + + LoadComponentAsync(newContent, c => + { + content.FadeIn(300, Easing.OutQuint); + + c.BuildSelected = ShowBuild; + content.Child = c; + }, (loadContentCancellation = new CancellationTokenSource()).Token); + } + } +} diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 908ec5f026..66a6672ab1 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -8,6 +8,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.Containers; diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index e3df81e455..aec78b962f 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -99,6 +99,7 @@ namespace osu.Game.Overlays.Chat private void pendingMessageResolved(Message existing, Message updated) { var found = ChatLineFlow.Children.LastOrDefault(c => c.Message == existing); + if (found != null) { Trace.Assert(updated.Id.HasValue, "An updated message was returned with no ID."); diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs index 52260506fe..7386bffb1a 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs @@ -13,8 +13,8 @@ namespace osu.Game.Overlays.Chat.Tabs public override bool IsSwitchable => false; - public ChannelSelectorTabItem(Channel value) - : base(value) + public ChannelSelectorTabItem() + : base(new ChannelSelectorTabChannel()) { Depth = float.MaxValue; Width = 45; @@ -26,10 +26,18 @@ namespace osu.Game.Overlays.Chat.Tabs } [BackgroundDependencyLoader] - private new void load(OsuColour colour) + private void load(OsuColour colour) { BackgroundInactive = colour.Gray2; BackgroundActive = colour.Gray3; } + + public class ChannelSelectorTabChannel : Channel + { + public ChannelSelectorTabChannel() + { + Name = "+"; + } + } } } diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs index 2e7f2d5908..fafcb0a72d 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Chat.Tabs Margin = new MarginPadding(10), }); - AddTabItem(selectorTab = new ChannelSelectorTabItem(new Channel { Name = "+" })); + AddTabItem(selectorTab = new ChannelSelectorTabItem()); ChannelSelectorActive.BindTo(selectorTab.Active); } @@ -58,6 +58,7 @@ namespace osu.Game.Overlays.Chat.Tabs { default: return new ChannelTabItem(value) { OnRequestClose = tabCloseRequested }; + case ChannelType.PM: return new PrivateChannelTabItem(value) { OnRequestClose = tabCloseRequested }; } diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs index a4aefa4c4f..7f820e4ff7 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs @@ -6,6 +6,7 @@ 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.UserInterface; diff --git a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs index 8aa6d6fecd..b8165e70cb 100644 --- a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; using osu.Game.Users; using osuTK; @@ -18,9 +17,6 @@ namespace osu.Game.Overlays.Chat.Tabs { public class PrivateChannelTabItem : ChannelTabItem { - private readonly OsuSpriteText username; - private readonly Avatar avatarContainer; - protected override IconUsage DisplayIcon => FontAwesome.Solid.At; public PrivateChannelTabItem(Channel value) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 77f88ab4e7..eb95fabe02 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -199,12 +199,16 @@ namespace osu.Game.Overlays return; } + if (e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel) + return; + textbox.Current.Disabled = e.NewValue.ReadOnly; if (channelTabControl.Current.Value != e.NewValue) Scheduler.Add(() => channelTabControl.Current.Value = e.NewValue); var loaded = loadedChannels.Find(d => d.Channel == e.NewValue); + if (loaded == null) { currentChannelContainer.FadeOut(500, Easing.OutQuint); @@ -267,7 +271,7 @@ namespace osu.Game.Overlays private void selectTab(int index) { var channel = channelTabControl.Items.Skip(index).FirstOrDefault(); - if (channel != null && channel.Name != "+") + if (channel != null && !(channel is ChannelSelectorTabItem.ChannelSelectorTabChannel)) channelTabControl.Current.Value = channel; } @@ -288,6 +292,7 @@ namespace osu.Game.Overlays case Key.Number9: selectTab((int)e.Key - (int)Key.Number1); return true; + case Key.Number0: selectTab(9); return true; diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 91f42a491a..5949f1fcd4 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -6,6 +6,7 @@ using System.Linq; 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; @@ -72,6 +73,7 @@ namespace osu.Game.Overlays.Dialog set { buttonsContainer.ChildrenEnumerable = value; + foreach (PopupDialogButton b in value) { var action = b.Action; @@ -222,6 +224,7 @@ namespace osu.Game.Overlays.Dialog // press button at number if 1-9 on number row or keypad are pressed var k = e.Key; + if (k >= Key.Number1 && k <= Key.Number9) { pressButtonAtIndex(k - Key.Number1); diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs index eb73a50f99..5756a4593d 100644 --- a/osu.Game/Overlays/Direct/DirectGridPanel.cs +++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs @@ -15,6 +15,7 @@ using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Direct { @@ -119,28 +120,16 @@ namespace osu.Game.Overlays.Direct }, Children = new Drawable[] { - new FillFlowContainer + new LinkFlowContainer(s => { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new[] - { - new OsuSpriteText - { - Text = "mapped by ", - Font = OsuFont.GetFont(size: 14), - Shadow = false, - Colour = colours.Gray5, - }, - new OsuSpriteText - { - Text = SetInfo.Metadata.Author.Username, - Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold, italics: true), - Shadow = false, - Colour = colours.BlueDark, - }, - }, - }, + s.Shadow = false; + s.Font = OsuFont.GetFont(size: 14); + }).With(d => + { + d.AutoSizeAxes = Axes.Both; + d.AddText("mapped by ", t => t.Colour = colours.Gray5); + d.AddUserLink(SetInfo.Metadata.Author); + }), new Container { AutoSizeAxes = Axes.X, diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs index d645fd3bda..b731e95d54 100644 --- a/osu.Game/Overlays/Direct/DirectListPanel.cs +++ b/osu.Game/Overlays/Direct/DirectListPanel.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Direct { @@ -86,8 +87,9 @@ namespace osu.Game.Overlays.Direct { Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, - Size = new Vector2(height / 2), + Size = new Vector2(height / 3), FillMode = FillMode.Fit, + Margin = new MarginPadding { Right = 10 }, }, new FillFlowContainer { @@ -150,7 +152,7 @@ namespace osu.Game.Overlays.Direct Child = new DownloadButton(SetInfo) { Size = new Vector2(height - vertical_padding * 3), - Margin = new MarginPadding { Left = vertical_padding, Right = vertical_padding }, + Margin = new MarginPadding { Left = vertical_padding * 2, Right = vertical_padding }, }, }, new FillFlowContainer @@ -163,26 +165,21 @@ namespace osu.Game.Overlays.Direct { new Statistic(FontAwesome.Solid.PlayCircle, SetInfo.OnlineInfo?.PlayCount ?? 0), new Statistic(FontAwesome.Solid.Heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), - new FillFlowContainer + new LinkFlowContainer(s => + { + s.Shadow = false; + s.Font = OsuFont.GetFont(size: 14); + }) { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new[] - { - new OsuSpriteText - { - Text = "mapped by ", - Font = OsuFont.GetFont(size: 14) - }, - new OsuSpriteText - { - Text = SetInfo.Metadata.Author.Username, - Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold, italics: true) - }, - }, - }, + }.With(d => + { + d.AutoSizeAxes = Axes.Both; + d.AddText("mapped by "); + d.AddUserLink(SetInfo.Metadata.Author); + }), new OsuSpriteText { Text = SetInfo.Metadata.Source, diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index 2b509f370e..f413dc3771 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -8,6 +8,7 @@ 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.Input.Events; diff --git a/osu.Game/Overlays/Direct/DownloadButton.cs b/osu.Game/Overlays/Direct/DownloadButton.cs index 6107dc3af3..3f44d854e5 100644 --- a/osu.Game/Overlays/Direct/DownloadButton.cs +++ b/osu.Game/Overlays/Direct/DownloadButton.cs @@ -85,9 +85,11 @@ namespace osu.Game.Overlays.Direct case DownloadState.Downloaded: shakeContainer.Shake(); break; + case DownloadState.LocallyAvailable: game.PresentBeatmap(BeatmapSet.Value); break; + default: beatmaps.Download(BeatmapSet.Value, noVideo); break; @@ -110,9 +112,11 @@ namespace osu.Game.Overlays.Direct icon.MoveToX(0, 500, Easing.InOutExpo); checkmark.ScaleTo(Vector2.Zero, 500, Easing.InOutExpo); break; + case DownloadState.Downloaded: background.FadeColour(colours.Yellow, 500, Easing.InOutExpo); break; + case DownloadState.LocallyAvailable: background.FadeColour(colours.Green, 500, Easing.InOutExpo); icon.MoveToX(-8, 500, Easing.InOutExpo); diff --git a/osu.Game/Overlays/Direct/DownloadProgressBar.cs b/osu.Game/Overlays/Direct/DownloadProgressBar.cs index 9c2b1e5b63..57500b3531 100644 --- a/osu.Game/Overlays/Direct/DownloadProgressBar.cs +++ b/osu.Game/Overlays/Direct/DownloadProgressBar.cs @@ -43,10 +43,12 @@ namespace osu.Game.Overlays.Direct progressBar.Current.Value = 0; progressBar.FadeOut(500); break; + case DownloadState.Downloading: progressBar.FadeIn(400, Easing.OutQuint); progressBar.ResizeHeightTo(4, 400, Easing.OutQuint); break; + case DownloadState.Downloaded: progressBar.FadeIn(400, Easing.OutQuint); progressBar.ResizeHeightTo(4, 400, Easing.OutQuint); @@ -54,6 +56,7 @@ namespace osu.Game.Overlays.Direct progressBar.Current.Value = 1; progressBar.FillColour = colours.Yellow; break; + case DownloadState.LocallyAvailable: progressBar.FadeOut(500); break; diff --git a/osu.Game/Overlays/Direct/PlayButton.cs b/osu.Game/Overlays/Direct/PlayButton.cs index 6daebb3c15..2a77e7ca26 100644 --- a/osu.Game/Overlays/Direct/PlayButton.cs +++ b/osu.Game/Overlays/Direct/PlayButton.cs @@ -129,7 +129,7 @@ namespace osu.Game.Overlays.Direct if (Preview != null) { - Preview.Start(); + attemptStart(); return; } @@ -147,7 +147,7 @@ namespace osu.Game.Overlays.Direct // user may have changed their mind. if (Playing.Value) - preview.Start(); + attemptStart(); }); } else @@ -157,6 +157,12 @@ namespace osu.Game.Overlays.Direct } } + private void attemptStart() + { + if (Preview?.Start() != true) + Playing.Value = false; + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index 34edbbcc8b..975bf4e3ca 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -14,7 +14,6 @@ using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Direct; using osu.Game.Overlays.SearchableList; @@ -28,7 +27,6 @@ namespace osu.Game.Overlays { private const float panel_padding = 10f; - private IAPIProvider api; private RulesetStore rulesets; private readonly FillFlowContainer resultCountsContainer; @@ -58,6 +56,7 @@ namespace osu.Game.Overlays var artists = new List(); var songs = new List(); var tags = new List(); + foreach (var s in beatmapSets) { artists.Add(s.Metadata.Artist); @@ -86,8 +85,6 @@ namespace osu.Game.Overlays public DirectOverlay() { - RelativeSizeAxes = Axes.Both; - // osu!direct colours are not part of the standard palette Waves.FirstWaveColour = OsuColour.FromHex(@"19b0e2"); @@ -164,9 +161,8 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(OsuColour colours, IAPIProvider api, RulesetStore rulesets, PreviewTrackManager previewTrackManager) + private void load(OsuColour colours, RulesetStore rulesets, PreviewTrackManager previewTrackManager) { - this.api = api; this.rulesets = rulesets; this.previewTrackManager = previewTrackManager; @@ -210,6 +206,7 @@ namespace osu.Game.Overlays Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }; + default: return new DirectListPanel(b); } @@ -258,7 +255,7 @@ namespace osu.Game.Overlays if (State == Visibility.Hidden) return; - if (api == null) + if (API == null) return; previewTrackManager.StopAnyPlaying(this); @@ -284,7 +281,7 @@ namespace osu.Game.Overlays }); }; - api.Queue(getSetsRequest); + API.Queue(getSetsRequest); } private int distinctCount(List list) => list.Distinct().ToArray().Length; diff --git a/osu.Game/Overlays/FullscreenOverlay.cs b/osu.Game/Overlays/FullscreenOverlay.cs new file mode 100644 index 0000000000..9706f75087 --- /dev/null +++ b/osu.Game/Overlays/FullscreenOverlay.cs @@ -0,0 +1,75 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Effects; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.API; +using osuTK.Graphics; + +namespace osu.Game.Overlays +{ + public abstract class FullscreenOverlay : WaveOverlayContainer, IOnlineComponent + { + [Resolved] + protected IAPIProvider API { get; private set; } + + protected FullscreenOverlay() + { + Waves.FirstWaveColour = OsuColour.Gray(0.4f); + Waves.SecondWaveColour = OsuColour.Gray(0.3f); + Waves.ThirdWaveColour = OsuColour.Gray(0.2f); + Waves.FourthWaveColour = OsuColour.Gray(0.1f); + + RelativeSizeAxes = Axes.Both; + RelativePositionAxes = Axes.Both; + Width = 0.85f; + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; + + Masking = true; + + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0), + Type = EdgeEffectType.Shadow, + Radius = 10 + }; + } + + protected override void PopIn() + { + base.PopIn(); + FadeEdgeEffectTo(0.4f, WaveContainer.APPEAR_DURATION, Easing.Out); + } + + protected override void PopOut() + { + base.PopOut(); + FadeEdgeEffectTo(0, WaveContainer.DISAPPEAR_DURATION, Easing.In).OnComplete(_ => PopOutComplete()); + } + + protected virtual void PopOutComplete() + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + API.Register(this); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + API?.Unregister(this); + } + + public virtual void APIStateChanged(IAPIProvider api, APIState state) + { + } + } +} diff --git a/osu.Game/Overlays/HoldToConfirmOverlay.cs b/osu.Game/Overlays/HoldToConfirmOverlay.cs index 154aff605a..fb38ddcbd1 100644 --- a/osu.Game/Overlays/HoldToConfirmOverlay.cs +++ b/osu.Game/Overlays/HoldToConfirmOverlay.cs @@ -11,7 +11,7 @@ namespace osu.Game.Overlays { /// /// An overlay which will display a black screen that dims over a period before confirming an exit action. - /// Action is BYO (derived class will need to call and from a user event). + /// Action is BYO (derived class will need to call and from a user event). /// public abstract class HoldToConfirmOverlay : HoldToConfirmContainer { diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 8313dac50a..9a707adaea 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -8,6 +8,7 @@ using osu.Framework.Extensions; 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.Input.Bindings; using osu.Framework.Input.Events; @@ -117,6 +118,7 @@ namespace osu.Game.Overlays.KeyBinding public void RestoreDefaults() { int i = 0; + foreach (var d in Defaults) { var button = buttons[i++]; diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index 53cb3edeca..08288516e3 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.KeyBinding this.variant = variant; FlowContent.Spacing = new Vector2(0, 1); - FlowContent.Padding = new MarginPadding { Left = SettingsOverlay.CONTENT_MARGINS, Right = SettingsOverlay.CONTENT_MARGINS }; + FlowContent.Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS }; } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/KeyBindingPanel.cs b/osu.Game/Overlays/KeyBindingPanel.cs new file mode 100644 index 0000000000..928bd080fa --- /dev/null +++ b/osu.Game/Overlays/KeyBindingPanel.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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Input.Bindings; +using osu.Game.Overlays.KeyBinding; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets; + +namespace osu.Game.Overlays +{ + public class KeyBindingPanel : SettingsSubPanel + { + protected override Drawable CreateHeader() => new SettingsHeader("key configuration", "Customise your keys!"); + + [BackgroundDependencyLoader(permitNulls: true)] + private void load(RulesetStore rulesets, GlobalActionContainer global) + { + AddSection(new GlobalKeyBindingsSection(global)); + + foreach (var ruleset in rulesets.AvailableRulesets) + AddSection(new RulesetBindingsSection(ruleset)); + } + } +} diff --git a/osu.Game/Overlays/MainSettings.cs b/osu.Game/Overlays/MainSettings.cs deleted file mode 100644 index 61dd51d16f..0000000000 --- a/osu.Game/Overlays/MainSettings.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Overlays.Settings; -using osu.Game.Overlays.Settings.Sections; -using osuTK.Graphics; -using System.Collections.Generic; - -namespace osu.Game.Overlays -{ - public class MainSettings : SettingsOverlay - { - private readonly KeyBindingOverlay keyBindingOverlay; - - protected override IEnumerable CreateSections() => new SettingsSection[] - { - new GeneralSection(), - new GraphicsSection(), - new GameplaySection(), - new AudioSection(), - new SkinSection(), - new InputSection(keyBindingOverlay), - new OnlineSection(), - new MaintenanceSection(), - new DebugSection(), - }; - - protected override Drawable CreateHeader() => new SettingsHeader("settings", "Change the way osu! behaves"); - protected override Drawable CreateFooter() => new SettingsFooter(); - - public MainSettings() - : base(true) - { - keyBindingOverlay = new KeyBindingOverlay - { - Depth = 1, - Anchor = Anchor.TopRight, - }; - keyBindingOverlay.StateChanged += keyBindingOverlay_StateChanged; - } - - public override bool AcceptsFocus => keyBindingOverlay.State != Visibility.Visible; - - private void keyBindingOverlay_StateChanged(Visibility visibility) - { - switch (visibility) - { - case Visibility.Visible: - Background.FadeTo(0.9f, 300, Easing.OutQuint); - Sidebar?.FadeColour(Color4.DarkGray, 300, Easing.OutQuint); - - SectionsContainer.FadeOut(300, Easing.OutQuint); - ContentContainer.MoveToX(-WIDTH, 500, Easing.OutQuint); - break; - case Visibility.Hidden: - Background.FadeTo(0.6f, 500, Easing.OutQuint); - Sidebar?.FadeColour(Color4.White, 300, Easing.OutQuint); - - SectionsContainer.FadeIn(500, Easing.OutQuint); - ContentContainer.MoveToX(0, 500, Easing.OutQuint); - break; - } - } - - protected override float ExpandedPosition => keyBindingOverlay.State == Visibility.Visible ? -WIDTH : base.ExpandedPosition; - - [BackgroundDependencyLoader] - private void load() - { - ContentContainer.Add(keyBindingOverlay); - } - } -} diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index a5703eba92..6d82db5603 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -19,6 +19,7 @@ using osu.Framework.Graphics.Textures; using osuTK.Input; using osu.Framework.Graphics.Shapes; using System; +using osu.Framework.Graphics.Effects; using osu.Framework.Input.Events; using osu.Framework.MathUtils; diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index 431ae98c2c..f1ae5d64f5 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -156,11 +156,13 @@ namespace osu.Game.Overlays.MedalSplash case DisplayState.None: medalContainer.ScaleTo(0); break; + case DisplayState.Icon: medalContainer .FadeIn(duration) .ScaleTo(1, duration, Easing.OutElastic); break; + case DisplayState.MedalUnlocked: medalContainer .FadeTo(1) @@ -170,6 +172,7 @@ namespace osu.Game.Overlays.MedalSplash this.MoveToY(MedalOverlay.DISC_SIZE / 2 - 30, duration, Easing.OutExpo); unlocked.FadeInFromZero(duration); break; + case DisplayState.Full: medalContainer .FadeTo(1) diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 23b75caedc..fa1ee500a8 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -81,6 +81,7 @@ namespace osu.Game.Overlays.Mods backgroundIcon.RotateTo(-rotate_angle * direction, mod_switch_duration, mod_switch_easing); backgroundIcon.Icon = modAfter.Icon; + using (BeginDelayedSequence(mod_switch_duration, true)) { foregroundIcon @@ -139,6 +140,7 @@ namespace osu.Game.Overlays.Mods } createIcons(); + if (Mods.Length > 0) { displayMod(Mods[0]); @@ -168,6 +170,7 @@ namespace osu.Game.Overlays.Mods case MouseButton.Left: SelectNext(1); break; + case MouseButton.Right: SelectNext(-1); break; @@ -219,6 +222,7 @@ namespace osu.Game.Overlays.Mods private void createIcons() { iconsContainer.Clear(); + if (Mods.Length > 1) { iconsContainer.AddRange(new[] diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index a118357f21..50400e254f 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -77,6 +77,7 @@ namespace osu.Game.Overlays.Mods public void DeselectTypes(IEnumerable modTypes, bool immediate = false) { int delay = 0; + foreach (var button in buttons) { Mod selected = button.SelectedMod; diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index aa41723ca6..97769fe5aa 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -42,19 +42,19 @@ namespace osu.Game.Overlays.Mods protected readonly FillFlowContainer ModSectionsContainer; - protected readonly Bindable> SelectedMods = new Bindable>(new Mod[] { }); + protected readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); protected readonly IBindable Ruleset = new Bindable(); [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, IBindable ruleset, AudioManager audio, Bindable> selectedMods) + private void load(OsuColour colours, IBindable ruleset, AudioManager audio, Bindable> mods) { LowMultiplierColour = colours.Red; HighMultiplierColour = colours.Green; UnrankedLabel.Colour = colours.Blue; Ruleset.BindTo(ruleset); - if (selectedMods != null) SelectedMods.BindTo(selectedMods); + if (mods != null) SelectedMods.BindTo(mods); sampleOn = audio.Sample.Get(@"UI/check-on"); sampleOff = audio.Sample.Get(@"UI/check-off"); @@ -87,14 +87,14 @@ namespace osu.Game.Overlays.Mods // attempt to re-select any already selected mods. // this may be the first time we are receiving the ruleset, in which case they will still match. - selectedModsChanged(new ValueChangedEvent>(SelectedMods.Value, SelectedMods.Value)); + selectedModsChanged(new ValueChangedEvent>(SelectedMods.Value, SelectedMods.Value)); // write the mods back to the SelectedMods bindable in the case a change was not applicable. // this generally isn't required as the previous line will perform deselection; just here for safety. refreshSelectedMods(); } - private void selectedModsChanged(ValueChangedEvent> e) + private void selectedModsChanged(ValueChangedEvent> e) { foreach (ModSection section in ModSectionsContainer.Children) section.SelectTypes(e.NewValue.Select(m => m.GetType()).ToList()); diff --git a/osu.Game/Overlays/Music/CollectionsDropdown.cs b/osu.Game/Overlays/Music/CollectionsDropdown.cs index aa93e349e8..4f59b053b6 100644 --- a/osu.Game/Overlays/Music/CollectionsDropdown.cs +++ b/osu.Game/Overlays/Music/CollectionsDropdown.cs @@ -6,7 +6,7 @@ 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.Effects; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index 310c6c919f..89d166b788 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -190,6 +190,7 @@ namespace osu.Game.Overlays.Music // the item positions as they are being transformed float heightAccumulator = 0; int dstIndex = 0; + for (; dstIndex < items.Count; dstIndex++) { // Using BoundingBox here takes care of scale, paddings, etc... diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 8cbea63fe3..4431288a1a 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -8,6 +8,7 @@ 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.Game.Beatmaps; using osu.Game.Graphics; @@ -81,6 +82,7 @@ namespace osu.Game.Overlays.Music filter.Search.OnCommit = (sender, newText) => { BeatmapInfo toSelect = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault(); + if (toSelect != null) { beatmap.Value = beatmaps.GetWorkingBeatmap(toSelect); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index b24c6c3508..ea3e1ca00c 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -10,6 +10,7 @@ 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.Textures; @@ -22,6 +23,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Music; +using osu.Game.Rulesets.Mods; using osuTK; using osuTK.Graphics; @@ -49,12 +51,15 @@ namespace osu.Game.Overlays private BeatmapManager beatmaps; private List beatmapSets; - private BeatmapSetInfo currentSet; private Container dragContainer; private Container playerContainer; - private readonly Bindable beatmap = new Bindable(); + [Resolved] + private Bindable beatmap { get; set; } + + [Resolved] + private IBindable> mods { get; set; } /// /// Provide a source for the toolbar height. @@ -73,7 +78,6 @@ namespace osu.Game.Overlays [BackgroundDependencyLoader] private void load(Bindable beatmap, BeatmapManager beatmaps, OsuColour colours) { - this.beatmap.BindTo(beatmap); this.beatmaps = beatmaps; Children = new Drawable[] @@ -231,6 +235,7 @@ namespace osu.Game.Overlays { beatmap.BindValueChanged(beatmapChanged, true); beatmap.BindDisabledChanged(beatmapDisabledChanged, true); + mods.BindValueChanged(_ => updateAudioAdjustments(), true); base.LoadComplete(); } @@ -296,6 +301,7 @@ namespace osu.Game.Overlays queuedDirection = TransformDirection.Prev; var playable = beatmapSets.TakeWhile(i => i.ID != current.BeatmapSetInfo.ID).LastOrDefault() ?? beatmapSets.LastOrDefault(); + if (playable != null) { beatmap.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); @@ -309,6 +315,7 @@ namespace osu.Game.Overlays queuedDirection = TransformDirection.Next; var playable = beatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? beatmapSets.FirstOrDefault(); + if (playable != null) { beatmap.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); @@ -354,10 +361,23 @@ namespace osu.Game.Overlays progressBar.CurrentTime = 0; updateDisplay(current, direction); + updateAudioAdjustments(); queuedDirection = null; } + private void updateAudioAdjustments() + { + var track = current?.Track; + if (track == null) + return; + + track.ResetSpeedAdjustments(); + + foreach (var mod in mods.Value.OfType()) + mod.ApplyToClock(track); + } + private void currentTrackCompleted() => Schedule(() => { if (!current.Track.Looping && !beatmap.Disabled && beatmapSets.Any()) @@ -399,6 +419,7 @@ namespace osu.Game.Overlays newBackground.MoveToX(0, 500, Easing.OutCubic); background.MoveToX(-400, 500, Easing.OutCubic); break; + case TransformDirection.Prev: newBackground.Position = new Vector2(-400, 0); newBackground.MoveToX(0, 500, Easing.OutCubic); diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 522e039cdb..2dc6b39a92 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -7,6 +7,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Game.Graphics; using osuTK; using osuTK.Graphics; diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 75e70b18ea..857a0bda9e 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -54,11 +54,13 @@ namespace osu.Game.Overlays.Notifications Light.Pulsate = false; progressBar.Active = false; break; + case ProgressNotificationState.Active: Light.Colour = colourActive; Light.Pulsate = true; progressBar.Active = true; break; + case ProgressNotificationState.Cancelled: Light.Colour = colourCancelled; Light.Pulsate = false; @@ -145,6 +147,7 @@ namespace osu.Game.Overlays.Notifications case ProgressNotificationState.Cancelled: base.Close(); break; + case ProgressNotificationState.Active: case ProgressNotificationState.Queued: if (CancelRequested?.Invoke() != false) diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs index 5e45fbf081..88a1edddc5 100644 --- a/osu.Game/Overlays/OnScreenDisplay.cs +++ b/osu.Game/Overlays/OnScreenDisplay.cs @@ -14,6 +14,7 @@ using osu.Game.Graphics; using osuTK; using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Transforms; using osu.Framework.Threading; using osu.Game.Configuration; @@ -188,6 +189,7 @@ namespace osu.Game.Overlays optionCount = 1; if (val) selectedOption = 0; break; + case Enum _: var values = Enum.GetValues(description.RawValue.GetType()); optionCount = values.Length; diff --git a/osu.Game/Overlays/OverlayHeader.cs b/osu.Game/Overlays/OverlayHeader.cs new file mode 100644 index 0000000000..2e032db2ba --- /dev/null +++ b/osu.Game/Overlays/OverlayHeader.cs @@ -0,0 +1,70 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays +{ + public abstract class OverlayHeader : Container + { + protected readonly OverlayHeaderTabControl TabControl; + + private const float cover_height = 150; + private const float cover_info_height = 75; + + protected OverlayHeader() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + Height = cover_height, + Masking = true, + Child = CreateBackground() + }, + new Container + { + Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }, + Y = cover_height, + Height = cover_info_height, + RelativeSizeAxes = Axes.X, + Anchor = Anchor.TopLeft, + Origin = Anchor.BottomLeft, + Depth = -float.MaxValue, + Children = new Drawable[] + { + CreateTitle().With(t => t.X = -ScreenTitle.ICON_WIDTH), + TabControl = new OverlayHeaderTabControl + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = cover_info_height - 30, + Margin = new MarginPadding { Left = -UserProfileOverlay.CONTENT_X_MARGIN }, + Padding = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN } + } + } + }, + new Container + { + Margin = new MarginPadding { Top = cover_height }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = CreateContent() + } + }; + } + + protected abstract Drawable CreateBackground(); + + protected abstract Drawable CreateContent(); + + protected abstract ScreenTitle CreateTitle(); + } +} diff --git a/osu.Game/Overlays/OverlayHeaderTabControl.cs b/osu.Game/Overlays/OverlayHeaderTabControl.cs new file mode 100644 index 0000000000..dfe7e52420 --- /dev/null +++ b/osu.Game/Overlays/OverlayHeaderTabControl.cs @@ -0,0 +1,154 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays +{ + public class OverlayHeaderTabControl : TabControl + { + private readonly Box bar; + + private Color4 accentColour = Color4.White; + + public Color4 AccentColour + { + get => accentColour; + set + { + if (accentColour == value) + return; + + accentColour = value; + bar.Colour = value; + + foreach (TabItem tabItem in TabContainer) + { + ((HeaderTabItem)tabItem).AccentColour = value; + } + } + } + + public new MarginPadding Padding + { + get => TabContainer.Padding; + set => TabContainer.Padding = value; + } + + public OverlayHeaderTabControl() + { + TabContainer.Masking = false; + TabContainer.Spacing = new Vector2(15, 0); + + AddInternal(bar = new Box + { + RelativeSizeAxes = Axes.X, + Height = 2, + Anchor = Anchor.BottomLeft, + Origin = Anchor.CentreLeft + }); + } + + protected override Dropdown CreateDropdown() => null; + + protected override TabItem CreateTabItem(string value) => new HeaderTabItem(value) + { + AccentColour = AccentColour + }; + + private class HeaderTabItem : TabItem + { + private readonly OsuSpriteText text; + private readonly ExpandingBar bar; + + private Color4 accentColour; + + public Color4 AccentColour + { + get => accentColour; + set + { + if (accentColour == value) + return; + + accentColour = value; + bar.Colour = value; + + updateState(); + } + } + + public HeaderTabItem(string value) + : base(value) + { + AutoSizeAxes = Axes.X; + RelativeSizeAxes = Axes.Y; + + Children = new Drawable[] + { + text = new OsuSpriteText + { + Margin = new MarginPadding { Bottom = 10 }, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Text = value, + Font = OsuFont.GetFont() + }, + bar = new ExpandingBar + { + Anchor = Anchor.BottomCentre, + ExpandedSize = 7.5f, + CollapsedSize = 0 + }, + new HoverClickSounds() + }; + } + + 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() + { + if (Active.Value || IsHovered) + { + text.FadeColour(Color4.White, 120, Easing.InQuad); + bar.Expand(); + + if (Active.Value) + text.Font = text.Font.With(weight: FontWeight.Bold); + } + else + { + text.FadeColour(AccentColour, 120, Easing.InQuad); + bar.Collapse(); + text.Font = text.Font.With(weight: FontWeight.Medium); + } + } + } + } +} diff --git a/osu.Game/Overlays/Profile/Components/DrawableJoinDate.cs b/osu.Game/Overlays/Profile/Components/DrawableJoinDate.cs deleted file mode 100644 index 93f08768f7..0000000000 --- a/osu.Game/Overlays/Profile/Components/DrawableJoinDate.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 System; -using osu.Game.Graphics; - -namespace osu.Game.Overlays.Profile.Components -{ - public class DrawableJoinDate : DrawableDate - { - public DrawableJoinDate(DateTimeOffset date) - : base(date) - { - } - - protected override string Format() => Text = Date.ToUniversalTime().Year < 2008 ? "Here since the beginning" : $"{Date:MMMM yyyy}"; - - public override string TooltipText => $"{Date:MMMM d, yyyy}"; - } -} diff --git a/osu.Game/Overlays/Profile/Components/GradeBadge.cs b/osu.Game/Overlays/Profile/Components/GradeBadge.cs deleted file mode 100644 index ca56780663..0000000000 --- a/osu.Game/Overlays/Profile/Components/GradeBadge.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; - -namespace osu.Game.Overlays.Profile.Components -{ - public class GradeBadge : Container - { - private const float width = 50; - private readonly string grade; - private readonly Sprite badge; - private readonly SpriteText numberText; - - public int DisplayCount - { - set => numberText.Text = value.ToString(@"#,0"); - } - - public GradeBadge(string grade) - { - this.grade = grade; - Width = width; - Height = 41; - Add(badge = new Sprite - { - Width = width, - Height = 26 - }); - Add(numberText = new OsuSpriteText - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold) - }); - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - badge.Texture = textures.Get($"Grades/{grade}"); - } - } -} diff --git a/osu.Game/Overlays/Profile/Header/BadgeContainer.cs b/osu.Game/Overlays/Profile/Header/BadgeContainer.cs deleted file mode 100644 index ff4d7a10dc..0000000000 --- a/osu.Game/Overlays/Profile/Header/BadgeContainer.cs +++ /dev/null @@ -1,197 +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.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Users; -using osuTK; - -namespace osu.Game.Overlays.Profile.Header -{ - public class BadgeContainer : Container - { - private static readonly Vector2 badge_size = new Vector2(86, 40); - private static readonly MarginPadding outer_padding = new MarginPadding(3); - - private OsuSpriteText badgeCountText; - private FillFlowContainer badgeFlowContainer; - private FillFlowContainer outerBadgeContainer; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Child = new Container - { - Masking = true, - CornerRadius = 4, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Gray3 - }, - outerBadgeContainer = new OuterBadgeContainer(onOuterHover, onOuterHoverLost) - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Direction = FillDirection.Vertical, - Padding = outer_padding, - Width = DrawableBadge.DRAWABLE_BADGE_SIZE.X + outer_padding.TotalHorizontal, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - badgeCountText = new OsuSpriteText - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Alpha = 0, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular) - }, - new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - AutoSizeAxes = Axes.Both, - Child = badgeFlowContainer = new FillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - } - } - } - }, - } - }; - - Scheduler.AddDelayed(rotateBadges, 3000, true); - } - - private void rotateBadges() - { - if (outerBadgeContainer.IsHovered) return; - - visibleBadge = (visibleBadge + 1) % badgeCount; - - badgeFlowContainer.MoveToX(-DrawableBadge.DRAWABLE_BADGE_SIZE.X * visibleBadge, 500, Easing.InOutQuad); - } - - private int visibleBadge; - private int badgeCount; - - public void ShowBadges(Badge[] badges) - { - if (badges == null || badges.Length == 0) - { - Hide(); - return; - } - - badgeCount = badges.Length; - - badgeCountText.FadeTo(badgeCount > 1 ? 1 : 0); - badgeCountText.Text = $"{badges.Length} badges"; - - Show(); - visibleBadge = 0; - - badgeFlowContainer.Clear(); - for (var index = 0; index < badges.Length; index++) - { - int displayIndex = index; - LoadComponentAsync(new DrawableBadge(badges[index]) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }, asyncBadge => - { - badgeFlowContainer.Add(asyncBadge); - - // load in stable order regardless of async load order. - badgeFlowContainer.SetLayoutPosition(asyncBadge, displayIndex); - }); - } - } - - private void onOuterHover() - { - badgeFlowContainer.ClearTransforms(); - badgeFlowContainer.X = 0; - badgeFlowContainer.Direction = FillDirection.Full; - outerBadgeContainer.AutoSizeAxes = Axes.Both; - - badgeFlowContainer.MaximumSize = new Vector2(ChildSize.X, float.MaxValue); - } - - private void onOuterHoverLost() - { - badgeFlowContainer.X = -DrawableBadge.DRAWABLE_BADGE_SIZE.X * visibleBadge; - badgeFlowContainer.Direction = FillDirection.Horizontal; - outerBadgeContainer.AutoSizeAxes = Axes.Y; - outerBadgeContainer.Width = DrawableBadge.DRAWABLE_BADGE_SIZE.X + outer_padding.TotalHorizontal; - } - - private class OuterBadgeContainer : FillFlowContainer - { - private readonly Action hoverAction; - private readonly Action hoverLostAction; - - public OuterBadgeContainer(Action hoverAction, Action hoverLostAction) - { - this.hoverAction = hoverAction; - this.hoverLostAction = hoverLostAction; - } - - protected override bool OnHover(HoverEvent e) - { - hoverAction(); - return true; - } - - protected override void OnHoverLost(HoverLostEvent e) => hoverLostAction(); - } - - private class DrawableBadge : Container, IHasTooltip - { - public static readonly Vector2 DRAWABLE_BADGE_SIZE = badge_size + outer_padding.Total; - - private readonly Badge badge; - - public DrawableBadge(Badge badge) - { - this.badge = badge; - Padding = outer_padding; - Size = DRAWABLE_BADGE_SIZE; - } - - [BackgroundDependencyLoader] - private void load(LargeTextureStore textures) - { - Child = new Sprite - { - FillMode = FillMode.Fit, - RelativeSizeAxes = Axes.Both, - Texture = textures.Get(badge.ImageUrl), - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - Child.FadeInFromZero(200); - } - - public string TooltipText => badge.Description; - } - } -} diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs new file mode 100644 index 0000000000..ffbb9ad218 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -0,0 +1,154 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions; +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.Users; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile.Header +{ + public class BottomHeaderContainer : CompositeDrawable + { + public readonly Bindable User = new Bindable(); + + private LinkFlowContainer topLinkContainer; + private LinkFlowContainer bottomLinkContainer; + + private Color4 iconColour; + + public BottomHeaderContainer() + { + AutoSizeAxes = Axes.Y; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + iconColour = colours.GreySeafoamLighter; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.GreySeafoamDark, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + topLinkContainer = new LinkFlowContainer(text => text.Font = text.Font.With(size: 12)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + bottomLinkContainer = new LinkFlowContainer(text => text.Font = text.Font.With(size: 12)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } + } + } + }; + + User.BindValueChanged(user => updateDisplay(user.NewValue)); + } + + private void updateDisplay(User user) + { + topLinkContainer.Clear(); + bottomLinkContainer.Clear(); + + if (user == null) return; + + if (user.JoinDate.ToUniversalTime().Year < 2008) + topLinkContainer.AddText("Here since the beginning"); + else + { + topLinkContainer.AddText("Joined "); + topLinkContainer.AddText(new DrawableDate(user.JoinDate), embolden); + } + + addSpacer(topLinkContainer); + + if (user.LastVisit.HasValue) + { + topLinkContainer.AddText("Last seen "); + topLinkContainer.AddText(new DrawableDate(user.LastVisit.Value), embolden); + + addSpacer(topLinkContainer); + } + + if (user.PlayStyles?.Length > 0) + { + topLinkContainer.AddText("Plays with "); + topLinkContainer.AddText(string.Join(", ", user.PlayStyles.Select(style => style.GetDescription())), embolden); + + addSpacer(topLinkContainer); + } + + topLinkContainer.AddText("Contributed "); + topLinkContainer.AddLink($@"{user.PostCount:#,##0} forum posts", $"https://osu.ppy.sh/users/{user.Id}/posts", creationParameters: embolden); + + string websiteWithoutProtcol = user.Website; + + if (!string.IsNullOrEmpty(websiteWithoutProtcol)) + { + if (Uri.TryCreate(websiteWithoutProtcol, UriKind.Absolute, out var uri)) + { + websiteWithoutProtcol = uri.Host + uri.PathAndQuery + uri.Fragment; + websiteWithoutProtcol = websiteWithoutProtcol.TrimEnd('/'); + } + } + + tryAddInfo(FontAwesome.Solid.MapMarker, user.Location); + tryAddInfo(OsuIcon.Heart, user.Interests); + tryAddInfo(FontAwesome.Solid.Suitcase, user.Occupation); + bottomLinkContainer.NewLine(); + if (!string.IsNullOrEmpty(user.Twitter)) + tryAddInfo(FontAwesome.Brands.Twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); + tryAddInfo(FontAwesome.Brands.Discord, user.Discord); + tryAddInfo(FontAwesome.Brands.Skype, user.Skype, @"skype:" + user.Skype + @"?chat"); + tryAddInfo(FontAwesome.Brands.Lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}"); + tryAddInfo(FontAwesome.Solid.Link, websiteWithoutProtcol, user.Website); + } + + private void addSpacer(OsuTextFlowContainer textFlow) => textFlow.AddArbitraryDrawable(new Container { Width = 15 }); + + private void tryAddInfo(IconUsage icon, string content, string link = null) + { + if (string.IsNullOrEmpty(content)) return; + + bottomLinkContainer.AddIcon(icon, text => + { + text.Font = text.Font.With(size: 10); + text.Colour = iconColour; + }); + + if (link != null) + bottomLinkContainer.AddLink(" " + content, link, creationParameters: embolden); + else + bottomLinkContainer.AddText(" " + content, embolden); + + addSpacer(bottomLinkContainer); + } + + private void embolden(SpriteText text) => text.Font = text.Font.With(weight: FontWeight.Bold); + } +} diff --git a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs new file mode 100644 index 0000000000..68fd77dd84 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs @@ -0,0 +1,150 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header +{ + public class CentreHeaderContainer : CompositeDrawable + { + public readonly BindableBool DetailsVisible = new BindableBool(true); + public readonly Bindable User = new Bindable(); + + private OverlinedInfoContainer hiddenDetailGlobal; + private OverlinedInfoContainer hiddenDetailCountry; + + public CentreHeaderContainer() + { + Height = 60; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, TextureStore textures) + { + Container hiddenDetailContainer; + Container expandedDetailContainer; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.GreySeafoam + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding { Vertical = 10 }, + Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new AddFriendButton + { + RelativeSizeAxes = Axes.Y, + User = { BindTarget = User } + }, + new MessageUserButton + { + User = { BindTarget = User } + }, + } + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Y, + Padding = new MarginPadding { Vertical = 10 }, + Width = UserProfileOverlay.CONTENT_X_MARGIN, + Child = new ExpandDetailsButton + { + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + DetailsVisible = { BindTarget = DetailsVisible } + }, + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Right = UserProfileOverlay.CONTENT_X_MARGIN }, + Children = new Drawable[] + { + new LevelBadge + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(40), + User = { BindTarget = User } + }, + expandedDetailContainer = new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Width = 200, + Height = 6, + Margin = new MarginPadding { Right = 50 }, + Child = new LevelProgressBar + { + RelativeSizeAxes = Axes.Both, + User = { BindTarget = User } + } + }, + hiddenDetailContainer = new FillFlowContainer + { + Direction = FillDirection.Horizontal, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Width = 200, + AutoSizeAxes = Axes.Y, + Alpha = 0, + Spacing = new Vector2(10, 0), + Margin = new MarginPadding { Right = 50 }, + Children = new[] + { + hiddenDetailGlobal = new OverlinedInfoContainer + { + Title = "Global Ranking", + LineColour = colours.Yellow + }, + hiddenDetailCountry = new OverlinedInfoContainer + { + Title = "Country Ranking", + LineColour = colours.Yellow + }, + } + } + } + } + }; + + DetailsVisible.BindValueChanged(visible => + { + hiddenDetailContainer.FadeTo(visible.NewValue ? 0 : 1, 200, Easing.OutQuint); + expandedDetailContainer.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint); + }); + + User.BindValueChanged(user => updateDisplay(user.NewValue)); + } + + private void updateDisplay(User user) + { + hiddenDetailGlobal.Content = user?.Statistics?.Ranks.Global?.ToString("\\##,##0") ?? "-"; + hiddenDetailCountry.Content = user?.Statistics?.Ranks.Country?.ToString("\\##,##0") ?? "-"; + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/AddFriendButton.cs b/osu.Game/Overlays/Profile/Header/Components/AddFriendButton.cs new file mode 100644 index 0000000000..2e4fd6fe3d --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/AddFriendButton.cs @@ -0,0 +1,58 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class AddFriendButton : ProfileHeaderButton + { + public readonly Bindable User = new Bindable(); + + public override string TooltipText => "friends"; + + private OsuSpriteText followerText; + + [BackgroundDependencyLoader] + private void load() + { + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding { Right = 10 }, + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.User, + FillMode = FillMode.Fit, + Size = new Vector2(50, 14) + }, + followerText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(weight: FontWeight.Bold) + } + } + }; + + User.BindValueChanged(user => updateFollowers(user.NewValue), true); + } + + private void updateFollowers(User user) => followerText.Text = user?.FollowerCount?.Length > 0 ? user.FollowerCount[0].ToString("#,##0") : "0"; + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs b/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs new file mode 100644 index 0000000000..ea259fe49a --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs @@ -0,0 +1,46 @@ +// 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.Cursor; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class DrawableBadge : CompositeDrawable, IHasTooltip + { + public static readonly Vector2 DRAWABLE_BADGE_SIZE = new Vector2(86, 40); + + private readonly Badge badge; + + public DrawableBadge(Badge badge) + { + this.badge = badge; + Size = DRAWABLE_BADGE_SIZE; + } + + [BackgroundDependencyLoader] + private void load(LargeTextureStore textures) + { + InternalChild = new Sprite + { + FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Both, + Texture = textures.Get(badge.ImageUrl), + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + InternalChild.FadeInFromZero(200); + } + + public string TooltipText => badge.Description; + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs new file mode 100644 index 0000000000..46d24608ed --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class ExpandDetailsButton : ProfileHeaderButton + { + public readonly BindableBool DetailsVisible = new BindableBool(); + + public override string TooltipText => DetailsVisible.Value ? "collapse" : "expand"; + + private SpriteIcon icon; + + public ExpandDetailsButton() + { + Action = () => DetailsVisible.Toggle(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + IdleColour = colours.GreySeafoamLight; + HoverColour = colours.GreySeafoamLight.Darken(0.2f); + + Child = icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(20, 12) + }; + + DetailsVisible.BindValueChanged(visible => updateState(visible.NewValue), true); + } + + private void updateState(bool detailsVisible) => icon.Icon = detailsVisible ? FontAwesome.Solid.ChevronUp : FontAwesome.Solid.ChevronDown; + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs new file mode 100644 index 0000000000..8069937810 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Users; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class LevelBadge : CompositeDrawable, IHasTooltip + { + public readonly Bindable User = new Bindable(); + + public string TooltipText { get; } + + private OsuSpriteText levelText; + + public LevelBadge() + { + TooltipText = "Level"; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, TextureStore textures) + { + InternalChildren = new Drawable[] + { + new Sprite + { + RelativeSizeAxes = Axes.Both, + Texture = textures.Get("Profile/levelbadge"), + Colour = colours.Yellow, + }, + levelText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 20) + } + }; + + User.BindValueChanged(user => updateLevel(user.NewValue)); + } + + private void updateLevel(User user) + { + levelText.Text = user?.Statistics?.Level.Current.ToString() ?? "0"; + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs new file mode 100644 index 0000000000..6a6532764f --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs @@ -0,0 +1,65 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Users; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class LevelProgressBar : CompositeDrawable, IHasTooltip + { + public readonly Bindable User = new Bindable(); + + public string TooltipText { get; } + + private Bar levelProgressBar; + private OsuSpriteText levelProgressText; + + public LevelProgressBar() + { + TooltipText = "Progress to next level"; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChildren = new Drawable[] + { + new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = levelProgressBar = new Bar + { + RelativeSizeAxes = Axes.Both, + BackgroundColour = Color4.Black, + Direction = BarDirection.LeftToRight, + AccentColour = colours.Yellow + } + }, + levelProgressText = new OsuSpriteText + { + Anchor = Anchor.BottomRight, + Origin = Anchor.TopRight, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold) + } + }; + + User.BindValueChanged(user => updateProgress(user.NewValue)); + } + + private void updateProgress(User user) + { + levelProgressBar.Length = user?.Statistics?.Level.Progress / 100f ?? 0; + levelProgressText.Text = user?.Statistics?.Level.Progress.ToString("0'%'"); + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs new file mode 100644 index 0000000000..cc6edcdd6a --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs @@ -0,0 +1,59 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Online.API; +using osu.Game.Online.Chat; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class MessageUserButton : ProfileHeaderButton + { + public readonly Bindable User = new Bindable(); + + public override string TooltipText => "send message"; + + [Resolved(CanBeNull = true)] + private ChannelManager channelManager { get; set; } + + [Resolved(CanBeNull = true)] + private UserProfileOverlay userOverlay { get; set; } + + [Resolved(CanBeNull = true)] + private ChatOverlay chatOverlay { get; set; } + + [Resolved] + private IAPIProvider apiProvider { get; set; } + + public MessageUserButton() + { + Content.Alpha = 0; + RelativeSizeAxes = Axes.Y; + + Child = new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.Envelope, + FillMode = FillMode.Fit, + Size = new Vector2(50, 14) + }; + + Action = () => + { + if (!Content.IsPresent) return; + + channelManager?.OpenPrivateChannel(User.Value); + userOverlay?.Hide(); + chatOverlay?.Show(); + }; + + User.ValueChanged += e => Content.Alpha = !e.NewValue.PMFriendsOnly && apiProvider.LocalUser.Value.Id != e.NewValue.Id ? 1 : 0; + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs b/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs new file mode 100644 index 0000000000..c40ddca688 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.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 osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class OverlinedInfoContainer : CompositeDrawable + { + private readonly Circle line; + private readonly OsuSpriteText title; + private readonly OsuSpriteText content; + + public string Title + { + set => title.Text = value; + } + + public string Content + { + set => content.Text = value; + } + + public Color4 LineColour + { + set => line.Colour = value; + } + + public OverlinedInfoContainer(bool big = false, int minimumWidth = 60) + { + AutoSizeAxes = Axes.Both; + InternalChild = new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + line = new Circle + { + RelativeSizeAxes = Axes.X, + Height = 4, + }, + title = new OsuSpriteText + { + Font = OsuFont.GetFont(size: big ? 14 : 12, weight: FontWeight.Bold) + }, + content = new OsuSpriteText + { + Font = OsuFont.GetFont(size: big ? 40 : 18, weight: FontWeight.Light) + }, + new Container //Add a minimum size to the FillFlowContainer + { + Width = minimumWidth, + } + } + }; + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs b/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs new file mode 100644 index 0000000000..2c88a83680 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Game.Graphics; +using osu.Game.Users; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class OverlinedTotalPlayTime : CompositeDrawable, IHasTooltip + { + public readonly Bindable User = new Bindable(); + + public string TooltipText { get; set; } + + private OverlinedInfoContainer info; + + public OverlinedTotalPlayTime() + { + AutoSizeAxes = Axes.Both; + + TooltipText = "0 hours"; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChild = info = new OverlinedInfoContainer + { + Title = "Total Play Time", + LineColour = colours.Yellow, + }; + + User.BindValueChanged(updateTime, true); + } + + private void updateTime(ValueChangedEvent user) + { + TooltipText = (user.NewValue?.Statistics?.PlayTime ?? 0) / 3600 + " hours"; + info.Content = formatTime(user.NewValue?.Statistics?.PlayTime); + } + + private string formatTime(int? secondsNull) + { + if (secondsNull == null) return "0h 0m"; + + int seconds = secondsNull.Value; + string time = ""; + + int days = seconds / 86400; + seconds -= days * 86400; + if (days > 0) + time += days + "d "; + + int hours = seconds / 3600; + seconds -= hours * 3600; + time += hours + "h "; + + int minutes = seconds / 60; + time += minutes + "m"; + + return time; + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs new file mode 100644 index 0000000000..ddcf011277 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.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 System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public abstract class ProfileHeaderButton : OsuHoverContainer + { + private readonly Box background; + private readonly Container content; + + protected override Container Content => content; + + protected override IEnumerable EffectTargets => new[] { background }; + + protected ProfileHeaderButton() + { + AutoSizeAxes = Axes.X; + + IdleColour = Color4.Black; + HoverColour = OsuColour.Gray(0.1f); + + base.Content.Add(new CircularContainer + { + Masking = true, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + content = new Container + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = 10 }, + } + } + }); + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs new file mode 100644 index 0000000000..5f79386b76 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -0,0 +1,283 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class RankGraph : Container, IHasCustomTooltip + { + private const float secondary_textsize = 13; + private const float padding = 10; + private const float fade_duration = 150; + private const int ranked_days = 88; + + private readonly RankChartLineGraph graph; + private readonly OsuSpriteText placeholder; + + private KeyValuePair[] ranks; + private int dayIndex; + public Bindable User = new Bindable(); + + public RankGraph() + { + Padding = new MarginPadding { Vertical = padding }; + Children = new Drawable[] + { + placeholder = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "No recent plays", + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular) + }, + graph = new RankChartLineGraph + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.Both, + Y = -secondary_textsize, + Alpha = 0, + } + }; + + graph.OnBallMove += i => dayIndex = i; + + User.ValueChanged += userChanged; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + graph.LineColour = colours.Yellow; + } + + private void userChanged(ValueChangedEvent e) + { + placeholder.FadeIn(fade_duration, Easing.Out); + + if (e.NewValue?.Statistics?.Ranks.Global == null) + { + graph.FadeOut(fade_duration, Easing.Out); + ranks = null; + return; + } + + int[] userRanks = e.NewValue.RankHistory?.Data ?? new[] { e.NewValue.Statistics.Ranks.Global.Value }; + ranks = userRanks.Select((x, index) => new KeyValuePair(index, x)).Where(x => x.Value != 0).ToArray(); + + if (ranks.Length > 1) + { + placeholder.FadeOut(fade_duration, Easing.Out); + + graph.DefaultValueCount = ranks.Length; + graph.Values = ranks.Select(x => -(float)Math.Log(x.Value)); + } + + graph.FadeTo(ranks.Length > 1 ? 1 : 0, fade_duration, Easing.Out); + } + + protected override bool OnHover(HoverEvent e) + { + if (ranks?.Length > 1) + { + graph.UpdateBallPosition(e.MousePosition.X); + graph.ShowBar(); + } + + return base.OnHover(e); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + if (ranks?.Length > 1) + graph.UpdateBallPosition(e.MousePosition.X); + + return base.OnMouseMove(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + if (ranks?.Length > 1) + { + graph.HideBar(); + } + + base.OnHoverLost(e); + } + + private class RankChartLineGraph : LineGraph + { + private readonly CircularContainer movingBall; + private readonly Container bar; + private readonly Box ballBg; + private readonly Box line; + + public Action OnBallMove; + + public RankChartLineGraph() + { + Add(bar = new Container + { + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Alpha = 0, + RelativePositionAxes = Axes.Both, + Children = new Drawable[] + { + line = new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Width = 1.5f, + }, + movingBall = new CircularContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + Size = new Vector2(18), + Masking = true, + BorderThickness = 4, + RelativePositionAxes = Axes.Y, + Child = ballBg = new Box { RelativeSizeAxes = Axes.Both } + } + } + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + ballBg.Colour = colours.GreySeafoamDarker; + movingBall.BorderColour = line.Colour = colours.Yellow; + } + + public void UpdateBallPosition(float mouseXPosition) + { + const int duration = 200; + int index = calculateIndex(mouseXPosition); + Vector2 position = calculateBallPosition(index); + movingBall.MoveToY(position.Y, duration, Easing.OutQuint); + bar.MoveToX(position.X, duration, Easing.OutQuint); + OnBallMove.Invoke(index); + } + + public void ShowBar() => bar.FadeIn(fade_duration); + + public void HideBar() => bar.FadeOut(fade_duration); + + private int calculateIndex(float mouseXPosition) => (int)Math.Round(mouseXPosition / DrawWidth * (DefaultValueCount - 1)); + + private Vector2 calculateBallPosition(int index) + { + float y = GetYPosition(Values.ElementAt(index)); + return new Vector2(index / (float)(DefaultValueCount - 1), y); + } + } + + public string TooltipText => User.Value?.Statistics?.Ranks.Global == null ? "" : $"#{ranks[dayIndex].Value:#,##0}|{ranked_days - ranks[dayIndex].Key + 1}"; + + public ITooltip GetCustomTooltip() => new RankGraphTooltip(); + + public class RankGraphTooltip : VisibilityContainer, ITooltip + { + private readonly OsuSpriteText globalRankingText, timeText; + private readonly Box background; + + public string TooltipText { get; set; } + + public RankGraphTooltip() + { + AutoSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 10; + + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(10), + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Text = "Global Ranking " + }, + globalRankingText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + } + } + }, + timeText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.GreySeafoamDark; + } + + public void Refresh() + { + var info = TooltipText.Split('|'); + globalRankingText.Text = info[0]; + timeText.Text = info[1] == "0" ? "now" : $"{info[1]} days ago"; + } + + private bool instantMove = true; + + public void Move(Vector2 pos) + { + if (instantMove) + { + Position = pos; + instantMove = false; + } + else + this.MoveTo(pos, 200, Easing.OutQuint); + } + + protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); + + protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs new file mode 100644 index 0000000000..c5e61f68f4 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs @@ -0,0 +1,86 @@ +// 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.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class SupporterIcon : CompositeDrawable, IHasTooltip + { + private readonly Box background; + private readonly FillFlowContainer iconContainer; + private readonly CircularContainer content; + + public string TooltipText => "osu!supporter"; + + public int SupportLevel + { + set + { + int count = MathHelper.Clamp(value, 0, 3); + + if (count == 0) + { + content.Hide(); + } + else + { + content.Show(); + iconContainer.Clear(); + + for (int i = 0; i < count; i++) + { + iconContainer.Add(new SpriteIcon + { + Width = 12, + RelativeSizeAxes = Axes.Y, + Icon = FontAwesome.Solid.Heart, + }); + } + + iconContainer.Padding = new MarginPadding { Horizontal = DrawHeight / 2 }; + } + } + } + + public SupporterIcon() + { + AutoSizeAxes = Axes.X; + + InternalChild = content = new CircularContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + Alpha = 0, + Children = new Drawable[] + { + background = new Box { RelativeSizeAxes = Axes.Both }, + iconContainer = new FillFlowContainer + { + Direction = FillDirection.Horizontal, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Height = 0.6f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Pink; + iconContainer.Colour = colours.GreySeafoam; + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs new file mode 100644 index 0000000000..f26cc360a2 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -0,0 +1,219 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Leaderboards; +using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Scoring; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header +{ + public class DetailHeaderContainer : CompositeDrawable + { + private readonly Dictionary scoreRankInfos = new Dictionary(); + private OverlinedInfoContainer medalInfo; + private OverlinedInfoContainer ppInfo; + private OverlinedInfoContainer detailGlobalRank; + private OverlinedInfoContainer detailCountryRank; + private FillFlowContainer fillFlow; + private RankGraph rankGraph; + + public readonly Bindable User = new Bindable(); + + private bool expanded = true; + + public bool Expanded + { + set + { + if (expanded == value) return; + + expanded = value; + + if (fillFlow == null) return; + + fillFlow.ClearTransforms(); + + if (expanded) + fillFlow.AutoSizeAxes = Axes.Y; + else + { + fillFlow.AutoSizeAxes = Axes.None; + fillFlow.ResizeHeightTo(0, 200, Easing.OutQuint); + } + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AutoSizeAxes = Axes.Y; + + User.ValueChanged += e => updateDisplay(e.NewValue); + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.GreySeafoamDarker, + }, + fillFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = expanded ? Axes.Y : Axes.None, + AutoSizeDuration = 200, + AutoSizeEasing = Easing.OutQuint, + Masking = true, + Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new OverlinedTotalPlayTime + { + User = { BindTarget = User } + }, + medalInfo = new OverlinedInfoContainer + { + Title = "Medals", + LineColour = colours.GreenLight, + }, + ppInfo = new OverlinedInfoContainer + { + Title = "pp", + LineColour = colours.Red, + }, + } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Direction = FillDirection.Horizontal, + Children = new[] + { + scoreRankInfos[ScoreRank.XH] = new ScoreRankInfo(ScoreRank.XH), + scoreRankInfos[ScoreRank.X] = new ScoreRankInfo(ScoreRank.X), + scoreRankInfos[ScoreRank.SH] = new ScoreRankInfo(ScoreRank.SH), + scoreRankInfos[ScoreRank.S] = new ScoreRankInfo(ScoreRank.S), + scoreRankInfos[ScoreRank.A] = new ScoreRankInfo(ScoreRank.A), + } + } + } + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Right = 130 }, + Children = new Drawable[] + { + rankGraph = new RankGraph + { + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + Width = 130, + Anchor = Anchor.TopRight, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Horizontal = 10 }, + Spacing = new Vector2(0, 20), + Children = new Drawable[] + { + detailGlobalRank = new OverlinedInfoContainer(true, 110) + { + Title = "Global Ranking", + LineColour = colours.Yellow, + }, + detailCountryRank = new OverlinedInfoContainer(false, 110) + { + Title = "Country Ranking", + LineColour = colours.Yellow, + }, + } + } + } + }, + } + }, + }; + } + + private void updateDisplay(User user) + { + medalInfo.Content = user?.Achievements?.Length.ToString() ?? "0"; + ppInfo.Content = user?.Statistics?.PP?.ToString("#,##0") ?? "0"; + + foreach (var scoreRankInfo in scoreRankInfos) + scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0; + + detailGlobalRank.Content = user?.Statistics?.Ranks.Global?.ToString("\\##,##0") ?? "-"; + detailCountryRank.Content = user?.Statistics?.Ranks.Country?.ToString("\\##,##0") ?? "-"; + + rankGraph.User.Value = user; + } + + private class ScoreRankInfo : CompositeDrawable + { + private readonly OsuSpriteText rankCount; + + public int RankCount + { + set => rankCount.Text = value.ToString("#,##0"); + } + + public ScoreRankInfo(ScoreRank rank) + { + AutoSizeAxes = Axes.Both; + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + Width = 56, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new DrawableRank(rank) + { + RelativeSizeAxes = Axes.X, + Height = 30, + }, + rankCount = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + } + } + }; + } + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs new file mode 100644 index 0000000000..67229a80c0 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs @@ -0,0 +1,94 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using 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.Game.Graphics; +using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Users; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile.Header +{ + public class MedalHeaderContainer : CompositeDrawable + { + private FillFlowContainer badgeFlowContainer; + + public readonly Bindable User = new Bindable(); + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Alpha = 0; + AutoSizeAxes = Axes.Y; + User.ValueChanged += e => updateDisplay(e.NewValue); + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.GreySeafoamDarker, + }, + new Container //artificial shadow + { + RelativeSizeAxes = Axes.X, + Height = 3, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new ColourInfo + { + TopLeft = Color4.Black.Opacity(0.2f), + TopRight = Color4.Black.Opacity(0.2f), + BottomLeft = Color4.Black.Opacity(0), + BottomRight = Color4.Black.Opacity(0) + } + }, + }, + badgeFlowContainer = new FillFlowContainer + { + Direction = FillDirection.Full, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 5 }, + Spacing = new Vector2(10, 10), + Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, + } + }; + } + + private void updateDisplay(User user) + { + var badges = user.Badges; + badgeFlowContainer.Clear(); + + if (badges?.Length > 0) + { + Show(); + + for (var index = 0; index < badges.Length; index++) + { + int displayIndex = index; + LoadComponentAsync(new DrawableBadge(badges[index]), asyncBadge => + { + badgeFlowContainer.Add(asyncBadge); + + // load in stable order regardless of async load order. + badgeFlowContainer.SetLayoutPosition(asyncBadge, displayIndex); + }); + } + } + else + { + Hide(); + } + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/RankGraph.cs b/osu.Game/Overlays/Profile/Header/RankGraph.cs deleted file mode 100644 index 3df0677576..0000000000 --- a/osu.Game/Overlays/Profile/Header/RankGraph.cs +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Users; -using osuTK; - -namespace osu.Game.Overlays.Profile.Header -{ - public class RankGraph : Container - { - private const float primary_textsize = 25; - private const float secondary_textsize = 13; - private const float padding = 10; - private const float fade_duration = 150; - private const int ranked_days = 88; - - private readonly SpriteText rankText, performanceText, relativeText; - private readonly RankChartLineGraph graph; - private readonly OsuSpriteText placeholder; - - private KeyValuePair[] ranks; - public Bindable User = new Bindable(); - - public RankGraph() - { - Padding = new MarginPadding { Vertical = padding }; - Children = new Drawable[] - { - placeholder = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "No recent plays", - Font = OsuFont.GetFont(size: 14, weight: FontWeight.Regular, italics: true) - }, - rankText = new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Font = OsuFont.GetFont(size: primary_textsize, weight: FontWeight.Regular, italics: true), - }, - relativeText = new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Font = OsuFont.GetFont(size: secondary_textsize, weight: FontWeight.Regular, italics: true), - Y = 25, - }, - performanceText = new OsuSpriteText - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Font = OsuFont.GetFont(size: secondary_textsize, weight: FontWeight.Regular, italics: true) - }, - graph = new RankChartLineGraph - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.X, - Height = 60, - Y = -secondary_textsize, - Alpha = 0, - } - }; - - graph.OnBallMove += showHistoryRankTexts; - - User.ValueChanged += userChanged; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - graph.Colour = colours.Yellow; - } - - private void userChanged(ValueChangedEvent e) - { - placeholder.FadeIn(fade_duration, Easing.Out); - - if (e.NewValue?.Statistics?.Ranks.Global == null) - { - rankText.Text = string.Empty; - performanceText.Text = string.Empty; - relativeText.Text = string.Empty; - graph.FadeOut(fade_duration, Easing.Out); - ranks = null; - return; - } - - int[] userRanks = e.NewValue.RankHistory?.Data ?? new[] { e.NewValue.Statistics.Ranks.Global.Value }; - ranks = userRanks.Select((x, index) => new KeyValuePair(index, x)).Where(x => x.Value != 0).ToArray(); - - if (ranks.Length > 1) - { - placeholder.FadeOut(fade_duration, Easing.Out); - - graph.DefaultValueCount = ranks.Length; - graph.Values = ranks.Select(x => -(float)Math.Log(x.Value)); - graph.SetStaticBallPosition(); - } - - graph.FadeTo(ranks.Length > 1 ? 1 : 0, fade_duration, Easing.Out); - - updateRankTexts(); - } - - private void updateRankTexts() - { - var user = User.Value; - - performanceText.Text = user.Statistics.PP != null ? $"{user.Statistics.PP:#,0}pp" : string.Empty; - rankText.Text = user.Statistics.Ranks.Global > 0 ? $"#{user.Statistics.Ranks.Global:#,0}" : "no rank"; - relativeText.Text = user.Country != null && user.Statistics.Ranks.Country > 0 ? $"{user.Country.FullName} #{user.Statistics.Ranks.Country:#,0}" : "no rank"; - } - - private void showHistoryRankTexts(int dayIndex) - { - rankText.Text = $"#{ranks[dayIndex].Value:#,0}"; - relativeText.Text = dayIndex + 1 == ranks.Length ? "Now" : $"{ranked_days - ranks[dayIndex].Key} days ago"; - } - - protected override bool OnHover(HoverEvent e) - { - if (ranks?.Length > 1) - { - graph.UpdateBallPosition(e.MousePosition.X); - graph.ShowBall(); - } - - return base.OnHover(e); - } - - protected override bool OnMouseMove(MouseMoveEvent e) - { - if (ranks?.Length > 1) - graph.UpdateBallPosition(e.MousePosition.X); - - return base.OnMouseMove(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - if (ranks?.Length > 1) - { - graph.HideBall(); - updateRankTexts(); - } - - base.OnHoverLost(e); - } - - private class RankChartLineGraph : LineGraph - { - private readonly CircularContainer staticBall; - private readonly CircularContainer movingBall; - - public Action OnBallMove; - - public RankChartLineGraph() - { - Add(staticBall = new CircularContainer - { - Origin = Anchor.Centre, - Size = new Vector2(8), - Masking = true, - RelativePositionAxes = Axes.Both, - Child = new Box { RelativeSizeAxes = Axes.Both } - }); - Add(movingBall = new CircularContainer - { - Origin = Anchor.Centre, - Size = new Vector2(8), - Alpha = 0, - Masking = true, - RelativePositionAxes = Axes.Both, - Child = new Box { RelativeSizeAxes = Axes.Both } - }); - } - - public void SetStaticBallPosition() => staticBall.Position = new Vector2(1, GetYPosition(Values.Last())); - - public void UpdateBallPosition(float mouseXPosition) - { - int index = calculateIndex(mouseXPosition); - movingBall.Position = calculateBallPosition(index); - OnBallMove.Invoke(index); - } - - public void ShowBall() => movingBall.FadeIn(fade_duration); - - public void HideBall() => movingBall.FadeOut(fade_duration); - - private int calculateIndex(float mouseXPosition) => (int)Math.Round(mouseXPosition / DrawWidth * (DefaultValueCount - 1)); - - private Vector2 calculateBallPosition(int index) - { - float y = GetYPosition(Values.ElementAt(index)); - return new Vector2(index / (float)(DefaultValueCount - 1), y); - } - } - } -} diff --git a/osu.Game/Overlays/Profile/Header/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/SupporterIcon.cs deleted file mode 100644 index 5c9126dbe0..0000000000 --- a/osu.Game/Overlays/Profile/Header/SupporterIcon.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; -using osu.Game.Graphics.Backgrounds; -using osuTK; - -namespace osu.Game.Overlays.Profile.Header -{ - public class SupporterIcon : CircularContainer, IHasTooltip - { - private readonly Box background; - - public string TooltipText => "osu!supporter"; - - public SupporterIcon() - { - Masking = true; - Children = new Drawable[] - { - new Box { RelativeSizeAxes = Axes.Both }, - new CircularContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Scale = new Vector2(0.8f), - Masking = true, - Children = new Drawable[] - { - background = new Box { RelativeSizeAxes = Axes.Both }, - new Triangles - { - TriangleScale = 0.2f, - ColourLight = OsuColour.FromHex(@"ff7db7"), - ColourDark = OsuColour.FromHex(@"de5b95"), - RelativeSizeAxes = Axes.Both, - Velocity = 0.3f, - }, - } - }, - new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.Solid.Heart, - Scale = new Vector2(0.45f), - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.Pink; - } - } -} diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs new file mode 100644 index 0000000000..6fe55e2368 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -0,0 +1,202 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header +{ + public class TopHeaderContainer : CompositeDrawable + { + private const float avatar_size = 110; + + public readonly Bindable User = new Bindable(); + + private SupporterIcon supporterTag; + private UpdateableAvatar avatar; + private OsuSpriteText usernameText; + private ExternalLinkButton openUserExternally; + private OsuSpriteText titleText; + private DrawableFlag userFlag; + private OsuSpriteText userCountryText; + private FillFlowContainer userStats; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Height = 150; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.GreySeafoamDark, + }, + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }, + Height = avatar_size, + AutoSizeAxes = Axes.X, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Children = new[] + { + avatar = new UpdateableAvatar + { + Size = new Vector2(avatar_size), + Masking = true, + CornerRadius = avatar_size * 0.25f, + OpenOnClick = { Value = false }, + }, + new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Padding = new MarginPadding { Left = 10 }, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + usernameText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 24, weight: FontWeight.Regular) + }, + openUserExternally = new ExternalLinkButton + { + Margin = new MarginPadding { Left = 5 }, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + } + }, + new FillFlowContainer + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + titleText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 18, weight: FontWeight.Regular) + }, + supporterTag = new SupporterIcon + { + Height = 20, + Margin = new MarginPadding { Top = 5 } + }, + new Box + { + RelativeSizeAxes = Axes.X, + Height = 1.5f, + Margin = new MarginPadding { Top = 10 }, + Colour = colours.GreySeafoamLighter, + }, + new Container + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Top = 5 }, + Children = new Drawable[] + { + userFlag = new DrawableFlag + { + Size = new Vector2(30, 20) + }, + userCountryText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 17.5f, weight: FontWeight.Regular), + Margin = new MarginPadding { Left = 40 }, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Colour = colours.GreySeafoamLighter, + } + } + }, + } + } + } + } + } + }, + userStats = new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Y, + Width = 300, + Margin = new MarginPadding { Right = UserProfileOverlay.CONTENT_X_MARGIN }, + Padding = new MarginPadding { Vertical = 15 }, + Spacing = new Vector2(0, 2) + } + }; + + User.BindValueChanged(user => updateUser(user.NewValue)); + } + + private void updateUser(User user) + { + avatar.User = user; + usernameText.Text = user?.Username ?? string.Empty; + openUserExternally.Link = $@"https://osu.ppy.sh/users/{user?.Id ?? 0}"; + userFlag.Country = user?.Country; + userCountryText.Text = user?.Country?.FullName ?? "Alien"; + supporterTag.SupportLevel = user?.SupportLevel ?? 0; + titleText.Text = user?.Title ?? string.Empty; + titleText.Colour = OsuColour.FromHex(user?.Colour ?? "fff"); + + userStats.Clear(); + + if (user?.Statistics != null) + { + userStats.Add(new UserStatsLine("Ranked Score", user.Statistics.RankedScore.ToString("#,##0"))); + userStats.Add(new UserStatsLine("Hit Accuracy", Math.Round(user.Statistics.Accuracy, 2).ToString("#0.00'%'"))); + userStats.Add(new UserStatsLine("Play Count", user.Statistics.PlayCount.ToString("#,##0"))); + userStats.Add(new UserStatsLine("Total Score", user.Statistics.TotalScore.ToString("#,##0"))); + userStats.Add(new UserStatsLine("Total Hits", user.Statistics.TotalHits.ToString("#,##0"))); + userStats.Add(new UserStatsLine("Maximum Combo", user.Statistics.MaxCombo.ToString("#,##0"))); + userStats.Add(new UserStatsLine("Replays Watched by Others", user.Statistics.ReplaysWatched.ToString("#,##0"))); + } + } + + private class UserStatsLine : Container + { + public UserStatsLine(string left, string right) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Children = new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 15), + Text = left, + }, + new OsuSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold), + Text = right, + }, + }; + } + } + } +} diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 138e522cd7..76613c156d 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -1,483 +1,115 @@ // 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.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Profile.Components; using osu.Game.Overlays.Profile.Header; using osu.Game.Users; -using Humanizer; namespace osu.Game.Overlays.Profile { - public class ProfileHeader : Container + public class ProfileHeader : OverlayHeader { - private readonly LinkFlowContainer infoTextLeft; - private readonly LinkFlowContainer infoTextRight; - private readonly FillFlowContainer scoreText, scoreNumberText; - private readonly RankGraph rankGraph; + private UserCoverBackground coverContainer; - public readonly SupporterIcon SupporterTag; - private readonly Container coverContainer; - private readonly Sprite levelBadge; - private readonly SpriteText levelText; - private readonly GradeBadge gradeSSPlus, gradeSS, gradeSPlus, gradeS, gradeA; - private readonly Box colourBar; - private readonly DrawableFlag countryFlag; - private readonly BadgeContainer badgeContainer; + public Bindable User = new Bindable(); - private const float cover_height = 350; - private const float info_height = 150; - private const float info_width = 220; - private const float avatar_size = 110; - private const float level_position = 30; - private const float level_height = 60; - private const float stats_width = 280; + private CentreHeaderContainer centreHeaderContainer; + private DetailHeaderContainer detailHeaderContainer; - public ProfileHeader(User user) + public ProfileHeader() { - RelativeSizeAxes = Axes.X; - Height = cover_height + info_height; + User.ValueChanged += e => updateDisplay(e.NewValue); - Children = new Drawable[] - { - coverContainer = new Container - { - RelativeSizeAxes = Axes.X, - Height = cover_height, - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.1f), Color4.Black.Opacity(0.75f)) - }, - new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Padding = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN, Bottom = 20, Right = stats_width + UserProfileOverlay.CONTENT_X_MARGIN }, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Children = new Drawable[] - { - new UpdateableAvatar - { - User = user, - Size = new Vector2(avatar_size), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Masking = true, - CornerRadius = 5, - OpenOnClick = { Value = false }, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.25f), - Radius = 4, - }, - }, - new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - X = avatar_size + 10, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - SupporterTag = new SupporterIcon - { - Alpha = 0, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Y = -75, - Size = new Vector2(25, 25) - }, - new FillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Y = -48, - Children = new Drawable[] - { - usernameText = new OsuSpriteText - { - Text = user.Username, - Font = OsuFont.GetFont(size: 30, weight: FontWeight.Regular, italics: true) - }, - new ExternalLinkButton($@"https://osu.ppy.sh/users/{user.Id}") - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding { Left = 3, Bottom = 3 }, //To better lineup with the font - }, - } - }, - countryFlag = new DrawableFlag(user.Country) - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Width = 30, - Height = 20 - } - } - }, - badgeContainer = new BadgeContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding { Bottom = 5 }, - Alpha = 0, - }, - } - }, - colourBar = new Box - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - X = UserProfileOverlay.CONTENT_X_MARGIN, - Height = 5, - Width = info_width, - Alpha = 0 - } - } - }, - new Box // this is a temporary workaround for incorrect masking behaviour of FillMode.Fill used in UserCoverBackground (see https://github.com/ppy/osu-framework/issues/1675) - { - RelativeSizeAxes = Axes.X, - Height = 1, - Y = cover_height, - Colour = OsuColour.Gray(34), - }, - infoTextLeft = new LinkFlowContainer(t => t.Font = t.Font.With(size: 14)) - { - X = UserProfileOverlay.CONTENT_X_MARGIN, - Y = cover_height + 20, - Width = info_width, - AutoSizeAxes = Axes.Y, - ParagraphSpacing = 0.8f, - LineSpacing = 0.2f - }, - infoTextRight = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Regular, italics: true)) - { - X = UserProfileOverlay.CONTENT_X_MARGIN + info_width + 20, - Y = cover_height + 20, - Width = info_width, - AutoSizeAxes = Axes.Y, - ParagraphSpacing = 0.8f, - LineSpacing = 0.2f - }, - new Container - { - X = -UserProfileOverlay.CONTENT_X_MARGIN, - RelativeSizeAxes = Axes.Y, - Width = stats_width, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.X, - Y = level_position, - Height = level_height, - Children = new Drawable[] - { - new Box - { - Colour = Color4.Black.Opacity(0.5f), - RelativeSizeAxes = Axes.Both - }, - levelBadge = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Height = 50, - Width = 50, - Alpha = 0 - }, - levelText = new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Y = 11, - Font = OsuFont.GetFont(size: 20) - } - } - }, - new Container - { - RelativeSizeAxes = Axes.X, - Y = cover_height, - Anchor = Anchor.TopCentre, - Origin = Anchor.BottomCentre, - Height = cover_height - level_height - level_position - 5, - Children = new Drawable[] - { - new Box - { - Colour = Color4.Black.Opacity(0.5f), - RelativeSizeAxes = Axes.Both - }, - scoreText = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Horizontal = 20, Vertical = 18 }, - Spacing = new Vector2(0, 2) - }, - scoreNumberText = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Horizontal = 20, Vertical = 18 }, - Spacing = new Vector2(0, 2) - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Y = -64, - Spacing = new Vector2(20, 0), - Children = new[] - { - gradeSSPlus = new GradeBadge("SSPlus") { Alpha = 0 }, - gradeSS = new GradeBadge("SS") { Alpha = 0 }, - } - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Y = -18, - Spacing = new Vector2(20, 0), - Children = new[] - { - gradeSPlus = new GradeBadge("SPlus") { Alpha = 0 }, - gradeS = new GradeBadge("S") { Alpha = 0 }, - gradeA = new GradeBadge("A") { Alpha = 0 }, - } - } - } - }, - new Container - { - RelativeSizeAxes = Axes.X, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Height = info_height - 15, - Children = new Drawable[] - { - new Box - { - Colour = Color4.Black.Opacity(0.25f), - RelativeSizeAxes = Axes.Both - }, - rankGraph = new RankGraph - { - RelativeSizeAxes = Axes.Both - } - } - } - } - } - }; + TabControl.AddItem("Info"); + TabControl.AddItem("Modding"); + + centreHeaderContainer.DetailsVisible.BindValueChanged(visible => detailHeaderContainer.Expanded = visible.NewValue, true); } [BackgroundDependencyLoader] - private void load(TextureStore textures) + private void load(OsuColour colours) { - levelBadge.Texture = textures.Get(@"Profile/levelbadge"); + TabControl.AccentColour = colours.Seafoam; } - private readonly OsuSpriteText usernameText; - - private User user; - - public User User - { - get => user; - set - { - user = value; - loadUser(); - } - } - - private void loadUser() - { - LoadComponentAsync(new UserCoverBackground(user) + protected override Drawable CreateBackground() => + new Container { RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fill, - Depth = float.MaxValue, - }, background => - { - coverContainer.Add(background); - background.FadeInFromZero(200); - }); - - if (user.IsSupporter) - SupporterTag.Show(); - - usernameText.Text = user.Username; - - if (!string.IsNullOrEmpty(user.Colour)) - { - colourBar.Colour = OsuColour.FromHex(user.Colour); - colourBar.Show(); - } - - void boldItalic(SpriteText t) => t.Font = t.Font.With(Typeface.Exo, weight: FontWeight.Bold, italics: true); - void lightText(SpriteText t) => t.Alpha = 0.8f; - - OsuSpriteText createScoreText(string text) => new OsuSpriteText - { - Font = OsuFont.GetFont(size: 14), - Text = text + Children = new Drawable[] + { + coverContainer = new UserCoverBackground + { + RelativeSizeAxes = Axes.Both, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(OsuColour.FromHex("222").Opacity(0.8f), OsuColour.FromHex("222").Opacity(0.2f)) + }, + } }; - OsuSpriteText createScoreNumberText(string text) => new OsuSpriteText - { - Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold), - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Text = text - }; - - if (user.Country != null) - { - infoTextLeft.AddText("From ", lightText); - infoTextLeft.AddText(user.Country.FullName, boldItalic); - countryFlag.Country = user.Country; - } - - infoTextLeft.NewParagraph(); - - if (user.JoinDate.ToUniversalTime().Year < 2008) - { - infoTextLeft.AddText(new DrawableJoinDate(user.JoinDate), lightText); - } - else - { - infoTextLeft.AddText("Joined ", lightText); - infoTextLeft.AddText(new DrawableJoinDate(user.JoinDate), boldItalic); - } - - if (user.LastVisit.HasValue) - { - infoTextLeft.NewLine(); - infoTextLeft.AddText("Last seen ", lightText); - infoTextLeft.AddText(new DrawableDate(user.LastVisit.Value), boldItalic); - } - - if (user.PlayStyle?.Length > 0) - { - infoTextLeft.NewParagraph(); - infoTextLeft.AddText("Plays with ", lightText); - infoTextLeft.AddText(string.Join(", ", user.PlayStyle), boldItalic); - } - - infoTextLeft.NewLine(); - infoTextLeft.AddText("Contributed ", lightText); - infoTextLeft.AddLink("forum post".ToQuantity(user.PostCount), url: $"https://osu.ppy.sh/users/{user.Id}/posts", creationParameters: boldItalic); - - string websiteWithoutProtcol = user.Website; - if (!string.IsNullOrEmpty(websiteWithoutProtcol)) - { - int protocolIndex = websiteWithoutProtcol.IndexOf("//", StringComparison.Ordinal); - if (protocolIndex >= 0) - websiteWithoutProtcol = websiteWithoutProtcol.Substring(protocolIndex + 2); - } - - tryAddInfoRightLine(FontAwesome.Solid.MapMarker, user.Location); - tryAddInfoRightLine(FontAwesome.Regular.Heart, user.Interests); - tryAddInfoRightLine(FontAwesome.Solid.Suitcase, user.Occupation); - infoTextRight.NewParagraph(); - if (!string.IsNullOrEmpty(user.Twitter)) - tryAddInfoRightLine(FontAwesome.Brands.Twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); - tryAddInfoRightLine(FontAwesome.Solid.Gamepad, user.Discord); - tryAddInfoRightLine(FontAwesome.Brands.Skype, user.Skype, @"skype:" + user.Skype + @"?chat"); - tryAddInfoRightLine(FontAwesome.Brands.Lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}"); - tryAddInfoRightLine(FontAwesome.Solid.Globe, websiteWithoutProtcol, user.Website); - - if (user.Statistics != null) - { - levelBadge.Show(); - levelText.Text = user.Statistics.Level.Current.ToString(); - - scoreText.Add(createScoreText("Ranked Score")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.RankedScore.ToString(@"#,0"))); - scoreText.Add(createScoreText("Accuracy")); - scoreNumberText.Add(createScoreNumberText($"{user.Statistics.Accuracy:0.##}%")); - scoreText.Add(createScoreText("Play Count")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.PlayCount.ToString(@"#,0"))); - scoreText.Add(createScoreText("Total Score")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.TotalScore.ToString(@"#,0"))); - scoreText.Add(createScoreText("Total Hits")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.TotalHits.ToString(@"#,0"))); - scoreText.Add(createScoreText("Max Combo")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.MaxCombo.ToString(@"#,0"))); - scoreText.Add(createScoreText("Replays Watched by Others")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.ReplaysWatched.ToString(@"#,0"))); - - gradeSSPlus.DisplayCount = user.Statistics.GradesCount.SSPlus; - gradeSSPlus.Show(); - gradeSS.DisplayCount = user.Statistics.GradesCount.SS; - gradeSS.Show(); - gradeSPlus.DisplayCount = user.Statistics.GradesCount.SPlus; - gradeSPlus.Show(); - gradeS.DisplayCount = user.Statistics.GradesCount.S; - gradeS.Show(); - gradeA.DisplayCount = user.Statistics.GradesCount.A; - gradeA.Show(); - - rankGraph.User.Value = user; - } - - badgeContainer.ShowBadges(user.Badges); - } - - private void tryAddInfoRightLine(IconUsage icon, string str, string url = null) + protected override Drawable CreateContent() => new FillFlowContainer { - if (string.IsNullOrEmpty(str)) return; - - infoTextRight.AddIcon(icon); - if (url != null) + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - infoTextRight.AddLink(" " + str, url); + new TopHeaderContainer + { + RelativeSizeAxes = Axes.X, + User = { BindTarget = User }, + }, + centreHeaderContainer = new CentreHeaderContainer + { + RelativeSizeAxes = Axes.X, + User = { BindTarget = User }, + }, + detailHeaderContainer = new DetailHeaderContainer + { + RelativeSizeAxes = Axes.X, + User = { BindTarget = User }, + }, + new MedalHeaderContainer + { + RelativeSizeAxes = Axes.X, + User = { BindTarget = User }, + }, + new BottomHeaderContainer + { + RelativeSizeAxes = Axes.X, + User = { BindTarget = User }, + }, } - else + }; + + protected override ScreenTitle CreateTitle() => new ProfileHeaderTitle(); + + private void updateDisplay(User user) => coverContainer.User = user; + + private class ProfileHeaderTitle : ScreenTitle + { + public ProfileHeaderTitle() { - infoTextRight.AddText(" " + str); + Title = "Player"; + Section = "Info"; } - infoTextRight.NewLine(); + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.Seafoam; + } } } } diff --git a/osu.Game/Overlays/Profile/ProfileSection.cs b/osu.Game/Overlays/Profile/ProfileSection.cs index 6da736432f..4d891384e8 100644 --- a/osu.Game/Overlays/Profile/ProfileSection.cs +++ b/osu.Game/Overlays/Profile/ProfileSection.cs @@ -2,13 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Users; +using osuTK; using osuTK.Graphics; namespace osu.Game.Overlays.Profile diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs index bb55816880..16326900f1 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -16,7 +15,7 @@ namespace osu.Game.Overlays.Profile.Sections /// /// Display artist/title/mapper information, commonly used as the left portion of a profile or score display row (see ). /// - public class BeatmapMetadataContainer : OsuHoverContainer, IHasTooltip + public class BeatmapMetadataContainer : OsuHoverContainer { private readonly BeatmapInfo beatmap; @@ -27,8 +26,6 @@ namespace osu.Game.Overlays.Profile.Sections TooltipText = $"{beatmap.Metadata.Artist} - {beatmap.Metadata.Title}"; } - public string TooltipText { get; } - [BackgroundDependencyLoader(true)] private void load(BeatmapSetOverlay beatmapSetOverlay) { diff --git a/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs b/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs index a93fefdd75..23fe6e9cd5 100644 --- a/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs +++ b/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs @@ -5,6 +5,7 @@ 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.Input.Events; using osu.Game.Graphics; diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs index b0609e685e..1b286f92d3 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs @@ -17,7 +17,6 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { private readonly BeatmapInfo beatmap; private readonly int playCount; - private OsuHoverContainer mapperContainer; public DrawableMostPlayedRow(BeatmapInfo beatmap, int playCount) { @@ -34,36 +33,20 @@ namespace osu.Game.Overlays.Profile.Sections.Historical CoverType = BeatmapSetCoverType.List, }; - [BackgroundDependencyLoader(true)] - private void load(UserProfileOverlay profileOverlay) + [BackgroundDependencyLoader] + private void load() { LeftFlowContainer.Add(new BeatmapMetadataContainer(beatmap)); - LeftFlowContainer.Add(new FillFlowContainer + LeftFlowContainer.Add(new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: 12)) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = @"mapped by ", - Font = OsuFont.GetFont(size: 12) - }, - mapperContainer = new OsuHoverContainer - { - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = beatmap.Metadata.AuthorString, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Medium, italics: true) - } - } - }, - } - }); + }.With(d => + { + d.AddText("mapped by "); + d.AddUserLink(beatmap.Metadata.Author); + })); RightFlowContainer.Add(new FillFlowContainer { @@ -89,9 +72,6 @@ namespace osu.Game.Overlays.Profile.Sections.Historical }, } }); - - if (profileOverlay != null) - mapperContainer.Action = () => profileOverlay.ShowUser(beatmap.BeatmapSet.Metadata.Author); } } } diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs index 3c69082e9d..aabfa56ee6 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs @@ -3,8 +3,6 @@ using osu.Framework.Bindables; using osuTK; -using osuTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -13,6 +11,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Users; +using osu.Framework.Allocation; namespace osu.Game.Overlays.Profile.Sections.Kudosu { @@ -23,46 +22,27 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu public KudosuInfo(Bindable user) { this.user.BindTo(user); - CountSection total; CountSection avaliable; - RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Masking = true; CornerRadius = 3; - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Offset = new Vector2(0f, 1f), - Radius = 3f, - Colour = Color4.Black.Opacity(0.2f), - }; Children = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(0.2f) - }, new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), Children = new[] { - total = new CountSection( - "Total Kudosu Earned", - "Based on how much of a contribution the user has made to beatmap moderation. See this link for more information." - ), - avaliable = new CountSection( - "Kudosu Avaliable", - "Kudosu can be traded for kudosu stars, which will help your beatmap get more attention. This is the number of kudosu you haven't traded in yet." - ), + total = new CountTotal(), + avaliable = new CountAvailable() } } }; - this.user.ValueChanged += u => { total.Count = u.NewValue?.Kudosu.Total ?? 0; @@ -72,21 +52,43 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu protected override bool OnClick(ClickEvent e) => true; + private class CountAvailable : CountSection + { + public CountAvailable() + : base("Kudosu Avaliable") + { + DescriptionText.Text = "Kudosu can be traded for kudosu stars, which will help your beatmap get more attention. This is the number of kudosu you haven't traded in yet."; + } + } + + private class CountTotal : CountSection + { + public CountTotal() + : base("Total Kudosu Earned") + { + DescriptionText.AddText("Based on how much of a contribution the user has made to beatmap moderation. See "); + DescriptionText.AddLink("this link", "https://osu.ppy.sh/wiki/Kudosu"); + DescriptionText.AddText(" for more information."); + } + } + private class CountSection : Container { private readonly OsuSpriteText valueText; + protected readonly LinkFlowContainer DescriptionText; + private readonly Box lineBackground; public new int Count { set => valueText.Text = value.ToString(); } - public CountSection(string header, string description) + public CountSection(string header) { RelativeSizeAxes = Axes.X; Width = 0.5f; AutoSizeAxes = Axes.Y; - Padding = new MarginPadding { Horizontal = 10, Top = 10, Bottom = 20 }; + Padding = new MarginPadding { Top = 10, Bottom = 20 }; Child = new FillFlowContainer { AutoSizeAxes = Axes.Y, @@ -95,39 +97,42 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu Spacing = new Vector2(0, 5), Children = new Drawable[] { - new FillFlowContainer + new CircularContainer { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5, 0), - Children = new Drawable[] + Masking = true, + RelativeSizeAxes = Axes.X, + Height = 5, + Child = lineBackground = new Box { - new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Text = header + ":", - Font = OsuFont.GetFont(size: 20, weight: FontWeight.Regular, italics: true) - }, - valueText = new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Text = "0", - Font = OsuFont.GetFont(size: 40, weight: FontWeight.Regular, italics: true), - UseFullGlyphHeight = false, - } + RelativeSizeAxes = Axes.Both, } }, - new OsuTextFlowContainer(t => t.Font = t.Font.With(size: 19)) + new OsuSpriteText + { + Text = header, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold) + }, + valueText = new OsuSpriteText + { + Text = "0", + Font = OsuFont.GetFont(size: 40, weight: FontWeight.Light), + UseFullGlyphHeight = false, + }, + DescriptionText = new LinkFlowContainer(t => t.Font = t.Font.With(size: 14)) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Text = description } } }; } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + lineBackground.Colour = colours.Yellow; + DescriptionText.Colour = colours.GreySeafoamLighter; + } } } } diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 95a18ccfa9..470bed2854 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -54,6 +54,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks default: drawableScores = scores.Select(score => new DrawablePerformanceScore(score, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null)); break; + case ScoreType.Recent: drawableScores = scores.Select(score => new DrawableTotalScore(score)); break; diff --git a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs index 87c369e246..293ee4bcda 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs @@ -11,7 +11,7 @@ using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.SearchableList { - public abstract class SearchableListOverlay : WaveOverlayContainer + public abstract class SearchableListOverlay : FullscreenOverlay { public static readonly float WIDTH_PADDING = 80; } @@ -32,8 +32,6 @@ namespace osu.Game.Overlays.SearchableList protected SearchableListOverlay() { - RelativeSizeAxes = Axes.Both; - Children = new Drawable[] { new Box diff --git a/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs b/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs index a16e852902..93b07fbac7 100644 --- a/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs +++ b/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Configuration; @@ -9,7 +10,7 @@ namespace osu.Game.Overlays.Settings { /// /// A which provides subclasses with the - /// from the 's . + /// from the 's . /// public abstract class RulesetSettingsSubsection : SettingsSubsection { diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index e4ddc53e17..2f56ace24d 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -16,6 +16,7 @@ using System.ComponentModel; using osu.Game.Graphics; using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; @@ -87,6 +88,7 @@ namespace osu.Game.Overlays.Settings.Sections.General } }; break; + case APIState.Failing: case APIState.Connecting: LinkFlowContainer linkFlow; @@ -112,6 +114,7 @@ namespace osu.Game.Overlays.Settings.Sections.General linkFlow.AddLink("cancel", api.Logout, string.Empty); break; + case APIState.Online: Children = new Drawable[] { @@ -160,14 +163,17 @@ namespace osu.Game.Overlays.Settings.Sections.General api.LocalUser.Value.Status.Value = new UserStatusOnline(); dropdown.StatusColour = colours.Green; break; + case UserAction.DoNotDisturb: api.LocalUser.Value.Status.Value = new UserStatusDoNotDisturb(); dropdown.StatusColour = colours.Red; break; + case UserAction.AppearOffline: api.LocalUser.Value.Status.Value = new UserStatusOffline(); dropdown.StatusColour = colours.Gray7; break; + case UserAction.SignOut: api.Logout(); break; diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs index 3f1e77d482..55c7210d6c 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs @@ -9,7 +9,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input { protected override string Header => "Keyboard"; - public KeyboardSettings(KeyBindingOverlay keyConfig) + public KeyboardSettings(KeyBindingPanel keyConfig) { Children = new Drawable[] { diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs index 6a3f8783b0..2a348b4e03 100644 --- a/osu.Game/Overlays/Settings/Sections/InputSection.cs +++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Settings.Sections public override string Header => "Input"; public override IconUsage Icon => FontAwesome.Regular.Keyboard; - public InputSection(KeyBindingOverlay keyConfig) + public InputSection(KeyBindingPanel keyConfig) { Children = new Drawable[] { diff --git a/osu.Game/Overlays/Settings/SettingsButton.cs b/osu.Game/Overlays/Settings/SettingsButton.cs index ef98c28285..088d69c031 100644 --- a/osu.Game/Overlays/Settings/SettingsButton.cs +++ b/osu.Game/Overlays/Settings/SettingsButton.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.Settings public SettingsButton() { RelativeSizeAxes = Axes.X; - Padding = new MarginPadding { Left = SettingsOverlay.CONTENT_MARGINS, Right = SettingsOverlay.CONTENT_MARGINS }; + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS }; } public string TooltipText { get; set; } diff --git a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs index 11cdbf6e0a..9f09f251c2 100644 --- a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs +++ b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings { protected override OsuDropdown CreateDropdown() => new DropdownControl(); - protected class DropdownControl : OsuEnumDropdown + protected new class DropdownControl : OsuEnumDropdown { public DropdownControl() { diff --git a/osu.Game/Overlays/Settings/SettingsHeader.cs b/osu.Game/Overlays/Settings/SettingsHeader.cs index fbf29f7ff5..d8ec00bd99 100644 --- a/osu.Game/Overlays/Settings/SettingsHeader.cs +++ b/osu.Game/Overlays/Settings/SettingsHeader.cs @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Settings Font = OsuFont.GetFont(size: 40), Margin = new MarginPadding { - Left = SettingsOverlay.CONTENT_MARGINS, + Left = SettingsPanel.CONTENT_MARGINS, Top = Toolbar.Toolbar.TOOLTIP_HEIGHT }, }, @@ -52,7 +52,7 @@ namespace osu.Game.Overlays.Settings Font = OsuFont.GetFont(size: 18), Margin = new MarginPadding { - Left = SettingsOverlay.CONTENT_MARGINS, + Left = SettingsPanel.CONTENT_MARGINS, Bottom = 30 }, }, diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 02e9d48f40..4776cd6442 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -9,6 +9,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; @@ -64,6 +65,7 @@ namespace osu.Game.Overlays.Settings { bindable = value; controlWithCurrent?.Current.BindTo(bindable); + if (ShowsDefaultIndicator) { restoreDefaultButton.Bindable = bindable.GetBoundCopy(); @@ -85,7 +87,7 @@ namespace osu.Game.Overlays.Settings { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - Padding = new MarginPadding { Right = SettingsOverlay.CONTENT_MARGINS }; + Padding = new MarginPadding { Right = SettingsPanel.CONTENT_MARGINS }; InternalChildren = new Drawable[] { @@ -94,7 +96,7 @@ namespace osu.Game.Overlays.Settings { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Left = SettingsOverlay.CONTENT_MARGINS }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, Child = Control = CreateControl() }, }; @@ -129,7 +131,7 @@ namespace osu.Game.Overlays.Settings public RestoreDefaultValueButton() { RelativeSizeAxes = Axes.Y; - Width = SettingsOverlay.CONTENT_MARGINS; + Width = SettingsPanel.CONTENT_MARGINS; Alpha = 0f; } diff --git a/osu.Game/Overlays/Settings/SettingsSection.cs b/osu.Game/Overlays/Settings/SettingsSection.cs index e92f28d5d2..c878a9fc65 100644 --- a/osu.Game/Overlays/Settings/SettingsSection.cs +++ b/osu.Game/Overlays/Settings/SettingsSection.cs @@ -83,7 +83,7 @@ namespace osu.Game.Overlays.Settings Font = OsuFont.GetFont(size: header_size), Text = Header, Colour = colours.Yellow, - Margin = new MarginPadding { Left = SettingsOverlay.CONTENT_MARGINS, Right = SettingsOverlay.CONTENT_MARGINS } + Margin = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS } }, FlowContent } diff --git a/osu.Game/Overlays/Settings/SettingsSubsection.cs b/osu.Game/Overlays/Settings/SettingsSubsection.cs index a1b4d8b131..c9c763e8d4 100644 --- a/osu.Game/Overlays/Settings/SettingsSubsection.cs +++ b/osu.Game/Overlays/Settings/SettingsSubsection.cs @@ -53,7 +53,7 @@ namespace osu.Game.Overlays.Settings new OsuSpriteText { Text = Header.ToUpperInvariant(), - Margin = new MarginPadding { Bottom = 10, Left = SettingsOverlay.CONTENT_MARGINS, Right = SettingsOverlay.CONTENT_MARGINS }, + Margin = new MarginPadding { Bottom = 10, Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS }, Font = OsuFont.GetFont(weight: FontWeight.Black), }, FlowContent diff --git a/osu.Game/Overlays/Settings/Sidebar.cs b/osu.Game/Overlays/Settings/Sidebar.cs index 969686e36d..3c18627f23 100644 --- a/osu.Game/Overlays/Settings/Sidebar.cs +++ b/osu.Game/Overlays/Settings/Sidebar.cs @@ -102,6 +102,7 @@ namespace osu.Game.Overlays.Settings default: this.ResizeTo(new Vector2(DEFAULT_WIDTH, Height), 500, Easing.OutQuint); break; + case ExpandedState.Expanded: this.ResizeTo(new Vector2(EXPANDED_WIDTH, Height), 500, Easing.OutQuint); break; diff --git a/osu.Game/Overlays/Settings/SidebarButton.cs b/osu.Game/Overlays/Settings/SidebarButton.cs index c7736d6047..a94f76e7af 100644 --- a/osu.Game/Overlays/Settings/SidebarButton.cs +++ b/osu.Game/Overlays/Settings/SidebarButton.cs @@ -46,6 +46,7 @@ namespace osu.Game.Overlays.Settings set { selected = value; + if (selected) { selectionIndicator.FadeIn(50); diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index 174d3a3de1..6e3eaae0a1 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -1,224 +1,85 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Collections.Generic; -using System.Linq; -using osuTK; -using osuTK.Graphics; using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; +using osu.Game.Overlays.Settings.Sections; +using osuTK.Graphics; +using System.Collections.Generic; +using System.Linq; namespace osu.Game.Overlays { - public abstract class SettingsOverlay : OsuFocusedOverlayContainer + public class SettingsOverlay : SettingsPanel { - public const float CONTENT_MARGINS = 15; - - public const float TRANSITION_LENGTH = 600; - - private const float sidebar_width = Sidebar.DEFAULT_WIDTH; - - protected const float WIDTH = 400; - - private const float sidebar_padding = 10; - - protected Container ContentContainer; - - protected override Container Content => ContentContainer; - - protected Sidebar Sidebar; - private SidebarButton selectedSidebarButton; - - protected SettingsSectionsContainer SectionsContainer; - - private SearchTextBox searchTextBox; - - /// - /// Provide a source for the toolbar height. - /// - public Func GetToolbarHeight; - - private readonly bool showSidebar; - - protected Box Background; - - protected SettingsOverlay(bool showSidebar) + protected override IEnumerable CreateSections() => new SettingsSection[] + { + new GeneralSection(), + new GraphicsSection(), + new GameplaySection(), + new AudioSection(), + new SkinSection(), + new InputSection(createSubPanel(new KeyBindingPanel())), + new OnlineSection(), + new MaintenanceSection(), + new DebugSection(), + }; + + private readonly List subPanels = new List(); + + protected override Drawable CreateHeader() => new SettingsHeader("settings", "Change the way osu! behaves"); + protected override Drawable CreateFooter() => new SettingsFooter(); + + public SettingsOverlay() + : base(true) { - this.showSidebar = showSidebar; - RelativeSizeAxes = Axes.Y; - AutoSizeAxes = Axes.X; } - protected virtual IEnumerable CreateSections() => null; + public override bool AcceptsFocus => subPanels.All(s => s.State != Visibility.Visible); + + private T createSubPanel(T subPanel) + where T : SettingsSubPanel + { + subPanel.Depth = 1; + subPanel.Anchor = Anchor.TopRight; + subPanel.StateChanged += subPanelStateChanged; + + subPanels.Add(subPanel); + + return subPanel; + } + + private void subPanelStateChanged(Visibility visibility) + { + switch (visibility) + { + case Visibility.Visible: + Background.FadeTo(0.9f, 300, Easing.OutQuint); + Sidebar?.FadeColour(Color4.DarkGray, 300, Easing.OutQuint); + + SectionsContainer.FadeOut(300, Easing.OutQuint); + ContentContainer.MoveToX(-WIDTH, 500, Easing.OutQuint); + break; + + case Visibility.Hidden: + Background.FadeTo(0.6f, 500, Easing.OutQuint); + Sidebar?.FadeColour(Color4.White, 300, Easing.OutQuint); + + SectionsContainer.FadeIn(500, Easing.OutQuint); + ContentContainer.MoveToX(0, 500, Easing.OutQuint); + break; + } + } + + protected override float ExpandedPosition => subPanels.Any(s => s.State == Visibility.Visible) ? -WIDTH : base.ExpandedPosition; [BackgroundDependencyLoader] private void load() { - InternalChild = ContentContainer = new Container - { - Width = WIDTH, - RelativeSizeAxes = Axes.Y, - Children = new Drawable[] - { - Background = new Box - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Scale = new Vector2(2, 1), // over-extend to the left for transitions - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.6f, - }, - SectionsContainer = new SettingsSectionsContainer - { - Masking = true, - RelativeSizeAxes = Axes.Both, - ExpandableHeader = CreateHeader(), - FixedHeader = searchTextBox = new SearchTextBox - { - RelativeSizeAxes = Axes.X, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Width = 0.95f, - Margin = new MarginPadding - { - Top = 20, - Bottom = 20 - }, - Exit = Hide, - }, - Footer = CreateFooter() - }, - } - }; - - if (showSidebar) - { - AddInternal(Sidebar = new Sidebar { Width = sidebar_width }); - - SectionsContainer.SelectedSection.ValueChanged += section => - { - selectedSidebarButton.Selected = false; - selectedSidebarButton = Sidebar.Children.Single(b => b.Section == section.NewValue); - selectedSidebarButton.Selected = true; - }; - } - - searchTextBox.Current.ValueChanged += term => SectionsContainer.SearchContainer.SearchTerm = term.NewValue; - - CreateSections()?.ForEach(AddSection); - } - - protected void AddSection(SettingsSection section) - { - SectionsContainer.Add(section); - - if (Sidebar != null) - { - var button = new SidebarButton - { - Section = section, - Action = s => - { - SectionsContainer.ScrollTo(s); - Sidebar.State = ExpandedState.Contracted; - }, - }; - - Sidebar.Add(button); - - if (selectedSidebarButton == null) - { - selectedSidebarButton = Sidebar.Children.First(); - selectedSidebarButton.Selected = true; - } - } - } - - protected virtual Drawable CreateHeader() => new Container(); - - protected virtual Drawable CreateFooter() => new Container(); - - protected override void PopIn() - { - base.PopIn(); - - ContentContainer.MoveToX(ExpandedPosition, TRANSITION_LENGTH, Easing.OutQuint); - - Sidebar?.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); - this.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint); - - searchTextBox.HoldFocus = true; - } - - protected virtual float ExpandedPosition => 0; - - protected override void PopOut() - { - base.PopOut(); - - ContentContainer.MoveToX(-WIDTH, TRANSITION_LENGTH, Easing.OutQuint); - - Sidebar?.MoveToX(-sidebar_width, TRANSITION_LENGTH, Easing.OutQuint); - this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint); - - searchTextBox.HoldFocus = false; - if (searchTextBox.HasFocus) - GetContainingInputManager().ChangeFocus(null); - } - - public override bool AcceptsFocus => true; - - protected override void OnFocus(FocusEvent e) - { - searchTextBox.TakeFocus(); - base.OnFocus(e); - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - ContentContainer.Margin = new MarginPadding { Left = Sidebar?.DrawWidth ?? 0 }; - ContentContainer.Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; - } - - protected class SettingsSectionsContainer : SectionsContainer - { - public SearchContainer SearchContainer; - - protected override FlowContainer CreateScrollContentContainer() - => SearchContainer = new SearchContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - }; - - public SettingsSectionsContainer() - { - HeaderBackground = new Box - { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both - }; - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - // no null check because the usage of this class is strict - HeaderBackground.Alpha = -ExpandableHeader.Y / ExpandableHeader.LayoutSize.Y * 0.5f; - } + foreach (var s in subPanels) + ContentContainer.Add(s); } } } diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs new file mode 100644 index 0000000000..474f529bb1 --- /dev/null +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -0,0 +1,222 @@ +// 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 osuTK; +using osuTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Overlays +{ + public abstract class SettingsPanel : OsuFocusedOverlayContainer + { + public const float CONTENT_MARGINS = 15; + + public const float TRANSITION_LENGTH = 600; + + private const float sidebar_width = Sidebar.DEFAULT_WIDTH; + + protected const float WIDTH = 400; + + protected Container ContentContainer; + + protected override Container Content => ContentContainer; + + protected Sidebar Sidebar; + private SidebarButton selectedSidebarButton; + + protected SettingsSectionsContainer SectionsContainer; + + private SearchTextBox searchTextBox; + + /// + /// Provide a source for the toolbar height. + /// + public Func GetToolbarHeight; + + private readonly bool showSidebar; + + protected Box Background; + + protected SettingsPanel(bool showSidebar) + { + this.showSidebar = showSidebar; + RelativeSizeAxes = Axes.Y; + AutoSizeAxes = Axes.X; + } + + protected virtual IEnumerable CreateSections() => null; + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = ContentContainer = new Container + { + Width = WIDTH, + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + Background = new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Scale = new Vector2(2, 1), // over-extend to the left for transitions + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.6f, + }, + SectionsContainer = new SettingsSectionsContainer + { + Masking = true, + RelativeSizeAxes = Axes.Both, + ExpandableHeader = CreateHeader(), + FixedHeader = searchTextBox = new SearchTextBox + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Width = 0.95f, + Margin = new MarginPadding + { + Top = 20, + Bottom = 20 + }, + Exit = Hide, + }, + Footer = CreateFooter() + }, + } + }; + + if (showSidebar) + { + AddInternal(Sidebar = new Sidebar { Width = sidebar_width }); + + SectionsContainer.SelectedSection.ValueChanged += section => + { + selectedSidebarButton.Selected = false; + selectedSidebarButton = Sidebar.Children.Single(b => b.Section == section.NewValue); + selectedSidebarButton.Selected = true; + }; + } + + searchTextBox.Current.ValueChanged += term => SectionsContainer.SearchContainer.SearchTerm = term.NewValue; + + CreateSections()?.ForEach(AddSection); + } + + protected void AddSection(SettingsSection section) + { + SectionsContainer.Add(section); + + if (Sidebar != null) + { + var button = new SidebarButton + { + Section = section, + Action = s => + { + SectionsContainer.ScrollTo(s); + Sidebar.State = ExpandedState.Contracted; + }, + }; + + Sidebar.Add(button); + + if (selectedSidebarButton == null) + { + selectedSidebarButton = Sidebar.Children.First(); + selectedSidebarButton.Selected = true; + } + } + } + + protected virtual Drawable CreateHeader() => new Container(); + + protected virtual Drawable CreateFooter() => new Container(); + + protected override void PopIn() + { + base.PopIn(); + + ContentContainer.MoveToX(ExpandedPosition, TRANSITION_LENGTH, Easing.OutQuint); + + Sidebar?.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); + this.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint); + + searchTextBox.HoldFocus = true; + } + + protected virtual float ExpandedPosition => 0; + + protected override void PopOut() + { + base.PopOut(); + + ContentContainer.MoveToX(-WIDTH, TRANSITION_LENGTH, Easing.OutQuint); + + Sidebar?.MoveToX(-sidebar_width, TRANSITION_LENGTH, Easing.OutQuint); + this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint); + + searchTextBox.HoldFocus = false; + if (searchTextBox.HasFocus) + GetContainingInputManager().ChangeFocus(null); + } + + public override bool AcceptsFocus => true; + + protected override void OnFocus(FocusEvent e) + { + searchTextBox.TakeFocus(); + base.OnFocus(e); + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + ContentContainer.Margin = new MarginPadding { Left = Sidebar?.DrawWidth ?? 0 }; + ContentContainer.Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; + } + + protected class SettingsSectionsContainer : SectionsContainer + { + public SearchContainer SearchContainer; + + protected override FlowContainer CreateScrollContentContainer() + => SearchContainer = new SearchContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + }; + + public SettingsSectionsContainer() + { + HeaderBackground = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both + }; + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + // no null check because the usage of this class is strict + HeaderBackground.Alpha = -ExpandableHeader.Y / ExpandableHeader.LayoutSize.Y * 0.5f; + } + } + } +} diff --git a/osu.Game/Overlays/KeyBindingOverlay.cs b/osu.Game/Overlays/SettingsSubPanel.cs similarity index 84% rename from osu.Game/Overlays/KeyBindingOverlay.cs rename to osu.Game/Overlays/SettingsSubPanel.cs index b223d4701d..576be71ee6 100644 --- a/osu.Game/Overlays/KeyBindingOverlay.cs +++ b/osu.Game/Overlays/SettingsSubPanel.cs @@ -10,26 +10,22 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Input.Bindings; -using osu.Game.Overlays.KeyBinding; using osu.Game.Overlays.Settings; -using osu.Game.Rulesets; using osu.Game.Screens.Ranking; using osuTK; namespace osu.Game.Overlays { - public class KeyBindingOverlay : SettingsOverlay + public abstract class SettingsSubPanel : SettingsPanel { - protected override Drawable CreateHeader() => new SettingsHeader("key configuration", "Customise your keys!"); - - [BackgroundDependencyLoader(permitNulls: true)] - private void load(RulesetStore rulesets, GlobalActionContainer global) + protected SettingsSubPanel() + : base(true) { - AddSection(new GlobalKeyBindingsSection(global)); - - foreach (var ruleset in rulesets.AvailableRulesets) - AddSection(new RulesetBindingsSection(ruleset)); + } + [BackgroundDependencyLoader] + private void load() + { AddInternal(new BackButton { Anchor = Anchor.BottomLeft, @@ -38,11 +34,6 @@ namespace osu.Game.Overlays }); } - public KeyBindingOverlay() - : base(true) - { - } - private class BackButton : OsuClickableContainer, IKeyBindingHandler { private AspectContainer aspect; diff --git a/osu.Game/Overlays/Social/SocialPanel.cs b/osu.Game/Overlays/Social/SocialPanel.cs index 738f940484..555527670a 100644 --- a/osu.Game/Overlays/Social/SocialPanel.cs +++ b/osu.Game/Overlays/Social/SocialPanel.cs @@ -5,7 +5,7 @@ using osuTK; using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Input.Events; using osu.Game.Users; diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index daf3d1c576..2baa614365 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osuTK; using osuTK.Graphics; @@ -20,9 +19,8 @@ using osu.Framework.Threading; namespace osu.Game.Overlays { - public class SocialOverlay : SearchableListOverlay, IOnlineComponent + public class SocialOverlay : SearchableListOverlay { - private IAPIProvider api; private readonly LoadingAnimation loading; private FillFlowContainer panels; @@ -88,13 +86,6 @@ namespace osu.Game.Overlays currentQuery.BindTo(Filter.Search.Current); } - [BackgroundDependencyLoader] - private void load(IAPIProvider api) - { - this.api = api; - api.Register(this); - } - private void recreatePanels(PanelDisplayStyle displayStyle) { clearPanels(); @@ -111,6 +102,7 @@ namespace osu.Game.Overlays ChildrenEnumerable = Users.Select(u => { SocialPanel panel; + switch (displayStyle) { case PanelDisplayStyle.Grid: @@ -120,6 +112,7 @@ namespace osu.Game.Overlays Origin = Anchor.TopCentre }; break; + default: panel = new SocialListPanel(u); break; @@ -157,7 +150,7 @@ namespace osu.Game.Overlays loading.Hide(); getUsersRequest?.Cancel(); - if (api?.IsLoggedIn != true) + if (API?.IsLoggedIn != true) return; switch (Header.Tabs.Current.Value) @@ -165,12 +158,13 @@ namespace osu.Game.Overlays case SocialTab.Friends: var friendRequest = new GetFriendsRequest(); // TODO filter arguments? friendRequest.Success += updateUsers; - api.Queue(getUsersRequest = friendRequest); + API.Queue(getUsersRequest = friendRequest); break; + default: var userRequest = new GetUsersRequest(); // TODO filter arguments! userRequest.Success += response => updateUsers(response.Select(r => r.User)); - api.Queue(getUsersRequest = userRequest); + API.Queue(getUsersRequest = userRequest); break; } @@ -193,13 +187,14 @@ namespace osu.Game.Overlays } } - public void APIStateChanged(IAPIProvider api, APIState state) + public override void APIStateChanged(IAPIProvider api, APIState state) { switch (state) { case APIState.Online: Scheduler.AddOnce(updateSearch); break; + default: Users = null; clearPanels(); diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index a7f2a0e8d0..3c8b96fe8a 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -69,6 +69,7 @@ namespace osu.Game.Overlays.Toolbar AutoSizeAxes = Axes.X, Children = new Drawable[] { + new ToolbarChangelogButton(), new ToolbarDirectButton(), new ToolbarChatButton(), new ToolbarSocialButton(), diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 71374d5180..2b2b19b73a 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -4,6 +4,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; diff --git a/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs new file mode 100644 index 0000000000..84210e27a4 --- /dev/null +++ b/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Overlays.Toolbar +{ + public class ToolbarChangelogButton : ToolbarOverlayToggleButton + { + public ToolbarChangelogButton() + { + SetIcon(FontAwesome.Solid.Bullhorn); + } + + [BackgroundDependencyLoader(true)] + private void load(ChangelogOverlay changelog) + { + StateContainer = changelog; + } + } +} diff --git a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs index ca86ce7aa7..b2ae273e31 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs @@ -21,6 +21,7 @@ namespace osu.Game.Overlays.Toolbar set { stateContainer = value; + if (stateContainer != null) { Action = stateContainer.ToggleVisibility; @@ -55,6 +56,7 @@ namespace osu.Game.Overlays.Toolbar case Visibility.Hidden: stateBackground.FadeOut(200); break; + case Visibility.Visible: stateBackground.FadeIn(200); break; diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs index f729810fbc..87b18ba9f4 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Game.Rulesets; using osuTK.Graphics; diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index d01eab4dab..84a41b6547 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osuTK; using osuTK.Input; using osuTK.Graphics; @@ -71,6 +72,7 @@ namespace osu.Game.Overlays.Toolbar private void load(RulesetStore rulesets, Bindable parentRuleset) { this.rulesets = rulesets; + foreach (var r in rulesets.AvailableRulesets) { modeButtons.Add(new ToolbarRulesetButton diff --git a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs index 08f8f867fd..79942012f9 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Toolbar } [BackgroundDependencyLoader(true)] - private void load(MainSettings settings) + private void load(SettingsOverlay settings) { StateContainer = settings; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index 356ffa5180..ea15e5498b 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -4,7 +4,7 @@ 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.Game.Graphics; using osu.Game.Online.API; using osu.Game.Users; @@ -58,6 +58,7 @@ namespace osu.Game.Overlays.Toolbar Text = @"Guest"; avatar.User = new User(); break; + case APIState.Online: Text = api.LocalUser.Value.Username; avatar.User = api.LocalUser.Value; diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 48ce055975..8a133a1d1e 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -3,75 +3,30 @@ 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.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Profile; using osu.Game.Overlays.Profile.Sections; using osu.Game.Users; using osuTK; -using osuTK.Graphics; namespace osu.Game.Overlays { - public class UserProfileOverlay : WaveOverlayContainer + public class UserProfileOverlay : FullscreenOverlay { private ProfileSection lastSection; private ProfileSection[] sections; private GetUserRequest userReq; - private IAPIProvider api; protected ProfileHeader Header; private SectionsContainer sectionsContainer; private ProfileTabControl tabs; - public const float CONTENT_X_MARGIN = 50; - - public UserProfileOverlay() - { - Waves.FirstWaveColour = OsuColour.Gray(0.4f); - Waves.SecondWaveColour = OsuColour.Gray(0.3f); - Waves.ThirdWaveColour = OsuColour.Gray(0.2f); - Waves.FourthWaveColour = OsuColour.Gray(0.1f); - - RelativeSizeAxes = Axes.Both; - RelativePositionAxes = Axes.Both; - Width = 0.85f; - Anchor = Anchor.TopCentre; - Origin = Anchor.TopCentre; - - Masking = true; - EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.Black.Opacity(0), - Type = EdgeEffectType.Shadow, - Radius = 10 - }; - } - - [BackgroundDependencyLoader] - private void load(IAPIProvider api) - { - this.api = api; - } - - protected override void PopIn() - { - base.PopIn(); - FadeEdgeEffectTo(0.5f, WaveContainer.APPEAR_DURATION, Easing.In); - } - - protected override void PopOut() - { - base.PopOut(); - FadeEdgeEffectTo(0, WaveContainer.DISAPPEAR_DURATION, Easing.Out); - } + public const float CONTENT_X_MARGIN = 70; public void ShowUser(long userId) => ShowUser(new User { Id = userId }); @@ -81,7 +36,7 @@ namespace osu.Game.Overlays Show(); - if (user.Id == Header?.User?.Id) + if (user.Id == Header?.User.Value?.Id) return; userReq?.Cancel(); @@ -113,12 +68,10 @@ namespace osu.Game.Overlays Colour = OsuColour.Gray(0.2f) }); - Header = new ProfileHeader(user); - Add(sectionsContainer = new SectionsContainer { RelativeSizeAxes = Axes.Both, - ExpandableHeader = Header, + ExpandableHeader = Header = new ProfileHeader(), FixedHeader = tabs, HeaderBackground = new Box { @@ -156,7 +109,7 @@ namespace osu.Game.Overlays { userReq = new GetUserRequest(user.Id); userReq.Success += userLoadComplete; - api.Queue(userReq); + API.Queue(userReq); } else { @@ -169,13 +122,14 @@ namespace osu.Game.Overlays private void userLoadComplete(User user) { - Header.User = user; + Header.User.Value = user; if (user.ProfileOrder != null) { foreach (string id in user.ProfileOrder) { var sec = sections.FirstOrDefault(s => s.Identifier == id); + if (sec != null) { sec.User.Value = user; diff --git a/osu.Game/Overlays/Volume/MuteButton.cs b/osu.Game/Overlays/Volume/MuteButton.cs index 2b1f78243b..a4884dc2c1 100644 --- a/osu.Game/Overlays/Volume/MuteButton.cs +++ b/osu.Game/Overlays/Volume/MuteButton.cs @@ -4,21 +4,20 @@ 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.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; using osuTK; using osuTK.Graphics; namespace osu.Game.Overlays.Volume { - public class MuteButton : Container, IHasCurrentValue + public class MuteButton : OsuButton, IHasCurrentValue { private readonly Bindable current = new Bindable(); @@ -36,63 +35,57 @@ namespace osu.Game.Overlays.Volume } private Color4 hoveredColour, unhoveredColour; + private const float width = 100; public const float HEIGHT = 35; public MuteButton() { - Masking = true; - BorderThickness = 3; - CornerRadius = HEIGHT / 2; + Content.BorderThickness = 3; + Content.CornerRadius = HEIGHT / 2; + Size = new Vector2(width, HEIGHT); + + Action = () => Current.Value = !Current.Value; } [BackgroundDependencyLoader] private void load(OsuColour colours) { hoveredColour = colours.YellowDark; - BorderColour = unhoveredColour = colours.Gray1.Opacity(0.9f); + + Content.BorderColour = unhoveredColour = colours.Gray1; + BackgroundColour = colours.Gray1; SpriteIcon icon; + AddRange(new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Gray1, - Alpha = 0.9f, - }, icon = new SpriteIcon { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Size = new Vector2(20), } }); Current.ValueChanged += muted => { - icon.Icon = muted.NewValue ? FontAwesome.Solid.VolumeOff : FontAwesome.Solid.VolumeUp; - icon.Margin = new MarginPadding { Left = muted.NewValue ? width / 2 - 15 : width / 2 - 10 }; //Magic numbers to line up both icons because they're different widths + icon.Icon = muted.NewValue ? FontAwesome.Solid.VolumeMute : FontAwesome.Solid.VolumeUp; }; + Current.TriggerChange(); } protected override bool OnHover(HoverEvent e) { - this.TransformTo("BorderColour", hoveredColour, 500, Easing.OutQuint); + Content.TransformTo, SRGBColour>("BorderColour", hoveredColour, 500, Easing.OutQuint); return false; } protected override void OnHoverLost(HoverLostEvent e) { - this.TransformTo("BorderColour", unhoveredColour, 500, Easing.OutQuint); - } - - protected override bool OnClick(ClickEvent e) - { - Current.Value = !Current.Value; - return true; + Content.TransformTo, SRGBColour>("BorderColour", unhoveredColour, 500, Easing.OutQuint); } } } diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index e2e480ef53..34b15d958d 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -105,12 +105,14 @@ namespace osu.Game.Overlays else volumeMeterMaster.Decrease(amount, isPrecise); return true; + case GlobalAction.IncreaseVolume: if (State == Visibility.Hidden) Show(); else volumeMeterMaster.Increase(amount, isPrecise); return true; + case GlobalAction.ToggleMute: Show(); muteButton.Current.Value = !muteButton.Current.Value; diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index aad55f8a38..e31c963403 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -39,8 +39,7 @@ namespace osu.Game.Rulesets.Difficulty { mods = mods.Select(m => m.CreateCopy()).ToArray(); - beatmap.Mods.Value = mods; - IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo); + IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods); var clock = new StopwatchClock(); mods.OfType().ForEach(m => m.ApplyToClock(clock)); @@ -106,7 +105,7 @@ namespace osu.Game.Rulesets.Difficulty /// public Mod[] CreateDifficultyAdjustmentModCombinations() { - return createDifficultyAdjustmentModCombinations(Enumerable.Empty(), DifficultyAdjustmentMods).ToArray(); + return createDifficultyAdjustmentModCombinations(Array.Empty(), DifficultyAdjustmentMods).ToArray(); IEnumerable createDifficultyAdjustmentModCombinations(IEnumerable currentSet, Mod[] adjustmentSet, int currentSetCount = 0, int adjustmentSetStart = 0) { @@ -117,10 +116,12 @@ namespace osu.Game.Rulesets.Difficulty yield return new ModNoMod(); break; + case 1: yield return currentSet.Single(); break; + default: yield return new MultiMod(currentSet.ToArray()); @@ -166,7 +167,7 @@ namespace osu.Game.Rulesets.Difficulty /// /// Creates the s to calculate the difficulty of an . /// - /// The whose difficulty will be calculated.The whose difficulty will be calculated. /// The s. protected abstract Skill[] CreateSkills(IBeatmap beatmap); } diff --git a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs index 2b627ee8e6..9ab81b9580 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs @@ -26,8 +26,7 @@ namespace osu.Game.Rulesets.Difficulty Ruleset = ruleset; Score = score; - beatmap.Mods.Value = score.Mods; - Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo); + Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, score.Mods); Attributes = ruleset.CreateDifficultyCalculator(beatmap).Calculate(score.Mods); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 41de0c36fc..38ec09535d 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -14,6 +14,7 @@ using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; @@ -185,8 +186,8 @@ namespace osu.Game.Rulesets.Edit } internal override DrawableEditRuleset CreateDrawableRuleset() - => new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, Beatmap.Value)); + => new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, Beatmap.Value, Array.Empty())); - protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap); + protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods); } } diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 74aa9ace2d..757c269358 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Compose; using osuTK; @@ -108,7 +109,8 @@ namespace osu.Game.Rulesets.Edit } /// - /// Invokes , refreshing and parameters for the . + /// Invokes , + /// refreshing and parameters for the . /// protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.Value.Beatmap.ControlPointInfo, beatmap.Value.Beatmap.BeatmapInfo.BaseDifficulty); @@ -122,8 +124,10 @@ namespace osu.Game.Rulesets.Edit { case ScrollEvent _: return false; + case MouseEvent _: return true; + default: return false; } diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 85b6c91a07..e94604554c 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -68,20 +68,25 @@ namespace osu.Game.Rulesets.Edit get => state; set { - if (state == value) return; + if (state == value) + return; state = value; + switch (state) { case SelectionState.Selected: Show(); Selected?.Invoke(this); break; + case SelectionState.NotSelected: Hide(); Deselected?.Invoke(this); break; } + + StateChanged?.Invoke(state); } } diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 89db954c36..2150726a42 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -89,6 +89,7 @@ namespace osu.Game.Rulesets.Judgements { case HitResult.None: break; + case HitResult.Miss: JudgementBody.ScaleTo(1.6f); JudgementBody.ScaleTo(1, 100, Easing.In); @@ -98,6 +99,7 @@ namespace osu.Game.Rulesets.Judgements this.Delay(600).FadeOut(200); break; + default: ApplyHitAnimations(); break; @@ -113,13 +115,17 @@ namespace osu.Game.Rulesets.Judgements case HitResult.Perfect: case HitResult.Great: return colours.Blue; + case HitResult.Ok: case HitResult.Good: return colours.Green; + case HitResult.Meh: return colours.Yellow; + case HitResult.Miss: return colours.Red; + default: return Color4.White; } diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index e14eedd3dc..f07f76a2b8 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -31,6 +31,11 @@ namespace osu.Game.Rulesets.Judgements /// public int MaxNumericResult => NumericResultFor(MaxResult); + /// + /// The health increase for the maximum achievable result. + /// + public double MaxHealthIncrease => HealthIncreaseFor(MaxResult); + /// /// Retrieves the numeric score representation of a . /// diff --git a/osu.Game/Rulesets/Judgements/JudgementResult.cs b/osu.Game/Rulesets/Judgements/JudgementResult.cs index d4ef5750b1..195fe316ac 100644 --- a/osu.Game/Rulesets/Judgements/JudgementResult.cs +++ b/osu.Game/Rulesets/Judgements/JudgementResult.cs @@ -37,6 +37,11 @@ namespace osu.Game.Rulesets.Judgements /// public int HighestComboAtJudgement { get; internal set; } + /// + /// The health prior to this occurring. + /// + public double HealthAtJudgement { get; internal set; } + /// /// Whether a miss or hit occurred. /// diff --git a/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs b/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs index eb80fa131a..8cefb02904 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs @@ -2,14 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mods { /// /// Interface for a that applies changes to a . /// - /// The type of converted . public interface IApplicableToBeatmapConverter : IApplicableMod { /// diff --git a/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs b/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs index c13b62812b..f7f81c92c0 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mods public interface IApplicableToHitObject : IApplicableMod { /// - /// Applies this to a . + /// Applies this to a . /// /// The to apply to. void ApplyToHitObject(HitObject hitObject); diff --git a/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs b/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs index 1d0ed94ef4..cb00770868 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Mods { @@ -10,6 +11,14 @@ namespace osu.Game.Rulesets.Mods /// public interface IApplicableToScoreProcessor : IApplicableMod { + /// + /// Provide a to a mod. Called once on initialisation of a play instance. + /// void ApplyToScoreProcessor(ScoreProcessor scoreProcessor); + + /// + /// Called every time a rank calculation is requested. Allows mods to adjust the final rank. + /// + ScoreRank AdjustRank(ScoreRank rank, double accuracy); } } diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index 448ad0eb30..a5e19f293c 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -1,11 +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 Newtonsoft.Json; namespace osu.Game.Rulesets.Mods { - public interface IMod + public interface IMod : IEquatable { /// /// The shortened name of this mod. diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index d2d0a5bb26..023d37497a 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -70,5 +70,7 @@ namespace osu.Game.Rulesets.Mods /// Creates a copy of this initialised to a default state. /// public virtual Mod CreateCopy() => (Mod)Activator.CreateInstance(GetType()); + + public bool Equals(IMod other) => GetType() == other?.GetType(); } } diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 0ad99d13ff..e174a25df3 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -16,6 +16,7 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osuTK; using osuTK.Graphics; @@ -46,6 +47,8 @@ namespace osu.Game.Rulesets.Mods Combo.BindTo(scoreProcessor.Combo); } + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; + public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { var flashlight = CreateFlashlight(); @@ -64,24 +67,12 @@ namespace osu.Game.Rulesets.Mods internal BindableInt Combo; private IShader shader; - protected override DrawNode CreateDrawNode() => new FlashlightDrawNode(); + protected override DrawNode CreateDrawNode() => new FlashlightDrawNode(this); public override bool RemoveCompletedTransforms => false; public List Breaks; - protected override void ApplyDrawNode(DrawNode node) - { - base.ApplyDrawNode(node); - - var flashNode = (FlashlightDrawNode)node; - - flashNode.Shader = shader; - flashNode.ScreenSpaceDrawQuad = ScreenSpaceDrawQuad; - flashNode.FlashlightPosition = Vector2Extensions.Transform(FlashlightPosition, DrawInfo.Matrix); - flashNode.FlashlightSize = Vector2Extensions.Transform(FlashlightSize, DrawInfo.Matrix); - } - [BackgroundDependencyLoader] private void load(ShaderManager shaderManager) { @@ -94,14 +85,15 @@ namespace osu.Game.Rulesets.Mods Combo.ValueChanged += OnComboChange; - this.FadeInFromZero(FLASHLIGHT_FADE_DURATION); - - foreach (var breakPeriod in Breaks) + using (BeginAbsoluteSequence(0)) { - if (breakPeriod.Duration < FLASHLIGHT_FADE_DURATION * 2) continue; + foreach (var breakPeriod in Breaks) + { + if (breakPeriod.Duration < FLASHLIGHT_FADE_DURATION * 2) continue; - this.Delay(breakPeriod.StartTime + FLASHLIGHT_FADE_DURATION).FadeOutFromOne(FLASHLIGHT_FADE_DURATION); - this.Delay(breakPeriod.EndTime - FLASHLIGHT_FADE_DURATION).FadeInFromZero(FLASHLIGHT_FADE_DURATION); + this.Delay(breakPeriod.StartTime + FLASHLIGHT_FADE_DURATION).FadeOutFromOne(FLASHLIGHT_FADE_DURATION); + this.Delay(breakPeriod.EndTime - FLASHLIGHT_FADE_DURATION).FadeInFromZero(FLASHLIGHT_FADE_DURATION); + } } } @@ -136,27 +128,61 @@ namespace osu.Game.Rulesets.Mods Invalidate(Invalidation.DrawNode); } } - } - private class FlashlightDrawNode : DrawNode - { - public IShader Shader; - public Quad ScreenSpaceDrawQuad; - public Vector2 FlashlightPosition; - public Vector2 FlashlightSize; + private float flashlightDim; - public override void Draw(Action vertexAction) + public float FlashlightDim { - base.Draw(vertexAction); + get => flashlightDim; + set + { + if (flashlightDim == value) return; - Shader.Bind(); + flashlightDim = value; + Invalidate(Invalidation.DrawNode); + } + } - Shader.GetUniform("flashlightPos").UpdateValue(ref FlashlightPosition); - Shader.GetUniform("flashlightSize").UpdateValue(ref FlashlightSize); + private class FlashlightDrawNode : DrawNode + { + protected new Flashlight Source => (Flashlight)base.Source; - Texture.WhitePixel.DrawQuad(ScreenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction); + private IShader shader; + private Quad screenSpaceDrawQuad; + private Vector2 flashlightPosition; + private Vector2 flashlightSize; + private float flashlightDim; - Shader.Unbind(); + public FlashlightDrawNode(Flashlight source) + : base(source) + { + } + + public override void ApplyState() + { + base.ApplyState(); + + shader = Source.shader; + screenSpaceDrawQuad = Source.ScreenSpaceDrawQuad; + flashlightPosition = Vector2Extensions.Transform(Source.FlashlightPosition, DrawInfo.Matrix); + flashlightSize = Source.FlashlightSize * DrawInfo.Matrix.ExtractScale().Xy; + flashlightDim = Source.FlashlightDim; + } + + public override void Draw(Action vertexAction) + { + base.Draw(vertexAction); + + shader.Bind(); + + shader.GetUniform("flashlightPos").UpdateValue(ref flashlightPosition); + shader.GetUniform("flashlightSize").UpdateValue(ref flashlightSize); + shader.GetUniform("flashlightDim").UpdateValue(ref flashlightDim); + + Texture.WhitePixel.DrawQuad(screenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction); + + shader.Unbind(); + } } } } diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index c7e3f0a78f..0934992f55 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -8,10 +8,12 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Mods { - public abstract class ModHidden : Mod, IReadFromConfig, IApplicableToDrawableHitObjects + public abstract class ModHidden : Mod, IReadFromConfig, IApplicableToDrawableHitObjects, IApplicableToScoreProcessor { public override string Name => "Hidden"; public override string Acronym => "HD"; @@ -32,6 +34,27 @@ namespace osu.Game.Rulesets.Mods d.ApplyCustomUpdateState += ApplyHiddenState; } + public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) + { + // Default value of ScoreProcessor's Rank in Hidden Mod should be SS+ + scoreProcessor.Rank.Value = ScoreRank.XH; + } + + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) + { + switch (rank) + { + case ScoreRank.X: + return ScoreRank.XH; + + case ScoreRank.S: + return ScoreRank.SH; + + default: + return rank; + } + } + protected virtual void ApplyHiddenState(DrawableHitObject hitObject, ArmedState state) { } diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index 6a82050d26..809661db8e 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Mods { @@ -24,6 +25,8 @@ namespace osu.Game.Rulesets.Mods scoreProcessor.FailConditions += FailCondition; } + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; + protected virtual bool FailCondition(ScoreProcessor scoreProcessor) => scoreProcessor.Combo.Value == 0; } } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 62407907c1..a5f96087c0 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -73,10 +73,12 @@ namespace osu.Game.Rulesets.Mods pitch.PitchAdjust /= lastAdjust; pitch.PitchAdjust *= adjust; break; + case IHasTempoAdjust tempo: tempo.TempoAdjust /= lastAdjust; tempo.TempoAdjust *= adjust; break; + default: clock.Rate /= lastAdjust; clock.Rate *= adjust; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index a7cfbd3300..e91100608b 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; +using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Game.Audio; using osu.Game.Graphics; @@ -58,7 +59,7 @@ namespace osu.Game.Rulesets.Objects.Drawables public bool AllJudged => Judged && NestedHitObjects.All(h => h.AllJudged); /// - /// Whether this has been hit. This occurs if is . + /// Whether this has been hit. This occurs if is hit. /// Note: This does NOT include nested hitobjects. /// public bool IsHit => Result?.IsHit ?? false; @@ -97,6 +98,7 @@ namespace osu.Game.Rulesets.Objects.Drawables private void load() { var judgement = HitObject.CreateJudgement(); + if (judgement != null) { Result = CreateResult(judgement); @@ -211,9 +213,11 @@ namespace osu.Game.Rulesets.Objects.Drawables { case HitResult.None: break; + case HitResult.Miss: State.Value = ArmedState.Miss; break; + default: State.Value = ArmedState.Hit; break; @@ -223,7 +227,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } /// - /// Will called at least once after the of this has been passed. + /// Will called at least once after the of this has been passed. /// internal void OnLifetimeEnd() { diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index fd542be67d..cede2e50d0 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -53,8 +53,6 @@ namespace osu.Game.Rulesets.Objects [JsonIgnore] public bool Kiai { get; private set; } - private float overallDifficulty = BeatmapDifficulty.DEFAULT_DIFFICULTY; - /// /// The hit windows for this . /// @@ -115,7 +113,7 @@ namespace osu.Game.Rulesets.Objects /// Creates the for this . /// This can be null to indicate that the has no . /// - /// This will only be invoked if hasn't been set externally (e.g. from a . + /// This will only be invoked if hasn't been set externally (e.g. from a . /// /// protected virtual HitWindows CreateHitWindows() => new HitWindows(); diff --git a/osu.Game/Rulesets/Objects/HitWindows.cs b/osu.Game/Rulesets/Objects/HitWindows.cs index c5b7686da6..fe099aaee7 100644 --- a/osu.Game/Rulesets/Objects/HitWindows.cs +++ b/osu.Game/Rulesets/Objects/HitWindows.cs @@ -77,6 +77,7 @@ namespace osu.Game.Rulesets.Objects case HitResult.Perfect: case HitResult.Ok: return false; + default: return true; } @@ -126,16 +127,22 @@ namespace osu.Game.Rulesets.Objects { case HitResult.Perfect: return Perfect / 2; + case HitResult.Great: return Great / 2; + case HitResult.Good: return Good / 2; + case HitResult.Ok: return Ok / 2; + case HitResult.Meh: return Meh / 2; + case HitResult.Miss: return Miss / 2; + default: throw new ArgumentException(nameof(result)); } @@ -143,7 +150,7 @@ namespace osu.Game.Rulesets.Objects /// /// Given a time offset, whether the can ever be hit in the future with a non- result. - /// This happens if is less than what is required for a result. + /// This happens if is less than what is required for . /// /// The time offset. /// Whether the can be hit at any point in the future from this time offset. diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 8d6bb8bd3f..c14f3b6a42 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -84,6 +84,7 @@ namespace osu.Game.Rulesets.Objects.Legacy var points = new Vector2[pointCount]; int pointIndex = 1; + foreach (string t in pointSplit) { if (t.Length == 1) @@ -93,12 +94,15 @@ namespace osu.Game.Rulesets.Objects.Legacy case @"C": pathType = PathType.Catmull; break; + case @"B": pathType = PathType.Bezier; break; + case @"L": pathType = PathType.Linear; break; + case @"P": pathType = PathType.PerfectCurve; break; @@ -143,6 +147,7 @@ namespace osu.Game.Rulesets.Objects.Legacy if (split.Length > 9 && split[9].Length > 0) { string[] sets = split[9].Split('|'); + for (int i = 0; i < nodes; i++) { if (i >= sets.Length) @@ -162,6 +167,7 @@ namespace osu.Game.Rulesets.Objects.Legacy if (split.Length > 8 && split[8].Length > 0) { string[] adds = split[8].Split('|'); + for (int i = 0; i < nodes; i++) { if (i >= adds.Length) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 1e9767a54f..bc9571c85d 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -95,6 +95,7 @@ namespace osu.Game.Rulesets.Objects path.Clear(); int i = 0; + for (; i < calculatedPath.Count && cumulativeLength[i] < d0; ++i) { } @@ -142,6 +143,7 @@ namespace osu.Game.Rulesets.Objects { case PathType.Linear: return PathApproximator.ApproximateLinear(subControlPoints); + case PathType.PerfectCurve: //we can only use CircularArc iff we have exactly three control points and no dissection. if (ControlPoints.Length != 3 || subControlPoints.Length != 3) @@ -155,6 +157,7 @@ namespace osu.Game.Rulesets.Objects break; return subpath; + case PathType.Catmull: return PathApproximator.ApproximateCatmull(subControlPoints); } @@ -277,12 +280,5 @@ namespace osu.Game.Rulesets.Objects return ControlPoints.SequenceEqual(other.ControlPoints) && ExpectedDistance.Equals(other.ExpectedDistance) && Type == other.Type; } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - - return obj is SliderPath other && Equals(other); - } } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index cdfe02b60b..42b1322cae 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -53,9 +53,10 @@ namespace osu.Game.Rulesets /// Attempt to create a hit renderer for a beatmap /// /// The beatmap to create the hit renderer for. + /// The s to apply. /// Unable to successfully load the beatmap to be usable with this ruleset. /// - public abstract DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap); + public abstract DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods); /// /// Creates a to convert a to one that is applicable for this . diff --git a/osu.Game/Rulesets/RulesetConfigCache.cs b/osu.Game/Rulesets/RulesetConfigCache.cs index 4ac866fc90..9a5a4d4acd 100644 --- a/osu.Game/Rulesets/RulesetConfigCache.cs +++ b/osu.Game/Rulesets/RulesetConfigCache.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; +using System.Collections.Concurrent; using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Rulesets.Configuration; @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets /// public class RulesetConfigCache : Component { - private readonly Dictionary configCache = new Dictionary(); + private readonly ConcurrentDictionary configCache = new ConcurrentDictionary(); private readonly SettingsStore settingsStore; public RulesetConfigCache(SettingsStore settingsStore) @@ -34,10 +34,7 @@ namespace osu.Game.Rulesets if (ruleset.RulesetInfo.ID == null) throw new InvalidOperationException("The provided ruleset doesn't have a valid id."); - if (configCache.TryGetValue(ruleset.RulesetInfo.ID.Value, out var existing)) - return existing; - - return configCache[ruleset.RulesetInfo.ID.Value] = ruleset.CreateConfig(settingsStore); + return configCache.GetOrAdd(ruleset.RulesetInfo.ID.Value, _ => ruleset.CreateConfig(settingsStore)); } } } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 0fddb19a6c..ce94ca9c7d 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -8,8 +8,10 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Extensions.TypeExtensions; +using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Scoring; @@ -60,6 +62,11 @@ namespace osu.Game.Rulesets.Scoring /// public readonly BindableInt Combo = new BindableInt(); + /// + /// The current selected mods + /// + public readonly Bindable> Mods = new Bindable>(Array.Empty()); + /// /// Create a for this processor. /// @@ -93,12 +100,17 @@ namespace osu.Game.Rulesets.Scoring /// /// The default conditions for failing. /// - protected virtual bool DefaultFailCondition => Health.Value == Health.MinValue; + protected virtual bool DefaultFailCondition => Precision.AlmostBigger(Health.MinValue, Health.Value); protected ScoreProcessor() { Combo.ValueChanged += delegate { HighestCombo.Value = Math.Max(HighestCombo.Value, Combo.Value); }; - Accuracy.ValueChanged += delegate { Rank.Value = rankFrom(Accuracy.Value); }; + Accuracy.ValueChanged += delegate + { + Rank.Value = rankFrom(Accuracy.Value); + foreach (var mod in Mods.Value.OfType()) + Rank.Value = mod.AdjustRank(Rank.Value, Accuracy.Value); + }; } private ScoreRank rankFrom(double acc) @@ -154,7 +166,6 @@ namespace osu.Game.Rulesets.Scoring /// /// Notifies subscribers of that a new judgement has occurred. /// - /// The judgement to notify subscribers of. /// The judgement scoring result to notify subscribers of. protected void NotifyNewJudgement(JudgementResult result) { @@ -283,7 +294,6 @@ namespace osu.Game.Rulesets.Scoring /// /// Reverts the score change of a that was applied to this . /// - /// The judgement to remove. /// The judgement scoring result. private void revertResult(JudgementResult result) { @@ -301,6 +311,7 @@ namespace osu.Game.Rulesets.Scoring { result.ComboAtJudgement = Combo.Value; result.HighestComboAtJudgement = HighestCombo.Value; + result.HealthAtJudgement = Health.Value; JudgedHits++; @@ -313,9 +324,11 @@ namespace osu.Game.Rulesets.Scoring { case HitResult.None: break; + case HitResult.Miss: Combo.Value = 0; break; + default: Combo.Value++; break; @@ -332,20 +345,25 @@ namespace osu.Game.Rulesets.Scoring baseScore += result.Judgement.NumericResultFor(result); rollingMaxBaseScore += result.Judgement.MaxNumericResult; } + + Health.Value += HealthAdjustmentFactorFor(result) * result.Judgement.HealthIncreaseFor(result); } /// /// Reverts the score change of a that was applied to this . /// - /// The judgement to remove. /// The judgement scoring result. protected virtual void RevertResult(JudgementResult result) { Combo.Value = result.ComboAtJudgement; HighestCombo.Value = result.HighestComboAtJudgement; + Health.Value = result.HealthAtJudgement; JudgedHits--; + if (result.Type != HitResult.None) + scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) - 1; + if (result.Judgement.IsBonus) { if (result.IsHit) @@ -358,6 +376,13 @@ namespace osu.Game.Rulesets.Scoring } } + /// + /// An adjustment factor which is multiplied into the health increase provided by a . + /// + /// The for which the adjustment should apply. + /// The adjustment factor. + protected virtual double HealthAdjustmentFactorFor(JudgementResult result) => 1; + private void updateScore() { if (rollingMaxBaseScore != 0) @@ -373,6 +398,7 @@ namespace osu.Game.Rulesets.Scoring default: case ScoringMode.Standardised: return max_score * (base_portion * baseScore / maxBaseScore + combo_portion * HighestCombo.Value / maxHighestCombo) + bonusScore; + 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) / 25); diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 27137d9f98..7db24d36a5 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -11,8 +11,8 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; +using System.Threading; using osu.Framework.Bindables; using osu.Framework.Graphics.Cursor; using osu.Framework.Input; @@ -60,6 +60,8 @@ namespace osu.Game.Rulesets.UI /// public Container Overlays { get; private set; } + public override GameplayClock FrameStableClock => frameStabilityContainer.GameplayClock; + /// /// Invoked when a has been applied by a . /// @@ -82,7 +84,8 @@ namespace osu.Game.Rulesets.UI /// /// The mods which are to be applied. /// - private readonly IEnumerable mods; + [Cached(typeof(IReadOnlyList))] + private readonly IReadOnlyList mods; private FrameStabilityContainer frameStabilityContainer; @@ -93,16 +96,19 @@ namespace osu.Game.Rulesets.UI /// /// The ruleset being represented. /// The beatmap to create the hit renderer for. - protected DrawableRuleset(Ruleset ruleset, WorkingBeatmap workingBeatmap) + /// The s to apply. + protected DrawableRuleset(Ruleset ruleset, WorkingBeatmap workingBeatmap, IReadOnlyList mods) : base(ruleset) { - Debug.Assert(workingBeatmap != null, "DrawableRuleset initialized with a null beatmap."); + if (workingBeatmap == null) + throw new ArgumentException("Beatmap cannot be null.", nameof(workingBeatmap)); + + this.mods = mods.ToArray(); RelativeSizeAxes = Axes.Both; - Beatmap = (Beatmap)workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo); + Beatmap = (Beatmap)workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods); - mods = workingBeatmap.Mods.Value; applyBeatmapMods(mods); KeyBindingInputManager = CreateInputManager(); @@ -124,6 +130,7 @@ namespace osu.Game.Rulesets.UI onScreenDisplay = dependencies.Get(); Config = dependencies.Get().GetConfigFor(Ruleset); + if (Config != null) { dependencies.Cache(Config); @@ -136,11 +143,11 @@ namespace osu.Game.Rulesets.UI public virtual PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new PlayfieldAdjustmentContainer(); [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, CancellationToken? cancellationToken) { InternalChildren = new Drawable[] { - frameStabilityContainer = new FrameStabilityContainer + frameStabilityContainer = new FrameStabilityContainer(GameplayStartTime) { Child = KeyBindingInputManager .WithChild(CreatePlayfieldAdjustmentContainer() @@ -159,16 +166,21 @@ namespace osu.Game.Rulesets.UI applyRulesetMods(mods, config); - loadObjects(); + loadObjects(cancellationToken); } /// /// Creates and adds drawable representations of hit objects to the play field. /// - private void loadObjects() + private void loadObjects(CancellationToken? cancellationToken) { foreach (TObject h in Beatmap.HitObjects) + { + cancellationToken?.ThrowIfCancellationRequested(); addHitObject(h); + } + + cancellationToken?.ThrowIfCancellationRequested(); Playfield.PostProcess(); @@ -223,6 +235,12 @@ namespace osu.Game.Rulesets.UI if (replayInputManager.ReplayInputHandler != null) replayInputManager.ReplayInputHandler.GamefieldToScreenSpace = Playfield.GamefieldToScreenSpace; + + if (!ProvidingUserCursor) + { + // The cursor is hidden by default (see Playfield.load()), but should be shown when there's a replay + Playfield.Cursor?.Show(); + } } /// @@ -255,7 +273,7 @@ namespace osu.Game.Rulesets.UI /// Applies the active mods to the Beatmap. /// /// - private void applyBeatmapMods(IEnumerable mods) + private void applyBeatmapMods(IReadOnlyList mods) { if (mods == null) return; @@ -267,8 +285,9 @@ namespace osu.Game.Rulesets.UI /// /// Applies the active mods to this DrawableRuleset. /// - /// - private void applyRulesetMods(IEnumerable mods, OsuConfigManager config) + /// The s to apply. + /// The to apply. + private void applyRulesetMods(IReadOnlyList mods, OsuConfigManager config) { if (mods == null) return; @@ -323,6 +342,11 @@ namespace osu.Game.Rulesets.UI /// public readonly BindableBool IsPaused = new BindableBool(); + /// + /// The frame-stable clock which is being used for playfield display. + /// + public abstract GameplayClock FrameStableClock { get; } + /// ~ /// The associated ruleset. /// diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index deec2b8eac..1cc56fff8b 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -17,19 +17,29 @@ namespace osu.Game.Rulesets.UI /// public class FrameStabilityContainer : Container, IHasReplayHandler { - public FrameStabilityContainer() + private readonly double gameplayStartTime; + + /// + /// The number of frames (per parent frame) which can be run in an attempt to catch-up to real-time. + /// + public int MaxCatchUpFrames { get; set; } = 5; + + [Cached] + public GameplayClock GameplayClock { get; } + + public FrameStabilityContainer(double gameplayStartTime = double.MinValue) { RelativeSizeAxes = Axes.Both; - gameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock())); + + GameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock())); + + this.gameplayStartTime = gameplayStartTime; } private readonly ManualClock manualClock; private readonly FramedClock framedClock; - [Cached] - private GameplayClock gameplayClock; - private IFrameBasedClock parentGameplayClock; [BackgroundDependencyLoader(true)] @@ -38,7 +48,7 @@ namespace osu.Game.Rulesets.UI if (clock != null) { parentGameplayClock = clock; - gameplayClock.IsPaused.BindTo(clock.IsPaused); + GameplayClock.IsPaused.BindTo(clock.IsPaused); } } @@ -64,18 +74,18 @@ namespace osu.Game.Rulesets.UI private bool isAttached => ReplayInputHandler != null; - private const int max_catch_up_updates_per_frame = 50; - private const double sixty_frame_time = 1000.0 / 60; + private bool firstConsumption = true; + public override bool UpdateSubTree() { requireMoreUpdateLoops = true; - validState = !gameplayClock.IsPaused.Value; + validState = !GameplayClock.IsPaused.Value; int loops = 0; - while (validState && requireMoreUpdateLoops && loops++ < max_catch_up_updates_per_frame) + while (validState && requireMoreUpdateLoops && loops++ < MaxCatchUpFrames) { updateClock(); @@ -103,9 +113,22 @@ namespace osu.Game.Rulesets.UI try { - if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) + if (firstConsumption) { - newProposedTime = manualClock.Rate > 0 + // On the first update, frame-stability seeking would result in unexpected/unwanted behaviour. + // Instead we perform an initial seek to the proposed time. + manualClock.CurrentTime = newProposedTime; + + // do a second process to clear out ElapsedTime + framedClock.ProcessFrame(); + + firstConsumption = false; + } + else if (manualClock.CurrentTime < gameplayStartTime) + manualClock.CurrentTime = newProposedTime = Math.Min(gameplayStartTime, newProposedTime); + else if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) + { + newProposedTime = newProposedTime > manualClock.CurrentTime ? Math.Min(newProposedTime, manualClock.CurrentTime + sixty_frame_time) : Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time); } @@ -147,7 +170,7 @@ namespace osu.Game.Rulesets.UI if (parentGameplayClock == null) parentGameplayClock = Clock; - Clock = gameplayClock; + Clock = GameplayClock; ProcessCustomClock = false; } diff --git a/osu.Game/Rulesets/UI/GameplayCursorContainer.cs b/osu.Game/Rulesets/UI/GameplayCursorContainer.cs index de73c08809..41edfa0b68 100644 --- a/osu.Game/Rulesets/UI/GameplayCursorContainer.cs +++ b/osu.Game/Rulesets/UI/GameplayCursorContainer.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.UI public class GameplayCursorContainer : CursorContainer { /// - /// Because Show/Hide are executed by a parent, is updated immediately even if the cursor + /// Because Show/Hide are executed by a parent, is updated immediately even if the cursor /// is in a non-updating state (via limitations). /// /// This holds the true visibility value. diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index f9f6b5cc2f..86feea09a8 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -76,18 +76,22 @@ namespace osu.Game.Rulesets.UI backgroundColour = colours.Yellow; highlightedColour = colours.YellowLight; break; + case ModType.DifficultyReduction: backgroundColour = colours.Green; highlightedColour = colours.GreenLight; break; + case ModType.Automation: backgroundColour = colours.Blue; highlightedColour = colours.BlueLight; break; + case ModType.Conversion: backgroundColour = colours.Purple; highlightedColour = colours.PurpleLight; break; + case ModType.Fun: backgroundColour = colours.Pink; highlightedColour = colours.PinkLight; diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 48b950c070..f2e7f51b52 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -57,16 +57,24 @@ namespace osu.Game.Rulesets.UI hitObjectContainerLazy = new Lazy(CreateHitObjectContainer); } - private WorkingBeatmap beatmap; + [Resolved] + private IBindable beatmap { get; set; } + + [Resolved] + private IReadOnlyList mods { get; set; } [BackgroundDependencyLoader] - private void load(IBindable beatmap) + private void load() { - this.beatmap = beatmap.Value; - Cursor = CreateCursor(); + if (Cursor != null) + { + // initial showing of the cursor will be handed by MenuCursorContainer (via DrawableRuleset's IProvideCursor implementation). + Cursor.Hide(); + AddInternal(Cursor); + } } /// @@ -93,7 +101,6 @@ namespace osu.Game.Rulesets.UI /// /// Provide an optional cursor which is to be used for gameplay. - /// If providing a cursor, must also point to a valid target container. /// /// The cursor, or null if a cursor is not rqeuired. protected virtual GameplayCursorContainer CreateCursor() => null; @@ -123,7 +130,7 @@ namespace osu.Game.Rulesets.UI base.Update(); if (beatmap != null) - foreach (var mod in beatmap.Mods.Value) + foreach (var mod in mods) if (mod is IUpdatableByPlayfield updatable) updatable.Update(this); } diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index b4271085f5..e25c3bd0e7 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -105,6 +105,7 @@ namespace osu.Game.Rulesets.UI return false; break; + case MouseUpEvent mouseUp: if (!CurrentState.Mouse.IsPressed(mouseUp.Button)) return false; diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs index a104b0629f..b7a5eedc22 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms /// The current time. /// The amount of visible time. /// The absolute spatial length through . - /// The time at which == . + /// The time at which == . double TimeAt(float position, double currentTime, double timeRange, float scrollLength); /// diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 3b368652f2..42ec0b79b9 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -13,6 +13,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Input.Bindings; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Timing; @@ -64,7 +65,7 @@ namespace osu.Game.Rulesets.UI.Scrolling protected virtual ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Sequential; /// - /// Whether the player can change . + /// Whether the player can change . /// protected virtual bool UserScrollSpeedAdjustment => true; @@ -80,8 +81,8 @@ namespace osu.Game.Rulesets.UI.Scrolling [Cached(Type = typeof(IScrollingInfo))] private readonly LocalScrollingInfo scrollingInfo; - protected DrawableScrollingRuleset(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + protected DrawableScrollingRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + : base(ruleset, beatmap, mods) { scrollingInfo = new LocalScrollingInfo(); scrollingInfo.Direction.BindTo(Direction); @@ -92,9 +93,11 @@ namespace osu.Game.Rulesets.UI.Scrolling case ScrollVisualisationMethod.Sequential: scrollingInfo.Algorithm = new SequentialScrollAlgorithm(controlPoints); break; + case ScrollVisualisationMethod.Overlapping: scrollingInfo.Algorithm = new OverlappingScrollAlgorithm(controlPoints); break; + case ScrollVisualisationMethod.Constant: scrollingInfo.Algorithm = new ConstantScrollAlgorithm(); break; @@ -159,6 +162,7 @@ namespace osu.Game.Rulesets.UI.Scrolling case GlobalAction.IncreaseScrollSpeed: this.TransformBindableTo(TimeRange, TimeRange.Value - time_span_step, 200, Easing.OutQuint); return true; + case GlobalAction.DecreaseScrollSpeed: this.TransformBindableTo(TimeRange, TimeRange.Value + time_span_step, 200, Easing.OutQuint); return true; diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index ed3534fb36..069e2d1a0b 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -72,6 +72,7 @@ namespace osu.Game.Rulesets.UI.Scrolling case ScrollingDirection.Down: scrollLength = DrawSize.Y; break; + default: scrollLength = DrawSize.X; break; @@ -97,6 +98,7 @@ namespace osu.Game.Rulesets.UI.Scrolling case ScrollingDirection.Down: hitObject.Height = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, endTime.EndTime, timeRange.Value, scrollLength); break; + case ScrollingDirection.Left: case ScrollingDirection.Right: hitObject.Width = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, endTime.EndTime, timeRange.Value, scrollLength); @@ -129,12 +131,15 @@ namespace osu.Game.Rulesets.UI.Scrolling case ScrollingDirection.Up: hitObject.Y = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); break; + case ScrollingDirection.Down: hitObject.Y = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); break; + case ScrollingDirection.Left: hitObject.X = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); break; + case ScrollingDirection.Right: hitObject.X = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); break; diff --git a/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs b/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs index df80f848e3..e66f93ec8d 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs @@ -41,6 +41,7 @@ namespace osu.Game.Scoring.Legacy case 3: Statistics[HitResult.Great] = value; break; + case 2: Statistics[HitResult.Perfect] = value; break; @@ -81,6 +82,7 @@ namespace osu.Game.Scoring.Legacy case 1: Statistics[HitResult.Good] = value; break; + case 3: Statistics[HitResult.Ok] = value; break; diff --git a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs index 3491a5779a..d2c9ce81c3 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs @@ -89,6 +89,7 @@ namespace osu.Game.Scoring.Legacy throw new IOException("input .lzma is too short"); long outSize = 0; + for (int i = 0; i < 8; i++) { int v = replayInStream.ReadByte(); diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index d36f963016..8bdc30ac94 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -177,6 +177,8 @@ namespace osu.Game.Scoring protected class DeserializedMod : IMod { public string Acronym { get; set; } + + public bool Equals(IMod other) => Acronym == other?.Acronym; } public override string ToString() => $"{User} playing {Beatmap}"; diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 7a527bfc69..6b737dc734 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -25,9 +25,9 @@ namespace osu.Game.Scoring protected override string ImportFromStablePath => "Replays"; private readonly RulesetStore rulesets; - private readonly BeatmapManager beatmaps; + private readonly Func beatmaps; - public ScoreManager(RulesetStore rulesets, BeatmapManager beatmaps, Storage storage, IDatabaseContextFactory contextFactory, IIpcHost importHost = null) + public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, IDatabaseContextFactory contextFactory, IIpcHost importHost = null) : base(storage, contextFactory, new ScoreStore(contextFactory, storage), importHost) { this.rulesets = rulesets; @@ -43,7 +43,7 @@ namespace osu.Game.Scoring { try { - return new DatabasedLegacyScoreParser(rulesets, beatmaps).Parse(stream).ScoreInfo; + return new DatabasedLegacyScoreParser(rulesets, beatmaps()).Parse(stream).ScoreInfo; } catch (LegacyScoreParser.BeatmapNotFoundException e) { @@ -53,7 +53,7 @@ namespace osu.Game.Scoring } } - public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps, Files.Store); + public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps(), Files.Store); public List GetAllUsableScores() => ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList(); diff --git a/osu.Game/Scoring/ScoreRank.cs b/osu.Game/Scoring/ScoreRank.cs index 82c33748bb..a93d015f1b 100644 --- a/osu.Game/Scoring/ScoreRank.cs +++ b/osu.Game/Scoring/ScoreRank.cs @@ -25,13 +25,13 @@ namespace osu.Game.Scoring [Description(@"S")] S, - [Description(@"SPlus")] + [Description(@"S+")] SH, [Description(@"SS")] X, - [Description(@"SSPlus")] + [Description(@"SS+")] XH, } } diff --git a/osu.Game/Screens/BackgroundScreenStack.cs b/osu.Game/Screens/BackgroundScreenStack.cs index 5f82329496..9c0c5da0fb 100644 --- a/osu.Game/Screens/BackgroundScreenStack.cs +++ b/osu.Game/Screens/BackgroundScreenStack.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens //public float ParallaxAmount { set => parallax.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * value; } - public new void Push(BackgroundScreen screen) + public void Push(BackgroundScreen screen) { if (screen == null) return; diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 6df418753c..b6c2d016d2 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -76,6 +76,7 @@ namespace osu.Game.Screens.Backgrounds private void switchBackground(BeatmapBackground b) { float newDepth = 0; + if (Background != null) { newDepth = Background.Depth + 1; diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs index 1ad69afe91..70c0cf623e 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs @@ -6,6 +6,7 @@ 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; diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 9a7ac8dfd0..ebf8c9c309 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -236,10 +236,12 @@ namespace osu.Game.Screens.Edit.Compose.Components beatDivisor.Next(); OnUserChange(Current.Value); return true; + case Key.Left: beatDivisor.Previous(); OnUserChange(Current.Value); return true; + default: return false; } @@ -307,18 +309,25 @@ namespace osu.Game.Screens.Edit.Compose.Components { case 2: return colours.BlueLight; + case 4: return colours.Blue; + case 8: return colours.BlueDarker; + case 16: return colours.PurpleDark; + case 3: return colours.YellowLight; + case 6: return colours.Yellow; + case 12: return colours.YellowDarker; + default: return Color4.White; } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index bcb2bee601..11e649168f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -121,6 +121,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Handle a blueprint requesting selection. /// /// The blueprint. + /// The input state at the point of selection. internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) { if (state.Keyboard.ControlPressed) @@ -166,8 +167,6 @@ namespace osu.Game.Screens.Edit.Compose.Components var topLeft = new Vector2(float.MaxValue, float.MaxValue); var bottomRight = new Vector2(float.MinValue, float.MinValue); - bool hasSelection = false; - foreach (var blueprint in selectedBlueprints) { topLeft = Vector2.ComponentMin(topLeft, ToLocalSpace(blueprint.SelectionQuad.TopLeft)); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 1e94a20dc7..829437d599 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -92,11 +92,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - protected override void Update() + protected override void LoadComplete() { - base.Update(); + base.LoadComplete(); - zoomedContent.Width = DrawWidth * currentZoom; + // This width only gets updated on the application of a transform, so this needs to be initialized here. + updateZoomedContentWidth(); } protected override bool OnScroll(ScrollEvent e) @@ -109,6 +110,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return true; } + private void updateZoomedContentWidth() => zoomedContent.Width = DrawWidth * currentZoom; + private float zoomTarget = 1; private void setZoomTarget(float newZoom, float focusPoint) @@ -138,7 +141,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly float scrollOffset; /// - /// Transforms to a new value. + /// Transforms to a new value. /// /// The focus point in absolute coordinates local to the content. /// The size of the content. @@ -169,6 +172,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline float targetOffset = expectedWidth * (focusPoint / contentSize) - focusOffset; d.currentZoom = newZoom; + + d.updateZoomedContentWidth(); + // Temporarily here to make sure ScrollTo gets the correct DrawSize for scrollable area. + // TODO: Make sure draw size gets invalidated properly on the framework side, and remove this once it is. + d.Invalidate(Invalidation.DrawSize); d.ScrollTo(targetOffset, false); } diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 9ccf974244..5699ef0a84 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -100,6 +100,7 @@ namespace osu.Game.Screens.Edit.Compose }; var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance(); + if (ruleset == null) { Logger.Log("Beatmap doesn't have a ruleset assigned."); @@ -108,6 +109,7 @@ namespace osu.Game.Screens.Edit.Compose } composer = ruleset.CreateHitObjectComposer(); + if (composer == null) { Logger.Log($"Ruleset {ruleset.Description} doesn't support hitobject composition."); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 0ba1e74aca..cb01e33282 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -65,11 +65,9 @@ namespace osu.Game.Screens.Edit dependencies.Cache(beatDivisor); EditorMenuBar menuBar; - TimeInfoContainer timeInfo; - SummaryTimeline timeline; - PlaybackControl playback; var fileMenuItems = new List(); + if (RuntimeInfo.IsDesktop) { fileMenuItems.Add(new EditorMenuItem("Export", MenuItemType.Standard, exportBeatmap)); @@ -173,6 +171,7 @@ namespace osu.Game.Screens.Edit case Key.Left: seek(e, -1); return true; + case Key.Right: seek(e, 1); return true; @@ -218,6 +217,7 @@ namespace osu.Game.Screens.Edit public override bool OnExiting(IScreen next) { Background.FadeColour(Color4.White, 500); + if (Beatmap.Value.Track != null) { Beatmap.Value.Track.Tempo.Value = 1; @@ -238,9 +238,11 @@ namespace osu.Game.Screens.Edit case EditorScreenMode.Compose: currentScreen = new ComposeScreen(); break; + case EditorScreenMode.Design: currentScreen = new DesignScreen(); break; + default: currentScreen = new EditorScreen(); break; diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 8f65366650..24fb561f04 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -83,6 +83,7 @@ namespace osu.Game.Screens.Edit if (amount <= 0) throw new ArgumentException("Value should be greater than zero", nameof(amount)); var timingPoint = ControlPointInfo.TimingPointAt(CurrentTime); + if (direction < 0 && timingPoint.Time == CurrentTime) { // When going backwards and we're at the boundary of two timing points, we compute the seek distance with the timing point which we are seeking into diff --git a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs index 50d524d1f5..1c53fc7088 100644 --- a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs +++ b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs @@ -60,14 +60,7 @@ namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents set => label.Colour = value; } - public Color4 BackgroundColour - { - get => content.Colour; - set => content.Colour = value; - } - private readonly OsuTextBox textBox; - private readonly Container content; private readonly OsuSpriteText label; public LabelledTextBox() diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index 383150cbd3..7d48f619d9 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -16,6 +16,7 @@ using osuTK.Input; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics.Containers; using osu.Framework.Audio.Track; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Beatmaps.ControlPoints; @@ -262,6 +263,7 @@ namespace osu.Game.Screens.Menu box.ScaleTo(new Vector2(0, 1), 500, Easing.OutExpo); this.FadeOut(500); break; + case 1: box.ScaleTo(new Vector2(0, 1), 400, Easing.InSine); this.FadeOut(800); @@ -269,11 +271,13 @@ namespace osu.Game.Screens.Menu } break; + case ButtonState.Expanded: const int expand_duration = 500; box.ScaleTo(new Vector2(1, 1), expand_duration, Easing.OutExpo); this.FadeIn(expand_duration / 6f); break; + case ButtonState.Exploded: const int explode_duration = 200; box.ScaleTo(new Vector2(2, 1), explode_duration, Easing.OutExpo); @@ -296,10 +300,12 @@ namespace osu.Game.Screens.Menu case ButtonSystemState.Initial: State = ButtonState.Contracted; break; + case ButtonSystemState.EnteringMode: ContractStyle = 1; State = ButtonState.Contracted; break; + default: if (value == VisibleState) State = ButtonState.Expanded; diff --git a/osu.Game/Screens/Menu/ButtonArea.cs b/osu.Game/Screens/Menu/ButtonArea.cs index d6e1aef63c..c7650a08fa 100644 --- a/osu.Game/Screens/Menu/ButtonArea.cs +++ b/osu.Game/Screens/Menu/ButtonArea.cs @@ -32,6 +32,7 @@ namespace osu.Game.Screens.Menu RelativeSizeAxes = Axes.X, Size = new Vector2(1, BUTTON_AREA_HEIGHT), Alpha = 0, + AlwaysPresent = true, // Always needs to be present for correct tracking on initial -> toplevel state change Children = new Drawable[] { buttonAreaBackground = new ButtonAreaBackground(), @@ -57,6 +58,7 @@ namespace osu.Game.Screens.Menu case ButtonSystemState.EnteringMode: State = Visibility.Hidden; break; + case ButtonSystemState.TopLevel: case ButtonSystemState.Play: State = Visibility.Visible; @@ -109,6 +111,7 @@ namespace osu.Game.Screens.Menu case ButtonAreaBackgroundState.Flat: this.ScaleTo(new Vector2(2, 0), 300, Easing.InSine); break; + case ButtonAreaBackgroundState.Normal: this.ScaleTo(Vector2.One, 400, Easing.OutQuint); break; @@ -127,6 +130,7 @@ namespace osu.Game.Screens.Menu default: State = ButtonAreaBackgroundState.Normal; break; + case ButtonSystemState.Initial: case ButtonSystemState.Exit: case ButtonSystemState.EnteringMode: diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 80fbce2cf8..868d37d922 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -17,6 +18,7 @@ using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.Online.API; @@ -47,6 +49,10 @@ namespace osu.Game.Screens.Menu private OsuLogo logo; + /// + /// Assign the that this ButtonSystem should manage the position of. + /// + /// The instance of the logo to be assigned. If null, we are suspending from the screen that uses this ButtonSystem. public void SetOsuLogo(OsuLogo logo) { this.logo = logo; @@ -60,9 +66,13 @@ namespace osu.Game.Screens.Menu updateLogoState(); } + else + { + // We should stop tracking as the facade is now out of scope. + logoTrackingContainer.StopTracking(); + } } - private readonly Drawable iconFacade; private readonly ButtonArea buttonArea; private readonly Button backButton; @@ -72,26 +82,29 @@ namespace osu.Game.Screens.Menu private SampleChannel sampleBack; + private readonly LogoTrackingContainer logoTrackingContainer; + public ButtonSystem() { RelativeSizeAxes = Axes.Both; - Child = buttonArea = new ButtonArea(); + Child = logoTrackingContainer = new LogoTrackingContainer + { + RelativeSizeAxes = Axes.Both, + Child = buttonArea = new ButtonArea() + }; - buttonArea.AddRange(new[] + buttonArea.AddRange(new Drawable[] { new Button(@"settings", string.Empty, FontAwesome.Solid.Cog, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O), backButton = new Button(@"back", @"button-back-select", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH) { VisibleState = ButtonSystemState.Play, }, - iconFacade = new Container //need a container to make the osu! icon flow properly. - { - Size = new Vector2(0, ButtonArea.BUTTON_AREA_HEIGHT) - } + logoTrackingContainer.LogoFacade.With(d => d.Scale = new Vector2(0.74f)) }); - buttonArea.Flow.CentreTarget = iconFacade; + buttonArea.Flow.CentreTarget = logoTrackingContainer.LogoFacade; } [Resolved(CanBeNull = true)] @@ -124,6 +137,15 @@ namespace osu.Game.Screens.Menu buttonArea.AddRange(buttonsPlay); buttonArea.AddRange(buttonsTopLevel); + buttonArea.ForEach(b => + { + if (b is Button) + { + b.Origin = Anchor.CentreLeft; + b.Anchor = Anchor.CentreLeft; + } + }); + isIdle.ValueChanged += idle => updateIdleState(idle.NewValue); if (idleTracker != null) isIdle.BindTo(idleTracker.IsIdle); @@ -154,7 +176,7 @@ namespace osu.Game.Screens.Menu private void updateIdleState(bool isIdle) { - if (isIdle && State != ButtonSystemState.Exit) + if (isIdle && State != ButtonSystemState.Exit && State != ButtonSystemState.EnteringMode) State = ButtonSystemState.Initial; } @@ -164,9 +186,11 @@ namespace osu.Game.Screens.Menu { case GlobalAction.Back: return goBack(); + case GlobalAction.Select: logo?.Click(); return true; + default: return false; } @@ -182,9 +206,11 @@ namespace osu.Game.Screens.Menu State = ButtonSystemState.Initial; sampleBack?.Play(); return true; + case ButtonSystemState.Play: backButton.Click(); return true; + default: return false; } @@ -196,12 +222,15 @@ namespace osu.Game.Screens.Menu { default: return true; + case ButtonSystemState.Initial: State = ButtonSystemState.TopLevel; return true; + case ButtonSystemState.TopLevel: buttonsTopLevel.First().Click(); return false; + case ButtonSystemState.Play: buttonsPlay.First().Click(); return false; @@ -256,76 +285,57 @@ namespace osu.Game.Screens.Menu logoDelayedAction?.Cancel(); logoDelayedAction = Scheduler.AddDelayed(() => { - logoTracking = false; + logoTrackingContainer.StopTracking(); game?.Toolbar.Hide(); logo.ClearTransforms(targetMember: nameof(Position)); - logo.RelativePositionAxes = Axes.Both; - logo.MoveTo(new Vector2(0.5f), 800, Easing.OutExpo); logo.ScaleTo(1, 800, Easing.OutExpo); }, buttonArea.Alpha * 150); break; + case ButtonSystemState.TopLevel: case ButtonSystemState.Play: switch (lastState) { case ButtonSystemState.TopLevel: // coming from toplevel to play break; + case ButtonSystemState.Initial: logo.ClearTransforms(targetMember: nameof(Position)); - logo.RelativePositionAxes = Axes.None; bool impact = logo.Scale.X > 0.6f; if (lastState == ButtonSystemState.Initial) logo.ScaleTo(0.5f, 200, Easing.In); - logo.MoveTo(logoTrackingPosition, lastState == ButtonSystemState.EnteringMode ? 0 : 200, Easing.In); + logoTrackingContainer.StartTracking(logo, lastState == ButtonSystemState.EnteringMode ? 0 : 200, Easing.In); logoDelayedAction?.Cancel(); logoDelayedAction = Scheduler.AddDelayed(() => { - logoTracking = true; - if (impact) logo.Impact(); game?.Toolbar.Show(); }, 200); break; + default: logo.ClearTransforms(targetMember: nameof(Position)); - logo.RelativePositionAxes = Axes.None; - logoTracking = true; + logoTrackingContainer.StartTracking(logo, 0, Easing.In); logo.ScaleTo(0.5f, 200, Easing.OutQuint); break; } break; + case ButtonSystemState.EnteringMode: - logoTracking = true; + logoTrackingContainer.StartTracking(logo, 0, Easing.In); break; } } - - private Vector2 logoTrackingPosition => logo.Parent.ToLocalSpace(iconFacade.ScreenSpaceDrawQuad.Centre); - - private bool logoTracking; - - protected override void Update() - { - base.Update(); - - if (logo != null) - { - if (logoTracking && logo.RelativePositionAxes == Axes.None && iconFacade.IsLoaded) - logo.Position = logoTrackingPosition; - - iconFacade.Width = logo.SizeForFlow * 0.5f; - } - } } public enum ButtonSystemState diff --git a/osu.Game/Screens/Menu/FlowContainerWithOrigin.cs b/osu.Game/Screens/Menu/FlowContainerWithOrigin.cs index ec7333ec02..8310ab06eb 100644 --- a/osu.Game/Screens/Menu/FlowContainerWithOrigin.cs +++ b/osu.Game/Screens/Menu/FlowContainerWithOrigin.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Menu if (CentreTarget == null) return base.OriginPosition; - return CentreTarget.DrawPosition + CentreTarget.DrawSize / 2; + return CentreTarget.DrawPosition + CentreTarget.DrawSize / 2 * CentreTarget.Scale; } } } diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 8283bf7ea2..2925689d20 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -139,6 +139,7 @@ namespace osu.Game.Screens.Menu base.Update(); float decayFactor = (float)Time.Elapsed * decay_per_milisecond; + for (int i = 0; i < bars_per_visualiser; i++) { //3% of extra bar length to make it a little faster when bar is almost at it's minimum @@ -150,62 +151,67 @@ namespace osu.Game.Screens.Menu Invalidate(Invalidation.DrawNode, shallPropagate: false); } - protected override DrawNode CreateDrawNode() => new VisualisationDrawNode(); - - protected override void ApplyDrawNode(DrawNode node) - { - base.ApplyDrawNode(node); - - var visNode = (VisualisationDrawNode)node; - - visNode.Shader = shader; - visNode.Texture = texture; - visNode.Size = DrawSize.X; - visNode.Colour = AccentColour; - visNode.AudioData = frequencyAmplitudes; - } + protected override DrawNode CreateDrawNode() => new VisualisationDrawNode(this); private class VisualisationDrawNode : DrawNode { - public IShader Shader; - public Texture Texture; + protected new LogoVisualisation Source => (LogoVisualisation)base.Source; + + private IShader shader; + private Texture texture; //Asuming the logo is a circle, we don't need a second dimension. - public float Size; + private float size; - public Color4 Colour; - public float[] AudioData; + private Color4 colour; + private float[] audioData; private readonly QuadBatch vertexBatch = new QuadBatch(100, 10); + public VisualisationDrawNode(LogoVisualisation source) + : base(source) + { + } + + public override void ApplyState() + { + base.ApplyState(); + + shader = Source.shader; + texture = Source.texture; + size = Source.DrawSize.X; + colour = Source.AccentColour; + audioData = Source.frequencyAmplitudes; + } + public override void Draw(Action vertexAction) { base.Draw(vertexAction); - Shader.Bind(); - Texture.TextureGL.Bind(); + shader.Bind(); + texture.TextureGL.Bind(); Vector2 inflation = DrawInfo.MatrixInverse.ExtractScale().Xy; ColourInfo colourInfo = DrawColourInfo.Colour; - colourInfo.ApplyChild(Colour); + colourInfo.ApplyChild(colour); - if (AudioData != null) + if (audioData != null) { for (int j = 0; j < visualiser_rounds; j++) { for (int i = 0; i < bars_per_visualiser; i++) { - if (AudioData[i] < amplitude_dead_zone) + if (audioData[i] < amplitude_dead_zone) continue; float rotation = MathHelper.DegreesToRadians(i / (float)bars_per_visualiser * 360 + j * 360 / visualiser_rounds); float rotationCos = (float)Math.Cos(rotation); float rotationSin = (float)Math.Sin(rotation); //taking the cos and sin to the 0..1 range - var barPosition = new Vector2(rotationCos / 2 + 0.5f, rotationSin / 2 + 0.5f) * Size; + var barPosition = new Vector2(rotationCos / 2 + 0.5f, rotationSin / 2 + 0.5f) * size; - var barSize = new Vector2(Size * (float)Math.Sqrt(2 * (1 - Math.Cos(MathHelper.DegreesToRadians(360f / bars_per_visualiser)))) / 2f, bar_length * AudioData[i]); + var barSize = new Vector2(size * (float)Math.Sqrt(2 * (1 - Math.Cos(MathHelper.DegreesToRadians(360f / bars_per_visualiser)))) / 2f, bar_length * audioData[i]); //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. @@ -218,7 +224,7 @@ namespace osu.Game.Screens.Menu Vector2Extensions.Transform(barPosition + bottomOffset + amplitudeOffset, DrawInfo.Matrix) ); - Texture.DrawQuad( + texture.DrawQuad( rectangle, colourInfo, null, @@ -229,7 +235,7 @@ namespace osu.Game.Screens.Menu } } - Shader.Unbind(); + shader.Unbind(); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 5403f7c702..3afe9b9371 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -20,6 +20,7 @@ using osu.Game.Screens.Multi; using osu.Game.Screens.Select; using osu.Game.Screens.Tournament; using osu.Framework.Platform; +using osu.Game.Overlays; namespace osu.Game.Screens.Menu { @@ -45,7 +46,7 @@ namespace osu.Game.Screens.Menu protected override BackgroundScreen CreateBackground() => background; [BackgroundDependencyLoader(true)] - private void load(OsuGame game = null) + private void load(DirectOverlay direct, SettingsOverlay settings) { if (host.CanExit) AddInternal(new ExitConfirmOverlay { Action = this.Exit }); @@ -79,17 +80,15 @@ namespace osu.Game.Screens.Menu case ButtonSystemState.Exit: Background.FadeColour(Color4.White, 500, Easing.OutSine); break; + default: Background.FadeColour(OsuColour.Gray(0.8f), 500, Easing.OutSine); break; } }; - if (game != null) - { - buttons.OnSettings = game.ToggleSettings; - buttons.OnDirect = game.ToggleDirect; - } + buttons.OnSettings = () => settings?.ToggleVisibility(); + buttons.OnDirect = () => direct?.ToggleVisibility(); LoadComponentAsync(background = new BackgroundScreenDefault()); preloadSongSelect(); @@ -156,9 +155,11 @@ namespace osu.Game.Screens.Menu protected override void LogoSuspending(OsuLogo logo) { - logo.FadeOut(300, Easing.InSine) - .ScaleTo(0.2f, 300, Easing.InSine) - .OnComplete(l => buttons.SetOsuLogo(null)); + var seq = logo.FadeOut(300, Easing.InSine) + .ScaleTo(0.2f, 300, Easing.InSine); + + seq.OnComplete(_ => buttons.SetOsuLogo(null)); + seq.OnAbort(_ => buttons.SetOsuLogo(null)); } private void beatmap_ValueChanged(ValueChangedEvent e) diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index af697d37bd..4631f4e222 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -54,7 +54,13 @@ namespace osu.Game.Screens.Menu /// public Func Action; - public float SizeForFlow => logo == null ? 0 : logo.DrawSize.X * logo.Scale.X * logoBounceContainer.Scale.X * logoHoverContainer.Scale.X * 0.74f; + /// + /// The size of the logo Sprite with respect to the scale of its hover and bounce containers. + /// + /// Does not account for the scale of this + public float SizeForFlow => logo == null ? 0 : logo.DrawSize.X * logo.Scale.X * logoBounceContainer.Scale.X * logoHoverContainer.Scale.X; + + public bool IsTracking { get; set; } private readonly Sprite ripple; diff --git a/osu.Game/Screens/Multi/Components/DisableableTabControl.cs b/osu.Game/Screens/Multi/Components/DisableableTabControl.cs index b6b0332cf3..27b5aec4d3 100644 --- a/osu.Game/Screens/Multi/Components/DisableableTabControl.cs +++ b/osu.Game/Screens/Multi/Components/DisableableTabControl.cs @@ -13,15 +13,13 @@ namespace osu.Game.Screens.Multi.Components protected override void AddTabItem(TabItem tab, bool addToDropdown = true) { - if (tab is DisableableTabItem disableable) + if (tab is DisableableTabItem disableable) disableable.Enabled.BindTo(Enabled); base.AddTabItem(tab, addToDropdown); } - protected abstract class DisableableTabItem : TabItem + protected abstract class DisableableTabItem : TabItem { - public readonly BindableBool Enabled = new BindableBool(); - protected DisableableTabItem(T value) : base(value) { diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs index 7924086389..1cbf2a45e7 100644 --- a/osu.Game/Screens/Multi/Header.cs +++ b/osu.Game/Screens/Multi/Header.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Multi { Anchor = Anchor.CentreLeft, Origin = Anchor.BottomLeft, - X = -35, + X = -ScreenTitle.ICON_WIDTH, }, breadcrumbs = new HeaderBreadcrumbControl(stack) { @@ -79,7 +79,7 @@ namespace osu.Game.Screens.Multi [BackgroundDependencyLoader] private void load(OsuColour colours) { - Title = "multiplayer"; + Title = "multi"; Icon = OsuIcon.Multi; AccentColour = colours.Yellow; } diff --git a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs index dce597b276..6ec8f2bfe5 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs @@ -9,6 +9,7 @@ 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.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; diff --git a/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs b/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs index 51d3c93624..6570051040 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs @@ -25,8 +25,6 @@ namespace osu.Game.Screens.Multi.Lounge.Components private void load(OsuColour colours) { OsuSpriteText summary; - OsuSpriteText levelRangeHigher; - OsuSpriteText levelRangeLower; Container flagContainer; LinkFlowContainer hostText; @@ -45,21 +43,6 @@ namespace osu.Game.Screens.Multi.Lounge.Components Width = 22f, RelativeSizeAxes = Axes.Y, }, - /*new Container //todo: team banners - { - Width = 38f, - RelativeSizeAxes = Axes.Y, - CornerRadius = 2f, - Masking = true, - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex(@"ad387e"), - }, - }, - },*/ hostText = new LinkFlowContainer { Anchor = Anchor.CentreLeft, @@ -101,13 +84,6 @@ namespace osu.Game.Screens.Multi.Lounge.Components }, true); ParticipantCount.BindValueChanged(count => summary.Text = "participant".ToQuantity(count.NewValue), true); - - /*Participants.BindValueChanged(e => - { - var ranks = v.Select(u => u.Statistics.Ranks.Global); - levelRangeLower.Text = ranks.Min().ToString(); - levelRangeHigher.Text = ranks.Max().ToString(); - });*/ } } } diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs index 5798fce457..d597e5bb0f 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs @@ -97,7 +97,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Font = OsuFont.GetFont(size: 30), - Current = Name + Current = RoomName }, }, }, @@ -258,6 +258,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components // nice little progressive fade int time = 500; + foreach (var c in fill.Children) { c.Delay(500 - time).FadeOut(time, Easing.Out); diff --git a/osu.Game/Screens/Multi/Match/Components/GameTypePicker.cs b/osu.Game/Screens/Multi/Match/Components/GameTypePicker.cs index ccb957734f..b69cb9705d 100644 --- a/osu.Game/Screens/Multi/Match/Components/GameTypePicker.cs +++ b/osu.Game/Screens/Multi/Match/Components/GameTypePicker.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Multi.Match.Components AddItem(new GameTypeTimeshift()); } - private class GameTypePickerItem : DisableableTabItem + private class GameTypePickerItem : DisableableTabItem { private const float transition_duration = 200; diff --git a/osu.Game/Screens/Multi/Match/Components/Header.cs b/osu.Game/Screens/Multi/Match/Components/Header.cs index e1592532a3..2a6074882d 100644 --- a/osu.Game/Screens/Multi/Match/Components/Header.cs +++ b/osu.Game/Screens/Multi/Match/Components/Header.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -110,7 +109,7 @@ namespace osu.Game.Screens.Multi.Match.Components }, }; - CurrentItem.BindValueChanged(item => modDisplay.Current.Value = item.NewValue?.RequiredMods ?? Enumerable.Empty(), true); + CurrentItem.BindValueChanged(item => modDisplay.Current.Value = item.NewValue?.RequiredMods?.ToArray() ?? Array.Empty(), true); beatmapButton.Action = () => RequestBeatmapSelection?.Invoke(); } diff --git a/osu.Game/Screens/Multi/Match/Components/Info.cs b/osu.Game/Screens/Multi/Match/Components/Info.cs index a944d965bd..a185c4db50 100644 --- a/osu.Game/Screens/Multi/Match/Components/Info.cs +++ b/osu.Game/Screens/Multi/Match/Components/Info.cs @@ -30,7 +30,6 @@ namespace osu.Game.Screens.Multi.Match.Components ReadyButton readyButton; ViewBeatmapButton viewBeatmapButton; HostInfo hostInfo; - RoomStatusInfo statusInfo; InternalChildren = new Drawable[] { @@ -63,7 +62,7 @@ namespace osu.Game.Screens.Multi.Match.Components new OsuSpriteText { Font = OsuFont.GetFont(size: 30), - Current = Name + Current = RoomName }, new RoomStatusInfo(), } diff --git a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs index 586a986111..359b5824c0 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs @@ -265,7 +265,7 @@ namespace osu.Game.Screens.Multi.Match.Components }; TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue?.Name ?? string.Empty, true); - Name.BindValueChanged(name => NameField.Text = name.NewValue, true); + RoomName.BindValueChanged(name => NameField.Text = name.NewValue, true); Availability.BindValueChanged(availability => AvailabilityPicker.Current.Value = availability.NewValue, true); Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true); MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); @@ -285,7 +285,7 @@ namespace osu.Game.Screens.Multi.Match.Components { hideError(); - Name.Value = NameField.Text; + RoomName.Value = NameField.Text; Availability.Value = AvailabilityPicker.Current.Value; Type.Value = TypePicker.Current.Value; diff --git a/osu.Game/Screens/Multi/Match/Components/RoomAvailabilityPicker.cs b/osu.Game/Screens/Multi/Match/Components/RoomAvailabilityPicker.cs index 8751e27552..9de4a61cde 100644 --- a/osu.Game/Screens/Multi/Match/Components/RoomAvailabilityPicker.cs +++ b/osu.Game/Screens/Multi/Match/Components/RoomAvailabilityPicker.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Multi.Match.Components AddItem(RoomAvailability.InviteOnly); } - private class RoomAvailabilityPickerItem : DisableableTabItem + private class RoomAvailabilityPickerItem : DisableableTabItem { private const float transition_duration = 200; diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index a71106872e..a80056d164 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.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 System.Collections.Generic; -using System.Linq; +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -42,9 +41,6 @@ namespace osu.Game.Screens.Multi.Match [Resolved(typeof(Room))] protected Bindable CurrentItem { get; private set; } - [Resolved] - protected Bindable> SelectedMods { get; private set; } - [Resolved] private BeatmapManager beatmapManager { get; set; } @@ -149,6 +145,7 @@ namespace osu.Game.Screens.Multi.Match header.Tabs.Current.BindValueChanged(tab => { const float fade_duration = 500; + if (tab.NewValue is SettingsMatchPage) { settings.Show(); @@ -182,6 +179,9 @@ namespace osu.Game.Screens.Multi.Match public override bool OnExiting(IScreen next) { RoomManager?.PartRoom(); + + Mods.Value = Array.Empty(); + return base.OnExiting(next); } @@ -194,7 +194,7 @@ namespace osu.Game.Screens.Multi.Match var localBeatmap = e.NewValue?.Beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == e.NewValue.Beatmap.OnlineBeatmapID); Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); - SelectedMods.Value = e.NewValue?.RequiredMods ?? Enumerable.Empty(); + Mods.Value = e.NewValue?.RequiredMods?.ToArray() ?? Array.Empty(); if (e.NewValue?.Ruleset != null) Ruleset.Value = e.NewValue.Ruleset; } @@ -207,7 +207,7 @@ namespace osu.Game.Screens.Multi.Match if (Beatmap.Value != beatmapManager.DefaultBeatmap) return; - if (Beatmap.Value == null) + if (CurrentItem.Value == null) return; // Try to retrieve the corresponding local beatmap @@ -222,8 +222,6 @@ namespace osu.Game.Screens.Multi.Match private void onStart() { - Beatmap.Value.Mods.Value = SelectedMods.Value.ToArray(); - switch (type.Value) { default: diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 5e019a7b3a..155665e0d5 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -248,6 +248,7 @@ namespace osu.Game.Screens.Multi if (screenStack.CurrentScreen is MatchSubScreen) { var track = Beatmap.Value.Track; + if (track != null) { track.Looping = true; @@ -276,7 +277,7 @@ namespace osu.Game.Screens.Multi updatePollingRate(isIdle.Value); - if (screenStack.CurrentScreen == null) + if (screenStack.CurrentScreen == null && this.IsCurrentScreen()) this.Exit(); } diff --git a/osu.Game/Screens/Multi/MultiplayerComposite.cs b/osu.Game/Screens/Multi/MultiplayerComposite.cs index da6bba7865..8c09d576ff 100644 --- a/osu.Game/Screens/Multi/MultiplayerComposite.cs +++ b/osu.Game/Screens/Multi/MultiplayerComposite.cs @@ -16,8 +16,8 @@ namespace osu.Game.Screens.Multi [Resolved(typeof(Room))] protected Bindable RoomID { get; private set; } - [Resolved(typeof(Room))] - protected Bindable Name { get; private set; } + [Resolved(typeof(Room), nameof(Room.Name))] + protected Bindable RoomName { get; private set; } [Resolved(typeof(Room))] protected Bindable Host { get; private set; } diff --git a/osu.Game/Screens/Multi/MultiplayerSubScreen.cs b/osu.Game/Screens/Multi/MultiplayerSubScreen.cs index 65e501b114..ff94f63f01 100644 --- a/osu.Game/Screens/Multi/MultiplayerSubScreen.cs +++ b/osu.Game/Screens/Multi/MultiplayerSubScreen.cs @@ -3,22 +3,17 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Input.Bindings; using osu.Framework.Screens; using osu.Game.Graphics.Containers; -using osu.Game.Input.Bindings; namespace osu.Game.Screens.Multi { - public abstract class MultiplayerSubScreen : OsuScreen, IMultiplayerSubScreen, IKeyBindingHandler + public abstract class MultiplayerSubScreen : OsuScreen, IMultiplayerSubScreen { public override bool DisallowExternalBeatmapRulesetChanges => false; public virtual string ShortTitle => Title; - [Resolved(CanBeNull = true)] - protected OsuGame Game { get; private set; } - [Resolved(CanBeNull = true)] protected IRoomManager RoomManager { get; private set; } @@ -56,21 +51,6 @@ namespace osu.Game.Screens.Multi this.MoveToX(-200, WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint); } - public override bool OnPressed(GlobalAction action) - { - if (!this.IsCurrentScreen()) return false; - - if (action == GlobalAction.Back) - { - this.Exit(); - return true; - } - - return false; - } - - public bool OnReleased(GlobalAction action) => action == GlobalAction.Back; - public override string ToString() => Title; } } diff --git a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs index d5b8f1f0c8..88c6fc5e2e 100644 --- a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs +++ b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.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.Diagnostics; using System.Linq; using System.Threading; @@ -14,7 +13,6 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Multiplayer; using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Multi.Ranking; using osu.Game.Screens.Play; @@ -37,9 +35,6 @@ namespace osu.Game.Screens.Multi.Play [Resolved] private IBindable ruleset { get; set; } - [Resolved] - private Bindable> selectedMods { get; set; } - public TimeshiftPlayer(PlaylistItem playlistItem) { this.playlistItem = playlistItem; @@ -61,7 +56,7 @@ namespace osu.Game.Screens.Multi.Play if (ruleset.Value.ID != playlistItem.Ruleset.ID) throw new InvalidOperationException("Current Ruleset does not match PlaylistItem's Ruleset"); - if (!playlistItem.RequiredMods.All(m => selectedMods.Value.Contains(m))) + if (!playlistItem.RequiredMods.All(m => Mods.Value.Any(m.Equals))) throw new InvalidOperationException("Current Mods do not match PlaylistItem's RequiredMods"); var req = new CreateRoomScoreRequest(roomId.Value ?? 0, playlistItem.ID); diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index 385cbe20e5..6f473aaafa 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -171,7 +171,7 @@ namespace osu.Game.Screens.Multi /// /// Adds a to the list of available rooms. /// - /// The to add.< + /// The to add. private void addRoom(Room room) { var existing = rooms.FirstOrDefault(e => e.RoomID.Value == room.RoomID.Value); diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index e0a25deecf..9d53e43b80 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.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 Microsoft.EntityFrameworkCore.Internal; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -14,6 +15,7 @@ using osu.Game.Input.Bindings; using osu.Game.Rulesets; using osu.Game.Screens.Menu; using osu.Game.Overlays; +using osu.Game.Rulesets.Mods; namespace osu.Game.Screens { @@ -57,11 +59,15 @@ namespace osu.Game.Screens private SampleChannel sampleExit; + protected virtual bool PlayResumeSound => true; + public virtual float BackgroundParallaxAmount => 1; - public Bindable Beatmap { get; set; } + public Bindable Beatmap { get; private set; } - public Bindable Ruleset { get; set; } + public Bindable Ruleset { get; private set; } + + public Bindable> Mods { get; private set; } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { @@ -69,6 +75,7 @@ namespace osu.Game.Screens Beatmap = screenDependencies.Beatmap; Ruleset = screenDependencies.Ruleset; + Mods = screenDependencies.Mods; return base.CreateChildDependencies(screenDependencies); } @@ -112,7 +119,8 @@ namespace osu.Game.Screens public override void OnResuming(IScreen last) { - sampleExit?.Play(); + if (PlayResumeSound) + sampleExit?.Play(); applyArrivingDefaults(true); base.OnResuming(last); diff --git a/osu.Game/Screens/OsuScreenDependencies.cs b/osu.Game/Screens/OsuScreenDependencies.cs index 84e5de76de..115f4b7e1a 100644 --- a/osu.Game/Screens/OsuScreenDependencies.cs +++ b/osu.Game/Screens/OsuScreenDependencies.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; namespace osu.Game.Screens { @@ -14,27 +16,32 @@ namespace osu.Game.Screens public Bindable Ruleset { get; } + public Bindable> Mods { get; } + public OsuScreenDependencies(bool requireLease, IReadOnlyDependencyContainer parent) : base(parent) { if (requireLease) { Beatmap = parent.Get>()?.GetBoundCopy(); + if (Beatmap == null) - { Cache(Beatmap = parent.Get>().BeginLease(false)); - } Ruleset = parent.Get>()?.GetBoundCopy(); + if (Ruleset == null) - { Cache(Ruleset = parent.Get>().BeginLease(true)); - } + + Mods = parent.Get>>()?.GetBoundCopy(); + if (Mods == null) + Cache(Mods = parent.Get>>().BeginLease(true)); } else { Beatmap = (parent.Get>() ?? parent.Get>()).GetBoundCopy(); Ruleset = (parent.Get>() ?? parent.Get>()).GetBoundCopy(); + Mods = (parent.Get>>() ?? parent.Get>>()).GetBoundCopy(); } } } diff --git a/osu.Game/Screens/Play/Break/BreakInfoLine.cs b/osu.Game/Screens/Play/Break/BreakInfoLine.cs index 4b07405812..70e7b8f297 100644 --- a/osu.Game/Screens/Play/Break/BreakInfoLine.cs +++ b/osu.Game/Screens/Play/Break/BreakInfoLine.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 osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; @@ -60,7 +62,13 @@ namespace osu.Game.Screens.Play.Break valueText.Text = newText; } - protected virtual string Format(T count) => count.ToString(); + protected virtual string Format(T count) + { + if (count is Enum countEnum) + return countEnum.GetDescription(); + + return count.ToString(); + } [BackgroundDependencyLoader] private void load(OsuColour colours) diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index 3efcfa0f65..b1948d02d5 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -8,7 +8,7 @@ namespace osu.Game.Screens.Play { /// /// A clock which is used for gameplay elements that need to follow audio time 1:1. - /// Exposed via DI by . + /// Exposed via DI by . /// /// The main purpose of this clock is to stop components using it from accidentally processing the main /// , as this should only be done once to ensure accuracy. diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 5ded375259..c151e598f7 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using osu.Framework; @@ -23,6 +24,7 @@ namespace osu.Game.Screens.Play public class GameplayClockContainer : Container { private readonly WorkingBeatmap beatmap; + private readonly IReadOnlyList mods; /// /// The original source (usually a 's track). @@ -58,9 +60,10 @@ namespace osu.Game.Screens.Play private readonly FramedOffsetClock platformOffsetClock; - public GameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime) + public GameplayClockContainer(WorkingBeatmap beatmap, IReadOnlyList mods, double gameplayStartTime) { this.beatmap = beatmap; + this.mods = mods; this.gameplayStartTime = gameplayStartTime; RelativeSizeAxes = Axes.Both; @@ -92,8 +95,7 @@ namespace osu.Game.Screens.Play UserPlaybackRate.ValueChanged += _ => updateRate(); - Seek(Math.Min(0, gameplayStartTime - beatmap.BeatmapInfo.AudioLeadIn)); - adjustableClock.ProcessFrame(); + Seek(Math.Min(-beatmap.BeatmapInfo.AudioLeadIn, gameplayStartTime)); } public void Restart() @@ -107,11 +109,8 @@ namespace osu.Game.Screens.Play adjustableClock.ChangeSource(sourceClock); updateRate(); - this.Delay(750).Schedule(() => - { - if (!IsPaused.Value) - Start(); - }); + if (!IsPaused.Value) + Start(); }); }); } @@ -120,7 +119,7 @@ namespace osu.Game.Screens.Play { // Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time // This accounts for the audio clock source potentially taking time to enter a completely stopped state - adjustableClock.Seek(adjustableClock.CurrentTime); + Seek(GameplayClock.CurrentTime); adjustableClock.Start(); IsPaused.Value = false; } @@ -137,6 +136,9 @@ namespace osu.Game.Screens.Play // remove the offset component here because most of the time we want the seek to be aligned to gameplay, not the audio track. // we may want to consider reversing the application of offsets in the future as it may feel more correct. adjustableClock.Seek(time - totalOffset); + + // manually process frame to ensure GameplayClock is correctly updated after a seek. + userOffsetClock.ProcessFrame(); } public void Stop() @@ -170,7 +172,7 @@ namespace osu.Game.Screens.Play else sourceClock.Rate = UserPlaybackRate.Value; - foreach (var mod in beatmap.Mods.Value.OfType()) + foreach (var mod in mods.OfType()) mod.ApplyToClock(sourceClock); } } diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 2fac8de799..456fb4faf9 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -19,6 +19,7 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Input.Bindings; using Humanizer; +using osu.Framework.Graphics.Effects; namespace osu.Game.Screens.Play { @@ -221,6 +222,7 @@ namespace osu.Game.Screens.Play else selectionIndex--; return true; + case Key.Down: if (selectionIndex == -1 || selectionIndex == InternalButtons.Count - 1) selectionIndex = 0; @@ -240,6 +242,7 @@ namespace osu.Game.Screens.Play case GlobalAction.Back: BackAction.Invoke(); return true; + case GlobalAction.Select: SelectAction.Invoke(); return true; diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 6883f246e4..91c14591b1 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -24,6 +25,8 @@ namespace osu.Game.Screens.Play.HUD { public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + public readonly Bindable IsPaused = new Bindable(); + private readonly Button button; public Action Action @@ -50,7 +53,8 @@ namespace osu.Game.Screens.Play.HUD button = new Button { HoverGained = () => text.FadeIn(500, Easing.OutQuint), - HoverLost = () => text.FadeOut(500, Easing.OutQuint) + HoverLost = () => text.FadeOut(500, Easing.OutQuint), + IsPaused = { BindTarget = IsPaused } } }; AutoSizeAxes = Axes.Both; @@ -70,6 +74,11 @@ namespace osu.Game.Screens.Play.HUD return base.OnMouseMove(e); } + public bool PauseOnFocusLost + { + set => button.PauseOnFocusLost = value; + } + protected override void Update() { base.Update(); @@ -88,13 +97,17 @@ namespace osu.Game.Screens.Play.HUD private CircularProgress circularProgress; private Circle overlayCircle; + public readonly Bindable IsPaused = new Bindable(); + protected override bool AllowMultipleFires => true; public Action HoverGained; public Action HoverLost; + private readonly IBindable gameActive = new Bindable(true); + [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, Framework.Game game) { Size = new Vector2(60); @@ -129,12 +142,20 @@ namespace osu.Game.Screens.Play.HUD Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(15), - Icon = FontAwesome.Solid.TimesCircle + Icon = FontAwesome.Solid.Times }, } }; bind(); + + gameActive.BindTo(game.IsActive); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + gameActive.BindValueChanged(_ => updateActive(), true); } private void bind() @@ -184,6 +205,31 @@ namespace osu.Game.Screens.Play.HUD base.OnHoverLost(e); } + private bool pauseOnFocusLost = true; + + public bool PauseOnFocusLost + { + set + { + if (pauseOnFocusLost == value) + return; + + pauseOnFocusLost = value; + if (IsLoaded) + updateActive(); + } + } + + private void updateActive() + { + if (!pauseOnFocusLost || IsPaused.Value) return; + + if (gameActive.Value) + AbortConfirm(); + else + BeginConfirm(); + } + public bool OnPressed(GlobalAction action) { switch (action) diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 2c1293833f..0f3c92a962 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -18,15 +18,17 @@ using osu.Game.Graphics; namespace osu.Game.Screens.Play.HUD { - public class ModDisplay : Container, IHasCurrentValue> + public class ModDisplay : Container, IHasCurrentValue> { private const int fade_duration = 1000; public bool DisplayUnrankedText = true; - private readonly Bindable> current = new Bindable>(); + public bool AllowExpand = true; - public Bindable> Current + private readonly Bindable> current = new Bindable>(); + + public Bindable> Current { get => current; set @@ -68,6 +70,7 @@ namespace osu.Game.Screens.Play.HUD Current.ValueChanged += mods => { iconsContainer.Clear(); + foreach (Mod mod in mods.NewValue) { iconsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.6f) }); @@ -87,7 +90,9 @@ namespace osu.Game.Screens.Play.HUD protected override void LoadComplete() { base.LoadComplete(); + appearTransform(); + iconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint); } private void appearTransform() @@ -97,14 +102,17 @@ namespace osu.Game.Screens.Play.HUD else unrankedText.Hide(); - iconsContainer.FinishTransforms(); - iconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint); expand(); + using (iconsContainer.BeginDelayedSequence(1200)) contract(); } - private void expand() => iconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint); + private void expand() + { + if (AllowExpand) + iconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint); + } private void contract() => iconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, Easing.OutQuint); diff --git a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs index 8f09c2b2bf..315bc27a79 100644 --- a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs @@ -6,6 +6,7 @@ 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.Game.Graphics; using osu.Game.Rulesets.Judgements; using osuTK; diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index a7b7f96e7a..017bf70133 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -2,16 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.HUD; @@ -34,6 +35,10 @@ namespace osu.Game.Screens.Play public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; + private readonly ScoreProcessor scoreProcessor; + private readonly DrawableRuleset drawableRuleset; + private readonly IReadOnlyList mods; + private Bindable showHud; private readonly Container visibilityContainer; private readonly BindableBool replayLoaded = new BindableBool(); @@ -42,8 +47,12 @@ namespace osu.Game.Screens.Play public Action RequestSeek; - public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, WorkingBeatmap working) + public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods) { + this.scoreProcessor = scoreProcessor; + this.drawableRuleset = drawableRuleset; + this.mods = mods; + RelativeSizeAxes = Axes.Both; Children = new Drawable[] @@ -88,20 +97,21 @@ namespace osu.Game.Screens.Play } } }; + } + [BackgroundDependencyLoader(true)] + private void load(OsuConfigManager config, NotificationOverlay notificationOverlay) + { BindProcessor(scoreProcessor); BindDrawableRuleset(drawableRuleset); Progress.Objects = drawableRuleset.Objects; Progress.AllowSeeking = drawableRuleset.HasReplayLoaded.Value; Progress.RequestSeek = time => RequestSeek(time); + Progress.ReferenceClock = drawableRuleset.FrameStableClock; - ModDisplay.Current.BindTo(working.Mods); - } + ModDisplay.Current.Value = mods; - [BackgroundDependencyLoader(true)] - private void load(OsuConfigManager config, NotificationOverlay notificationOverlay) - { showHud = config.GetBindable(OsuSetting.ShowInterface); showHud.ValueChanged += visible => visibilityContainer.FadeTo(visible.NewValue ? 1 : 0, duration); showHud.TriggerChange(); @@ -121,8 +131,7 @@ namespace osu.Game.Screens.Play { base.LoadComplete(); - replayLoaded.ValueChanged += replayLoadedValueChanged; - replayLoaded.TriggerChange(); + replayLoaded.BindValueChanged(replayLoadedValueChanged, true); } private void replayLoadedValueChanged(ValueChangedEvent e) diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/KeyCounter.cs index 0626c40334..88a62ac8d4 100644 --- a/osu.Game/Screens/Play/KeyCounter.cs +++ b/osu.Game/Screens/Play/KeyCounter.cs @@ -52,6 +52,7 @@ namespace osu.Game.Screens.Play { isLit = value; updateGlowSprite(value); + if (value && IsCounting) { CountPresses++; diff --git a/osu.Game/Screens/Play/KeyCounterMouse.cs b/osu.Game/Screens/Play/KeyCounterMouse.cs index 13dbe40a8b..95fa58e5c0 100644 --- a/osu.Game/Screens/Play/KeyCounterMouse.cs +++ b/osu.Game/Screens/Play/KeyCounterMouse.cs @@ -25,8 +25,10 @@ namespace osu.Game.Screens.Play { default: return button.ToString(); + case MouseButton.Left: return @"M1"; + case MouseButton.Right: return @"M2"; } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0eebefec86..30214d1b9c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -39,12 +40,15 @@ namespace osu.Game.Screens.Play public override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered; + /// + /// Whether gameplay should pause when the game window focus is lost. + /// + protected virtual bool PauseOnFocusLost => true; + public Action RestartRequested; public bool HasFailed { get; private set; } - public bool PauseOnFocusLost { get; set; } = true; - private Bindable mouseWheelDisabled; private readonly Bindable storyboardReplacesBackground = new Bindable(); @@ -69,6 +73,10 @@ namespace osu.Game.Screens.Play protected GameplayClockContainer GameplayClockContainer { get; private set; } + [Cached] + [Cached(Type = typeof(IBindable>))] + protected new readonly Bindable> Mods = new Bindable>(Array.Empty()); + private readonly bool allowPause; private readonly bool showResults; @@ -88,6 +96,8 @@ namespace osu.Game.Screens.Play { this.api = api; + Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray(); + WorkingBeatmap working = loadBeatmap(); if (working == null) @@ -99,10 +109,12 @@ namespace osu.Game.Screens.Play showStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); ScoreProcessor = DrawableRuleset.CreateScoreProcessor(); + ScoreProcessor.Mods.BindTo(Mods); + if (!ScoreProcessor.Mode.Disabled) config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); - InternalChild = GameplayClockContainer = new GameplayClockContainer(working, DrawableRuleset.GameplayStartTime); + InternalChild = GameplayClockContainer = new GameplayClockContainer(working, Mods.Value, DrawableRuleset.GameplayStartTime); GameplayClockContainer.Children = new[] { @@ -123,9 +135,13 @@ namespace osu.Game.Screens.Play }, // display the cursor above some HUD elements. DrawableRuleset.Cursor?.CreateProxy() ?? new Container(), - HUDOverlay = new HUDOverlay(ScoreProcessor, DrawableRuleset, working) + HUDOverlay = new HUDOverlay(ScoreProcessor, DrawableRuleset, Mods.Value) { - HoldToQuit = { Action = performUserRequestedExit }, + HoldToQuit = + { + Action = performUserRequestedExit, + IsPaused = { BindTarget = GameplayClockContainer.IsPaused } + }, PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } }, KeyCounter = { Visible = { BindTarget = DrawableRuleset.HasReplayLoaded } }, RequestSeek = GameplayClockContainer.Seek, @@ -160,6 +176,8 @@ namespace osu.Game.Screens.Play } }; + DrawableRuleset.HasReplayLoaded.BindValueChanged(e => HUDOverlay.HoldToQuit.PauseOnFocusLost = !e.NewValue && PauseOnFocusLost, true); + // bind clock into components that require it DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); @@ -170,7 +188,7 @@ namespace osu.Game.Screens.Play ScoreProcessor.AllJudged += onCompletion; ScoreProcessor.Failed += onFail; - foreach (var mod in Beatmap.Value.Mods.Value.OfType()) + foreach (var mod in Mods.Value.OfType()) mod.ApplyToScoreProcessor(ScoreProcessor); } @@ -192,7 +210,7 @@ namespace osu.Game.Screens.Play try { - DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(working); + DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(working, Mods.Value); } catch (BeatmapInvalidForRulesetException) { @@ -200,7 +218,7 @@ namespace osu.Game.Screens.Play // let's try again forcing the beatmap's ruleset. ruleset = beatmap.BeatmapInfo.Ruleset; rulesetInstance = ruleset.CreateInstance(); - DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(Beatmap.Value); + DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(Beatmap.Value, Mods.Value); } if (!DrawableRuleset.Objects.Any()) @@ -231,6 +249,10 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; sampleRestart?.Play(); + + // if a restart has been requested, cancel any pending completion (user has shown intent to restart). + onCompletionEvent = null; + ValidForResume = false; RestartRequested?.Invoke(); this.Exit(); @@ -271,7 +293,7 @@ namespace osu.Game.Screens.Play { Beatmap = Beatmap.Value.BeatmapInfo, Ruleset = ruleset, - Mods = Beatmap.Value.Mods.Value.ToArray(), + Mods = Mods.Value.ToArray(), User = api.LocalUser.Value, }; @@ -325,7 +347,7 @@ namespace osu.Game.Screens.Play private bool onFail() { - if (Beatmap.Value.Mods.Value.OfType().Any(m => !m.AllowFail)) + if (Mods.Value.OfType().Any(m => !m.AllowFail)) return false; GameplayClockContainer.Stop(); @@ -379,15 +401,6 @@ namespace osu.Game.Screens.Play // already resuming && !IsResuming; - protected override void Update() - { - base.Update(); - - // eagerly pause when we lose window focus (if we are locally playing). - if (PauseOnFocusLost && !Game.IsActive.Value) - Pause(); - } - public void Pause() { if (!canPause) return; @@ -405,8 +418,9 @@ namespace osu.Game.Screens.Play IsResuming = true; PauseOverlay.Hide(); - // time-based conditions may allow instant resume. - if (GameplayClockContainer.GameplayClock.CurrentTime < Beatmap.Value.Beatmap.HitObjects.First().StartTime) + // breaks and time-based conditions may allow instant resume. + double time = GameplayClockContainer.GameplayClock.CurrentTime; + if (Beatmap.Value.Beatmap.Breaks.Any(b => b.Contains(time)) || time < Beatmap.Value.Beatmap.HitObjects.First().StartTime) completeResume(); else DrawableRuleset.RequestResume(completeResume); diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index e9ee5d3fa8..908a95c18b 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; @@ -14,8 +15,10 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.Menu; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.PlayerSettings; @@ -32,7 +35,7 @@ namespace osu.Game.Screens.Play private Player player; - private Container content; + private LogoTrackingContainer content; private BeatmapMetadataDisplay info; @@ -41,6 +44,8 @@ namespace osu.Game.Screens.Play public override bool DisallowExternalBeatmapRulesetChanges => true; + protected override bool PlayResumeSound => false; + private Task loadTask; private InputManager inputManager; @@ -59,35 +64,34 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load() { - InternalChild = content = new Container + InternalChild = (content = new LogoTrackingContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + }).WithChildren(new Drawable[] + { + info = new BeatmapMetadataDisplay(Beatmap.Value, Mods.Value, content.LogoFacade) { - info = new BeatmapMetadataDisplay(Beatmap.Value) + Alpha = 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Margin = new MarginPadding(25), + Children = new PlayerSettingsGroup[] { - Alpha = 0, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new FillFlowContainer - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 20), - Margin = new MarginPadding(25), - Children = new PlayerSettingsGroup[] - { - VisualSettings = new VisualSettings(), - new InputSettings() - } + VisualSettings = new VisualSettings(), + new InputSettings() } } - }; + }); loadNewPlayer(); } @@ -127,6 +131,9 @@ namespace osu.Game.Screens.Play private void contentOut() { + // Ensure the logo is no longer tracking before we scale the content + content.StopTracking(); + content.ScaleTo(0.7f, 300, Easing.InQuint); content.FadeOut(250); } @@ -148,11 +155,27 @@ namespace osu.Game.Screens.Play { base.LogoArriving(logo, resuming); - logo.ScaleTo(new Vector2(0.15f), 300, Easing.In); - logo.MoveTo(new Vector2(0.5f), 300, Easing.In); + const double duration = 300; + + if (!resuming) + { + logo.MoveTo(new Vector2(0.5f), duration, Easing.In); + } + + logo.ScaleTo(new Vector2(0.15f), duration, Easing.In); logo.FadeIn(350); - logo.Delay(resuming ? 0 : 500).MoveToOffset(new Vector2(0, -0.24f), 500, Easing.InOutExpo); + Scheduler.AddDelayed(() => + { + if (this.IsCurrentScreen()) + content.StartTracking(logo, resuming ? 0 : 500, Easing.InOutExpo); + }, resuming ? 0 : 500); + } + + protected override void LogoExiting(OsuLogo logo) + { + base.LogoExiting(logo); + content.StopTracking(); } protected override void LoadComplete() @@ -164,7 +187,7 @@ namespace osu.Game.Screens.Play private ScheduledDelegate pushDebounce; protected VisualSettings VisualSettings; - // Hhere because IsHovered will not update unless we do so. + // Here because IsHovered will not update unless we do so. public override bool HandlePositionalInput => true; private bool readyForPush => player.LoadState == LoadState.Ready && IsHovered && GetContainingInputManager()?.DraggedDrawable == null; @@ -299,9 +322,10 @@ namespace osu.Game.Screens.Play } private readonly WorkingBeatmap beatmap; + private readonly IReadOnlyList mods; + private readonly Drawable facade; private LoadingAnimation loading; private Sprite backgroundSprite; - private ModDisplay modDisplay; public bool Loading { @@ -320,9 +344,11 @@ namespace osu.Game.Screens.Play } } - public BeatmapMetadataDisplay(WorkingBeatmap beatmap) + public BeatmapMetadataDisplay(WorkingBeatmap beatmap, IReadOnlyList mods, Drawable facade) { this.beatmap = beatmap; + this.mods = mods; + this.facade = facade; } [BackgroundDependencyLoader] @@ -339,14 +365,20 @@ namespace osu.Game.Screens.Play Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, Direction = FillDirection.Vertical, - Children = new Drawable[] + Children = new[] { + facade.With(d => + { + d.Anchor = Anchor.TopCentre; + d.Origin = Anchor.TopCentre; + }), new OsuSpriteText { Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)), Font = OsuFont.GetFont(size: 36, italics: true), Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, + Margin = new MarginPadding { Top = 15 }, }, new OsuSpriteText { @@ -403,7 +435,7 @@ namespace osu.Game.Screens.Play Origin = Anchor.TopCentre, AutoSizeAxes = Axes.Both, Margin = new MarginPadding { Top = 20 }, - Current = beatmap.Mods + Current = { Value = mods } } }, } diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 738877232d..e3c56e1c2c 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -38,6 +38,10 @@ namespace osu.Game.Screens.Play public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; protected override bool BlockPositionalInput => false; + /// + /// Displays a skip overlay, giving the user the ability to skip forward. + /// + /// The time at which gameplay begins to appear. public SkipOverlay(double startTime) { this.startTime = startTime; @@ -87,16 +91,21 @@ namespace osu.Game.Screens.Play }; } - private const double skip_required_cutoff = 3000; + /// + /// Duration before gameplay start time required before skip button displays. + /// + private const double skip_buffer = 1000; + private const double fade_time = 300; - private double beginFadeTime => startTime - skip_required_cutoff - fade_time; + private double beginFadeTime => startTime - fade_time; protected override void LoadComplete() { base.LoadComplete(); - if (startTime < skip_required_cutoff) + // skip is not required if there is no extra "empty" time to skip. + if (Clock.CurrentTime > beginFadeTime - skip_buffer) { Alpha = 0; Expire(); @@ -107,7 +116,7 @@ namespace osu.Game.Screens.Play using (BeginAbsoluteSequence(beginFadeTime)) this.FadeOut(fade_time); - button.Action = () => RequestSeek?.Invoke(startTime - skip_required_cutoff - fade_time); + button.Action = () => RequestSeek?.Invoke(beginFadeTime); displayTime = Time.Current; @@ -174,6 +183,7 @@ namespace osu.Game.Screens.Play using (BeginDelayedSequence(1000)) scheduledHide = Schedule(() => State = Visibility.Hidden); break; + case Visibility.Hidden: this.FadeOut(1000, Easing.OutExpo); break; diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index 94b25e04a3..d478454f00 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -10,6 +10,7 @@ using osu.Game.Graphics; using osu.Framework.Allocation; using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Timing; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.UI; @@ -55,7 +56,9 @@ namespace osu.Game.Screens.Play private readonly BindableBool replayLoaded = new BindableBool(); - private GameplayClock gameplayClock; + public IClock ReferenceClock; + + private IClock gameplayClock; [BackgroundDependencyLoader(true)] private void load(OsuColour colours, GameplayClock clock) @@ -154,10 +157,12 @@ namespace osu.Game.Screens.Play if (objects == null) return; - double position = gameplayClock?.CurrentTime ?? Time.Current; - double progress = Math.Min(1, (position - firstHitTime) / (lastHitTime - firstHitTime)); + double gameplayTime = gameplayClock?.CurrentTime ?? Time.Current; + double frameStableTime = ReferenceClock?.CurrentTime ?? gameplayTime; - bar.CurrentTime = position; + double progress = Math.Min(1, (frameStableTime - firstHitTime) / (lastHitTime - firstHitTime)); + + bar.CurrentTime = gameplayTime; graph.Progress = (int)(graph.ColumnCount * progress); } } diff --git a/osu.Game/Screens/Play/SongProgressBar.cs b/osu.Game/Screens/Play/SongProgressBar.cs index 2e7d452fe7..dd7b5826d5 100644 --- a/osu.Game/Screens/Play/SongProgressBar.cs +++ b/osu.Game/Screens/Play/SongProgressBar.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Framework.MathUtils; namespace osu.Game.Screens.Play { @@ -107,9 +108,17 @@ namespace osu.Game.Screens.Play protected override void UpdateValue(float value) { - var xFill = value * UsableWidth; - fill.Width = xFill; - handleBase.X = xFill; + // handled in update + } + + protected override void Update() + { + base.Update(); + + float newX = (float)Interpolation.Lerp(handleBase.X, NormalizedValue * UsableWidth, MathHelper.Clamp(Time.Elapsed / 40, 0, 1)); + + fill.Width = newX; + handleBase.X = newX; } protected override void OnUserChange(double value) => OnSeek?.Invoke(value); diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index d10034d552..5b7a9574b6 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -169,6 +169,7 @@ namespace osu.Game.Screens.Play var max = values.Max(); float step = values.Length / (float)ColumnCount; + for (float i = 0; i < values.Length; i += step) { newValues.Add((float)values[(int)i] / max); diff --git a/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs index 043bf55d2b..fab227c7f4 100644 --- a/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs +++ b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs @@ -180,6 +180,7 @@ namespace osu.Game.Screens.Ranking.Pages scoreCounter.Increment(Score.TotalScore); int delay = 0; + foreach (var s in statisticsContainer.Children) { s.FadeOut() @@ -336,6 +337,7 @@ namespace osu.Game.Screens.Ranking.Pages versionMapper.Colour = colours.Gray8; var creator = beatmap.Metadata.Author?.Username; + if (!string.IsNullOrEmpty(creator)) { versionMapper.Text = $"mapped by {creator}"; diff --git a/osu.Game/Screens/Ranking/ResultModeButton.cs b/osu.Game/Screens/Ranking/ResultModeButton.cs index 109d0195db..1383511241 100644 --- a/osu.Game/Screens/Ranking/ResultModeButton.cs +++ b/osu.Game/Screens/Ranking/ResultModeButton.cs @@ -6,6 +6,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osuTK; diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index dafb4c0aad..bebeaee00a 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -8,6 +8,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Sprites; using osu.Framework.Screens; using osu.Game.Graphics.Containers; diff --git a/osu.Game/Screens/Ranking/ResultsPage.cs b/osu.Game/Screens/Ranking/ResultsPage.cs index 1b17dda563..8776c599dd 100644 --- a/osu.Game/Screens/Ranking/ResultsPage.cs +++ b/osu.Game/Screens/Ranking/ResultsPage.cs @@ -5,6 +5,7 @@ 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; using osu.Game.Graphics; diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index d7240a40ad..63ad3b6ab2 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -257,6 +257,7 @@ namespace osu.Game.Screens.Select select(beatmap); return; + case CarouselBeatmapSet set: if (skipDifficulties) select(set); @@ -292,6 +293,7 @@ namespace osu.Game.Screens.Select if (RandomAlgorithm.Value == RandomSelectAlgorithm.RandomPermutation) { var notYetVisitedSets = visibleSets.Except(previouslyVisitedRandomSets).ToList(); + if (!notYetVisitedSets.Any()) { previouslyVisitedRandomSets.RemoveAll(s => visibleSets.Contains(s)); @@ -394,13 +396,16 @@ namespace osu.Game.Screens.Select case Key.Up: direction = -1; break; + case Key.Down: direction = 1; break; + case Key.Left: direction = -1; skipDifficulties = true; break; + case Key.Right: direction = 1; skipDifficulties = true; @@ -465,8 +470,10 @@ namespace osu.Game.Screens.Select case LoadState.NotLoaded: LoadComponentAsync(item); break; + case LoadState.Loading: break; + default: scrollableContent.Add(item); break; @@ -557,6 +564,7 @@ namespace osu.Game.Screens.Select set.MoveToX(set.Item.State.Value == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); set.MoveToY(currentY, 750, Easing.OutExpo); break; + case DrawableCarouselBeatmap beatmap: if (beatmap.Item.State.Value == CarouselItemState.Selected) scrollTarget = currentY + beatmap.DrawHeight / 2 - DrawHeight / 2; diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index d32387c1d3..1508de2730 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -22,9 +22,11 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Select @@ -309,12 +311,12 @@ namespace osu.Game.Screens.Select try { // Try to get the beatmap with the user's ruleset - playableBeatmap = beatmap.GetPlayableBeatmap(ruleset); + playableBeatmap = beatmap.GetPlayableBeatmap(ruleset, Array.Empty()); } catch (BeatmapInvalidForRulesetException) { // Can't be converted to the user's ruleset, so use the beatmap's own ruleset - playableBeatmap = beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset); + playableBeatmap = beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset, Array.Empty()); } labels.AddRange(playableBeatmap.GetStatistics().Select(s => new InfoLabel(s))); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 8e7ea8f964..5c334b126c 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -38,10 +38,13 @@ namespace osu.Game.Screens.Select.Carousel default: case SortMode.Artist: return string.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist, StringComparison.InvariantCultureIgnoreCase); + case SortMode.Title: return string.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title, StringComparison.InvariantCultureIgnoreCase); + case SortMode.Author: return string.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username, StringComparison.InvariantCultureIgnoreCase); + case SortMode.Difficulty: return BeatmapSet.MaxStarDifficulty.CompareTo(otherSet.BeatmapSet.MaxStarDifficulty); } diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index 5d8f4f0ec6..6ebd2d41cc 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -66,6 +66,7 @@ namespace osu.Game.Screens.Select.Carousel case CarouselItemState.NotSelected: InternalChildren.ForEach(c => c.State.Value = CarouselItemState.Collapsed); break; + case CarouselItemState.Selected: InternalChildren.ForEach(c => { diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index 67e8282b76..045c682dc3 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -69,6 +69,7 @@ namespace osu.Game.Screens.Select.Carousel case CarouselItemState.Selected: updateSelected(item); break; + case CarouselItemState.NotSelected: case CarouselItemState.Collapsed: attemptSelection(); diff --git a/osu.Game/Screens/Select/Carousel/CarouselItem.cs b/osu.Game/Screens/Select/Carousel/CarouselItem.cs index a0f5969b3c..79c1a4cb6b 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselItem.cs @@ -31,8 +31,6 @@ namespace osu.Game.Screens.Select.Carousel } } - private int creationOrder; - protected CarouselItem() { DrawableRepresentation = new Lazy(CreateDrawableRepresentation); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 3c1b7cc831..f1d6343e72 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -7,6 +7,7 @@ using osu.Framework.Audio.Sample; 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.Input.Events; using osu.Framework.MathUtils; @@ -106,6 +107,7 @@ namespace osu.Game.Screens.Select.Carousel case CarouselItemState.NotSelected: Deselected(); break; + case CarouselItemState.Selected: Selected(); break; diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index 03b9826e9b..6ba29751b0 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using osuTK; using osuTK.Graphics; -using osuTK.Input; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -20,9 +19,6 @@ namespace osu.Game.Screens.Select { private readonly Box modeLight; - private const float play_song_select_button_width = 100; - private const float play_song_select_button_height = 50; - public const float HEIGHT = 50; public const int TRANSITION_LENGTH = 300; @@ -33,57 +29,36 @@ namespace osu.Game.Screens.Select private readonly FillFlowContainer buttons; - /// Text on the button. - /// Colour of the button. - /// Hotkey of the button. - /// Action the button does. - /// - /// Higher depth to be put on the left, and lower to be put on the right. - /// Notice this is different to ! - /// - public void AddButton(string text, Color4 colour, Action action, Key? hotkey = null, float depth = 0) - { - var button = new FooterButton - { - Text = text, - Height = play_song_select_button_height, - Width = play_song_select_button_width, - Depth = depth, - SelectedColour = colour, - DeselectedColour = colour.Opacity(0.5f), - Hotkey = hotkey, - Hovered = updateModeLight, - HoverLost = updateModeLight, - Action = action, - }; - - buttons.Add(button); - buttons.SetLayoutPosition(button, -depth); - } - private readonly List overlays = new List(); - /// Text on the button. - /// Colour of the button. - /// Hotkey of the button. + /// THe button to be added. /// The to be toggled by this button. - /// - /// Higher depth to be put on the left, and lower to be put on the right. - /// Notice this is different to ! - /// - public void AddButton(string text, Color4 colour, OverlayContainer overlay, Key? hotkey = null, float depth = 0) + public void AddButton(FooterButton button, OverlayContainer overlay) { overlays.Add(overlay); - AddButton(text, colour, () => + button.Action = () => showOverlay(overlay); + + AddButton(button); + } + + /// Button to be added. + public void AddButton(FooterButton button) + { + button.Hovered = updateModeLight; + button.HoverLost = updateModeLight; + + buttons.Add(button); + } + + private void showOverlay(OverlayContainer overlay) + { + foreach (var o in overlays) { - foreach (var o in overlays) - { - if (o == overlay) - o.ToggleVisibility(); - else - o.Hide(); - } - }, hotkey, depth); + if (o == overlay) + o.ToggleVisibility(); + else + o.Hide(); + } } private void updateModeLight() => modeLight.FadeColour(buttons.FirstOrDefault(b => b.IsHovered)?.SelectedColour ?? Color4.Transparent, TRANSITION_LENGTH, Easing.OutQuint); diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index 9b98e344ce..e18a086a10 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -6,6 +6,7 @@ using osuTK; using osuTK.Graphics; using osuTK.Input; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; @@ -20,11 +21,11 @@ namespace osu.Game.Screens.Select public string Text { - get => spriteText?.Text; + get => SpriteText?.Text; set { - if (spriteText != null) - spriteText.Text = value; + if (SpriteText != null) + SpriteText.Text = value; } } @@ -53,7 +54,8 @@ namespace osu.Game.Screens.Select } } - private readonly SpriteText spriteText; + protected readonly Container TextContainer; + protected readonly SpriteText SpriteText; private readonly Box box; private readonly Box light; @@ -61,8 +63,18 @@ namespace osu.Game.Screens.Select public FooterButton() { + AutoSizeAxes = Axes.Both; Children = new Drawable[] { + TextContainer = new Container + { + Size = new Vector2(100, 50), + Child = SpriteText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }, box = new Box { RelativeSizeAxes = Axes.Both, @@ -78,11 +90,6 @@ namespace osu.Game.Screens.Select EdgeSmoothness = new Vector2(2, 0), RelativeSizeAxes = Axes.X, }, - spriteText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } }; } diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs new file mode 100644 index 0000000000..c96c5022c0 --- /dev/null +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -0,0 +1,60 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Screens.Play.HUD; +using osu.Game.Rulesets.Mods; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Screens.Select +{ + public class FooterButtonMods : FooterButton + { + public FooterButtonMods(Bindable> mods) + { + FooterModDisplay modDisplay; + + Add(new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Child = modDisplay = new FooterModDisplay + { + DisplayUnrankedText = false, + Scale = new Vector2(0.8f) + }, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Left = 70 } + }); + + if (mods != null) + modDisplay.Current = mods; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + SelectedColour = colours.Yellow; + DeselectedColour = SelectedColour.Opacity(0.5f); + Text = @"mods"; + Hotkey = Key.F1; + } + + private class FooterModDisplay : ModDisplay + { + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.Parent?.ReceivePositionalInputAt(screenSpacePos) ?? false; + + public FooterModDisplay() + { + AllowExpand = false; + } + } + } +} diff --git a/osu.Game/Screens/Select/FooterButtonOptions.cs b/osu.Game/Screens/Select/FooterButtonOptions.cs new file mode 100644 index 0000000000..c000d8a8c8 --- /dev/null +++ b/osu.Game/Screens/Select/FooterButtonOptions.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics; +using osuTK.Input; + +namespace osu.Game.Screens.Select +{ + public class FooterButtonOptions : FooterButton + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + SelectedColour = colours.Blue; + DeselectedColour = SelectedColour.Opacity(0.5f); + Text = @"options"; + Hotkey = Key.F3; + } + } +} diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs new file mode 100644 index 0000000000..14c9eb2035 --- /dev/null +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -0,0 +1,68 @@ +// 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.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK.Input; + +namespace osu.Game.Screens.Select +{ + public class FooterButtonRandom : FooterButton + { + private readonly SpriteText secondaryText; + private bool secondaryActive; + + public FooterButtonRandom() + { + TextContainer.Add(secondaryText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = @"rewind", + Alpha = 0 + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + SelectedColour = colours.Green; + DeselectedColour = SelectedColour.Opacity(0.5f); + Text = @"random"; + Hotkey = Key.F2; + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + secondaryActive = e.ShiftPressed; + updateText(); + return base.OnKeyDown(e); + } + + protected override bool OnKeyUp(KeyUpEvent e) + { + secondaryActive = e.ShiftPressed; + updateText(); + return base.OnKeyUp(e); + } + + private void updateText() + { + if (secondaryActive) + { + SpriteText.FadeOut(120, Easing.InQuad); + secondaryText.FadeIn(120, Easing.InQuad); + } + else + { + SpriteText.FadeIn(120, Easing.InQuad); + secondaryText.FadeOut(120, Easing.InQuad); + } + } + } +} diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index fa5dc4c1d1..c5fa9e2396 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; -using System.Linq; using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -26,9 +24,6 @@ namespace osu.Game.Screens.Select [Resolved(typeof(Room))] protected Bindable CurrentItem { get; private set; } - [Resolved] - private Bindable> selectedMods { get; set; } - [Resolved] private BeatmapManager beatmaps { get; set; } @@ -46,7 +41,7 @@ namespace osu.Game.Screens.Select RulesetID = Ruleset.Value.ID ?? 0 }; - item.RequiredMods.AddRange(SelectedMods.Value); + item.RequiredMods.AddRange(Mods.Value); Selected?.Invoke(item); @@ -65,11 +60,12 @@ namespace osu.Game.Screens.Select { Ruleset.Value = CurrentItem.Value.Ruleset; Beatmap.Value = beatmaps.GetWorkingBeatmap(CurrentItem.Value.Beatmap); - Beatmap.Value.Mods.Value = selectedMods.Value = CurrentItem.Value.RequiredMods ?? Enumerable.Empty(); + Mods.Value = CurrentItem.Value.RequiredMods?.ToArray() ?? Array.Empty(); } Beatmap.Disabled = true; Ruleset.Disabled = true; + Mods.Disabled = true; return false; } @@ -80,6 +76,7 @@ namespace osu.Game.Screens.Select Beatmap.Disabled = false; Ruleset.Disabled = false; + Mods.Disabled = false; } } } diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs index a9616ee535..a8b5bbbd00 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs @@ -4,6 +4,7 @@ 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; diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 340ceb6864..77a8054981 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -52,10 +52,11 @@ namespace osu.Game.Screens.Select var auto = Ruleset.Value.CreateInstance().GetAutoplayMod(); var autoType = auto.GetType(); - var mods = SelectedMods.Value; + var mods = Mods.Value; + if (mods.All(m => m.GetType() != autoType)) { - SelectedMods.Value = mods.Append(auto); + Mods.Value = mods.Append(auto).ToArray(); removeAutoModOnResume = true; } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index b60e693cbf..fed1f7a944 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -39,6 +39,7 @@ namespace osu.Game.Screens.Select public abstract class SongSelect : OsuScreen { private static readonly Vector2 wedged_container_size = new Vector2(0.5f, 245); + protected const float BACKGROUND_BLUR = 20; private const float left_area_padding = 20; @@ -84,13 +85,11 @@ namespace osu.Game.Screens.Select private readonly Bindable decoupledRuleset = new Bindable(); [Cached] - [Cached(Type = typeof(IBindable>))] - protected readonly Bindable> SelectedMods = new Bindable>(new Mod[] { }); + [Cached(Type = typeof(IBindable>))] + private readonly Bindable> mods = new Bindable>(Array.Empty()); // Bound to the game's mods, but is not reset on exiting protected SongSelect() { - const float carousel_width = 640; - AddRangeInternal(new Drawable[] { new ParallaxContainer @@ -103,7 +102,8 @@ namespace osu.Game.Screens.Select new WedgeBackground { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = carousel_width * 0.76f }, + Padding = new MarginPadding { Right = -150 }, + Size = new Vector2(wedged_container_size.X, 1), } } }, @@ -144,8 +144,8 @@ namespace osu.Game.Screens.Select Carousel = new BeatmapCarousel { Masking = false, - RelativeSizeAxes = Axes.Y, - Size = new Vector2(carousel_width, 1), + RelativeSizeAxes = Axes.Both, + Size = new Vector2(1 - wedged_container_size.X, 1), Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, SelectionChanged = updateSelectedBeatmap, @@ -217,16 +217,15 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader(true)] - private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins, Bindable> selectedMods) + private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins) { - if (selectedMods != null) - SelectedMods.BindTo(selectedMods); + mods.BindTo(Mods); if (Footer != null) { - Footer.AddButton(@"mods", colours.Yellow, ModSelect, Key.F1); - Footer.AddButton(@"random", colours.Green, triggerRandom, Key.F2); - Footer.AddButton(@"options", colours.Blue, BeatmapOptions, Key.F3); + Footer.AddButton(new FooterButtonMods(mods), ModSelect); + Footer.AddButton(new FooterButtonRandom { Action = triggerRandom }); + Footer.AddButton(new FooterButtonOptions(), BeatmapOptions); BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue); BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null, Key.Number1); @@ -269,6 +268,7 @@ namespace osu.Game.Screens.Select protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs(this); dependencies.CacheAs(decoupledRuleset); dependencies.CacheAs>(decoupledRuleset); @@ -388,13 +388,11 @@ namespace osu.Game.Screens.Select { Logger.Log($"updating selection with beatmap:{beatmap?.ID.ToString() ?? "null"} ruleset:{ruleset?.ID.ToString() ?? "null"}"); - bool preview = false; - if (ruleset?.Equals(decoupledRuleset.Value) == false) { Logger.Log($"ruleset changed from \"{decoupledRuleset.Value}\" to \"{ruleset}\""); - Beatmap.Value.Mods.Value = Enumerable.Empty(); + mods.Value = Array.Empty(); decoupledRuleset.Value = ruleset; // force a filter before attempting to change the beatmap. @@ -507,6 +505,8 @@ namespace osu.Game.Screens.Select { ModSelect.Hide(); + BeatmapOptions.Hide(); + this.ScaleTo(1.1f, 250, Easing.InSine); this.FadeOut(250); @@ -529,8 +529,8 @@ namespace osu.Game.Screens.Select if (Beatmap.Value.Track != null) Beatmap.Value.Track.Looping = false; - SelectedMods.UnbindAll(); - Beatmap.Value.Mods.Value = new Mod[] { }; + mods.UnbindAll(); + Mods.Value = Array.Empty(); return false; } @@ -557,8 +557,6 @@ namespace osu.Game.Screens.Select /// The working beatmap. protected virtual void UpdateBeatmap(WorkingBeatmap beatmap) { - beatmap.Mods.BindTo(SelectedMods); - Logger.Log($"working beatmap updated to {beatmap}"); if (Background is BackgroundScreenBeatmap backgroundModeBeatmap) diff --git a/osu.Game/Screens/Tournament/Drawings.cs b/osu.Game/Screens/Tournament/Drawings.cs index f8445a4a7d..8499b56847 100644 --- a/osu.Game/Screens/Tournament/Drawings.cs +++ b/osu.Game/Screens/Tournament/Drawings.cs @@ -318,6 +318,7 @@ namespace osu.Game.Screens.Tournament using (StreamReader sr = new StreamReader(stream)) { string line; + while ((line = sr.ReadLine()?.Trim()) != null) { if (string.IsNullOrEmpty(line)) diff --git a/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs b/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs index 0bcf1b1816..02f7f73399 100644 --- a/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs +++ b/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs @@ -108,12 +108,14 @@ namespace osu.Game.Screens.Tournament speedTo(1000f, 200); tracker.FadeOut(100); break; + case ScrollState.Stopping: speedTo(0f, 2000); tracker.FadeIn(200); delayedStateChangeDelegate = Scheduler.AddDelayed(() => scrollState = ScrollState.Stopped, 2300); break; + case ScrollState.Stopped: // Find closest to center if (!Children.Any()) @@ -155,6 +157,7 @@ namespace osu.Game.Screens.Tournament delayedStateChangeDelegate = Scheduler.AddDelayed(() => scrollState = ScrollState.Idle, 10000); break; + case ScrollState.Idle: resetSelected(); diff --git a/osu.Game/Skinning/ISkin.cs b/osu.Game/Skinning/ISkin.cs new file mode 100644 index 0000000000..0e67a1897c --- /dev/null +++ b/osu.Game/Skinning/ISkin.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. + +using System; +using osu.Framework.Audio.Sample; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Textures; + +namespace osu.Game.Skinning +{ + /// + /// Provides access to skinnable elements. + /// + public interface ISkin + { + Drawable GetDrawableComponent(string componentName); + + Texture GetTexture(string componentName); + + SampleChannel GetSample(string sampleName); + + TValue GetValue(Func query) where TConfiguration : SkinConfiguration; + } +} diff --git a/osu.Game/Skinning/ISkinSource.cs b/osu.Game/Skinning/ISkinSource.cs index 6d2b9e6fe2..337d2a87a4 100644 --- a/osu.Game/Skinning/ISkinSource.cs +++ b/osu.Game/Skinning/ISkinSource.cs @@ -2,25 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Audio.Sample; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Textures; namespace osu.Game.Skinning { /// /// Provides access to skinnable elements. /// - public interface ISkinSource + public interface ISkinSource : ISkin { event Action SourceChanged; - - Drawable GetDrawableComponent(string componentName); - - Texture GetTexture(string componentName); - - SampleChannel GetSample(string sampleName); - - TValue GetValue(Func query) where TConfiguration : SkinConfiguration; } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 358b2b222b..ea4a777b47 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -49,15 +49,19 @@ namespace osu.Game.Skinning case "Play/Miss": componentName = "hit0"; break; + case "Play/Meh": componentName = "hit50"; break; + case "Play/Good": componentName = "hit100"; break; + case "Play/Great": componentName = "hit300"; break; + case "Play/osu/number-text": return !hasFont(Configuration.HitCircleFont) ? null @@ -82,6 +86,7 @@ namespace osu.Game.Skinning float ratio = 2; var texture = Textures.Get($"{componentName}@2x"); + if (texture == null) { ratio = 1; @@ -184,6 +189,7 @@ namespace osu.Game.Skinning float ratio = 36; var texture = textures.Get($"{textureName}@2x"); + if (texture == null) { ratio = 18; diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index a655c884be..ecb112955c 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.cs @@ -17,6 +17,7 @@ namespace osu.Game.Skinning line = StripComments(line); var pair = SplitKeyVal(line); + switch (section) { case Section.General: @@ -25,12 +26,18 @@ namespace osu.Game.Skinning case @"Name": skin.SkinInfo.Name = pair.Value; break; + case @"Author": skin.SkinInfo.Creator = pair.Value; break; + case @"CursorExpand": skin.CursorExpand = pair.Value != "0"; break; + + case @"SliderBorderSize": + skin.SliderBorderSize = Parsing.ParseFloat(pair.Value); + break; } break; @@ -41,6 +48,7 @@ namespace osu.Game.Skinning case "HitCirclePrefix": skin.HitCircleFont = pair.Value; break; + case "HitCircleOverlap": skin.HitCircleOverlap = int.Parse(pair.Value); break; diff --git a/osu.Game/Skinning/LocalSkinOverrideContainer.cs b/osu.Game/Skinning/LocalSkinOverrideContainer.cs index 955ef7b65b..f1ed14595e 100644 --- a/osu.Game/Skinning/LocalSkinOverrideContainer.cs +++ b/osu.Game/Skinning/LocalSkinOverrideContainer.cs @@ -22,18 +22,18 @@ namespace osu.Game.Skinning private readonly Bindable beatmapSkins = new Bindable(); private readonly Bindable beatmapHitsounds = new Bindable(); - private readonly ISkinSource source; + private readonly ISkin skin; private ISkinSource fallbackSource; - public LocalSkinOverrideContainer(ISkinSource source) + public LocalSkinOverrideContainer(ISkin skin) { - this.source = source; + this.skin = skin; } public Drawable GetDrawableComponent(string componentName) { Drawable sourceDrawable; - if (beatmapSkins.Value && (sourceDrawable = source.GetDrawableComponent(componentName)) != null) + if (beatmapSkins.Value && (sourceDrawable = skin.GetDrawableComponent(componentName)) != null) return sourceDrawable; return fallbackSource?.GetDrawableComponent(componentName); @@ -42,7 +42,7 @@ namespace osu.Game.Skinning public Texture GetTexture(string componentName) { Texture sourceTexture; - if (beatmapSkins.Value && (sourceTexture = source.GetTexture(componentName)) != null) + if (beatmapSkins.Value && (sourceTexture = skin.GetTexture(componentName)) != null) return sourceTexture; return fallbackSource.GetTexture(componentName); @@ -51,7 +51,7 @@ namespace osu.Game.Skinning public SampleChannel GetSample(string sampleName) { SampleChannel sourceChannel; - if (beatmapHitsounds.Value && (sourceChannel = source.GetSample(sampleName)) != null) + if (beatmapHitsounds.Value && (sourceChannel = skin.GetSample(sampleName)) != null) return sourceChannel; return fallbackSource?.GetSample(sampleName); @@ -60,7 +60,7 @@ namespace osu.Game.Skinning public TValue GetValue(Func query) where TConfiguration : SkinConfiguration { TValue val; - if ((source as Skin)?.Configuration is TConfiguration conf) + if ((skin as Skin)?.Configuration is TConfiguration conf) if (beatmapSkins.Value && (val = query.Invoke(conf)) != null) return val; diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 1d14f9cd6a..09c0d3d0bc 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -8,14 +8,12 @@ using osu.Framework.Graphics.Textures; namespace osu.Game.Skinning { - public abstract class Skin : IDisposable, ISkinSource + public abstract class Skin : IDisposable, ISkin { public readonly SkinInfo SkinInfo; public virtual SkinConfiguration Configuration { get; protected set; } - public event Action SourceChanged; - public abstract Drawable GetDrawableComponent(string componentName); public abstract SampleChannel GetSample(string sampleName); diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index 82faec4e9d..043622f8ce 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.cs @@ -25,6 +25,8 @@ namespace osu.Game.Skinning public int HitCircleOverlap { get; set; } + public float? SliderBorderSize { get; set; } + public bool? CursorExpand { get; set; } = true; } } diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index f6bbbc8355..3a4d44f608 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -76,6 +76,7 @@ namespace osu.Game.Skinning base.Populate(model, archive); Skin reference = getSkin(model); + if (!string.IsNullOrEmpty(reference.Configuration.SkinInfo.Name)) { model.Name = reference.Configuration.SkinInfo.Name; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index 1182cacfc1..2b27a56844 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Threading; using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -57,7 +58,7 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader(true)] - private void load(FileStore fileStore, GameplayClock clock) + private void load(FileStore fileStore, GameplayClock clock, CancellationToken? cancellationToken) { if (clock != null) Clock = clock; @@ -65,7 +66,11 @@ namespace osu.Game.Storyboards.Drawables dependencies.Cache(new TextureStore(new TextureLoaderStore(fileStore.Store), false, scaleAdjust: 1)); foreach (var layer in Storyboard.Layers) + { + cancellationToken?.ThrowIfCancellationRequested(); + Add(layer.CreateDrawable()); + } } private void updateLayerVisibility() diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 0b9ebaf3a7..de3077c025 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.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 osuTK; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -66,12 +67,11 @@ namespace osu.Game.Storyboards.Drawables [BackgroundDependencyLoader] private void load(IBindable beatmap, TextureStore textureStore) { - var basePath = Animation.Path.ToLowerInvariant(); for (var frame = 0; frame < Animation.FrameCount; frame++) { - var framePath = basePath.Replace(".", frame + "."); + var framePath = Animation.Path.Replace(".", frame + "."); - var path = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.ToLowerInvariant() == framePath)?.FileInfo.StoragePath; + var path = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.Equals(framePath, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; if (path == null) continue; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs index 106ebfaf5d..fd2d441f34 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -24,10 +25,12 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load() + private void load(CancellationToken? cancellationToken) { foreach (var element in Layer.Elements) { + cancellationToken?.ThrowIfCancellationRequested(); + if (element.IsDrawable) AddInternal(element.CreateDrawable()); } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index 604260a38c..5f1f5ddacb 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.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 osuTK; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -65,8 +66,7 @@ namespace osu.Game.Storyboards.Drawables [BackgroundDependencyLoader] private void load(IBindable beatmap, TextureStore textureStore) { - var spritePath = Sprite.Path.ToLowerInvariant(); - var path = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.ToLowerInvariant() == spritePath)?.FileInfo.StoragePath; + var path = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; if (path == null) return; diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 0cc753ff7e..3d988c5fe3 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -5,11 +5,10 @@ using osu.Game.Beatmaps; using osu.Game.Storyboards.Drawables; using System.Collections.Generic; using System.Linq; -using System; namespace osu.Game.Storyboards { - public class Storyboard : IDisposable + public class Storyboard { private readonly Dictionary layers = new Dictionary(); public IEnumerable Layers => layers.Values; @@ -56,30 +55,5 @@ namespace osu.Game.Storyboards drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard ? 16 / 9f : 4 / 3f); return drawable; } - - #region Disposal - - ~Storyboard() - { - Dispose(false); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private bool isDisposed; - - protected virtual void Dispose(bool isDisposing) - { - if (isDisposed) - return; - - isDisposed = true; - } - - #endregion } } diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index b91b05bd04..8f8ec22aae 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -82,6 +82,7 @@ namespace osu.Game.Storyboards where T : struct { var initialized = false; + foreach (var command in commands.OrderBy(l => l)) { if (!initialized) diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 44ac38044d..6a5e17eb38 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -35,6 +35,7 @@ namespace osu.Game.Tests.Beatmaps Assert.Multiple(() => { int mappingCounter = 0; + while (true) { if (mappingCounter >= ourResult.Mappings.Count && mappingCounter >= expectedResult.Mappings.Count) @@ -61,6 +62,7 @@ namespace osu.Game.Tests.Beatmaps Assert.Multiple(() => { int objectCounter = 0; + while (true) { if (objectCounter >= ourMapping.Objects.Count && objectCounter >= expectedMapping.Objects.Count) diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs index 78f9103a74..c558275f62 100644 --- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osuTK; namespace osu.Game.Tests.Beatmaps { @@ -67,9 +68,10 @@ namespace osu.Game.Tests.Beatmaps public override bool Seek(double seek) { - offset = Math.Min(seek, Length); + offset = MathHelper.Clamp(seek, 0, Length); lastReferenceTime = null; - return true; + + return offset == seek; } public override void Start() diff --git a/osu.Game/Tests/Visual/AllPlayersTestCase.cs b/osu.Game/Tests/Visual/AllPlayersTestScene.cs similarity index 79% rename from osu.Game/Tests/Visual/AllPlayersTestCase.cs rename to osu.Game/Tests/Visual/AllPlayersTestScene.cs index 4ef9b346b0..454fbe1222 100644 --- a/osu.Game/Tests/Visual/AllPlayersTestCase.cs +++ b/osu.Game/Tests/Visual/AllPlayersTestScene.cs @@ -3,15 +3,14 @@ using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps; -using osuTK.Graphics; namespace osu.Game.Tests.Visual { @@ -19,25 +18,18 @@ namespace osu.Game.Tests.Visual /// A base class which runs test for all available rulesets. /// Steps to be run for each ruleset should be added via . /// - public abstract class AllPlayersTestCase : RateAdjustedBeatmapTestCase + public abstract class AllPlayersTestScene : RateAdjustedBeatmapTestScene { protected Player Player; [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { - Add(new Box - { - RelativeSizeAxes = Framework.Graphics.Axes.Both, - Colour = Color4.Black, - Depth = int.MaxValue - }); - foreach (var r in rulesets.AvailableRulesets) { Player p = null; AddStep(r.Name, () => p = loadPlayerFor(r)); - AddUntilStep(() => + AddUntilStep("player loaded", () => { if (p?.IsLoaded == true) { @@ -46,10 +38,14 @@ namespace osu.Game.Tests.Visual } return false; - }, "player loaded"); + }); AddCheckSteps(); } + + OsuConfigManager manager; + Dependencies.Cache(manager = new OsuConfigManager(LocalStorage)); + manager.GetBindable(OsuSetting.DimLevel).Value = 1.0; } protected abstract void AddCheckSteps(); @@ -68,7 +64,7 @@ namespace osu.Game.Tests.Visual var working = CreateWorkingBeatmap(beatmap, Clock); Beatmap.Value = working; - Beatmap.Value.Mods.Value = new[] { r.GetAllMods().First(m => m is ModNoFail) }; + Mods.Value = new[] { r.GetAllMods().First(m => m is ModNoFail) }; Player?.Exit(); Player = null; @@ -80,6 +76,6 @@ namespace osu.Game.Tests.Visual return Player; } - protected virtual Player CreatePlayer(Ruleset ruleset) => new Player(false, false); + protected virtual Player CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false); } } diff --git a/osu.Game/Tests/Visual/EditorClockTestCase.cs b/osu.Game/Tests/Visual/EditorClockTestScene.cs similarity index 93% rename from osu.Game/Tests/Visual/EditorClockTestCase.cs rename to osu.Game/Tests/Visual/EditorClockTestScene.cs index 7f36a0e142..58a443ed3d 100644 --- a/osu.Game/Tests/Visual/EditorClockTestCase.cs +++ b/osu.Game/Tests/Visual/EditorClockTestScene.cs @@ -15,12 +15,12 @@ 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 EditorClockTestCase : OsuTestCase + public abstract class EditorClockTestScene : OsuTestScene { protected readonly BindableBeatDivisor BeatDivisor = new BindableBeatDivisor(); - protected readonly EditorClock Clock; + protected new readonly EditorClock Clock; - protected EditorClockTestCase() + protected EditorClockTestScene() { Clock = new EditorClock(new ControlPointInfo(), 5000, BeatDivisor) { IsCoupled = false }; } diff --git a/osu.Game/Tests/Visual/EditorTestCase.cs b/osu.Game/Tests/Visual/EditorTestScene.cs similarity index 87% rename from osu.Game/Tests/Visual/EditorTestCase.cs rename to osu.Game/Tests/Visual/EditorTestScene.cs index 96e70e018e..14c0f0950f 100644 --- a/osu.Game/Tests/Visual/EditorTestCase.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -10,13 +10,13 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual { - public abstract class EditorTestCase : ScreenTestCase + public abstract class EditorTestScene : ScreenTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(Editor), typeof(EditorScreen) }; private readonly Ruleset ruleset; - protected EditorTestCase(Ruleset ruleset) + protected EditorTestScene(Ruleset ruleset) { this.ruleset = ruleset; } diff --git a/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs b/osu.Game/Tests/Visual/ManualInputManagerTestScene.cs similarity index 61% rename from osu.Game/Tests/Visual/ManualInputManagerTestCase.cs rename to osu.Game/Tests/Visual/ManualInputManagerTestScene.cs index f14ac833e4..a7a7f88ff7 100644 --- a/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs +++ b/osu.Game/Tests/Visual/ManualInputManagerTestScene.cs @@ -4,17 +4,24 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing.Input; +using osu.Game.Graphics.Cursor; namespace osu.Game.Tests.Visual { - public abstract class ManualInputManagerTestCase : OsuTestCase + public abstract class ManualInputManagerTestScene : OsuTestScene { - protected override Container Content => InputManager; + protected override Container Content => content; + private readonly Container content; + protected readonly ManualInputManager InputManager; - protected ManualInputManagerTestCase() + protected ManualInputManagerTestScene() { - base.Content.Add(InputManager = new ManualInputManager { UseParentInput = true }); + base.Content.Add(InputManager = new ManualInputManager + { + UseParentInput = true, + Child = content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }, + }); } /// diff --git a/osu.Game/Tests/Visual/MultiplayerTestCase.cs b/osu.Game/Tests/Visual/MultiplayerTestScene.cs similarity index 93% rename from osu.Game/Tests/Visual/MultiplayerTestCase.cs rename to osu.Game/Tests/Visual/MultiplayerTestScene.cs index bb866cf750..ffb431b4d3 100644 --- a/osu.Game/Tests/Visual/MultiplayerTestCase.cs +++ b/osu.Game/Tests/Visual/MultiplayerTestScene.cs @@ -7,7 +7,7 @@ using osu.Game.Online.Multiplayer; namespace osu.Game.Tests.Visual { - public abstract class MultiplayerTestCase : ScreenTestCase + public abstract class MultiplayerTestScene : ScreenTestScene { [Cached] private readonly Bindable currentRoom = new Bindable(new Room()); diff --git a/osu.Game/Tests/Visual/OsuTestCase.cs b/osu.Game/Tests/Visual/OsuTestScene.cs similarity index 68% rename from osu.Game/Tests/Visual/OsuTestCase.cs rename to osu.Game/Tests/Visual/OsuTestScene.cs index 495c5dfbad..9b775fd498 100644 --- a/osu.Game/Tests/Visual/OsuTestCase.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -10,38 +11,40 @@ using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; namespace osu.Game.Tests.Visual { - public abstract class OsuTestCase : TestCase + public abstract class OsuTestScene : TestScene { + [Cached(typeof(Bindable))] + [Cached(typeof(IBindable))] private readonly OsuTestBeatmap beatmap = new OsuTestBeatmap(new DummyWorkingBeatmap()); + protected BindableBeatmap Beatmap => beatmap; + [Cached] + [Cached(typeof(IBindable))] protected readonly Bindable Ruleset = new Bindable(); - protected DependencyContainer Dependencies { get; private set; } + [Cached] + [Cached(Type = typeof(IBindable>))] + protected readonly Bindable> Mods = new Bindable>(Array.Empty()); + + protected new DependencyContainer Dependencies { get; private set; } private readonly Lazy localStorage; protected Storage LocalStorage => localStorage.Value; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - Dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - // This is the earliest we can get OsuGameBase, which is used by the dummy working beatmap to find textures - beatmap.Default = new DummyWorkingBeatmap(Dependencies.Get()); + beatmap.Default = new DummyWorkingBeatmap(parent.Get()); - Dependencies.CacheAs>(beatmap); - Dependencies.CacheAs>(beatmap); - - Dependencies.CacheAs(Ruleset); - Dependencies.CacheAs>(Ruleset); - - return Dependencies; + return Dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); } - protected OsuTestCase() + protected OsuTestScene() { localStorage = new Lazy(() => new NativeStorage($"{GetType().Name}-{Guid.NewGuid()}")); } @@ -73,21 +76,21 @@ namespace osu.Game.Tests.Visual } } - protected override ITestCaseTestRunner CreateRunner() => new OsuTestCaseTestRunner(); + protected override ITestSceneTestRunner CreateRunner() => new OsuTestSceneTestRunner(); - public class OsuTestCaseTestRunner : OsuGameBase, ITestCaseTestRunner + public class OsuTestSceneTestRunner : OsuGameBase, ITestSceneTestRunner { - private TestCaseTestRunner.TestRunner runner; + private TestSceneTestRunner.TestRunner runner; protected override void LoadAsyncComplete() { // this has to be run here rather than LoadComplete because - // TestCase.cs is checking the IsLoaded state (on another thread) and expects + // TestScene.cs is checking the IsLoaded state (on another thread) and expects // the runner to be loaded at that point. - Add(runner = new TestCaseTestRunner.TestRunner()); + Add(runner = new TestSceneTestRunner.TestRunner()); } - public void RunTestBlocking(TestCase test) => runner.RunTestBlocking(test); + public void RunTestBlocking(TestScene test) => runner.RunTestBlocking(test); } private class OsuTestBeatmap : BindableBeatmap diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestCase.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs similarity index 93% rename from osu.Game/Tests/Visual/PlacementBlueprintTestCase.cs rename to osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs index ec4b0a1f62..c1561ffea1 100644 --- a/osu.Game/Tests/Visual/PlacementBlueprintTestCase.cs +++ b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs @@ -13,12 +13,12 @@ using osu.Game.Screens.Edit.Compose; namespace osu.Game.Tests.Visual { [Cached(Type = typeof(IPlacementHandler))] - public abstract class PlacementBlueprintTestCase : OsuTestCase, IPlacementHandler + public abstract class PlacementBlueprintTestScene : OsuTestScene, IPlacementHandler { protected readonly Container HitObjectContainer; private PlacementBlueprint currentBlueprint; - protected PlacementBlueprintTestCase() + protected PlacementBlueprintTestScene() { Beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize = 2; diff --git a/osu.Game/Tests/Visual/PlayerTestCase.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs similarity index 62% rename from osu.Game/Tests/Visual/PlayerTestCase.cs rename to osu.Game/Tests/Visual/PlayerTestScene.cs index 3bf707fade..0c39194088 100644 --- a/osu.Game/Tests/Visual/PlayerTestCase.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -1,27 +1,25 @@ -// 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.Linq; using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps; -using osuTK.Graphics; namespace osu.Game.Tests.Visual { - public abstract class PlayerTestCase : RateAdjustedBeatmapTestCase + public abstract class PlayerTestScene : RateAdjustedBeatmapTestScene { private readonly Ruleset ruleset; protected Player Player; - protected PlayerTestCase(Ruleset ruleset) + protected PlayerTestScene(Ruleset ruleset) { this.ruleset = ruleset; } @@ -29,19 +27,16 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load() { - Add(new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Depth = int.MaxValue - }); + OsuConfigManager manager; + Dependencies.Cache(manager = new OsuConfigManager(LocalStorage)); + manager.GetBindable(OsuSetting.DimLevel).Value = 1.0; } [SetUpSteps] - public void SetUpSteps() + public virtual void SetUpSteps() { AddStep(ruleset.RulesetInfo.Name, loadPlayer); - AddUntilStep(() => Player.IsLoaded, "player loaded"); + AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); } protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo); @@ -55,12 +50,12 @@ namespace osu.Game.Tests.Visual Beatmap.Value = new TestWorkingBeatmap(beatmap, Clock); if (!AllowFail) - Beatmap.Value.Mods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; + Mods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; Player = CreatePlayer(ruleset); LoadScreen(Player); } - protected virtual Player CreatePlayer(Ruleset ruleset) => new Player(false, false); + protected virtual Player CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false); } } diff --git a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestCase.cs b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs similarity index 88% rename from osu.Game/Tests/Visual/RateAdjustedBeatmapTestCase.cs rename to osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs index 3b3adb4d76..921a1d9789 100644 --- a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestCase.cs +++ b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs @@ -6,7 +6,7 @@ namespace osu.Game.Tests.Visual /// /// Test case which adjusts the beatmap's rate to match any speed adjustments in visual tests. /// - public abstract class RateAdjustedBeatmapTestCase : ScreenTestCase + public abstract class RateAdjustedBeatmapTestScene : ScreenTestScene { protected override void Update() { diff --git a/osu.Game/Tests/Visual/ScreenTestCase.cs b/osu.Game/Tests/Visual/ScreenTestScene.cs similarity index 90% rename from osu.Game/Tests/Visual/ScreenTestCase.cs rename to osu.Game/Tests/Visual/ScreenTestScene.cs index 4fd4c7c207..23f45e0d0f 100644 --- a/osu.Game/Tests/Visual/ScreenTestCase.cs +++ b/osu.Game/Tests/Visual/ScreenTestScene.cs @@ -10,7 +10,7 @@ namespace osu.Game.Tests.Visual /// /// A test case which can be used to test a screen (that relies on OnEntering being called to execute startup instructions). /// - public abstract class ScreenTestCase : ManualInputManagerTestCase + public abstract class ScreenTestScene : ManualInputManagerTestScene { private readonly OsuScreenStack stack; @@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual protected override Container Content => content; - protected ScreenTestCase() + protected ScreenTestScene() { base.Content.AddRange(new Drawable[] { diff --git a/osu.Game/Tests/Visual/ScrollingTestContainer.cs b/osu.Game/Tests/Visual/ScrollingTestContainer.cs index f2e03208fd..bdad3d278c 100644 --- a/osu.Game/Tests/Visual/ScrollingTestContainer.cs +++ b/osu.Game/Tests/Visual/ScrollingTestContainer.cs @@ -74,9 +74,11 @@ namespace osu.Game.Tests.Visual case ScrollVisualisationMethod.Constant: implementation = new ConstantScrollAlgorithm(); break; + case ScrollVisualisationMethod.Overlapping: implementation = new OverlappingScrollAlgorithm(ControlPoints); break; + case ScrollVisualisationMethod.Sequential: implementation = new SequentialScrollAlgorithm(ControlPoints); break; diff --git a/osu.Game/Tests/Visual/SelectionBlueprintTestCase.cs b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs similarity index 92% rename from osu.Game/Tests/Visual/SelectionBlueprintTestCase.cs rename to osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs index d17f670a2d..df3af2cc43 100644 --- a/osu.Game/Tests/Visual/SelectionBlueprintTestCase.cs +++ b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs @@ -10,14 +10,14 @@ using osu.Game.Rulesets.Edit; namespace osu.Game.Tests.Visual { - public abstract class SelectionBlueprintTestCase : OsuTestCase + public abstract class SelectionBlueprintTestScene : OsuTestScene { private SelectionBlueprint blueprint; protected override Container Content => content ?? base.Content; private readonly Container content; - protected SelectionBlueprintTestCase() + protected SelectionBlueprintTestScene() { base.Content.Add(content = new Container { diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs new file mode 100644 index 0000000000..b93a1466e0 --- /dev/null +++ b/osu.Game/Tests/Visual/TestPlayer.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. + +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual +{ + public class TestPlayer : Player + { + protected override bool PauseOnFocusLost => false; + + public TestPlayer(bool allowPause = true, bool showResults = true) + : base(allowPause, showResults) + { + } + } +} diff --git a/osu.Game/Users/Avatar.cs b/osu.Game/Users/Avatar.cs index 3df5957ff9..8937f94768 100644 --- a/osu.Game/Users/Avatar.cs +++ b/osu.Game/Users/Avatar.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; @@ -72,9 +71,9 @@ namespace osu.Game.Users game?.ShowUser(user.Id); } - private class ClickableArea : OsuClickableContainer, IHasTooltip + private class ClickableArea : OsuClickableContainer { - public string TooltipText => Enabled.Value ? @"View Profile" : null; + public override string TooltipText => Enabled.Value ? @"View Profile" : null; protected override bool OnClick(ClickEvent e) { diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 292ac90245..314684069a 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.ComponentModel; +using System.Linq; using Newtonsoft.Json; using osu.Framework.Bindables; @@ -59,6 +61,9 @@ namespace osu.Game.Users [JsonProperty(@"is_supporter")] public bool IsSupporter; + [JsonProperty(@"support_level")] + public int SupportLevel; + [JsonProperty(@"is_gmt")] public bool IsGMT; @@ -71,6 +76,9 @@ namespace osu.Game.Users [JsonProperty(@"is_active")] public bool Active; + [JsonProperty(@"pm_friends_only")] + public bool PMFriendsOnly; + [JsonProperty(@"interests")] public string Interests; @@ -104,8 +112,16 @@ namespace osu.Game.Users [JsonProperty(@"post_count")] public int PostCount; - [JsonProperty(@"playstyle")] - public string[] PlayStyle; + [JsonProperty(@"follower_count")] + public int[] FollowerCount; + + [JsonProperty] + private string[] playstyle + { + set { PlayStyles = value?.Select(str => Enum.Parse(typeof(PlayStyle), str, true)).Cast().ToArray(); } + } + + public PlayStyle[] PlayStyles; [JsonProperty(@"playmode")] public string PlayMode; @@ -143,6 +159,18 @@ namespace osu.Game.Users [JsonProperty("badges")] public Badge[] Badges; + [JsonProperty("user_achievements")] + public UserAchievement[] Achievements; + + public class UserAchievement + { + [JsonProperty("achieved_at")] + public DateTimeOffset AchievedAt; + + [JsonProperty("achievement_id")] + public int ID; + } + public override string ToString() => Username; /// @@ -153,5 +181,20 @@ namespace osu.Game.Users Username = "system", Id = 0 }; + + public enum PlayStyle + { + [Description("Keyboard")] + Keyboard, + + [Description("Mouse")] + Mouse, + + [Description("Tablet")] + Tablet, + + [Description("Touch Screen")] + Touch, + } } } diff --git a/osu.Game/Users/UserCoverBackground.cs b/osu.Game/Users/UserCoverBackground.cs index 4c72762498..dbc132995a 100644 --- a/osu.Game/Users/UserCoverBackground.cs +++ b/osu.Game/Users/UserCoverBackground.cs @@ -1,30 +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 osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osuTK.Graphics; namespace osu.Game.Users { - public class UserCoverBackground : Sprite + public class UserCoverBackground : ModelBackedDrawable { - private readonly User user; - - public UserCoverBackground(User user) + public User User { - this.user = user; + get => Model; + set => Model = value; } - [BackgroundDependencyLoader] - private void load(LargeTextureStore textures) - { - if (textures == null) - throw new ArgumentNullException(nameof(textures)); + [Resolved] + private LargeTextureStore textures { get; set; } - if (!string.IsNullOrEmpty(user.CoverUrl)) - Texture = textures.Get(user.CoverUrl); + protected override Drawable CreateDrawable(User user) + { + if (user == null) + { + return new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.1f), Color4.Black.Opacity(0.75f)) + }; + } + else + { + var sprite = new Sprite + { + RelativeSizeAxes = Axes.Both, + Texture = textures.Get(user.CoverUrl), + FillMode = FillMode.Fill, + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }; + sprite.OnLoadComplete += d => d.FadeInFromZero(400); + return sprite; + } } } } diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index e745aa54c8..47571b673d 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -16,9 +16,10 @@ using osu.Game.Overlays; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Containers; -using osu.Game.Overlays.Profile.Header; +using osu.Game.Overlays.Profile.Header.Components; namespace osu.Game.Users { @@ -76,12 +77,12 @@ namespace osu.Game.Users Children = new Drawable[] { - new DelayedLoadWrapper(coverBackground = new UserCoverBackground(user) + new DelayedLoadWrapper(coverBackground = new UserCoverBackground { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - FillMode = FillMode.Fill, + User = user, }, 300) { RelativeSizeAxes = Axes.Both }, new Box { @@ -189,8 +190,8 @@ namespace osu.Game.Users { infoContainer.Add(new SupporterIcon { - RelativeSizeAxes = Axes.Y, - Width = 20f, + Height = 20f, + SupportLevel = user.SupportLevel }); } diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index 2de54ed8be..7afbef01c5 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.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 Newtonsoft.Json; +using osu.Game.Scoring; namespace osu.Game.Users { @@ -40,6 +42,9 @@ namespace osu.Game.Users [JsonProperty(@"play_count")] public int PlayCount; + [JsonProperty(@"play_time")] + public int? PlayTime; + [JsonProperty(@"total_score")] public long TotalScore; @@ -71,6 +76,33 @@ namespace osu.Game.Users [JsonProperty(@"a")] public int A; + + public int this[ScoreRank rank] + { + get + { + switch (rank) + { + case ScoreRank.XH: + return SSPlus; + + case ScoreRank.X: + return SS; + + case ScoreRank.SH: + return SPlus; + + case ScoreRank.S: + return S; + + case ScoreRank.A: + return A; + + default: + throw new ArgumentException($"API does not return {rank.ToString()}"); + } + } + } } public struct UserRanks diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 74ed9f91dd..b77c724d1b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -5,20 +5,19 @@ Library AnyCPU true - 0 - - - - - + + + + + - + diff --git a/osu.iOS.props b/osu.iOS.props index 9fff64c61c..fc047aa5f0 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + diff --git a/osu.iOS/Application.cs b/osu.iOS/Application.cs index cb75e5c159..30e0e15ad1 100644 --- a/osu.iOS/Application.cs +++ b/osu.iOS/Application.cs @@ -9,7 +9,7 @@ namespace osu.iOS { public static void Main(string[] args) { - UIApplication.Main(args, null, "AppDelegate"); + UIApplication.Main(args, "GameUIApplication", "AppDelegate"); } } } diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 71cbd83e3e..c3e274569d 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -230,6 +230,8 @@ True True NEXT_LINE + 1 + 1 NEXT_LINE 1 1 @@ -268,6 +270,7 @@ MD5 NS OS + PM RGB RNG SHA